{"id":31262752,"url":"https://github.com/yortyrh/tagpilot-lib","last_synced_at":"2025-09-23T11:53:51.432Z","repository":{"id":312644669,"uuid":"1047620082","full_name":"yortyrh/tagpilot-lib","owner":"yortyrh","description":"Rust-powered Node lib for fast audio tags \u0026 cover art (files + Buffers), prebuilt binaries, WASI/Android.","archived":false,"fork":false,"pushed_at":"2025-09-16T11:15:55.000Z","size":757299,"stargazers_count":1,"open_issues_count":9,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-09-16T13:16:51.446Z","etag":null,"topics":["android","audio-metadata","cover-art","fingerprint","id3","id3v2","napi","napi-rs","nodejs","nodejs-api","npm","rust","rust-lang","tags","vorbis-comments","wasm"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yortyrh.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-08-30T20:35:25.000Z","updated_at":"2025-09-14T15:05:56.000Z","dependencies_parsed_at":"2025-09-16T13:11:02.920Z","dependency_job_id":"66dbb5c0-b304-4f6f-9e95-53e836692799","html_url":"https://github.com/yortyrh/tagpilot-lib","commit_stats":null,"previous_names":["yortyrh/tagpilot-lib"],"tags_count":21,"template":false,"template_full_name":null,"purl":"pkg:github/yortyrh/tagpilot-lib","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yortyrh%2Ftagpilot-lib","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yortyrh%2Ftagpilot-lib/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yortyrh%2Ftagpilot-lib/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yortyrh%2Ftagpilot-lib/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yortyrh","download_url":"https://codeload.github.com/yortyrh/tagpilot-lib/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yortyrh%2Ftagpilot-lib/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275653107,"owners_count":25503909,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-17T02:00:09.119Z","response_time":84,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","audio-metadata","cover-art","fingerprint","id3","id3v2","napi","napi-rs","nodejs","nodejs-api","npm","rust","rust-lang","tags","vorbis-comments","wasm"],"created_at":"2025-09-23T11:53:49.912Z","updated_at":"2025-09-23T11:53:51.422Z","avatar_url":"https://github.com/yortyrh.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# @yortyrh/tagpilot-lib\n\n[![CI](https://github.com/yortyrh/tagpilot-lib/actions/workflows/CI.yml/badge.svg)](https://github.com/yortyrh/tagpilot-lib/actions/workflows/CI.yml)\n[![NPM version](https://img.shields.io/npm/v/@yortyrh/tagpilot-lib.svg)](https://npmjs.org/package/@yortyrh/tagpilot-lib)\n[![npm downloads](http://img.shields.io/npm/dm/@yortyrh/tagpilot-lib.svg)](https://npmcharts.com/compare/@yortyrh/tagpilot-lib?start=600\u0026interval=30)\n[![codecov](https://codecov.io/github/yortyrh/tagpilot-lib/graph/badge.svg?token=3J6SF5S640)](https://codecov.io/github/yortyrh/tagpilot-lib)\n[![Known Vulnerabilities](https://snyk.io/test/github/yortyrh/tagpilot-lib/badge.svg)](https://snyk.io/test/github/yortyrh/tagpilot-lib)\n\nA high-performance Node.js library for reading and writing audio metadata and cover art, built with Rust and NAPI-RS.\n\n## Features\n\n- 🚀 **High Performance**: Native Rust implementation for maximum speed\n- 🎵 **Audio Metadata**: Read and write ID3 tags, MP3 metadata, and more\n- 🖼️ **Cover Art**: Extract, embed, and manage album artwork\n- 📦 **Buffer Support**: Work with audio data directly in memory\n- 🔄 **Async/Await**: Promise-based API for modern JavaScript\n- 🛡️ **TypeScript**: Full TypeScript support with type definitions\n- 🌐 **Cross Platform**: Works on Windows, macOS, and Linux\n\n## Installation\n\n```bash\nnpm install @yortyrh/tagpilot-lib\n```\n\n```bash\nyarn add @yortyrh/tagpilot-lib\n```\n\n### Cross-Platform Support\n\nThe package automatically provides the correct native binary for your platform:\n\n- **Windows**: x64, ARM64, IA32\n- **macOS**: x64 (Intel), ARM64 (Apple Silicon)\n- **Linux**: x64, ARM64, ARM (GNU and Musl variants)\n- **FreeBSD**: x64\n- **Android**: ARM, ARM64\n- **WebAssembly**: Browser and WASI environments\n\nEach platform gets an optimized binary, ensuring maximum performance on your system.\n\n## Quick Start\n\n```javascript\nconst { readTags, writeTags, readCoverImageFromBuffer, writeCoverImageToBuffer } = require('@yortyrh/tagpilot-lib')\n\n// Read audio metadata\nconst tags = await readTags('./music/song.mp3')\nconsole.log(tags.title) // \"Song Title\"\nconsole.log(tags.artist) // \"Artist Name\"\n\n// Write audio metadata\nawait writeTags('./music/song.mp3', {\n  title: 'New Title',\n  artists: ['New Artist'],\n  album: 'Album Name',\n  year: 2024,\n  genre: 'Rock',\n  track: {\n    no: 1,\n    of: 12,\n  },\n})\n\n// Read cover image\nconst coverImage = await readCoverImageFromBuffer(audioBuffer)\nif (coverImage) {\n  console.log('Cover image found:', coverImage.length, 'bytes')\n}\n\n// Write cover image\nconst imageBuffer = fs.readFileSync('./cover.jpg')\nconst modifiedAudio = await writeCoverImageToBuffer(audioBuffer, imageBuffer)\n```\n\n## API Reference\n\n### Audio Tags\n\n#### `readTags(filePath: string): Promise\u003cAudioTags\u003e`\n\nReads metadata from an audio file.\n\n**Parameters:**\n\n- `filePath` (string): Path to the audio file\n\n**Returns:** Promise\u003cAudioTags\u003e\n\n**Example:**\n\n```javascript\nconst tags = await readTags('./music/song.mp3')\nconsole.log(tags)\n// {\n//   title: \"Song Title\",\n//   artists: [\"Artist Name\"],\n//   album: \"Album Name\",\n//   year: 2024,\n//   genre: \"Rock\",\n//   track: {\n//     no: 1,\n//     of: 12\n//   },\n//   albumArtists: [\"Album Artist\"],\n//   comment: \"Comment\",\n//   disc: {\n//     no: 1,\n//     of: 2\n//   },\n//   image: {\n//     data: \u003cBuffer\u003e,\n//     mimeType: \"image/jpeg\",\n//     description: \"Cover Art\"\n//   }\n// }\n```\n\n#### `writeTags(filePath: string, tags: AudioTags): Promise\u003cvoid\u003e`\n\nWrites metadata to an audio file.\n\n**Parameters:**\n\n- `filePath` (string): Path to the audio file\n- `tags` (AudioTags): Metadata to write\n\n**Returns:** Promise\u003cvoid\u003e\n\n**Example:**\n\n```javascript\nawait writeTags('./music/song.mp3', {\n  title: 'New Title',\n  artists: ['New Artist'],\n  album: 'Album Name',\n  year: 2024,\n  genre: 'Rock',\n  track: {\n    no: 1,\n    of: 12,\n  },\n  albumArtists: ['Album Artist'],\n  comment: 'My comment',\n  disc: {\n    no: 1,\n    of: 2,\n  },\n})\n```\n\n#### `clearTags(filePath: string): Promise\u003cvoid\u003e`\n\nClears all metadata from an audio file.\n\n**Parameters:**\n\n- `filePath` (string): Path to the audio file\n\n**Returns:** Promise\u003cvoid\u003e\n\n**Example:**\n\n```javascript\nawait clearTags('./music/song.mp3')\n```\n\n### Buffer Operations\n\n#### `readTagsFromBuffer(buffer: Buffer): Promise\u003cAudioTags\u003e`\n\nReads metadata from an audio buffer.\n\n**Parameters:**\n\n- `buffer` (Buffer): Audio data buffer\n\n**Returns:** Promise\u003cAudioTags\u003e\n\n**Example:**\n\n```javascript\nconst audioBuffer = fs.readFileSync('./music/song.mp3')\nconst tags = await readTagsFromBuffer(audioBuffer)\n```\n\n#### `writeTagsToBuffer(buffer: Buffer, tags: AudioTags): Promise\u003cBuffer\u003e`\n\nWrites metadata to an audio buffer and returns the modified buffer.\n\n**Parameters:**\n\n- `buffer` (Buffer): Audio data buffer\n- `tags` (AudioTags): Metadata to write\n\n**Returns:** Promise\u003cBuffer\u003e\n\n**Example:**\n\n```javascript\nconst audioBuffer = fs.readFileSync('./music/song.mp3')\nconst modifiedBuffer = await writeTagsToBuffer(audioBuffer, {\n  title: 'New Title',\n  artists: ['New Artist'],\n})\nfs.writeFileSync('./music/modified-song.mp3', modifiedBuffer)\n```\n\n### Cover Art\n\n#### `readCoverImageFromBuffer(buffer: Buffer): Promise\u003cBuffer | null\u003e`\n\nReads cover art from an audio buffer.\n\n**Parameters:**\n\n- `buffer` (Buffer): Audio data buffer\n\n**Returns:** Promise\u003cBuffer | null\u003e\n\n**Example:**\n\n```javascript\nconst audioBuffer = fs.readFileSync('./music/song.mp3')\nconst coverImage = await readCoverImageFromBuffer(audioBuffer)\nif (coverImage) {\n  fs.writeFileSync('./cover.jpg', coverImage)\n}\n```\n\n#### `writeCoverImageToBuffer(buffer: Buffer, imageData: Buffer): Promise\u003cBuffer\u003e`\n\nWrites cover art to an audio buffer and returns the modified buffer.\n\n**Parameters:**\n\n- `buffer` (Buffer): Audio data buffer\n- `imageData` (Buffer): Image data (JPEG, PNG, GIF, BMP, TIFF)\n\n**Returns:** Promise\u003cBuffer\u003e\n\n**Example:**\n\n```javascript\nconst audioBuffer = fs.readFileSync('./music/song.mp3')\nconst imageBuffer = fs.readFileSync('./cover.jpg')\nconst modifiedAudio = await writeCoverImageToBuffer(audioBuffer, imageBuffer)\nfs.writeFileSync('./music/song-with-cover.mp3', modifiedAudio)\n```\n\n## Types\n\n### AudioTags\n\nThe main interface for audio file metadata, including cover art information.\n\n```typescript\ninterface AudioTags {\n  title?: string // Song title\n  artists?: Array\u003cstring\u003e // Array of primary artists\n  album?: string // Album name\n  year?: number // Release year\n  genre?: string // Music genre\n  track?: Position // Track position information\n  albumArtists?: Array\u003cstring\u003e // Array of album artists\n  comment?: string // Additional comments\n  disc?: Position // Disc position information\n  image?: Image // Primary cover art (CoverFront type)\n  allImages?: Array\u003cImage\u003e // All images, including cover art\n}\n```\n\n### Position\n\nRepresents position information for tracks and discs.\n\n```typescript\ninterface Position {\n  no?: number | null // Current position (track/disc number)\n  of?: number | null // Total count (total tracks/discs)\n}\n```\n\n### Image\n\nRepresents cover art information with image data and metadata.\n\n```typescript\ninterface Image {\n  data: Buffer // Raw image data\n  mimeType?: string // MIME type (e.g., \"image/jpeg\")\n  description?: string // Optional description of the image\n  picType?: string // Picture type (e.g., \"CoverFront\", \"CoverBack\", etc.)\n}\n```\n\n## Examples\n\n### Basic Usage\n\n```javascript\nconst { readTags, writeTags } = require('@yortyrh/tagpilot-lib')\n\n// Read metadata\nconst tags = await readTags('./music/song.mp3')\nconsole.log('Title:', tags.title)\nconsole.log('Artist:', tags.artists?.[0])\n\n// Update metadata\nawait writeTags('./music/song.mp3', {\n  title: 'New Title',\n  artists: ['New Artist'],\n  album: 'New Album',\n  year: 2024,\n})\n```\n\n### Image Handling\n\nThe library provides two ways to handle images in audio files:\n\n1. **Single Cover Image** (`image` field):\n   - Represents the primary cover image (CoverFront type)\n   - Convenient for simple cover art operations\n   - Backward compatible with older versions\n\n2. **Multiple Images** (`allImages` field):\n   - Contains all images, including the cover art\n   - Takes precedence over the `image` field when both are present\n   - Automatically ensures cover image (CoverFront) is first in the list\n   - Preserves order of non-cover images\n\n```javascript\nconst { readTags, writeTags } = require('@yortyrh/tagpilot-lib')\nconst fs = require('fs')\n\n// Read all images\nconst tags = await readTags('./music/song.mp3')\nif (tags.image) {\n  console.log('Cover image:', tags.image.data.length, 'bytes')\n}\nif (tags.allImages) {\n  console.log(\n    'All images:',\n    tags.allImages.map((img) =\u003e img.picType),\n  )\n}\n\n// Write multiple images\nawait writeTags('./music/song.mp3', {\n  // ... other tags ...\n  image: {\n    data: coverImageBuffer,\n    picType: 'CoverFront',\n    mimeType: 'image/jpeg',\n    description: 'Album cover',\n  },\n  allImages: [\n    {\n      data: coverImageBuffer,\n      picType: 'CoverFront',\n      mimeType: 'image/jpeg',\n      description: 'Album cover',\n    },\n    {\n      data: backImageBuffer,\n      picType: 'CoverBack',\n      mimeType: 'image/jpeg',\n      description: 'Album back',\n    },\n  ],\n})\n```\n\n### Cover Art Operations\n\n```javascript\nconst { readCoverImageFromBuffer, writeCoverImageToBuffer } = require('@yortyrh/tagpilot-lib')\nconst fs = require('fs')\n\n// Read cover art\nconst audioBuffer = fs.readFileSync('./music/song.mp3')\nconst coverImage = await readCoverImageFromBuffer(audioBuffer)\nif (coverImage) {\n  fs.writeFileSync('./cover.jpg', coverImage)\n}\n\n// Add cover art\nconst imageBuffer = fs.readFileSync('./new-cover.jpg')\nconst modifiedAudio = await writeCoverImageToBuffer(audioBuffer, imageBuffer)\nfs.writeFileSync('./music/song-with-cover.mp3', modifiedAudio)\n```\n\n### Batch Processing\n\n```javascript\nconst { readTags, writeTags } = require('@yortyrh/tagpilot-lib')\nconst fs = require('fs')\n\n// Update all MP3 files in a directory\nconst files = fs.readdirSync('./music').filter((f) =\u003e f.endsWith('.mp3'))\nfor (const file of files) {\n  const tags = await readTags(`./music/${file}`)\n  await writeTags(`./music/${file}`, {\n    ...tags,\n    album: 'My Greatest Hits',\n    year: 2024,\n  })\n  console.log(`Updated: ${file}`)\n}\n```\n\n## Supported Formats\n\n### Audio Formats\n\n- MP3 (ID3v1, ID3v2)\n- FLAC\n- M4A (MPEG-4 Audio)\n- OGG (Vorbis)\n- WAV\n- WAVPACK\n- AAC\n- AIFF\n- OPUS\n- Speex\n\n### Image Formats\n\n- JPEG\n- PNG\n- GIF\n- BMP\n- TIFF\n\n## Performance\n\n@yortyrh/tagpilot-lib is built with Rust and NAPI-RS for maximum performance:\n\n- **Fast**: Native implementation with minimal overhead\n- **Memory Efficient**: Direct buffer operations without temporary files\n- **Scalable**: Handles large audio files efficiently\n- **Concurrent**: Async operations for better throughput\n\n### Benchmark Results\n\nPerformance comparison against music-metadata library:\n\n![Benchmark Results](benchmark-results.jpg)\n\n**Key Performance Metrics:**\n\n- **12.3x faster** than music-metadata for reading audio metadata\n- **383 ops/s** vs 31 ops/s throughput\n- **28ms** vs 338ms average latency\n- Tested on 130 audio files across multiple formats (MP3, FLAC, OGG, OPUS, AIFF)\n\nRun the benchmark yourself:\n\n```bash\nnpm run bench\n```\n\n```bash\nyarn bench\n```\n\n## Development\n\n### Prerequisites\n\n- Node.js 16+\n- Rust toolchain\n- npm or yarn\n\n### Building from Source\n\n```bash\ngit clone https://github.com/yortyrh/tagpilot-lib.git\ncd tagpilot-lib\nnpm install\nnpm run build\n```\n\n```bash\ngit clone https://github.com/yortyrh/tagpilot-lib.git\ncd tagpilot-lib\nyarn install\nyarn build\n```\n\n### NPM Artifacts System\n\nThe project includes a comprehensive npm artifacts system for cross-platform distribution:\n\n```bash\n# Create npm directory structure\nyarn create-npm-dirs\n\n# Generate platform-specific packages\nyarn generate-npm-packages\n\n# Copy artifacts to npm directories\nyarn artifacts\n\n# Prepare for publishing\nyarn prepublishOnly\n```\n\nThis system automatically creates platform-specific packages with the correct native binaries for each target platform. See [NPM_ARTIFACTS_SETUP.md](NPM_ARTIFACTS_SETUP.md) for detailed documentation.\n\n### Running Tests\n\n```bash\nnpm test\n```\n\n### Running Examples\n\n```bash\n# Read tags example\nnode examples/read-tags-example.js ./music/song.mp3\n\n# Write tags example\nnode examples/write-tags-example.js ./music/song.mp3\n\n# Cover image example\nnode examples/cover-image-buffer-example.js ./music/song.mp3 ./cover.jpg\n\n# Read cover image as data URL\nnode examples/read-cover-image-example.js ./music/song.mp3\n```\n\n## Contributing\n\n1. Fork the repository\n2. Create a feature branch\n3. Make your changes\n4. Add tests for new functionality\n5. Run the test suite\n6. Submit a pull request\n\n## License\n\nMIT License - see [LICENSE](LICENSE) file for details.\n\n## Changelog\n\n### v1.0.0\n\n- Initial release\n- Audio metadata reading and writing\n- Cover art extraction and embedding\n- Buffer-based operations\n- TypeScript support\n- Comprehensive examples\n\n## Support\n\n- **Issues**: [GitHub Issues](https://github.com/yortyrh/tagpilot-lib/issues)\n- **Documentation**: [GitHub Wiki](https://github.com/yortyrh/tagpilot-lib/wiki)\n- **Discussions**: [GitHub Discussions](https://github.com/yortyrh/tagpilot-lib/discussions)\n\n## Acknowledgments\n\n- Built with [NAPI-RS](https://napi.rs/) for Node.js native addons\n- Audio metadata handling powered by [lofty](https://github.com/Serial-ATA/lofty-rs)\n- Image format detection using [infer](https://github.com/bojand/infer)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyortyrh%2Ftagpilot-lib","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyortyrh%2Ftagpilot-lib","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyortyrh%2Ftagpilot-lib/lists"}