taglib-wasm 0.2.7 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,17 +1,14 @@
1
1
  # taglib-wasm
2
2
 
3
- [TagLib](https://taglib.org/) is the most robust, de-facto standard for reading and editing metadata tags (Title, Album, Artist, etc.) in all popular audio formats. See [“Goals & Features”](https://taglib.org/) for the reasons TagLib is so great.
3
+ This is the Wasm version of [**TagLib**](https://taglib.org/), the most robust, de-facto standard for reading and editing metadata tags (Title, Album, Artist, etc.) in all popular audio formats. `taglib-wasm` exists because the JavaScipt/TypeScipt ecosystem had no battle-tested audio tagging library that supports reading and writing music metadata to all popular audio formats — until now!
4
4
 
5
- `taglib-wasm` is designed to be **TagLib for JavaScript/TypeScript** platforms — specifically Deno, Node.js, Bun, web browsers, and Cloudflare Workers. It does this by leveraging technologies including [TagLib](https://taglib.org/) itself, [Emscripten](https://emscripten.org/), and [Wasm](https://webassembly.org/) ([WebAssembly](https://webassembly.org/)).
5
+ `taglib-wasm` stands on the shoulders of giants, including [TagLib](https://taglib.org/) itself, [Emscripten](https://emscripten.org/), and [Wasm](https://webassembly.org/) ([WebAssembly](https://webassembly.org/)).
6
6
 
7
- > [!NOTE]
8
- > This project is a baby, and you’re likely to experience some surprises at this stage of its development. I’m extremely moditivated to help address them, though.
7
+ `taglib-wasm` aspires to be a universal solution for **JavaScript/TypeScript** platforms — Deno, Node.js, Bun, web browsers, and Cloudflare Workers. Note: This project is a baby, and you’re likely to experience some surprises at this stage of its development. I’m extremely motivated to help address them, since I’ll also be depending on this project.
9
8
 
10
- ## Why?
9
+ ## 🤔 Why?
11
10
 
12
- In the process of building a utility to improve the metadata of my music collection, I discovered that the JavaScipt/TypeScipt ecosystem had no battle-tested audio tagging library that supports reading and writing music metadata to all popular audio formats.
13
-
14
- [`mp3tag.js`](https://mp3tag.js.org/) is mature and active, but only supports MP3 files and ID3 tags. TagLib was an ideal choice from a maturity and capabilities point of view, but wrappers like `node-taglib` appeared to be dormant, and I wanted to avoid making users install platform-specific dependencies whenever possible.
11
+ Because there’s nothing like it. [`mp3tag.js`](https://mp3tag.js.org/) is mature and active, but only supports MP3 files and ID3 tags. TagLib was an ideal choice from a maturity and capabilities point of view, but wrappers like `node-taglib` appeared to be dormant, and I wanted to avoid making users install platform-specific dependencies whenever possible.
15
12
 
16
13
  ## 🎯 Features
17
14
 
@@ -29,7 +26,7 @@ In the process of building a utility to improve the metadata of my music collect
29
26
  ### Deno
30
27
 
31
28
  ```typescript
32
- import { TagLib } from "jsr:@charleswiltgen/taglib-wasm";
29
+ import { TagLib } from "npm:taglib-wasm";
33
30
  ```
34
31
 
35
32
  ### Node.js
@@ -38,6 +35,13 @@ import { TagLib } from "jsr:@charleswiltgen/taglib-wasm";
38
35
  npm install taglib-wasm
39
36
  ```
40
37
 
38
+ **Note:** The NPM package ships TypeScript source files. Use a TypeScript loader like [`tsx`](https://github.com/privatenumber/tsx):
39
+
40
+ ```bash
41
+ npm install --save-dev tsx
42
+ npx tsx your-script.ts
43
+ ```
44
+
41
45
  ### Bun
42
46
 
43
47
  ```bash
@@ -74,13 +78,13 @@ console.log(`Duration: ${props.length}s, Bitrate: ${props.bitrate} kbps`);
74
78
  Full control when you need it:
75
79
 
76
80
  ```typescript
77
- import { TagLib } from "jsr:@charleswiltgen/taglib-wasm";
81
+ import { TagLib } from "taglib-wasm";
78
82
 
79
83
  // Initialize taglib-wasm
80
84
  const taglib = await TagLib.initialize();
81
85
 
82
86
  // Load audio file from buffer
83
- const audioData = await Deno.readFile("song.mp3");
87
+ const audioData = await readFile("song.mp3"); // Node.js/Bun: fs.readFile, Deno: Deno.readFile
84
88
  const file = taglib.openFile(audioData);
85
89
 
86
90
  // Read metadata
@@ -93,16 +97,15 @@ console.log(`Duration: ${props.length}s`);
93
97
  console.log(`Bitrate: ${props.bitrate} kbps`);
94
98
 
95
99
  // Write metadata
96
- file.setTitle("New Title");
97
- file.setArtist("New Artist");
98
- file.setAlbum("New Album");
100
+ const tag = file.tag();
101
+ tag.setTitle("New Title");
102
+ tag.setArtist("New Artist");
103
+ tag.setAlbum("New Album");
99
104
 
100
- console.log("Updated tags:", file.tag());
105
+ // Save changes
106
+ file.save();
101
107
 
102
- // Automatic tag mapping (format-agnostic)
103
- file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
104
- file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
105
- file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
108
+ console.log("Updated tags:", file.tag());
106
109
 
107
110
  // Clean up
108
111
  file.dispose();
@@ -110,16 +113,17 @@ file.dispose();
110
113
 
111
114
  ## Platform examples
112
115
 
113
- ### Deno
116
+ ### Node.js
114
117
 
115
118
  ```typescript
116
- import { TagLib } from "jsr:@charleswiltgen/taglib-wasm";
119
+ import { TagLib } from "taglib-wasm";
120
+ import { readFile } from "fs/promises";
117
121
 
118
122
  // Initialize taglib-wasm
119
123
  const taglib = await TagLib.initialize();
120
124
 
121
125
  // Load audio file from filesystem
122
- const audioData = await Deno.readFile("song.mp3");
126
+ const audioData = await readFile("song.mp3");
123
127
  const file = taglib.openFile(audioData);
124
128
 
125
129
  // Read metadata
@@ -132,33 +136,31 @@ console.log(`Duration: ${props.length}s`);
132
136
  console.log(`Bitrate: ${props.bitrate} kbps`);
133
137
 
134
138
  // Write metadata
135
- file.setTitle("New Title");
136
- file.setArtist("New Artist");
137
- file.setAlbum("New Album");
139
+ const tag = file.tag();
140
+ tag.setTitle("New Title");
141
+ tag.setArtist("New Artist");
142
+ tag.setAlbum("New Album");
138
143
 
139
- console.log("Updated tags:", file.tag());
144
+ // Save changes
145
+ file.save();
140
146
 
141
- // Automatic tag mapping (format-agnostic)
142
- file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
143
- file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
144
- file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
147
+ console.log("Updated tags:", file.tag());
145
148
 
146
149
  // Clean up
147
150
  file.dispose();
148
151
  ```
149
152
 
150
- ### Node.js
153
+ ### Bun
151
154
 
152
155
  ```typescript
153
156
  import { TagLib } from "taglib-wasm";
154
- import { readFile } from "fs/promises";
155
157
 
156
158
  // Initialize taglib-wasm
157
159
  const taglib = await TagLib.initialize();
158
160
 
159
- // Load audio file from filesystem
160
- const audioData = await readFile("song.mp3");
161
- const file = taglib.openFile(audioData);
161
+ // Load from file system (Bun's native file API)
162
+ const audioData = await Bun.file("song.mp3").arrayBuffer();
163
+ const file = taglib.openFile(new Uint8Array(audioData));
162
164
 
163
165
  // Read metadata
164
166
  const tags = file.tag();
@@ -170,22 +172,21 @@ console.log(`Duration: ${props.length}s`);
170
172
  console.log(`Bitrate: ${props.bitrate} kbps`);
171
173
 
172
174
  // Write metadata
173
- file.setTitle("New Title");
174
- file.setArtist("New Artist");
175
- file.setAlbum("New Album");
175
+ const tag = file.tag();
176
+ tag.setTitle("New Title");
177
+ tag.setArtist("New Artist");
178
+ tag.setAlbum("New Album");
176
179
 
177
- console.log("Updated tags:", file.tag());
180
+ // Save changes
181
+ file.save();
178
182
 
179
- // Automatic tag mapping (format-agnostic)
180
- file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
181
- file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
182
- file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
183
+ console.log("Updated tags:", file.tag());
183
184
 
184
185
  // Clean up
185
186
  file.dispose();
186
187
  ```
187
188
 
188
- ### Bun
189
+ ### Browser
189
190
 
190
191
  ```typescript
191
192
  import { TagLib } from "taglib-wasm";
@@ -193,9 +194,11 @@ import { TagLib } from "taglib-wasm";
193
194
  // Initialize taglib-wasm
194
195
  const taglib = await TagLib.initialize();
195
196
 
196
- // Load from file system (Bun's native file API)
197
- const audioData = await Bun.file("song.mp3").arrayBuffer();
198
- const file = taglib.openFile(new Uint8Array(audioData));
197
+ // Load from file input or fetch
198
+ const fileInput = document.querySelector('input[type="file"]');
199
+ const audioFile = fileInput.files[0];
200
+ const audioData = new Uint8Array(await audioFile.arrayBuffer());
201
+ const file = taglib.openFile(audioData);
199
202
 
200
203
  // Read metadata
201
204
  const tags = file.tag();
@@ -207,33 +210,30 @@ console.log(`Duration: ${props.length}s`);
207
210
  console.log(`Bitrate: ${props.bitrate} kbps`);
208
211
 
209
212
  // Write metadata
210
- file.setTitle("New Title");
211
- file.setArtist("New Artist");
212
- file.setAlbum("New Album");
213
+ const tag = file.tag();
214
+ tag.setTitle("New Title");
215
+ tag.setArtist("New Artist");
216
+ tag.setAlbum("New Album");
213
217
 
214
- console.log("Updated tags:", file.tag());
218
+ // Save changes
219
+ file.save();
215
220
 
216
- // Automatic tag mapping (format-agnostic)
217
- file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
218
- file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
219
- file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
221
+ console.log("Updated tags:", file.tag());
220
222
 
221
223
  // Clean up
222
224
  file.dispose();
223
225
  ```
224
226
 
225
- ### Browser
227
+ ### Deno
226
228
 
227
229
  ```typescript
228
- import { TagLib } from "taglib-wasm";
230
+ import { TagLib } from "npm:taglib-wasm";
229
231
 
230
232
  // Initialize taglib-wasm
231
233
  const taglib = await TagLib.initialize();
232
234
 
233
- // Load from file input or fetch
234
- const fileInput = document.querySelector('input[type="file"]');
235
- const audioFile = fileInput.files[0];
236
- const audioData = new Uint8Array(await audioFile.arrayBuffer());
235
+ // Load audio file from filesystem
236
+ const audioData = await Deno.readFile("song.mp3");
237
237
  const file = taglib.openFile(audioData);
238
238
 
239
239
  // Read metadata
@@ -246,16 +246,15 @@ console.log(`Duration: ${props.length}s`);
246
246
  console.log(`Bitrate: ${props.bitrate} kbps`);
247
247
 
248
248
  // Write metadata
249
- file.setTitle("New Title");
250
- file.setArtist("New Artist");
251
- file.setAlbum("New Album");
249
+ const tag = file.tag();
250
+ tag.setTitle("New Title");
251
+ tag.setArtist("New Artist");
252
+ tag.setAlbum("New Album");
252
253
 
253
- console.log("Updated tags:", file.tag());
254
+ // Save changes
255
+ file.save();
254
256
 
255
- // Automatic tag mapping (format-agnostic)
256
- file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
257
- file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
258
- file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
257
+ console.log("Updated tags:", file.tag());
259
258
 
260
259
  // Clean up
261
260
  file.dispose();
@@ -318,72 +317,70 @@ export default {
318
317
 
319
318
  ## 📋 Supported Formats
320
319
 
321
- All formats are **fully tested and working**:
320
+ `tag-wasm` is designed to support all formats supported by TagLib:
322
321
 
323
- - ✅ **MP3**ID3v2 and ID3v1 tags
324
- - ✅ **MP4/M4A**Standard MPEG (iTunes-compatible) metadata atoms
325
- - ✅ **FLAC** – Vorbis comments and audio properties
326
- - ✅ **OGG Vorbis** Vorbis comments
327
- - ✅ **WAV** INFO chunk metadata
328
- - 🔄 **Additional formats**: Opus, APE, MPC, WavPack, TrueAudio, and more
322
+ - ✅ **.m4a (.mp4)** – Standard MPEG-4/AAC metadata for AAC and Apple Lossless audio
323
+ - ✅ **.mp3** – ID3v2 and ID3v1 tags
324
+ - ✅ **.flac** – Vorbis comments and audio properties
325
+ - ✅ **.wav** – INFO chunk metadata
326
+ - ✅ **Legacy formats**: Opus, APE, MPC, WavPack, TrueAudio, and more
329
327
 
330
- ## 🎯 Automatic Tag Mapping
328
+ ## 🎯 Extended Metadata with PropertyMap
331
329
 
332
- `taglib-wasm` supports **automatic tag mapping** so you don’t have to worry about how the same tag is stored differently in different audio container formats.
330
+ `taglib-wasm` provides a **PropertyMap API** for accessing extended metadata beyond the basic tags. This allows you to read and write format-specific fields and custom metadata.
333
331
 
334
332
  ### AcoustID example
335
333
 
336
334
  ```typescript
337
- // Single API works for ALL formats (MP3, FLAC, OGG, MP4)
338
- file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
339
- file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
340
-
341
- // Automatically stores in format-specific locations:
342
- // MP3: TXXX frames with proper descriptions
343
- // FLAC/OGG: ACOUSTID_FINGERPRINT and ACOUSTID_ID Vorbis comments
344
- // • MP4: ----:com.apple.iTunes:Acoustid... freeform atoms
335
+ // Using PropertyMap API to set extended metadata
336
+ file.setProperty("ACOUSTID_FINGERPRINT", "AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
337
+ file.setProperty("ACOUSTID_ID", "e7359e88-f1f7-41ed-b9f6-16e58e906997");
338
+
339
+ // Note: Property keys may vary by format
340
+ // Use file.properties() to see all available properties
341
+ file.save(); // Don't forget to save!
345
342
  ```
346
343
 
347
344
  ### MusicBrainz example
348
345
 
349
346
  ```typescript
350
- // Professional music database integration
351
- file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
352
- file.setMusicBrainzReleaseId("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
353
- file.setMusicBrainzArtistId("12345678-90ab-cdef-1234-567890abcdef");
347
+ // MusicBrainz metadata using PropertyMap
348
+ file.setProperty("MUSICBRAINZ_TRACKID", "f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
349
+ file.setProperty("MUSICBRAINZ_ALBUMID", "a1b2c3d4-e5f6-7890-abcd-ef1234567890");
350
+ file.setProperty("MUSICBRAINZ_ARTISTID", "12345678-90ab-cdef-1234-567890abcdef");
354
351
  ```
355
352
 
356
353
  ### Volume example
357
354
 
358
355
  ```typescript
359
- // ReplayGain support (automatic format mapping)
360
- file.setReplayGainTrackGain("-6.54 dB");
361
- file.setReplayGainTrackPeak("0.987654");
362
- file.setReplayGainAlbumGain("-8.12 dB");
363
- file.setReplayGainAlbumPeak("0.995432");
364
-
365
- // Apple Sound Check support
366
- file.setAppleSoundCheck("00000150 00000150 00000150 00000150...");
356
+ // ReplayGain volume normalization
357
+ file.setProperty("REPLAYGAIN_TRACK_GAIN", "-6.54 dB");
358
+ file.setProperty("REPLAYGAIN_TRACK_PEAK", "0.987654");
359
+ file.setProperty("REPLAYGAIN_ALBUM_GAIN", "-8.12 dB");
360
+ file.setProperty("REPLAYGAIN_ALBUM_PEAK", "0.995432");
367
361
  ```
368
362
 
369
363
  ### Extended fields
370
364
 
371
365
  ```typescript
372
- // Advanced metadata fields
373
- file.setExtendedTag({
374
- albumArtist: "Various Artists",
375
- composer: "Composer Name",
376
- bpm: 120,
377
- compilation: true,
378
- discNumber: 1,
379
- totalTracks: 12,
380
- // Volume normalization
381
- replayGainTrackGain: "-6.54 dB",
382
- appleSoundCheck: "00000150...",
366
+ // Using PropertyMap to set multiple properties at once
367
+ const properties = file.properties(); // Get current properties
368
+
369
+ // Set extended metadata
370
+ file.setProperties({
371
+ ALBUMARTIST: ["Various Artists"],
372
+ COMPOSER: ["Composer Name"],
373
+ BPM: ["120"],
374
+ COMPILATION: ["1"],
375
+ DISCNUMBER: ["1"],
376
+ TRACKTOTAL: ["12"],
377
+ // Note: Property keys vary by format
383
378
  });
384
- ```
385
379
 
386
- **📖 See [docs/Automatic-Tag-Mapping.md](docs/Automatic-Tag-Mapping.md) for complete documentation**
380
+ // Or set individual properties
381
+ file.setProperty("ALBUMARTIST", "Various Artists");
382
+ file.setProperty("COMPOSER", "Composer Name");
383
+ ```
387
384
 
388
385
  ## 🏗️ Development
389
386
 
@@ -418,8 +415,7 @@ build/
418
415
  ├── taglib.js # Generated Emscripten JavaScript
419
416
  └── taglib.wasm # Compiled WebAssembly module
420
417
 
421
- tests/ # Test files and sample audio files
422
- tests/ # Test suite
418
+ tests/ # Test suite and sample audio files
423
419
  examples/ # Usage examples for different runtimes
424
420
  ├── deno/ # Deno-specific examples
425
421
  ├── bun/ # Bun-specific examples
@@ -483,53 +479,63 @@ class TagLib {
483
479
  class AudioFile {
484
480
  // Validation
485
481
  isValid(): boolean;
486
- format(): string;
482
+ getFormat(): string;
487
483
 
488
484
  // Properties
489
485
  audioProperties(): AudioProperties;
490
- tag(): TagData;
491
486
 
492
- // Tag Writing
493
- setTitle(title: string): void;
494
- setArtist(artist: string): void;
495
- setAlbum(album: string): void;
496
- setComment(comment: string): void;
497
- setGenre(genre: string): void;
498
- setYear(year: number): void;
499
- setTrack(track: number): void;
487
+ // Tag Access (returns Tag object with getters and setters)
488
+ tag(): Tag;
489
+
490
+ // PropertyMap API for extended metadata
491
+ properties(): PropertyMap;
492
+ setProperties(properties: PropertyMap): void;
493
+ getProperty(key: string): string | undefined;
494
+ setProperty(key: string, value: string): void;
495
+
496
+ // MP4-specific methods
497
+ isMP4(): boolean;
498
+ getMP4Item(key: string): string | undefined;
499
+ setMP4Item(key: string, value: string): void;
500
+ removeMP4Item(key: string): void;
500
501
 
501
502
  // File Operations
502
503
  save(): boolean;
504
+ getFileBuffer(): Uint8Array;
503
505
  dispose(): void;
506
+ }
507
+ ```
504
508
 
505
- // Automatic Tag Mapping (Format-Agnostic)
506
- extendedTag(): ExtendedTag;
507
- setExtendedTag(tag: Partial<ExtendedTag>): void;
508
-
509
- // AcoustID Integration
510
- setAcoustidFingerprint(fingerprint: string): void;
511
- getAcoustidFingerprint(): string | undefined;
512
- setAcoustidId(id: string): void;
513
- getAcoustidId(): string | undefined;
514
-
515
- // MusicBrainz Integration
516
- setMusicBrainzTrackId(id: string): void;
517
- getMusicBrainzTrackId(): string | undefined;
518
-
519
- // Volume Normalization
520
- setReplayGainTrackGain(gain: string): void;
521
- getReplayGainTrackGain(): string | undefined;
522
- setReplayGainTrackPeak(peak: string): void;
523
- getReplayGainTrackPeak(): string | undefined;
524
- setReplayGainAlbumGain(gain: string): void;
525
- getReplayGainAlbumGain(): string | undefined;
526
- setReplayGainAlbumPeak(peak: string): void;
527
- getReplayGainAlbumPeak(): string | undefined;
528
- setAppleSoundCheck(iTunNORM: string): void;
529
- getAppleSoundCheck(): string | undefined;
509
+ ### Tag interface
510
+
511
+ ```typescript
512
+ interface Tag {
513
+ // Basic metadata (getters)
514
+ title: string;
515
+ artist: string;
516
+ album: string;
517
+ comment: string;
518
+ genre: string;
519
+ year: number;
520
+ track: number;
521
+
522
+ // Setters
523
+ setTitle(value: string): void;
524
+ setArtist(value: string): void;
525
+ setAlbum(value: string): void;
526
+ setComment(value: string): void;
527
+ setGenre(value: string): void;
528
+ setYear(value: number): void;
529
+ setTrack(value: number): void;
530
530
  }
531
531
  ```
532
532
 
533
+ ### PropertyMap type
534
+
535
+ ```typescript
536
+ type PropertyMap = { [key: string]: string[] };
537
+ ```
538
+
533
539
  ## 🎛️ Configuration
534
540
 
535
541
  ```typescript
@@ -548,7 +554,7 @@ interface TagLibConfig {
548
554
 
549
555
  | Runtime | Status | Installation | Performance | TypeScript |
550
556
  | ----------- | ------- | --------------------------------- | ----------- | ---------- |
551
- | **Deno** | ✅ Full | `jsr:@charleswiltgen/taglib-wasm` | Excellent | Native |
557
+ | **Deno** | ✅ Full | `npm:taglib-wasm` | Excellent | Native |
552
558
  | **Bun** | ✅ Full | `bun add taglib-wasm` | Excellent | Native |
553
559
  | **Node.js** | ✅ Full | `npm install taglib-wasm` | Good | Via loader |
554
560
  | **Browser** | ✅ Full | CDN/bundler | Good | Via build |