taglib-wasm 0.1.0
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/LICENSE +21 -0
- package/README.md +452 -0
- package/build/taglib.js +35 -0
- package/build/taglib.wasm +0 -0
- package/lib/taglib/COPYING.LGPL +502 -0
- package/lib/taglib/COPYING.MPL +470 -0
- package/lib/taglib/README.md +24 -0
- package/package.json +63 -0
- package/src/enhanced-api.ts +274 -0
- package/src/mod.ts +8 -0
- package/src/taglib.ts +441 -0
- package/src/types.ts +353 -0
- package/src/wasm.ts +232 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Charles Wiltgen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,452 @@
|
|
|
1
|
+
# taglib-wasm
|
|
2
|
+
|
|
3
|
+
[TagLib](https://taglib.org/) is the de-facto standard library 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.
|
|
4
|
+
|
|
5
|
+
`taglib-wasm` makes the robust, industry-standard TagLib tagging library available to browser, Deno, Node.js, and Bun apps for the first time, thanks to the magic of β¨[Wasm](https://webassembly.org/)β¨ ([WebAssembly](https://webassembly.org/)).
|
|
6
|
+
|
|
7
|
+
### Why?
|
|
8
|
+
|
|
9
|
+
In the process of building my own utility to improve the metadata of my own music collection, I discovered that the JavaScipt/TypeScipt ecosystem has no battle-tested audio tagging library that supports reading and writing music metadata to all popular audio formats.
|
|
10
|
+
|
|
11
|
+
[`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.
|
|
12
|
+
|
|
13
|
+
## π― Features
|
|
14
|
+
|
|
15
|
+
- **β
Universal compatibility** β Works in browsers, Deno, Node.js, and Bun
|
|
16
|
+
- **β
TypeScript first** β Complete type definitions and modern API
|
|
17
|
+
- **β
Full audio format support** β Supports all audio formats supported by TagLib
|
|
18
|
+
- **β
Format abstraction** β `taglib-wasm` deals with how tags are read from/written to in different file formats
|
|
19
|
+
- **β
Zero dependencies** β Self-contained WASM bundle
|
|
20
|
+
- **β
Memory efficient** β In-memory processing without filesystem access
|
|
21
|
+
- **β
Production ready** β Growing test suite helps ensure safety and reliability
|
|
22
|
+
|
|
23
|
+
## π¦ Installation
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
# For Deno projects
|
|
27
|
+
import { TagLib } from "jsr:@taglib/wasm";
|
|
28
|
+
|
|
29
|
+
# For NPM/Node.js projects
|
|
30
|
+
npm install taglib-wasm
|
|
31
|
+
|
|
32
|
+
# For Bun projects
|
|
33
|
+
bun add taglib-wasm
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## π Quick Start
|
|
37
|
+
|
|
38
|
+
### Deno
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
import { TagLib } from "./src/mod.ts";
|
|
42
|
+
|
|
43
|
+
// Initialize TagLib WASM
|
|
44
|
+
const taglib = await TagLib.initialize();
|
|
45
|
+
|
|
46
|
+
// Load audio file from buffer
|
|
47
|
+
const audioData = await Deno.readFile("song.mp3");
|
|
48
|
+
const file = taglib.openFile(audioData);
|
|
49
|
+
|
|
50
|
+
// Read metadata
|
|
51
|
+
const tags = file.tag();
|
|
52
|
+
const props = file.audioProperties();
|
|
53
|
+
|
|
54
|
+
console.log(`Title: ${tags.title}`);
|
|
55
|
+
console.log(`Artist: ${tags.artist}`);
|
|
56
|
+
console.log(`Duration: ${props.length}s`);
|
|
57
|
+
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
58
|
+
|
|
59
|
+
// Write metadata
|
|
60
|
+
file.setTitle("New Title");
|
|
61
|
+
file.setArtist("New Artist");
|
|
62
|
+
file.setAlbum("New Album");
|
|
63
|
+
|
|
64
|
+
console.log("Updated tags:", file.tag());
|
|
65
|
+
|
|
66
|
+
// Advanced metadata (format-agnostic)
|
|
67
|
+
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
68
|
+
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
69
|
+
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
70
|
+
|
|
71
|
+
// Clean up
|
|
72
|
+
file.dispose();
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Bun
|
|
76
|
+
|
|
77
|
+
```typescript
|
|
78
|
+
import { TagLib } from 'taglib-wasm';
|
|
79
|
+
|
|
80
|
+
// Initialize TagLib WASM
|
|
81
|
+
const taglib = await TagLib.initialize();
|
|
82
|
+
|
|
83
|
+
// Load from file system (Bun's native file API)
|
|
84
|
+
const audioData = await Bun.file("song.mp3").arrayBuffer();
|
|
85
|
+
const file = taglib.openFile(new Uint8Array(audioData));
|
|
86
|
+
|
|
87
|
+
// Read metadata
|
|
88
|
+
const tags = file.tag();
|
|
89
|
+
const props = file.audioProperties();
|
|
90
|
+
|
|
91
|
+
console.log(`Title: ${tags.title}`);
|
|
92
|
+
console.log(`Artist: ${tags.artist}`);
|
|
93
|
+
console.log(`Duration: ${props.length}s`);
|
|
94
|
+
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
95
|
+
|
|
96
|
+
// Write metadata
|
|
97
|
+
file.setTitle("New Title");
|
|
98
|
+
file.setArtist("New Artist");
|
|
99
|
+
file.setAlbum("New Album");
|
|
100
|
+
|
|
101
|
+
console.log("Updated tags:", file.tag());
|
|
102
|
+
|
|
103
|
+
// Advanced metadata (format-agnostic)
|
|
104
|
+
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
105
|
+
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
106
|
+
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
107
|
+
|
|
108
|
+
// Clean up
|
|
109
|
+
file.dispose();
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Node.js
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { TagLib } from 'taglib-wasm';
|
|
116
|
+
import { readFile } from 'fs/promises';
|
|
117
|
+
|
|
118
|
+
// Initialize TagLib WASM
|
|
119
|
+
const taglib = await TagLib.initialize();
|
|
120
|
+
|
|
121
|
+
// Load audio file from filesystem
|
|
122
|
+
const audioData = await readFile("song.mp3");
|
|
123
|
+
const file = taglib.openFile(audioData);
|
|
124
|
+
|
|
125
|
+
// Read metadata
|
|
126
|
+
const tags = file.tag();
|
|
127
|
+
const props = file.audioProperties();
|
|
128
|
+
|
|
129
|
+
console.log(`Title: ${tags.title}`);
|
|
130
|
+
console.log(`Artist: ${tags.artist}`);
|
|
131
|
+
console.log(`Duration: ${props.length}s`);
|
|
132
|
+
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
133
|
+
|
|
134
|
+
// Write metadata
|
|
135
|
+
file.setTitle("New Title");
|
|
136
|
+
file.setArtist("New Artist");
|
|
137
|
+
file.setAlbum("New Album");
|
|
138
|
+
|
|
139
|
+
console.log("Updated tags:", file.tag());
|
|
140
|
+
|
|
141
|
+
// Advanced metadata (format-agnostic)
|
|
142
|
+
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
143
|
+
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
144
|
+
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
145
|
+
|
|
146
|
+
// Clean up
|
|
147
|
+
file.dispose();
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Browser
|
|
151
|
+
|
|
152
|
+
```typescript
|
|
153
|
+
import { TagLib } from 'taglib-wasm';
|
|
154
|
+
|
|
155
|
+
// Initialize TagLib WASM
|
|
156
|
+
const taglib = await TagLib.initialize();
|
|
157
|
+
|
|
158
|
+
// Load from file input or fetch
|
|
159
|
+
const fileInput = document.querySelector('input[type="file"]');
|
|
160
|
+
const audioFile = fileInput.files[0];
|
|
161
|
+
const audioData = new Uint8Array(await audioFile.arrayBuffer());
|
|
162
|
+
const file = taglib.openFile(audioData);
|
|
163
|
+
|
|
164
|
+
// Read metadata
|
|
165
|
+
const tags = file.tag();
|
|
166
|
+
const props = file.audioProperties();
|
|
167
|
+
|
|
168
|
+
console.log(`Title: ${tags.title}`);
|
|
169
|
+
console.log(`Artist: ${tags.artist}`);
|
|
170
|
+
console.log(`Duration: ${props.length}s`);
|
|
171
|
+
console.log(`Bitrate: ${props.bitrate} kbps`);
|
|
172
|
+
|
|
173
|
+
// Write metadata
|
|
174
|
+
file.setTitle("New Title");
|
|
175
|
+
file.setArtist("New Artist");
|
|
176
|
+
file.setAlbum("New Album");
|
|
177
|
+
|
|
178
|
+
console.log("Updated tags:", file.tag());
|
|
179
|
+
|
|
180
|
+
// Advanced metadata (format-agnostic)
|
|
181
|
+
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
182
|
+
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
183
|
+
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
184
|
+
|
|
185
|
+
// Clean up
|
|
186
|
+
file.dispose();
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## π Supported Formats
|
|
190
|
+
|
|
191
|
+
All formats are **fully tested and working**:
|
|
192
|
+
|
|
193
|
+
- β
**MP3** β ID3v2 and ID3v1 tags
|
|
194
|
+
- β
**MP4/M4A** β Standard MPEG (iTunes-compatible) metadata atoms
|
|
195
|
+
- β
**FLAC** β Vorbis comments and audio properties
|
|
196
|
+
- β
**OGG Vorbis** β Vorbis comments
|
|
197
|
+
- β
**WAV** β INFO chunk metadata
|
|
198
|
+
- π **Additional formats**: Opus, APE, MPC, WavPack, TrueAudio, and more
|
|
199
|
+
|
|
200
|
+
## π― Advanced Metadata Support
|
|
201
|
+
|
|
202
|
+
TagLib WASM supports **format-agnostic tag naming** so you donβt have to worry about how the same tag is stored differently in different audio container formats.
|
|
203
|
+
|
|
204
|
+
### AcoustID example
|
|
205
|
+
```typescript
|
|
206
|
+
// Single API works for ALL formats (MP3, FLAC, OGG, MP4)
|
|
207
|
+
file.setAcoustidFingerprint("AQADtMmybfGO8NCNEESLnzHyXNOHeHnG...");
|
|
208
|
+
file.setAcoustidId("e7359e88-f1f7-41ed-b9f6-16e58e906997");
|
|
209
|
+
|
|
210
|
+
// Automatically stores in format-specific locations:
|
|
211
|
+
// β’ MP3: TXXX frames with proper descriptions
|
|
212
|
+
// β’ FLAC/OGG: ACOUSTID_FINGERPRINT and ACOUSTID_ID Vorbis comments
|
|
213
|
+
// β’ MP4: ----:com.apple.iTunes:Acoustid... freeform atoms
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### MusicBrainz example
|
|
217
|
+
```typescript
|
|
218
|
+
// Professional music database integration
|
|
219
|
+
file.setMusicBrainzTrackId("f4d1b6b8-8c1e-4d9a-9f2a-1234567890ab");
|
|
220
|
+
file.setMusicBrainzReleaseId("a1b2c3d4-e5f6-7890-abcd-ef1234567890");
|
|
221
|
+
file.setMusicBrainzArtistId("12345678-90ab-cdef-1234-567890abcdef");
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
### Volume example
|
|
225
|
+
```typescript
|
|
226
|
+
// ReplayGain support (automatic format mapping)
|
|
227
|
+
file.setReplayGainTrackGain("-6.54 dB");
|
|
228
|
+
file.setReplayGainTrackPeak("0.987654");
|
|
229
|
+
file.setReplayGainAlbumGain("-8.12 dB");
|
|
230
|
+
file.setReplayGainAlbumPeak("0.995432");
|
|
231
|
+
|
|
232
|
+
// Apple Sound Check support
|
|
233
|
+
file.setAppleSoundCheck("00000150 00000150 00000150 00000150...");
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
### Extended fields
|
|
237
|
+
```typescript
|
|
238
|
+
// Advanced metadata fields
|
|
239
|
+
file.setExtendedTag({
|
|
240
|
+
albumArtist: "Various Artists",
|
|
241
|
+
composer: "Composer Name",
|
|
242
|
+
bpm: 120,
|
|
243
|
+
compilation: true,
|
|
244
|
+
discNumber: 1,
|
|
245
|
+
totalTracks: 12,
|
|
246
|
+
// Volume normalization
|
|
247
|
+
replayGainTrackGain: "-6.54 dB",
|
|
248
|
+
appleSoundCheck: "00000150...",
|
|
249
|
+
});
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
**π See [ADVANCED_METADATA.md](ADVANCED_METADATA.md) for complete documentation**
|
|
253
|
+
|
|
254
|
+
## ποΈ Development
|
|
255
|
+
|
|
256
|
+
### Build from Source
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
# Prerequisites: Emscripten SDK
|
|
260
|
+
# Install via: https://emscripten.org/docs/getting_started/downloads.html
|
|
261
|
+
|
|
262
|
+
# Clone and build
|
|
263
|
+
git clone <repository>
|
|
264
|
+
cd taglib-wasm
|
|
265
|
+
|
|
266
|
+
# Build WASM module
|
|
267
|
+
deno task build:wasm
|
|
268
|
+
|
|
269
|
+
# Run tests
|
|
270
|
+
deno task test
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### Project Structure
|
|
274
|
+
|
|
275
|
+
```
|
|
276
|
+
src/
|
|
277
|
+
βββ mod.ts # Main module exports
|
|
278
|
+
βββ taglib.ts # Core TagLib and AudioFile classes
|
|
279
|
+
βββ types.ts # TypeScript type definitions
|
|
280
|
+
βββ wasm.ts # WASM module interface and utilities
|
|
281
|
+
|
|
282
|
+
build/
|
|
283
|
+
βββ build-wasm.sh # Complete build script with C++ wrapper
|
|
284
|
+
βββ taglib.js # Generated Emscripten JavaScript
|
|
285
|
+
βββ taglib.wasm # Compiled WebAssembly module
|
|
286
|
+
|
|
287
|
+
test-files/ # Sample audio files for testing
|
|
288
|
+
tests/ # Test suite
|
|
289
|
+
examples/ # Usage examples for different runtimes
|
|
290
|
+
βββ deno/ # Deno-specific examples
|
|
291
|
+
βββ bun/ # Bun-specific examples
|
|
292
|
+
βββ basic-usage.ts # General usage example
|
|
293
|
+
βββ *.ts # Advanced feature examples
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
## π§ͺ Testing
|
|
297
|
+
|
|
298
|
+
Comprehensive test suite validates all functionality:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
# Run with Deno
|
|
302
|
+
deno run --allow-read test-systematic.ts
|
|
303
|
+
|
|
304
|
+
# Run with Bun
|
|
305
|
+
bun run test-systematic.ts
|
|
306
|
+
|
|
307
|
+
# Run with Node.js
|
|
308
|
+
npm test
|
|
309
|
+
|
|
310
|
+
# Results: 5/5 formats working β
across all runtimes
|
|
311
|
+
# β
WAV - 44.1kHz, stereo, tag reading/writing
|
|
312
|
+
# β
MP3 - 44.1kHz, stereo, ID3 tag support
|
|
313
|
+
# β
FLAC - 44.1kHz, stereo, Vorbis comments
|
|
314
|
+
# β
OGG - 44.1kHz, stereo, Vorbis comments
|
|
315
|
+
# β
M4A - 44.1kHz, stereo, iTunes metadata
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
## π§ Technical Implementation
|
|
319
|
+
|
|
320
|
+
### Key architecture decisions
|
|
321
|
+
|
|
322
|
+
1. **Memory Management**: Uses Emscripten's `allocate()` for reliable JSβWASM data transfer
|
|
323
|
+
2. **Buffer-Based Processing**: `TagLib::ByteVectorStream` enables in-memory file processing
|
|
324
|
+
3. **C++ Wrapper**: Custom C functions bridge TagLib's C++ API to WASM exports
|
|
325
|
+
4. **Type Safety**: Complete TypeScript definitions for all audio formats and metadata
|
|
326
|
+
|
|
327
|
+
### Critical implementation details
|
|
328
|
+
|
|
329
|
+
- **ByteVectorStream**: Enables processing audio files from memory buffers without filesystem
|
|
330
|
+
- **ID-based Object Management**: C++ objects managed via integer IDs for memory safety
|
|
331
|
+
- **Emscripten allocate()**: Ensures proper memory synchronization between JS and WASM
|
|
332
|
+
- **UTF-8 String Handling**: Proper encoding for international metadata
|
|
333
|
+
|
|
334
|
+
## π API Reference
|
|
335
|
+
|
|
336
|
+
### TagLib class
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
class TagLib {
|
|
340
|
+
static async initialize(config?: TagLibConfig): Promise<TagLib>
|
|
341
|
+
openFile(buffer: Uint8Array): AudioFile
|
|
342
|
+
getModule(): TagLibModule
|
|
343
|
+
}
|
|
344
|
+
```
|
|
345
|
+
|
|
346
|
+
### AudioFile class
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
class AudioFile {
|
|
350
|
+
// Validation
|
|
351
|
+
isValid(): boolean
|
|
352
|
+
format(): string
|
|
353
|
+
|
|
354
|
+
// Properties
|
|
355
|
+
audioProperties(): AudioProperties
|
|
356
|
+
tag(): TagData
|
|
357
|
+
|
|
358
|
+
// Tag Writing
|
|
359
|
+
setTitle(title: string): void
|
|
360
|
+
setArtist(artist: string): void
|
|
361
|
+
setAlbum(album: string): void
|
|
362
|
+
setComment(comment: string): void
|
|
363
|
+
setGenre(genre: string): void
|
|
364
|
+
setYear(year: number): void
|
|
365
|
+
setTrack(track: number): void
|
|
366
|
+
|
|
367
|
+
// File Operations
|
|
368
|
+
save(): boolean
|
|
369
|
+
dispose(): void
|
|
370
|
+
|
|
371
|
+
// Advanced Metadata (Format-Agnostic)
|
|
372
|
+
extendedTag(): ExtendedTag
|
|
373
|
+
setExtendedTag(tag: Partial<ExtendedTag>): void
|
|
374
|
+
|
|
375
|
+
// AcoustID Integration
|
|
376
|
+
setAcoustidFingerprint(fingerprint: string): void
|
|
377
|
+
getAcoustidFingerprint(): string | undefined
|
|
378
|
+
setAcoustidId(id: string): void
|
|
379
|
+
getAcoustidId(): string | undefined
|
|
380
|
+
|
|
381
|
+
// MusicBrainz Integration
|
|
382
|
+
setMusicBrainzTrackId(id: string): void
|
|
383
|
+
getMusicBrainzTrackId(): string | undefined
|
|
384
|
+
|
|
385
|
+
// Volume Normalization
|
|
386
|
+
setReplayGainTrackGain(gain: string): void
|
|
387
|
+
getReplayGainTrackGain(): string | undefined
|
|
388
|
+
setReplayGainTrackPeak(peak: string): void
|
|
389
|
+
getReplayGainTrackPeak(): string | undefined
|
|
390
|
+
setReplayGainAlbumGain(gain: string): void
|
|
391
|
+
getReplayGainAlbumGain(): string | undefined
|
|
392
|
+
setReplayGainAlbumPeak(peak: string): void
|
|
393
|
+
getReplayGainAlbumPeak(): string | undefined
|
|
394
|
+
setAppleSoundCheck(iTunNORM: string): void
|
|
395
|
+
getAppleSoundCheck(): string | undefined
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## ποΈ Configuration
|
|
400
|
+
|
|
401
|
+
```typescript
|
|
402
|
+
interface TagLibConfig {
|
|
403
|
+
memory?: {
|
|
404
|
+
initial?: number; // Initial memory size (default: 16MB)
|
|
405
|
+
maximum?: number; // Maximum memory size (default: 256MB)
|
|
406
|
+
};
|
|
407
|
+
debug?: boolean; // Enable debug output
|
|
408
|
+
}
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## π Runtime Compatibility
|
|
412
|
+
|
|
413
|
+
TagLib WASM works seamlessly across all major JavaScript runtimes:
|
|
414
|
+
|
|
415
|
+
| Runtime | Status | Installation | Performance | TypeScript |
|
|
416
|
+
|---------|--------|--------------|-------------|------------|
|
|
417
|
+
| **Deno** | β
Full | `jsr:@taglib/wasm` | Excellent | Native |
|
|
418
|
+
| **Bun** | β
Full | `bun add taglib-wasm` | Excellent | Native |
|
|
419
|
+
| **Node.js** | β
Full | `npm install taglib-wasm` | Good | Via loader |
|
|
420
|
+
| **Browser** | β
Full | CDN/bundler | Good | Via build |
|
|
421
|
+
|
|
422
|
+
**π See [RUNTIME_COMPATIBILITY.md](RUNTIME_COMPATIBILITY.md) for detailed runtime information**
|
|
423
|
+
|
|
424
|
+
## π§ Known Limitations
|
|
425
|
+
|
|
426
|
+
- **File Writing**: Saves only affect in-memory representation (no filesystem persistence)
|
|
427
|
+
- **Large Files**: Memory usage scales with file size (entire file loaded into memory)
|
|
428
|
+
- **Concurrent Access**: Not thread-safe (JavaScript single-threaded nature)
|
|
429
|
+
|
|
430
|
+
## π€ Contributing
|
|
431
|
+
|
|
432
|
+
Contributions welcome! Areas of interest:
|
|
433
|
+
|
|
434
|
+
- Additional format support (DSF, DSDIFF, etc.)
|
|
435
|
+
- Advanced metadata implementation (PropertyMap integration)
|
|
436
|
+
- Performance optimizations
|
|
437
|
+
- Runtime-specific optimizations
|
|
438
|
+
- Documentation improvements
|
|
439
|
+
|
|
440
|
+
## π License
|
|
441
|
+
|
|
442
|
+
- **This project**: MIT License (see [LICENSE](LICENSE))
|
|
443
|
+
- **TagLib library**: LGPL/MPL dual license (see [lib/taglib/COPYING.LGPL](lib/taglib/COPYING.LGPL))
|
|
444
|
+
|
|
445
|
+
## π Acknowledgments
|
|
446
|
+
|
|
447
|
+
- [TagLib](https://taglib.org/) β Excellent audio metadata library
|
|
448
|
+
- [Emscripten](https://emscripten.org/) β WebAssembly compilation toolchain
|
|
449
|
+
|
|
450
|
+
---
|
|
451
|
+
|
|
452
|
+
**Status**: Production Ready - Universal audio metadata handling across all JavaScript runtimes.
|
package/build/taglib.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
var TagLibWASM = (() => {
|
|
2
|
+
var _scriptName = typeof document != 'undefined' ? document.currentScript?.src : undefined;
|
|
3
|
+
return (
|
|
4
|
+
async function(moduleArg = {}) {
|
|
5
|
+
var moduleRtn;
|
|
6
|
+
|
|
7
|
+
var c=moduleArg,aa="object"==typeof window,ba="undefined"!=typeof WorkerGlobalScope,m="object"==typeof process&&process.versions?.node&&"renderer"!=process.type;"undefined"!=typeof __filename&&(_scriptName=__filename);var p="",q,r;
|
|
8
|
+
if(m){var fs=require("fs");p=__dirname+"/";r=a=>{a=t(a)?new URL(a):a;return fs.readFileSync(a)};q=async a=>{a=t(a)?new URL(a):a;return fs.readFileSync(a,void 0)};process.argv.slice(2)}else if(aa||ba){try{p=(new URL(".",_scriptName)).href}catch{}q=async a=>{a=await fetch(a,{credentials:"same-origin"});if(a.ok)return a.arrayBuffer();throw Error(a.status+" : "+a.url);}}console.log.bind(console);var u=console.error.bind(console),v,w=!1,t=a=>a.startsWith("file://"),x,y,z,A,B,C,D,E,F,G,H,I=!1;
|
|
9
|
+
function J(){var a=z.buffer;A=new Int8Array(a);C=new Int16Array(a);B=new Uint8Array(a);new Uint16Array(a);D=new Int32Array(a);E=new Uint32Array(a);F=new Float32Array(a);G=new Float64Array(a);H=new BigInt64Array(a);new BigUint64Array(a)}var K=0,L=null;function M(a){c.onAbort?.(a);a="Aborted("+a+")";u(a);w=!0;a=new WebAssembly.RuntimeError(a+". Build with -sASSERTIONS for more info.");y?.(a);throw a;}var N;
|
|
10
|
+
async function ca(a){if(!v)try{var d=await q(a);return new Uint8Array(d)}catch{}if(a==N&&v)a=new Uint8Array(v);else if(r)a=r(a);else throw"both async and sync fetching of the wasm failed";return a}async function da(a,d){try{var b=await ca(a);return await WebAssembly.instantiate(b,d)}catch(e){u(`failed to asynchronously prepare wasm: ${e}`),M(e)}}
|
|
11
|
+
async function ea(a){var d=N;if(!v&&"function"==typeof WebAssembly.instantiateStreaming&&!m)try{var b=fetch(d,{credentials:"same-origin"});return await WebAssembly.instantiateStreaming(b,a)}catch(e){u(`wasm streaming compile failed: ${e}`),u("falling back to ArrayBuffer instantiation")}return da(d,a)}var O=a=>{for(;0<a.length;)a.shift()(c)},P=[],Q=[],fa=()=>{var a=c.preRun.shift();Q.push(a)};class ha{constructor(a){this.J=a-24}}
|
|
12
|
+
var R=0,ia=0,S=a=>{for(var d=0,b=0;b<a.length;++b){var e=a.charCodeAt(b);127>=e?d++:2047>=e?d+=2:55296<=e&&57343>=e?(d+=4,++b):d+=3}return d},T=(a,d,b,e)=>{if(!(0<e))return 0;var f=b;e=b+e-1;for(var h=0;h<a.length;++h){var g=a.codePointAt(h);if(127>=g){if(b>=e)break;d[b++]=g}else if(2047>=g){if(b+1>=e)break;d[b++]=192|g>>6;d[b++]=128|g&63}else if(65535>=g){if(b+2>=e)break;d[b++]=224|g>>12;d[b++]=128|g>>6&63;d[b++]=128|g&63}else{if(b+3>=e)break;d[b++]=240|g>>18;d[b++]=128|g>>12&63;d[b++]=128|g>>6&
|
|
13
|
+
63;d[b++]=128|g&63;h++}}d[b]=0;return b-f},U="undefined"!=typeof TextDecoder?new TextDecoder:void 0,ja=(a=0)=>{for(var d=B,b=a+NaN,e=a;d[e]&&!(e>=b);)++e;if(16<e-a&&d.buffer&&U)return U.decode(d.subarray(a,e));for(b="";a<e;){var f=d[a++];if(f&128){var h=d[a++]&63;if(192==(f&224))b+=String.fromCharCode((f&31)<<6|h);else{var g=d[a++]&63;f=224==(f&240)?(f&15)<<12|h<<6|g:(f&7)<<18|h<<12|g<<6|d[a++]&63;65536>f?b+=String.fromCharCode(f):(f-=65536,b+=String.fromCharCode(55296|f>>10,56320|f&1023))}}else b+=
|
|
14
|
+
String.fromCharCode(f)}return b},ma=(a,d,b,e)=>{var f={string:k=>{var l=0;if(null!==k&&void 0!==k&&0!==k){l=S(k)+1;var Y=V(l);T(k,B,Y,l);l=Y}return l},array:k=>{var l=V(k.length);A.set(k,l);return l}};a=c["_"+a];var h=[],g=0;if(e)for(var n=0;n<e.length;n++){var Z=f[b[n]];Z?(0===g&&(g=ka()),h[n]=Z(e[n])):h[n]=e[n]}b=a(...h);return b=function(k){0!==g&&la(g);return"string"===d?k?ja(k):"":"boolean"===d?!!k:k}(b)};c.printErr&&(u=c.printErr);c.wasmBinary&&(v=c.wasmBinary);c.ccall=ma;
|
|
15
|
+
c.cwrap=(a,d,b,e)=>{var f=!b||b.every(h=>"number"===h||"boolean"===h);return"string"!==d&&f&&!e?c["_"+a]:(...h)=>ma(a,d,b,h,e)};c.setValue=function(a,d,b="i8"){b.endsWith("*")&&(b="*");switch(b){case "i1":A[a]=d;break;case "i8":A[a]=d;break;case "i16":C[a>>1]=d;break;case "i32":D[a>>2]=d;break;case "i64":H[a>>3]=BigInt(d);break;case "float":F[a>>2]=d;break;case "double":G[a>>3]=d;break;case "*":E[a>>2]=d;break;default:M(`invalid type for setValue: ${b}`)}};
|
|
16
|
+
c.getValue=function(a,d="i8"){d.endsWith("*")&&(d="*");switch(d){case "i1":return A[a];case "i8":return A[a];case "i16":return C[a>>1];case "i32":return D[a>>2];case "i64":return H[a>>3];case "float":return F[a>>2];case "double":return G[a>>3];case "*":return E[a>>2];default:M(`invalid type for getValue: ${d}`)}};c.intArrayFromString=(a,d,b)=>{b=Array(0<b?b:S(a)+1);a=T(a,b,0,b.length);d&&(b.length=a);return b};c.ALLOC_NORMAL=0;
|
|
17
|
+
c.allocate=(a,d)=>{d=1==d?V(a.length):na(a.length);a.subarray||a.slice||(a=new Uint8Array(a));B.set(a,d);return d};
|
|
18
|
+
var na,la,V,ka,oa={a:(a,d,b)=>{var e=new ha(a);E[e.J+16>>2]=0;E[e.J+4>>2]=d;E[e.J+8>>2]=b;R=a;ia++;throw R;},b:()=>M(""),c:a=>{var d=B.length;a>>>=0;if(268435456<a)return!1;for(var b=1;4>=b;b*=2){var e=d*(1+.2/b);e=Math.min(e,a+100663296);a:{e=(Math.min(268435456,65536*Math.ceil(Math.max(a,e)/65536))-z.buffer.byteLength+65535)/65536|0;try{z.grow(e);J();var f=1;break a}catch(h){}f=void 0}if(f)return!0}return!1}},W=await (async function(){function a(b){W=b.exports;z=W.d;J();b=W;c._taglib_file_new_from_buffer=
|
|
19
|
+
b.f;c._taglib_file_delete=b.g;c._taglib_file_save=b.h;c._taglib_file_is_valid=b.i;c._taglib_file_format=b.j;c._taglib_file_tag=b.k;c._taglib_tag_title=b.l;c._taglib_tag_artist=b.m;c._taglib_tag_album=b.n;c._taglib_tag_comment=b.o;c._taglib_tag_genre=b.p;c._taglib_tag_year=b.q;c._taglib_tag_track=b.r;c._taglib_tag_set_title=b.s;c._taglib_tag_set_artist=b.t;c._taglib_tag_set_album=b.u;c._taglib_tag_set_comment=b.v;c._taglib_tag_set_genre=b.w;c._taglib_tag_set_year=b.x;c._taglib_tag_set_track=b.y;c._taglib_file_audioproperties=
|
|
20
|
+
b.z;c._taglib_audioproperties_length=b.A;c._taglib_audioproperties_bitrate=b.B;c._taglib_audioproperties_samplerate=b.C;c._taglib_audioproperties_channels=b.D;c._malloc=na=b.E;c._free=b.F;la=b.G;V=b.H;ka=b.I;K--;c.monitorRunDependencies?.(K);0==K&&L&&(b=L,L=null,b());return W}K++;c.monitorRunDependencies?.(K);var d={a:oa};if(c.instantiateWasm)return new Promise(b=>{c.instantiateWasm(d,(e,f)=>{b(a(e,f))})});N??=c.locateFile?c.locateFile("taglib.wasm",p):p+"taglib.wasm";return a((await ea(d)).instance)}());
|
|
21
|
+
function X(){function a(){c.calledRun=!0;if(!w){I=!0;W.e();x?.(c);c.onRuntimeInitialized?.();if(c.postRun)for("function"==typeof c.postRun&&(c.postRun=[c.postRun]);c.postRun.length;){var d=c.postRun.shift();P.push(d)}O(P)}}if(0<K)L=X;else{if(c.preRun)for("function"==typeof c.preRun&&(c.preRun=[c.preRun]);c.preRun.length;)fa();O(Q);0<K?L=X:c.setStatus?(c.setStatus("Running..."),setTimeout(()=>{setTimeout(()=>c.setStatus(""),1);a()},1)):a()}}
|
|
22
|
+
if(c.preInit)for("function"==typeof c.preInit&&(c.preInit=[c.preInit]);0<c.preInit.length;)c.preInit.shift()();X();I?moduleRtn=c:moduleRtn=new Promise((a,d)=>{x=a;y=d});
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
return moduleRtn;
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
})();
|
|
29
|
+
if (typeof exports === 'object' && typeof module === 'object') {
|
|
30
|
+
module.exports = TagLibWASM;
|
|
31
|
+
// This default export looks redundant, but it allows TS to import this
|
|
32
|
+
// commonjs style module.
|
|
33
|
+
module.exports.default = TagLibWASM;
|
|
34
|
+
} else if (typeof define === 'function' && define['amd'])
|
|
35
|
+
define([], () => TagLibWASM);
|
|
Binary file
|