tagpilot-lib
Version:
A high-performance Node.js library for reading and writing audio metadata and cover art, built with Rust and NAPI-RS
422 lines (317 loc) • 9.48 kB
Markdown
# tagpilot-lib
A high-performance Node.js library for reading and writing audio metadata and cover art, built with Rust and NAPI-RS.
## Features
- 🚀 **High Performance**: Native Rust implementation for maximum speed
- 🎵 **Audio Metadata**: Read and write ID3 tags, MP3 metadata, and more
- 🖼️ **Cover Art**: Extract, embed, and manage album artwork
- 📦 **Buffer Support**: Work with audio data directly in memory
- 🔄 **Async/Await**: Promise-based API for modern JavaScript
- 🛡️ **TypeScript**: Full TypeScript support with type definitions
- 🌐 **Cross Platform**: Works on Windows, macOS, and Linux
## Installation
```bash
npm install tagpilot-lib
```
## Quick Start
```javascript
const { readTags, writeTags, readCoverImage, writeCoverImage } = require('tagpilot-lib');
// Read audio metadata
const tags = await readTags('./music/song.mp3');
console.log(tags.title); // "Song Title"
console.log(tags.artist); // "Artist Name"
// Write audio metadata
await writeTags('./music/song.mp3', {
title: 'New Title',
artist: 'New Artist',
album: 'Album Name',
year: 2024,
genre: 'Rock',
track: 1,
trackTotal: 12
});
// Read cover image
const coverImage = await readCoverImage(audioBuffer);
if (coverImage) {
console.log('Cover image found:', coverImage.length, 'bytes');
}
// Write cover image
const imageBuffer = fs.readFileSync('./cover.jpg');
const modifiedAudio = await writeCoverImage(audioBuffer, imageBuffer);
```
## API Reference
### Audio Tags
#### `readTags(filePath: string): Promise<AudioTags>`
Reads metadata from an audio file.
**Parameters:**
- `filePath` (string): Path to the audio file
**Returns:** Promise<AudioTags>
**Example:**
```javascript
const tags = await readTags('./music/song.mp3');
console.log(tags);
// {
// title: "Song Title",
// artist: "Artist Name",
// album: "Album Name",
// year: 2024,
// genre: "Rock",
// track: 1,
// trackTotal: 12,
// albumArtist: "Album Artist",
// comment: "Comment",
// disc: 1,
// discTotal: 2
// }
```
#### `writeTags(filePath: string, tags: AudioTags): Promise<void>`
Writes metadata to an audio file.
**Parameters:**
- `filePath` (string): Path to the audio file
- `tags` (AudioTags): Metadata to write
**Returns:** Promise<void>
**Example:**
```javascript
await writeTags('./music/song.mp3', {
title: 'New Title',
artist: 'New Artist',
album: 'Album Name',
year: 2024,
genre: 'Rock',
track: 1,
trackTotal: 12,
albumArtist: 'Album Artist',
comment: 'My comment',
disc: 1,
discTotal: 2
});
```
#### `clearTags(filePath: string): Promise<void>`
Clears all metadata from an audio file.
**Parameters:**
- `filePath` (string): Path to the audio file
**Returns:** Promise<void>
**Example:**
```javascript
await clearTags('./music/song.mp3');
```
### Buffer Operations
#### `readTagsFromBuffer(buffer: Buffer): Promise<AudioTags>`
Reads metadata from an audio buffer.
**Parameters:**
- `buffer` (Buffer): Audio data buffer
**Returns:** Promise<AudioTags>
**Example:**
```javascript
const audioBuffer = fs.readFileSync('./music/song.mp3');
const tags = await readTagsFromBuffer(audioBuffer);
```
#### `writeTagsToBuffer(buffer: Buffer, tags: AudioTags): Promise<Buffer>`
Writes metadata to an audio buffer and returns the modified buffer.
**Parameters:**
- `buffer` (Buffer): Audio data buffer
- `tags` (AudioTags): Metadata to write
**Returns:** Promise<Buffer>
**Example:**
```javascript
const audioBuffer = fs.readFileSync('./music/song.mp3');
const modifiedBuffer = await writeTagsToBuffer(audioBuffer, {
title: 'New Title',
artist: 'New Artist'
});
fs.writeFileSync('./music/modified-song.mp3', modifiedBuffer);
```
### Cover Art
#### `readCoverImage(buffer: Buffer): Promise<Buffer | null>`
Reads cover art from an audio buffer.
**Parameters:**
- `buffer` (Buffer): Audio data buffer
**Returns:** Promise<Buffer | null>
**Example:**
```javascript
const audioBuffer = fs.readFileSync('./music/song.mp3');
const coverImage = await readCoverImage(audioBuffer);
if (coverImage) {
fs.writeFileSync('./cover.jpg', coverImage);
}
```
#### `writeCoverImage(buffer: Buffer, imageData: Buffer): Promise<Buffer>`
Writes cover art to an audio buffer and returns the modified buffer.
**Parameters:**
- `buffer` (Buffer): Audio data buffer
- `imageData` (Buffer): Image data (JPEG, PNG, GIF, BMP, TIFF)
**Returns:** Promise<Buffer>
**Example:**
```javascript
const audioBuffer = fs.readFileSync('./music/song.mp3');
const imageBuffer = fs.readFileSync('./cover.jpg');
const modifiedAudio = await writeCoverImage(audioBuffer, imageBuffer);
fs.writeFileSync('./music/song-with-cover.mp3', modifiedAudio);
```
## Types
### AudioTags
```typescript
interface AudioTags {
title?: string;
artist?: string;
album?: string;
year?: number;
genre?: string;
track?: number;
trackTotal?: number;
albumArtist?: string;
comment?: string;
disc?: number;
discTotal?: number;
}
```
## Examples
### Basic Usage
```javascript
const { readTags, writeTags } = require('tagpilot-lib');
async function updateSongMetadata() {
// Read existing metadata
const tags = await readTags('./music/song.mp3');
console.log('Current title:', tags.title);
// Update metadata
await writeTags('./music/song.mp3', {
...tags,
title: 'Updated Title',
year: 2024
});
console.log('Metadata updated successfully!');
}
```
### Cover Art Management
```javascript
const { readCoverImage, writeCoverImage } = require('tagpilot-lib');
const fs = require('fs');
async function manageCoverArt() {
const audioBuffer = fs.readFileSync('./music/song.mp3');
// Read existing cover art
const existingCover = await readCoverImage(audioBuffer);
if (existingCover) {
console.log('Existing cover art found:', existingCover.length, 'bytes');
fs.writeFileSync('./existing-cover.jpg', existingCover);
}
// Add new cover art
const newCover = fs.readFileSync('./new-cover.jpg');
const modifiedAudio = await writeCoverImage(audioBuffer, newCover);
fs.writeFileSync('./music/song-with-new-cover.mp3', modifiedAudio);
}
```
### Batch Processing
```javascript
const { readTags, writeTags } = require('tagpilot-lib');
const fs = require('fs');
const path = require('path');
async function batchUpdateMetadata() {
const musicDir = './music';
const files = fs.readdirSync(musicDir).filter(f => f.endsWith('.mp3'));
for (const file of files) {
const filePath = path.join(musicDir, file);
const tags = await readTags(filePath);
// Update all files with a common album name
await writeTags(filePath, {
...tags,
album: 'My Greatest Hits',
year: 2024
});
console.log(`Updated: ${file}`);
}
}
```
### Data URL Generation
```javascript
const { readCoverImage } = require('tagpilot-lib');
const fs = require('fs');
function bufferToDataURL(buffer, mimeType = 'image/jpeg') {
const base64 = buffer.toString('base64');
return `data:${mimeType};base64,${base64}`;
}
async function getCoverAsDataURL() {
const audioBuffer = fs.readFileSync('./music/song.mp3');
const coverImage = await readCoverImage(audioBuffer);
if (coverImage) {
const dataURL = bufferToDataURL(coverImage, 'image/jpeg');
console.log('Cover art data URL:', dataURL.substring(0, 100) + '...');
return dataURL;
}
return null;
}
```
## Supported Formats
### Audio Formats
- MP3 (ID3v1, ID3v2)
- FLAC
- M4A (MPEG-4 Audio)
- OGG (Vorbis)
- WAV
- WAVPACK
- AAC
- AIFF
- OPUS
- Speex
### Image Formats
- JPEG
- PNG
- GIF
- BMP
- TIFF
## Performance
tagpilot-lib is built with Rust and NAPI-RS for maximum performance:
- **Fast**: Native implementation with minimal overhead
- **Memory Efficient**: Direct buffer operations without temporary files
- **Scalable**: Handles large audio files efficiently
- **Concurrent**: Async operations for better throughput
## Development
### Prerequisites
- Node.js 16+
- Rust toolchain
- npm or yarn
### Building from Source
```bash
git clone https://github.com/yortyrh/tagpilot-lib.git
cd tagpilot-lib
npm install
npm run build
```
### Running Tests
```bash
npm test
```
### Running Examples
```bash
# Read tags example
node examples/read-tags-example.js ./music/song.mp3
# Write tags example
node examples/write-tags-example.js ./music/song.mp3
# Cover image example
node examples/cover-image-buffer-example.js ./music/song.mp3 ./cover.jpg
# Read cover image as data URL
node examples/read-cover-image-example.js ./music/song.mp3
```
## Contributing
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Add tests for new functionality
5. Run the test suite
6. Submit a pull request
## License
MIT License - see [LICENSE](LICENSE) file for details.
## Changelog
### v1.0.0
- Initial release
- Audio metadata reading and writing
- Cover art extraction and embedding
- Buffer-based operations
- TypeScript support
- Comprehensive examples
## Support
- **Issues**: [GitHub Issues](https://github.com/yortyrh/tagpilot-lib/issues)
- **Documentation**: [GitHub Wiki](https://github.com/yortyrh/tagpilot-lib/wiki)
- **Discussions**: [GitHub Discussions](https://github.com/yortyrh/tagpilot-lib/discussions)
## Acknowledgments
- Built with [NAPI-RS](https://napi.rs/) for Node.js native addons
- Audio metadata handling powered by [lofty](https://github.com/Serial-ATA/lofty)
- Image format detection using [infer](https://github.com/bojand/infer)