A high-performance Node.js library for reading and writing audio metadata and cover art, built with Rust and NAPI-RS.
- 🚀 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
npm install @yortyrh/tagpilot-libyarn add @yortyrh/tagpilot-libThe package automatically provides the correct native binary for your platform:
- Windows: x64, ARM64, IA32
- macOS: x64 (Intel), ARM64 (Apple Silicon)
- Linux: x64, ARM64, ARM (GNU and Musl variants)
- FreeBSD: x64
- Android: ARM, ARM64
- WebAssembly: Browser and WASI environments
Each platform gets an optimized binary, ensuring maximum performance on your system.
const { readTags, writeTags, readCoverImageFromBuffer, writeCoverImageToBuffer } = require('@yortyrh/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',
artists: ['New Artist'],
album: 'Album Name',
year: 2024,
genre: 'Rock',
track: {
no: 1,
of: 12,
},
})
// Read cover image
const coverImage = await readCoverImageFromBuffer(audioBuffer)
if (coverImage) {
console.log('Cover image found:', coverImage.length, 'bytes')
}
// Write cover image
const imageBuffer = fs.readFileSync('./cover.jpg')
const modifiedAudio = await writeCoverImageToBuffer(audioBuffer, imageBuffer)Reads metadata from an audio file.
Parameters:
filePath(string): Path to the audio file
Returns: Promise
Example:
const tags = await readTags('./music/song.mp3')
console.log(tags)
// {
// title: "Song Title",
// artists: ["Artist Name"],
// album: "Album Name",
// year: 2024,
// genre: "Rock",
// track: {
// no: 1,
// of: 12
// },
// albumArtists: ["Album Artist"],
// comment: "Comment",
// disc: {
// no: 1,
// of: 2
// },
// image: {
// data: <Buffer>,
// mimeType: "image/jpeg",
// description: "Cover Art"
// }
// }Writes metadata to an audio file.
Parameters:
filePath(string): Path to the audio filetags(AudioTags): Metadata to write
Returns: Promise
Example:
await writeTags('./music/song.mp3', {
title: 'New Title',
artists: ['New Artist'],
album: 'Album Name',
year: 2024,
genre: 'Rock',
track: {
no: 1,
of: 12,
},
albumArtists: ['Album Artist'],
comment: 'My comment',
disc: {
no: 1,
of: 2,
},
})Clears all metadata from an audio file.
Parameters:
filePath(string): Path to the audio file
Returns: Promise
Example:
await clearTags('./music/song.mp3')Reads metadata from an audio buffer.
Parameters:
buffer(Buffer): Audio data buffer
Returns: Promise
Example:
const audioBuffer = fs.readFileSync('./music/song.mp3')
const tags = await readTagsFromBuffer(audioBuffer)Writes metadata to an audio buffer and returns the modified buffer.
Parameters:
buffer(Buffer): Audio data buffertags(AudioTags): Metadata to write
Returns: Promise
Example:
const audioBuffer = fs.readFileSync('./music/song.mp3')
const modifiedBuffer = await writeTagsToBuffer(audioBuffer, {
title: 'New Title',
artists: ['New Artist'],
})
fs.writeFileSync('./music/modified-song.mp3', modifiedBuffer)Reads cover art from an audio buffer.
Parameters:
buffer(Buffer): Audio data buffer
Returns: Promise<Buffer | null>
Example:
const audioBuffer = fs.readFileSync('./music/song.mp3')
const coverImage = await readCoverImageFromBuffer(audioBuffer)
if (coverImage) {
fs.writeFileSync('./cover.jpg', coverImage)
}Writes cover art to an audio buffer and returns the modified buffer.
Parameters:
buffer(Buffer): Audio data bufferimageData(Buffer): Image data (JPEG, PNG, GIF, BMP, TIFF)
Returns: Promise
Example:
const audioBuffer = fs.readFileSync('./music/song.mp3')
const imageBuffer = fs.readFileSync('./cover.jpg')
const modifiedAudio = await writeCoverImageToBuffer(audioBuffer, imageBuffer)
fs.writeFileSync('./music/song-with-cover.mp3', modifiedAudio)The main interface for audio file metadata, including cover art information.
interface AudioTags {
title?: string // Song title
artists?: Array<string> // Array of primary artists
album?: string // Album name
year?: number // Release year
genre?: string // Music genre
track?: Position // Track position information
albumArtists?: Array<string> // Array of album artists
comment?: string // Additional comments
disc?: Position // Disc position information
image?: Image // Primary cover art (CoverFront type)
allImages?: Array<Image> // All images, including cover art
}Represents position information for tracks and discs.
interface Position {
no?: number | null // Current position (track/disc number)
of?: number | null // Total count (total tracks/discs)
}Represents cover art information with image data and metadata.
interface Image {
data: Buffer // Raw image data
mimeType?: string // MIME type (e.g., "image/jpeg")
description?: string // Optional description of the image
picType?: string // Picture type (e.g., "CoverFront", "CoverBack", etc.)
}const { readTags, writeTags } = require('@yortyrh/tagpilot-lib')
// Read metadata
const tags = await readTags('./music/song.mp3')
console.log('Title:', tags.title)
console.log('Artist:', tags.artists?.[0])
// Update metadata
await writeTags('./music/song.mp3', {
title: 'New Title',
artists: ['New Artist'],
album: 'New Album',
year: 2024,
})The library provides two ways to handle images in audio files:
-
Single Cover Image (
imagefield):- Represents the primary cover image (CoverFront type)
- Convenient for simple cover art operations
- Backward compatible with older versions
-
Multiple Images (
allImagesfield):- Contains all images, including the cover art
- Takes precedence over the
imagefield when both are present - Automatically ensures cover image (CoverFront) is first in the list
- Preserves order of non-cover images
const { readTags, writeTags } = require('@yortyrh/tagpilot-lib')
const fs = require('fs')
// Read all images
const tags = await readTags('./music/song.mp3')
if (tags.image) {
console.log('Cover image:', tags.image.data.length, 'bytes')
}
if (tags.allImages) {
console.log(
'All images:',
tags.allImages.map((img) => img.picType),
)
}
// Write multiple images
await writeTags('./music/song.mp3', {
// ... other tags ...
image: {
data: coverImageBuffer,
picType: 'CoverFront',
mimeType: 'image/jpeg',
description: 'Album cover',
},
allImages: [
{
data: coverImageBuffer,
picType: 'CoverFront',
mimeType: 'image/jpeg',
description: 'Album cover',
},
{
data: backImageBuffer,
picType: 'CoverBack',
mimeType: 'image/jpeg',
description: 'Album back',
},
],
})const { readCoverImageFromBuffer, writeCoverImageToBuffer } = require('@yortyrh/tagpilot-lib')
const fs = require('fs')
// Read cover art
const audioBuffer = fs.readFileSync('./music/song.mp3')
const coverImage = await readCoverImageFromBuffer(audioBuffer)
if (coverImage) {
fs.writeFileSync('./cover.jpg', coverImage)
}
// Add cover art
const imageBuffer = fs.readFileSync('./new-cover.jpg')
const modifiedAudio = await writeCoverImageToBuffer(audioBuffer, imageBuffer)
fs.writeFileSync('./music/song-with-cover.mp3', modifiedAudio)const { readTags, writeTags } = require('@yortyrh/tagpilot-lib')
const fs = require('fs')
// Update all MP3 files in a directory
const files = fs.readdirSync('./music').filter((f) => f.endsWith('.mp3'))
for (const file of files) {
const tags = await readTags(`./music/${file}`)
await writeTags(`./music/${file}`, {
...tags,
album: 'My Greatest Hits',
year: 2024,
})
console.log(`Updated: ${file}`)
}- MP3 (ID3v1, ID3v2)
- FLAC
- M4A (MPEG-4 Audio)
- OGG (Vorbis)
- WAV
- WAVPACK
- AAC
- AIFF
- OPUS
- Speex
- JPEG
- PNG
- GIF
- BMP
- TIFF
@yortyrh/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
Performance comparison against music-metadata library:
Key Performance Metrics:
- 12.3x faster than music-metadata for reading audio metadata
- 383 ops/s vs 31 ops/s throughput
- 28ms vs 338ms average latency
- Tested on 130 audio files across multiple formats (MP3, FLAC, OGG, OPUS, AIFF)
Run the benchmark yourself:
npm run benchyarn bench- Node.js 16+
- Rust toolchain
- npm or yarn
git clone https://github.com/yortyrh/tagpilot-lib.git
cd tagpilot-lib
npm install
npm run buildgit clone https://github.com/yortyrh/tagpilot-lib.git
cd tagpilot-lib
yarn install
yarn buildThe project includes a comprehensive npm artifacts system for cross-platform distribution:
# Create npm directory structure
yarn create-npm-dirs
# Generate platform-specific packages
yarn generate-npm-packages
# Copy artifacts to npm directories
yarn artifacts
# Prepare for publishing
yarn prepublishOnlyThis system automatically creates platform-specific packages with the correct native binaries for each target platform. See NPM_ARTIFACTS_SETUP.md for detailed documentation.
npm test# 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- Fork the repository
- Create a feature branch
- Make your changes
- Add tests for new functionality
- Run the test suite
- Submit a pull request
MIT License - see LICENSE file for details.
- Initial release
- Audio metadata reading and writing
- Cover art extraction and embedding
- Buffer-based operations
- TypeScript support
- Comprehensive examples
- Issues: GitHub Issues
- Documentation: GitHub Wiki
- Discussions: GitHub Discussions