taglib-wasm 0.2.8 → 0.3.2
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 +276 -160
- package/build/taglib.js +1 -1
- package/build/taglib.wasm +0 -0
- package/index.ts +6 -0
- package/package.json +10 -9
- package/src/constants.ts +227 -0
- package/src/mod.ts +14 -3
- package/src/simple.ts +5 -6
- package/src/taglib.ts +29 -1
- package/src/types.ts +5 -0
- package/src/wasm-workers.ts +9 -1
- package/src/wasm.ts +30 -0
- package/src/workers.ts +50 -29
- package/src/simple-jsr.ts +0 -204
- package/src/taglib-jsr.ts +0 -48
- package/src/wasm-jsr.ts +0 -13
package/README.md
CHANGED
|
@@ -1,32 +1,52 @@
|
|
|
1
1
|
# taglib-wasm
|
|
2
2
|
|
|
3
|
-
This is the Wasm version of [**TagLib**](https://taglib.org/), the most robust,
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
3
|
+
This is the Wasm version of [**TagLib**](https://taglib.org/), the most robust,
|
|
4
|
+
de-facto standard for reading and editing metadata tags (Title, Album, Artist,
|
|
5
|
+
etc.) in all popular audio formats. `taglib-wasm` exists because the
|
|
6
|
+
JavaScipt/TypeScipt ecosystem had no battle-tested audio tagging library that
|
|
7
|
+
supports reading and writing music metadata to all popular audio formats — until
|
|
8
|
+
now!
|
|
9
|
+
|
|
10
|
+
`taglib-wasm` stands on the shoulders of giants, including
|
|
11
|
+
[TagLib](https://taglib.org/) itself, [Emscripten](https://emscripten.org/), and
|
|
12
|
+
[Wasm](https://webassembly.org/) ([WebAssembly](https://webassembly.org/)).
|
|
13
|
+
|
|
14
|
+
`taglib-wasm` aspires to be a universal solution for **JavaScript/TypeScript**
|
|
15
|
+
platforms — Deno, Node.js, Bun, web browsers, and Cloudflare Workers. Note: This
|
|
16
|
+
project is a baby, and you’re likely to experience some surprises at this stage
|
|
17
|
+
of its development. I’m extremely motivated to help address them, since I’ll
|
|
18
|
+
also be depending on this project.
|
|
8
19
|
|
|
9
20
|
## 🤔 Why?
|
|
10
21
|
|
|
11
|
-
Because there’s nothing like it. [`mp3tag.js`](https://mp3tag.js.org/) is mature
|
|
22
|
+
Because there’s nothing like it. [`mp3tag.js`](https://mp3tag.js.org/) is mature
|
|
23
|
+
and active, but only supports MP3 files and ID3 tags. TagLib was an ideal choice
|
|
24
|
+
from a maturity and capabilities point of view, but wrappers like `node-taglib`
|
|
25
|
+
appeared to be dormant, and I wanted to avoid making users install
|
|
26
|
+
platform-specific dependencies whenever possible.
|
|
12
27
|
|
|
13
28
|
## 🎯 Features
|
|
14
29
|
|
|
15
|
-
- **✅ Universal compatibility** – Works with Deno, Node.js, Bun, web browsers,
|
|
30
|
+
- **✅ Universal compatibility** – Works with Deno, Node.js, Bun, web browsers,
|
|
31
|
+
and Cloudflare Workers
|
|
16
32
|
- **✅ TypeScript first** – Complete type definitions and modern API
|
|
17
|
-
- **✅ Full audio format support** – Supports all audio formats supported by
|
|
18
|
-
|
|
33
|
+
- **✅ Full audio format support** – Supports all audio formats supported by
|
|
34
|
+
TagLib
|
|
35
|
+
- **✅ Format abstraction** – `taglib-wasm` deals with how tags are read
|
|
36
|
+
from/written to in different file formats
|
|
19
37
|
- **✅ Zero dependencies** – Self-contained WASM bundle
|
|
20
38
|
- **✅ Memory efficient** – In-memory processing without filesystem access
|
|
21
|
-
- **✅ Production ready** – Growing test suite helps ensure safety and
|
|
22
|
-
|
|
39
|
+
- **✅ Production ready** – Growing test suite helps ensure safety and
|
|
40
|
+
reliability
|
|
41
|
+
- **🆕 Two API styles** – Choose between Simple (3 functions) or Core (full
|
|
42
|
+
control) APIs
|
|
23
43
|
|
|
24
44
|
## 📦 Installation
|
|
25
45
|
|
|
26
46
|
### Deno
|
|
27
47
|
|
|
28
48
|
```typescript
|
|
29
|
-
import { TagLib } from "
|
|
49
|
+
import { TagLib } from "npm:taglib-wasm";
|
|
30
50
|
```
|
|
31
51
|
|
|
32
52
|
### Node.js
|
|
@@ -35,7 +55,19 @@ import { TagLib } from "jsr:@charleswiltgen/taglib-wasm";
|
|
|
35
55
|
npm install taglib-wasm
|
|
36
56
|
```
|
|
37
57
|
|
|
38
|
-
|
|
58
|
+
The package uses TypeScript. You have two options:
|
|
59
|
+
|
|
60
|
+
#### Option 1: Use Node’s native TypeScript support
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# Node.js 22.6.0+ with experimental flag
|
|
64
|
+
node --experimental-strip-types your-script.ts
|
|
65
|
+
|
|
66
|
+
# Node.js 23.6.0+ (no flag needed)
|
|
67
|
+
node your-script.ts
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
#### Option 2: TypeScript loader (recommended for production)
|
|
39
71
|
|
|
40
72
|
```bash
|
|
41
73
|
npm install --save-dev tsx
|
|
@@ -52,16 +84,17 @@ bun add taglib-wasm
|
|
|
52
84
|
|
|
53
85
|
### Simple API
|
|
54
86
|
|
|
55
|
-
|
|
87
|
+
This was inspired by [go-taglib](https://github.com/sentriz/go-taglib), a
|
|
88
|
+
similar project for Go created at about the same time.
|
|
56
89
|
|
|
57
90
|
```typescript
|
|
58
91
|
import { readProperties, readTags, writeTags } from "taglib-wasm/simple";
|
|
59
92
|
|
|
60
|
-
// Read tags
|
|
93
|
+
// Read tags
|
|
61
94
|
const tags = await readTags("song.mp3");
|
|
62
95
|
console.log(tags.title, tags.artist, tags.album);
|
|
63
96
|
|
|
64
|
-
// Write tags
|
|
97
|
+
// Write tags
|
|
65
98
|
await writeTags("song.mp3", {
|
|
66
99
|
title: "New Title",
|
|
67
100
|
artist: "New Artist",
|
|
@@ -75,16 +108,16 @@ console.log(`Duration: ${props.length}s, Bitrate: ${props.bitrate} kbps`);
|
|
|
75
108
|
|
|
76
109
|
### Core API
|
|
77
110
|
|
|
78
|
-
|
|
111
|
+
The Core API provides full control for more advanced applications.
|
|
79
112
|
|
|
80
113
|
```typescript
|
|
81
|
-
import { TagLib } from "
|
|
114
|
+
import { TagLib } from "taglib-wasm";
|
|
82
115
|
|
|
83
116
|
// Initialize taglib-wasm
|
|
84
117
|
const taglib = await TagLib.initialize();
|
|
85
118
|
|
|
86
119
|
// Load audio file from buffer
|
|
87
|
-
const audioData = await
|
|
120
|
+
const audioData = await readFile("song.mp3"); // Node.js/Bun: fs.readFile, Deno: Deno.readFile
|
|
88
121
|
const file = taglib.openFile(audioData);
|
|
89
122
|
|
|
90
123
|
// Read metadata
|
|
@@ -97,23 +130,90 @@ console.log(`Duration: ${props.length}s`);
|
|
|
97
130
|
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
98
131
|
|
|
99
132
|
// Write metadata
|
|
100
|
-
file.
|
|
101
|
-
|
|
102
|
-
|
|
133
|
+
const tag = file.tag();
|
|
134
|
+
tag.setTitle("New Title");
|
|
135
|
+
tag.setArtist("New Artist");
|
|
136
|
+
tag.setAlbum("New Album");
|
|
103
137
|
|
|
104
|
-
|
|
138
|
+
// Save changes
|
|
139
|
+
file.save();
|
|
105
140
|
|
|
106
|
-
|
|
107
|
-
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
108
|
-
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
109
|
-
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
141
|
+
console.log("Updated tags:", file.tag());
|
|
110
142
|
|
|
111
143
|
// Clean up
|
|
112
144
|
file.dispose();
|
|
113
145
|
```
|
|
114
146
|
|
|
147
|
+
### Tag Constants
|
|
148
|
+
|
|
149
|
+
taglib-wasm provides type-safe tag constants with IDE autocomplete:
|
|
150
|
+
|
|
151
|
+
```typescript
|
|
152
|
+
import { Tags } from "taglib-wasm";
|
|
153
|
+
|
|
154
|
+
// Use constants for better type safety and autocomplete
|
|
155
|
+
const properties = file.properties();
|
|
156
|
+
|
|
157
|
+
// Read properties
|
|
158
|
+
const title = properties[Tags.Title]?.[0];
|
|
159
|
+
const albumArtist = properties[Tags.AlbumArtist]?.[0];
|
|
160
|
+
const musicBrainzId = properties[Tags.MusicBrainzArtistId]?.[0];
|
|
161
|
+
|
|
162
|
+
// Write properties
|
|
163
|
+
file.setProperties({
|
|
164
|
+
[Tags.Title]: ["My Song"],
|
|
165
|
+
[Tags.AlbumArtist]: ["Various Artists"],
|
|
166
|
+
[Tags.Bpm]: ["128"]
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// All constants provide IDE autocomplete
|
|
170
|
+
Tags.Title // → "TITLE"
|
|
171
|
+
Tags.Artist // → "ARTIST"
|
|
172
|
+
Tags.AlbumArtist // → "ALBUMARTIST"
|
|
173
|
+
Tags.TrackGain // → "REPLAYGAIN_TRACK_GAIN"
|
|
174
|
+
// ... and many more
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
See [Tag Name Constants](docs/Tag-Name-Constants.md) for the complete list of available tags and format-specific mappings.
|
|
178
|
+
|
|
115
179
|
## Platform examples
|
|
116
180
|
|
|
181
|
+
### Deno
|
|
182
|
+
|
|
183
|
+
```typescript
|
|
184
|
+
import { TagLib } from "npm:taglib-wasm";
|
|
185
|
+
|
|
186
|
+
// Initialize taglib-wasm
|
|
187
|
+
const taglib = await TagLib.initialize();
|
|
188
|
+
|
|
189
|
+
// Load audio file from filesystem
|
|
190
|
+
const audioData = await Deno.readFile("song.mp3");
|
|
191
|
+
const file = taglib.openFile(audioData);
|
|
192
|
+
|
|
193
|
+
// Read metadata
|
|
194
|
+
const tags = file.tag();
|
|
195
|
+
const props = file.audioProperties();
|
|
196
|
+
|
|
197
|
+
console.log(`Title: ${tags.title}`);
|
|
198
|
+
console.log(`Artist: ${tags.artist}`);
|
|
199
|
+
console.log(`Duration: ${props.length}s`);
|
|
200
|
+
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
201
|
+
|
|
202
|
+
// Write metadata
|
|
203
|
+
const tag = file.tag();
|
|
204
|
+
tag.setTitle("New Title");
|
|
205
|
+
tag.setArtist("New Artist");
|
|
206
|
+
tag.setAlbum("New Album");
|
|
207
|
+
|
|
208
|
+
// Save changes
|
|
209
|
+
file.save();
|
|
210
|
+
|
|
211
|
+
console.log("Updated tags:", file.tag());
|
|
212
|
+
|
|
213
|
+
// Clean up
|
|
214
|
+
file.dispose();
|
|
215
|
+
```
|
|
216
|
+
|
|
117
217
|
### Node.js
|
|
118
218
|
|
|
119
219
|
```typescript
|
|
@@ -137,16 +237,15 @@ console.log(`Duration: ${props.length}s`);
|
|
|
137
237
|
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
138
238
|
|
|
139
239
|
// Write metadata
|
|
140
|
-
file.
|
|
141
|
-
|
|
142
|
-
|
|
240
|
+
const tag = file.tag();
|
|
241
|
+
tag.setTitle("New Title");
|
|
242
|
+
tag.setArtist("New Artist");
|
|
243
|
+
tag.setAlbum("New Album");
|
|
143
244
|
|
|
144
|
-
|
|
245
|
+
// Save changes
|
|
246
|
+
file.save();
|
|
145
247
|
|
|
146
|
-
|
|
147
|
-
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
148
|
-
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
149
|
-
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
248
|
+
console.log("Updated tags:", file.tag());
|
|
150
249
|
|
|
151
250
|
// Clean up
|
|
152
251
|
file.dispose();
|
|
@@ -174,16 +273,15 @@ console.log(`Duration: ${props.length}s`);
|
|
|
174
273
|
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
175
274
|
|
|
176
275
|
// Write metadata
|
|
177
|
-
file.
|
|
178
|
-
|
|
179
|
-
|
|
276
|
+
const tag = file.tag();
|
|
277
|
+
tag.setTitle("New Title");
|
|
278
|
+
tag.setArtist("New Artist");
|
|
279
|
+
tag.setAlbum("New Album");
|
|
180
280
|
|
|
181
|
-
|
|
281
|
+
// Save changes
|
|
282
|
+
file.save();
|
|
182
283
|
|
|
183
|
-
|
|
184
|
-
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
185
|
-
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
186
|
-
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
284
|
+
console.log("Updated tags:", file.tag());
|
|
187
285
|
|
|
188
286
|
// Clean up
|
|
189
287
|
file.dispose();
|
|
@@ -213,16 +311,15 @@ console.log(`Duration: ${props.length}s`);
|
|
|
213
311
|
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
214
312
|
|
|
215
313
|
// Write metadata
|
|
216
|
-
file.
|
|
217
|
-
|
|
218
|
-
|
|
314
|
+
const tag = file.tag();
|
|
315
|
+
tag.setTitle("New Title");
|
|
316
|
+
tag.setArtist("New Artist");
|
|
317
|
+
tag.setAlbum("New Album");
|
|
219
318
|
|
|
220
|
-
|
|
319
|
+
// Save changes
|
|
320
|
+
file.save();
|
|
221
321
|
|
|
222
|
-
|
|
223
|
-
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
224
|
-
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
225
|
-
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
322
|
+
console.log("Updated tags:", file.tag());
|
|
226
323
|
|
|
227
324
|
// Clean up
|
|
228
325
|
file.dispose();
|
|
@@ -237,7 +334,8 @@ export default {
|
|
|
237
334
|
async fetch(request: Request): Promise<Response> {
|
|
238
335
|
if (request.method === "POST") {
|
|
239
336
|
try {
|
|
240
|
-
// Initialize taglib-wasm
|
|
337
|
+
// Initialize taglib-wasm with Workers-specific configuration
|
|
338
|
+
// See docs/Cloudflare-Workers.md for memory configuration details
|
|
241
339
|
const taglib = await TagLib.initialize({
|
|
242
340
|
memory: { initial: 8 * 1024 * 1024 }, // 8MB for Workers
|
|
243
341
|
});
|
|
@@ -287,69 +385,79 @@ export default {
|
|
|
287
385
|
|
|
288
386
|
`tag-wasm` is designed to support all formats supported by TagLib:
|
|
289
387
|
|
|
290
|
-
- ✅ **.m4a (.mp4)** – Standard MPEG-4/AAC metadata for AAC and Apple Lossless
|
|
388
|
+
- ✅ **.m4a (.mp4)** – Standard MPEG-4/AAC metadata for AAC and Apple Lossless
|
|
389
|
+
audio
|
|
291
390
|
- ✅ **.mp3** – ID3v2 and ID3v1 tags
|
|
292
391
|
- ✅ **.flac** – Vorbis comments and audio properties
|
|
293
392
|
- ✅ **.wav** – INFO chunk metadata
|
|
294
|
-
- ✅ **Legacy formats
|
|
393
|
+
- ✅ **Legacy formats** – Opus, APE, MPC, WavPack, TrueAudio, and more
|
|
295
394
|
|
|
296
|
-
## 🎯
|
|
395
|
+
## 🎯 Extended Metadata with PropertyMap
|
|
297
396
|
|
|
298
|
-
`taglib-wasm`
|
|
397
|
+
`taglib-wasm` provides a **PropertyMap API** for accessing extended metadata
|
|
398
|
+
beyond the basic tags. This allows you to read and write format-specific fields
|
|
399
|
+
and custom metadata.
|
|
299
400
|
|
|
300
401
|
### AcoustID example
|
|
301
402
|
|
|
302
403
|
```typescript
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
//
|
|
310
|
-
|
|
404
|
+
import { Tags } from "taglib-wasm";
|
|
405
|
+
|
|
406
|
+
// Using PropertyMap API to set extended metadata with tag constants
|
|
407
|
+
file.setProperty(Tags.AcoustidFingerprint, "AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
408
|
+
file.setProperty(Tags.AcoustidId, "e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
409
|
+
|
|
410
|
+
// Or using string property names
|
|
411
|
+
file.setProperty("ACOUSTID_FINGERPRINT", "AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
412
|
+
|
|
413
|
+
// Note: Property keys may vary by format
|
|
414
|
+
// Use file.properties() to see all available properties
|
|
415
|
+
file.save(); // Don't forget to save!
|
|
311
416
|
```
|
|
312
417
|
|
|
313
418
|
### MusicBrainz example
|
|
314
419
|
|
|
315
420
|
```typescript
|
|
316
|
-
//
|
|
317
|
-
file.
|
|
318
|
-
file.
|
|
319
|
-
file.
|
|
421
|
+
// MusicBrainz metadata using PropertyMap with tag constants
|
|
422
|
+
file.setProperty(Tags.MusicBrainzTrackId, "f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
423
|
+
file.setProperty(Tags.MusicBrainzAlbumId, "a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
|
424
|
+
file.setProperty(
|
|
425
|
+
Tags.MusicBrainzArtistId,
|
|
426
|
+
"12345678-90ab-cdef-1234-567890abcdef",
|
|
427
|
+
);
|
|
320
428
|
```
|
|
321
429
|
|
|
322
430
|
### Volume example
|
|
323
431
|
|
|
324
432
|
```typescript
|
|
325
|
-
// ReplayGain
|
|
326
|
-
file.
|
|
327
|
-
file.
|
|
328
|
-
file.
|
|
329
|
-
file.
|
|
330
|
-
|
|
331
|
-
// Apple Sound Check support
|
|
332
|
-
file.setAppleSoundCheck("00000150 00000150 00000150 00000150...");
|
|
433
|
+
// ReplayGain volume normalization with tag constants
|
|
434
|
+
file.setProperty(Tags.TrackGain, "-6.54 dB");
|
|
435
|
+
file.setProperty(Tags.TrackPeak, "0.987654");
|
|
436
|
+
file.setProperty(Tags.AlbumGain, "-8.12 dB");
|
|
437
|
+
file.setProperty(Tags.AlbumPeak, "0.995432");
|
|
333
438
|
```
|
|
334
439
|
|
|
335
440
|
### Extended fields
|
|
336
441
|
|
|
337
442
|
```typescript
|
|
338
|
-
//
|
|
339
|
-
file.
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
443
|
+
// Using PropertyMap to set multiple properties at once
|
|
444
|
+
const properties = file.properties(); // Get current properties
|
|
445
|
+
|
|
446
|
+
// Set extended metadata with tag constants
|
|
447
|
+
file.setProperties({
|
|
448
|
+
[Tags.AlbumArtist]: ["Various Artists"],
|
|
449
|
+
[Tags.Composer]: ["Composer Name"],
|
|
450
|
+
[Tags.Bpm]: ["120"],
|
|
451
|
+
[Tags.Compilation]: ["1"],
|
|
452
|
+
[Tags.DiscNumber]: ["1"],
|
|
453
|
+
[Tags.TrackTotal]: ["12"],
|
|
454
|
+
// Note: Property keys vary by format
|
|
349
455
|
});
|
|
350
|
-
```
|
|
351
456
|
|
|
352
|
-
|
|
457
|
+
// Or set individual properties
|
|
458
|
+
file.setProperty(Tags.AlbumArtist, "Various Artists");
|
|
459
|
+
file.setProperty(Tags.Composer, "Composer Name");
|
|
460
|
+
```
|
|
353
461
|
|
|
354
462
|
## 🏗️ Development
|
|
355
463
|
|
|
@@ -384,8 +492,7 @@ build/
|
|
|
384
492
|
├── taglib.js # Generated Emscripten JavaScript
|
|
385
493
|
└── taglib.wasm # Compiled WebAssembly module
|
|
386
494
|
|
|
387
|
-
tests/ # Test
|
|
388
|
-
tests/ # Test suite
|
|
495
|
+
tests/ # Test suite and sample audio files
|
|
389
496
|
examples/ # Usage examples for different runtimes
|
|
390
497
|
├── deno/ # Deno-specific examples
|
|
391
498
|
├── bun/ # Bun-specific examples
|
|
@@ -412,23 +519,29 @@ npm test
|
|
|
412
519
|
# ✅ MP3 - ID3v1/v2 tag support
|
|
413
520
|
# ✅ FLAC - Vorbis comments and properties
|
|
414
521
|
# ✅ OGG - Vorbis comments
|
|
415
|
-
# ✅ M4A -
|
|
522
|
+
# ✅ M4A - MPEG-4 (AAC and Apple Lossless) metadata
|
|
416
523
|
```
|
|
417
524
|
|
|
418
525
|
## 🔧 Technical Implementation
|
|
419
526
|
|
|
420
527
|
### Key architecture decisions
|
|
421
528
|
|
|
422
|
-
1. **Memory Management**: Uses Emscripten's `allocate()` for reliable JS↔WASM
|
|
423
|
-
|
|
529
|
+
1. **Memory Management**: Uses Emscripten's `allocate()` for reliable JS↔WASM
|
|
530
|
+
data transfer
|
|
531
|
+
2. **Buffer-Based Processing**: `TagLib::ByteVectorStream` enables in-memory
|
|
532
|
+
file processing
|
|
424
533
|
3. **C++ Wrapper**: Custom C functions bridge TagLib's C++ API to WASM exports
|
|
425
|
-
4. **Type Safety**: Complete TypeScript definitions for all audio formats and
|
|
534
|
+
4. **Type Safety**: Complete TypeScript definitions for all audio formats and
|
|
535
|
+
metadata
|
|
426
536
|
|
|
427
537
|
### Critical implementation details
|
|
428
538
|
|
|
429
|
-
- **ByteVectorStream**: Enables processing audio files from memory buffers
|
|
430
|
-
|
|
431
|
-
- **
|
|
539
|
+
- **ByteVectorStream**: Enables processing audio files from memory buffers
|
|
540
|
+
without filesystem
|
|
541
|
+
- **ID-based Object Management**: C++ objects managed via integer IDs for memory
|
|
542
|
+
safety
|
|
543
|
+
- **Emscripten allocate()**: Ensures proper memory synchronization between JS
|
|
544
|
+
and WASM
|
|
432
545
|
- **UTF-8 String Handling**: Proper encoding for international metadata
|
|
433
546
|
|
|
434
547
|
## 📚 API Reference
|
|
@@ -449,98 +562,101 @@ class TagLib {
|
|
|
449
562
|
class AudioFile {
|
|
450
563
|
// Validation
|
|
451
564
|
isValid(): boolean;
|
|
452
|
-
|
|
565
|
+
getFormat(): string;
|
|
453
566
|
|
|
454
567
|
// Properties
|
|
455
568
|
audioProperties(): AudioProperties;
|
|
456
|
-
tag(): TagData;
|
|
457
569
|
|
|
458
|
-
// Tag
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
570
|
+
// Tag Access (returns Tag object with getters and setters)
|
|
571
|
+
tag(): Tag;
|
|
572
|
+
|
|
573
|
+
// PropertyMap API for extended metadata
|
|
574
|
+
properties(): PropertyMap;
|
|
575
|
+
setProperties(properties: PropertyMap): void;
|
|
576
|
+
getProperty(key: string): string | undefined;
|
|
577
|
+
setProperty(key: string, value: string): void;
|
|
578
|
+
|
|
579
|
+
// MP4-specific methods
|
|
580
|
+
isMP4(): boolean;
|
|
581
|
+
getMP4Item(key: string): string | undefined;
|
|
582
|
+
setMP4Item(key: string, value: string): void;
|
|
583
|
+
removeMP4Item(key: string): void;
|
|
466
584
|
|
|
467
585
|
// File Operations
|
|
468
586
|
save(): boolean;
|
|
587
|
+
getFileBuffer(): Uint8Array;
|
|
469
588
|
dispose(): void;
|
|
470
|
-
|
|
471
|
-
// Automatic Tag Mapping (Format-Agnostic)
|
|
472
|
-
extendedTag(): ExtendedTag;
|
|
473
|
-
setExtendedTag(tag: Partial<ExtendedTag>): void;
|
|
474
|
-
|
|
475
|
-
// AcoustID Integration
|
|
476
|
-
setAcoustidFingerprint(fingerprint: string): void;
|
|
477
|
-
getAcoustidFingerprint(): string | undefined;
|
|
478
|
-
setAcoustidId(id: string): void;
|
|
479
|
-
getAcoustidId(): string | undefined;
|
|
480
|
-
|
|
481
|
-
// MusicBrainz Integration
|
|
482
|
-
setMusicBrainzTrackId(id: string): void;
|
|
483
|
-
getMusicBrainzTrackId(): string | undefined;
|
|
484
|
-
|
|
485
|
-
// Volume Normalization
|
|
486
|
-
setReplayGainTrackGain(gain: string): void;
|
|
487
|
-
getReplayGainTrackGain(): string | undefined;
|
|
488
|
-
setReplayGainTrackPeak(peak: string): void;
|
|
489
|
-
getReplayGainTrackPeak(): string | undefined;
|
|
490
|
-
setReplayGainAlbumGain(gain: string): void;
|
|
491
|
-
getReplayGainAlbumGain(): string | undefined;
|
|
492
|
-
setReplayGainAlbumPeak(peak: string): void;
|
|
493
|
-
getReplayGainAlbumPeak(): string | undefined;
|
|
494
|
-
setAppleSoundCheck(iTunNORM: string): void;
|
|
495
|
-
getAppleSoundCheck(): string | undefined;
|
|
496
589
|
}
|
|
497
590
|
```
|
|
498
591
|
|
|
499
|
-
|
|
592
|
+
### Tag interface
|
|
500
593
|
|
|
501
594
|
```typescript
|
|
502
|
-
interface
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
595
|
+
interface Tag {
|
|
596
|
+
// Basic metadata (getters)
|
|
597
|
+
title: string;
|
|
598
|
+
artist: string;
|
|
599
|
+
album: string;
|
|
600
|
+
comment: string;
|
|
601
|
+
genre: string;
|
|
602
|
+
year: number;
|
|
603
|
+
track: number;
|
|
604
|
+
|
|
605
|
+
// Setters
|
|
606
|
+
setTitle(value: string): void;
|
|
607
|
+
setArtist(value: string): void;
|
|
608
|
+
setAlbum(value: string): void;
|
|
609
|
+
setComment(value: string): void;
|
|
610
|
+
setGenre(value: string): void;
|
|
611
|
+
setYear(value: number): void;
|
|
612
|
+
setTrack(value: number): void;
|
|
508
613
|
}
|
|
509
614
|
```
|
|
510
615
|
|
|
616
|
+
### PropertyMap type
|
|
617
|
+
|
|
618
|
+
```typescript
|
|
619
|
+
type PropertyMap = { [key: string]: string[] };
|
|
620
|
+
```
|
|
621
|
+
|
|
622
|
+
## 📖 Additional Documentation
|
|
623
|
+
|
|
624
|
+
- [**Tag Name Constants**](docs/Tag-Name-Constants.md) - Comprehensive reference for standard tag names and cross-format mapping
|
|
625
|
+
- [**Automatic Tag Mapping**](docs/Automatic-Tag-Mapping.md) - How taglib-wasm handles format-specific tag differences
|
|
626
|
+
- [**Implementation Details**](docs/Implementation.md) - Technical details about the Wasm implementation
|
|
627
|
+
- [**Runtime Compatibility**](docs/Runtime-Compatibility.md) - Platform-specific setup and considerations
|
|
628
|
+
|
|
511
629
|
## 🌐 Runtime Compatibility
|
|
512
630
|
|
|
513
|
-
`taglib-wasm` works
|
|
631
|
+
`taglib-wasm` works across all major JavaScript runtimes:
|
|
514
632
|
|
|
515
|
-
| Runtime | Status | Installation
|
|
516
|
-
| ----------- | ------- |
|
|
517
|
-
| **Deno** | ✅ Full | `
|
|
518
|
-
| **Bun** | ✅ Full | `bun add taglib-wasm`
|
|
519
|
-
| **Node.js** | ✅ Full | `npm install taglib-wasm`
|
|
520
|
-
| **Browser** | ✅ Full | CDN/bundler
|
|
633
|
+
| Runtime | Status | Installation | Performance | TypeScript |
|
|
634
|
+
| ----------- | ------- | ------------------------- | ----------- | ---------- |
|
|
635
|
+
| **Deno** | ✅ Full | `npm:taglib-wasm` | Excellent | Native |
|
|
636
|
+
| **Bun** | ✅ Full | `bun add taglib-wasm` | Excellent | Native |
|
|
637
|
+
| **Node.js** | ✅ Full | `npm install taglib-wasm` | Good | Native/tsx |
|
|
638
|
+
| **Browser** | ✅ Full | CDN/bundler | Good | Via build |
|
|
521
639
|
|
|
522
|
-
**📖 See [docs/Runtime-Compatibility.md](docs/Runtime-Compatibility.md) for
|
|
640
|
+
**📖 See [docs/Runtime-Compatibility.md](docs/Runtime-Compatibility.md) for
|
|
641
|
+
detailed runtime information**
|
|
523
642
|
|
|
524
643
|
## 🚧 Known Limitations
|
|
525
644
|
|
|
526
|
-
- **File Writing
|
|
527
|
-
|
|
528
|
-
- **
|
|
645
|
+
- **File Writing** – Saves only affect in-memory representation (no filesystem
|
|
646
|
+
persistence)
|
|
647
|
+
- **Large Files** – Memory usage scales with file size (entire file loaded into
|
|
648
|
+
memory)
|
|
649
|
+
- **Concurrent Access** – Not thread-safe (JavaScript single-threaded nature)
|
|
529
650
|
|
|
530
651
|
## 🤝 Contributing
|
|
531
652
|
|
|
532
|
-
Contributions welcome!
|
|
533
|
-
|
|
534
|
-
- Additional format support (DSF, DSDIFF, etc.)
|
|
535
|
-
- Advanced metadata implementation (PropertyMap integration)
|
|
536
|
-
- Performance optimizations
|
|
537
|
-
- Runtime-specific optimizations
|
|
538
|
-
- Documentation improvements
|
|
653
|
+
Contributions welcome!
|
|
539
654
|
|
|
540
655
|
## 📄 License
|
|
541
656
|
|
|
542
657
|
- **This project**: MIT License (see [LICENSE](LICENSE))
|
|
543
|
-
- **TagLib library**: LGPL/MPL dual license (see
|
|
658
|
+
- **TagLib library**: LGPL/MPL dual license (see
|
|
659
|
+
[lib/taglib/COPYING.LGPL](lib/taglib/COPYING.LGPL))
|
|
544
660
|
|
|
545
661
|
## 🙏 Acknowledgments
|
|
546
662
|
|