taglib-wasm 0.3.3 → 0.3.9
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/CONTRIBUTING.md +293 -0
- package/NOTICE +34 -0
- package/README.md +122 -511
- package/dist/index.d.ts +132 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +137 -0
- package/dist/index.ts +220 -0
- package/dist/src/constants.d.ts +201 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.ts +227 -0
- package/dist/src/errors.d.ts +89 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.ts +237 -0
- package/dist/src/file-utils.d.ts +205 -0
- package/dist/src/file-utils.d.ts.map +1 -0
- package/dist/src/file-utils.ts +467 -0
- package/dist/src/file.js +47 -0
- package/dist/src/global.d.ts +10 -0
- package/dist/src/mod.d.ts +9 -0
- package/dist/src/mod.d.ts.map +1 -0
- package/dist/src/mod.ts +19 -0
- package/dist/src/simple.d.ts +347 -0
- package/dist/src/simple.d.ts.map +1 -0
- package/dist/src/simple.ts +659 -0
- package/dist/src/taglib.d.ts +502 -0
- package/dist/src/taglib.d.ts.map +1 -0
- package/dist/src/taglib.ts +959 -0
- package/dist/src/types.d.ts +323 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.ts +538 -0
- package/dist/src/utils/file.d.ts +15 -0
- package/dist/src/utils/file.d.ts.map +1 -0
- package/dist/src/utils/file.ts +82 -0
- package/dist/src/utils/write.d.ts +15 -0
- package/dist/src/utils/write.d.ts.map +1 -0
- package/dist/src/utils/write.ts +61 -0
- package/dist/src/wasm-workers.d.ts +33 -0
- package/dist/src/wasm-workers.d.ts.map +1 -0
- package/dist/src/wasm-workers.ts +176 -0
- package/dist/src/wasm.d.ts +97 -0
- package/dist/src/wasm.d.ts.map +1 -0
- package/dist/src/wasm.ts +133 -0
- package/dist/src/web-utils.d.ts +180 -0
- package/dist/src/web-utils.d.ts.map +1 -0
- package/dist/src/web-utils.ts +347 -0
- package/dist/src/workers.d.ts +219 -0
- package/dist/src/workers.d.ts.map +1 -0
- package/dist/src/workers.ts +465 -0
- package/dist/src/write.js +33 -0
- package/dist/taglib-wrapper.d.ts +5 -0
- package/dist/taglib-wrapper.js +14 -0
- package/dist/taglib.wasm +0 -0
- package/index.ts +100 -7
- package/package.json +40 -16
- package/src/errors.ts +237 -0
- package/src/file-utils.ts +467 -0
- package/src/global.d.ts +10 -0
- package/src/simple.ts +399 -84
- package/src/taglib.ts +522 -28
- package/src/types.ts +1 -1
- package/src/utils/file.ts +82 -0
- package/src/utils/write.ts +61 -0
- package/src/wasm-workers.ts +13 -4
- package/src/wasm.ts +1 -1
- package/src/web-utils.ts +347 -0
- package/src/workers.ts +32 -13
- package/build/taglib.js +0 -2407
- package/build/taglib.wasm +0 -0
package/src/taglib.ts
CHANGED
|
@@ -4,7 +4,16 @@ import type {
|
|
|
4
4
|
FileType,
|
|
5
5
|
PropertyMap,
|
|
6
6
|
Tag as BasicTag,
|
|
7
|
+
Picture,
|
|
7
8
|
} from "./types.ts";
|
|
9
|
+
import {
|
|
10
|
+
InvalidFormatError,
|
|
11
|
+
MetadataError,
|
|
12
|
+
TagLibInitializationError,
|
|
13
|
+
UnsupportedFormatError,
|
|
14
|
+
} from "./errors.ts";
|
|
15
|
+
import { readFileData } from "./utils/file.ts";
|
|
16
|
+
import { writeFileData } from "./utils/write.ts";
|
|
8
17
|
|
|
9
18
|
/**
|
|
10
19
|
* Extended Tag interface with read/write capabilities for audio metadata.
|
|
@@ -12,7 +21,7 @@ import type {
|
|
|
12
21
|
*
|
|
13
22
|
* @example
|
|
14
23
|
* ```typescript
|
|
15
|
-
* const file = await taglib.
|
|
24
|
+
* const file = await taglib.open("song.mp3");
|
|
16
25
|
* const tag = file.tag();
|
|
17
26
|
*
|
|
18
27
|
* // Read metadata
|
|
@@ -48,7 +57,7 @@ export interface Tag extends BasicTag {
|
|
|
48
57
|
*
|
|
49
58
|
* @example
|
|
50
59
|
* ```typescript
|
|
51
|
-
* const file = await taglib.
|
|
60
|
+
* const file = await taglib.open("song.mp3");
|
|
52
61
|
*
|
|
53
62
|
* // Check if valid
|
|
54
63
|
* if (!file.isValid()) {
|
|
@@ -161,17 +170,170 @@ export interface AudioFile {
|
|
|
161
170
|
*/
|
|
162
171
|
getFileBuffer(): Uint8Array;
|
|
163
172
|
|
|
173
|
+
/**
|
|
174
|
+
* Save all changes to a file on disk.
|
|
175
|
+
* This first saves changes to the in-memory buffer, then writes to the specified path.
|
|
176
|
+
* @param path - Optional file path. If not provided, saves to the original path (if opened from a file).
|
|
177
|
+
* @throws {Error} If no path is available or write fails
|
|
178
|
+
*/
|
|
179
|
+
saveToFile(path?: string): Promise<void>;
|
|
180
|
+
|
|
164
181
|
/**
|
|
165
182
|
* Check if the file was loaded successfully and is valid.
|
|
166
183
|
* @returns true if the file is valid and can be processed
|
|
167
184
|
*/
|
|
168
185
|
isValid(): boolean;
|
|
169
186
|
|
|
187
|
+
/**
|
|
188
|
+
* Get all pictures/cover art from the audio file.
|
|
189
|
+
* @returns Array of Picture objects
|
|
190
|
+
*/
|
|
191
|
+
getPictures(): Picture[];
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Set pictures/cover art in the audio file (replaces all existing).
|
|
195
|
+
* @param pictures - Array of Picture objects to set
|
|
196
|
+
*/
|
|
197
|
+
setPictures(pictures: Picture[]): void;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Add a single picture to the audio file.
|
|
201
|
+
* @param picture - Picture object to add
|
|
202
|
+
*/
|
|
203
|
+
addPicture(picture: Picture): void;
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Remove all pictures from the audio file.
|
|
207
|
+
*/
|
|
208
|
+
removePictures(): void;
|
|
209
|
+
|
|
170
210
|
/**
|
|
171
211
|
* Release all resources associated with this file.
|
|
172
212
|
* Always call this when done to prevent memory leaks.
|
|
173
213
|
*/
|
|
174
214
|
dispose(): void;
|
|
215
|
+
|
|
216
|
+
// Extended metadata helper methods
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Get MusicBrainz Track ID.
|
|
220
|
+
* @returns MusicBrainz Track ID or undefined if not set
|
|
221
|
+
*/
|
|
222
|
+
getMusicBrainzTrackId(): string | undefined;
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Set MusicBrainz Track ID.
|
|
226
|
+
* @param id - MusicBrainz Track ID (UUID format)
|
|
227
|
+
*/
|
|
228
|
+
setMusicBrainzTrackId(id: string): void;
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get MusicBrainz Release ID.
|
|
232
|
+
* @returns MusicBrainz Release ID or undefined if not set
|
|
233
|
+
*/
|
|
234
|
+
getMusicBrainzReleaseId(): string | undefined;
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Set MusicBrainz Release ID.
|
|
238
|
+
* @param id - MusicBrainz Release ID (UUID format)
|
|
239
|
+
*/
|
|
240
|
+
setMusicBrainzReleaseId(id: string): void;
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Get MusicBrainz Artist ID.
|
|
244
|
+
* @returns MusicBrainz Artist ID or undefined if not set
|
|
245
|
+
*/
|
|
246
|
+
getMusicBrainzArtistId(): string | undefined;
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Set MusicBrainz Artist ID.
|
|
250
|
+
* @param id - MusicBrainz Artist ID (UUID format)
|
|
251
|
+
*/
|
|
252
|
+
setMusicBrainzArtistId(id: string): void;
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Get AcoustID fingerprint.
|
|
256
|
+
* @returns AcoustID fingerprint or undefined if not set
|
|
257
|
+
*/
|
|
258
|
+
getAcoustIdFingerprint(): string | undefined;
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Set AcoustID fingerprint.
|
|
262
|
+
* @param fingerprint - AcoustID fingerprint
|
|
263
|
+
*/
|
|
264
|
+
setAcoustIdFingerprint(fingerprint: string): void;
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* Get AcoustID ID.
|
|
268
|
+
* @returns AcoustID ID or undefined if not set
|
|
269
|
+
*/
|
|
270
|
+
getAcoustIdId(): string | undefined;
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Set AcoustID ID.
|
|
274
|
+
* @param id - AcoustID ID
|
|
275
|
+
*/
|
|
276
|
+
setAcoustIdId(id: string): void;
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Get ReplayGain track gain.
|
|
280
|
+
* @returns ReplayGain track gain (e.g., "-6.54 dB") or undefined
|
|
281
|
+
*/
|
|
282
|
+
getReplayGainTrackGain(): string | undefined;
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Set ReplayGain track gain.
|
|
286
|
+
* @param gain - ReplayGain track gain (e.g., "-6.54 dB")
|
|
287
|
+
*/
|
|
288
|
+
setReplayGainTrackGain(gain: string): void;
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get ReplayGain track peak.
|
|
292
|
+
* @returns ReplayGain track peak (0.0-1.0) or undefined
|
|
293
|
+
*/
|
|
294
|
+
getReplayGainTrackPeak(): string | undefined;
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Set ReplayGain track peak.
|
|
298
|
+
* @param peak - ReplayGain track peak (0.0-1.0)
|
|
299
|
+
*/
|
|
300
|
+
setReplayGainTrackPeak(peak: string): void;
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Get ReplayGain album gain.
|
|
304
|
+
* @returns ReplayGain album gain (e.g., "-7.89 dB") or undefined
|
|
305
|
+
*/
|
|
306
|
+
getReplayGainAlbumGain(): string | undefined;
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Set ReplayGain album gain.
|
|
310
|
+
* @param gain - ReplayGain album gain (e.g., "-7.89 dB")
|
|
311
|
+
*/
|
|
312
|
+
setReplayGainAlbumGain(gain: string): void;
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Get ReplayGain album peak.
|
|
316
|
+
* @returns ReplayGain album peak (0.0-1.0) or undefined
|
|
317
|
+
*/
|
|
318
|
+
getReplayGainAlbumPeak(): string | undefined;
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Set ReplayGain album peak.
|
|
322
|
+
* @param peak - ReplayGain album peak (0.0-1.0)
|
|
323
|
+
*/
|
|
324
|
+
setReplayGainAlbumPeak(peak: string): void;
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get Apple Sound Check normalization data.
|
|
328
|
+
* @returns Apple Sound Check data (iTunNORM) or undefined
|
|
329
|
+
*/
|
|
330
|
+
getAppleSoundCheck(): string | undefined;
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Set Apple Sound Check normalization data.
|
|
334
|
+
* @param data - Apple Sound Check data (iTunNORM format)
|
|
335
|
+
*/
|
|
336
|
+
setAppleSoundCheck(data: string): void;
|
|
175
337
|
}
|
|
176
338
|
|
|
177
339
|
/**
|
|
@@ -179,18 +341,21 @@ export interface AudioFile {
|
|
|
179
341
|
* Wraps the native TagLib C++ FileHandle object.
|
|
180
342
|
*
|
|
181
343
|
* @internal This class is not meant to be instantiated directly.
|
|
182
|
-
* Use TagLib.
|
|
344
|
+
* Use TagLib.open() to create instances.
|
|
183
345
|
*/
|
|
184
346
|
export class AudioFileImpl implements AudioFile {
|
|
185
347
|
private fileHandle: any;
|
|
186
348
|
private cachedTag: Tag | null = null;
|
|
187
349
|
private cachedAudioProperties: AudioProperties | null = null;
|
|
350
|
+
private sourcePath?: string;
|
|
188
351
|
|
|
189
352
|
constructor(
|
|
190
353
|
private module: TagLibModule,
|
|
191
354
|
fileHandle: any,
|
|
355
|
+
sourcePath?: string,
|
|
192
356
|
) {
|
|
193
357
|
this.fileHandle = fileHandle;
|
|
358
|
+
this.sourcePath = sourcePath;
|
|
194
359
|
}
|
|
195
360
|
|
|
196
361
|
/** @inheritdoc */
|
|
@@ -202,7 +367,10 @@ export class AudioFileImpl implements AudioFile {
|
|
|
202
367
|
tag(): Tag {
|
|
203
368
|
const tagWrapper = this.fileHandle.getTag();
|
|
204
369
|
if (!tagWrapper) {
|
|
205
|
-
throw new
|
|
370
|
+
throw new MetadataError(
|
|
371
|
+
"read",
|
|
372
|
+
"Tag may be corrupted or format not fully supported"
|
|
373
|
+
);
|
|
206
374
|
}
|
|
207
375
|
|
|
208
376
|
return {
|
|
@@ -281,7 +449,11 @@ export class AudioFileImpl implements AudioFile {
|
|
|
281
449
|
/** @inheritdoc */
|
|
282
450
|
getMP4Item(key: string): string | undefined {
|
|
283
451
|
if (!this.isMP4()) {
|
|
284
|
-
|
|
452
|
+
const format = this.getFormat();
|
|
453
|
+
throw new UnsupportedFormatError(
|
|
454
|
+
format,
|
|
455
|
+
["MP4", "M4A"]
|
|
456
|
+
);
|
|
285
457
|
}
|
|
286
458
|
const value = this.fileHandle.getMP4Item(key);
|
|
287
459
|
return value === "" ? undefined : value;
|
|
@@ -290,7 +462,11 @@ export class AudioFileImpl implements AudioFile {
|
|
|
290
462
|
/** @inheritdoc */
|
|
291
463
|
setMP4Item(key: string, value: string): void {
|
|
292
464
|
if (!this.isMP4()) {
|
|
293
|
-
|
|
465
|
+
const format = this.getFormat();
|
|
466
|
+
throw new UnsupportedFormatError(
|
|
467
|
+
format,
|
|
468
|
+
["MP4", "M4A"]
|
|
469
|
+
);
|
|
294
470
|
}
|
|
295
471
|
this.fileHandle.setMP4Item(key, value);
|
|
296
472
|
}
|
|
@@ -298,7 +474,11 @@ export class AudioFileImpl implements AudioFile {
|
|
|
298
474
|
/** @inheritdoc */
|
|
299
475
|
removeMP4Item(key: string): void {
|
|
300
476
|
if (!this.isMP4()) {
|
|
301
|
-
|
|
477
|
+
const format = this.getFormat();
|
|
478
|
+
throw new UnsupportedFormatError(
|
|
479
|
+
format,
|
|
480
|
+
["MP4", "M4A"]
|
|
481
|
+
);
|
|
302
482
|
}
|
|
303
483
|
this.fileHandle.removeMP4Item(key);
|
|
304
484
|
}
|
|
@@ -314,34 +494,225 @@ export class AudioFileImpl implements AudioFile {
|
|
|
314
494
|
|
|
315
495
|
/** @inheritdoc */
|
|
316
496
|
getFileBuffer(): Uint8Array {
|
|
317
|
-
const
|
|
318
|
-
if (!
|
|
497
|
+
const buffer = this.fileHandle.getBuffer();
|
|
498
|
+
if (!buffer) {
|
|
319
499
|
return new Uint8Array(0);
|
|
320
500
|
}
|
|
321
501
|
|
|
322
|
-
//
|
|
323
|
-
const buffer = new Uint8Array(bufferString.length);
|
|
324
|
-
for (let i = 0; i < bufferString.length; i++) {
|
|
325
|
-
buffer[i] = bufferString.charCodeAt(i);
|
|
326
|
-
}
|
|
502
|
+
// The buffer is already a Uint8Array from the C++ side
|
|
327
503
|
return buffer;
|
|
328
504
|
}
|
|
329
505
|
|
|
506
|
+
/** @inheritdoc */
|
|
507
|
+
async saveToFile(path?: string): Promise<void> {
|
|
508
|
+
// Determine the target path
|
|
509
|
+
const targetPath = path || this.sourcePath;
|
|
510
|
+
if (!targetPath) {
|
|
511
|
+
throw new Error(
|
|
512
|
+
"No file path available. Either provide a path or open the file from a path."
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// First save to in-memory buffer
|
|
517
|
+
if (!this.save()) {
|
|
518
|
+
throw new Error("Failed to save changes to in-memory buffer");
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Get the updated buffer and write to file
|
|
522
|
+
const buffer = this.getFileBuffer();
|
|
523
|
+
await writeFileData(targetPath, buffer);
|
|
524
|
+
}
|
|
525
|
+
|
|
330
526
|
/** @inheritdoc */
|
|
331
527
|
isValid(): boolean {
|
|
332
528
|
return this.fileHandle.isValid();
|
|
333
529
|
}
|
|
334
530
|
|
|
531
|
+
/** @inheritdoc */
|
|
532
|
+
getPictures(): Picture[] {
|
|
533
|
+
const picturesArray = this.fileHandle.getPictures();
|
|
534
|
+
const pictures: Picture[] = [];
|
|
535
|
+
|
|
536
|
+
// Convert from Emscripten array to TypeScript array
|
|
537
|
+
for (let i = 0; i < picturesArray.length; i++) {
|
|
538
|
+
const pic = picturesArray[i];
|
|
539
|
+
pictures.push({
|
|
540
|
+
mimeType: pic.mimeType,
|
|
541
|
+
data: pic.data,
|
|
542
|
+
type: pic.type,
|
|
543
|
+
description: pic.description
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return pictures;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
/** @inheritdoc */
|
|
551
|
+
setPictures(pictures: Picture[]): void {
|
|
552
|
+
// Convert TypeScript array to format expected by C++
|
|
553
|
+
const picturesArray = pictures.map(pic => ({
|
|
554
|
+
mimeType: pic.mimeType,
|
|
555
|
+
data: pic.data,
|
|
556
|
+
type: pic.type,
|
|
557
|
+
description: pic.description || ""
|
|
558
|
+
}));
|
|
559
|
+
|
|
560
|
+
this.fileHandle.setPictures(picturesArray);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
/** @inheritdoc */
|
|
564
|
+
addPicture(picture: Picture): void {
|
|
565
|
+
const pic = {
|
|
566
|
+
mimeType: picture.mimeType,
|
|
567
|
+
data: picture.data,
|
|
568
|
+
type: picture.type,
|
|
569
|
+
description: picture.description || ""
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
this.fileHandle.addPicture(pic);
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
/** @inheritdoc */
|
|
576
|
+
removePictures(): void {
|
|
577
|
+
this.fileHandle.removePictures();
|
|
578
|
+
}
|
|
579
|
+
|
|
335
580
|
/** @inheritdoc */
|
|
336
581
|
dispose(): void {
|
|
337
582
|
if (this.fileHandle) {
|
|
338
|
-
//
|
|
339
|
-
|
|
583
|
+
// Explicitly destroy the C++ object to free memory immediately
|
|
584
|
+
if (typeof this.fileHandle.destroy === 'function') {
|
|
585
|
+
this.fileHandle.destroy();
|
|
586
|
+
}
|
|
587
|
+
// Clear all references
|
|
340
588
|
this.fileHandle = null;
|
|
341
589
|
this.cachedTag = null;
|
|
342
590
|
this.cachedAudioProperties = null;
|
|
343
591
|
}
|
|
344
592
|
}
|
|
593
|
+
|
|
594
|
+
// Extended metadata implementations
|
|
595
|
+
|
|
596
|
+
/** @inheritdoc */
|
|
597
|
+
getMusicBrainzTrackId(): string | undefined {
|
|
598
|
+
const value = this.getProperty("MUSICBRAINZ_TRACKID");
|
|
599
|
+
return value || undefined;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
/** @inheritdoc */
|
|
603
|
+
setMusicBrainzTrackId(id: string): void {
|
|
604
|
+
this.setProperty("MUSICBRAINZ_TRACKID", id);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/** @inheritdoc */
|
|
608
|
+
getMusicBrainzReleaseId(): string | undefined {
|
|
609
|
+
const value = this.getProperty("MUSICBRAINZ_ALBUMID");
|
|
610
|
+
return value || undefined;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/** @inheritdoc */
|
|
614
|
+
setMusicBrainzReleaseId(id: string): void {
|
|
615
|
+
this.setProperty("MUSICBRAINZ_ALBUMID", id);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
/** @inheritdoc */
|
|
619
|
+
getMusicBrainzArtistId(): string | undefined {
|
|
620
|
+
const value = this.getProperty("MUSICBRAINZ_ARTISTID");
|
|
621
|
+
return value || undefined;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
/** @inheritdoc */
|
|
625
|
+
setMusicBrainzArtistId(id: string): void {
|
|
626
|
+
this.setProperty("MUSICBRAINZ_ARTISTID", id);
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
/** @inheritdoc */
|
|
630
|
+
getAcoustIdFingerprint(): string | undefined {
|
|
631
|
+
const value = this.getProperty("ACOUSTID_FINGERPRINT");
|
|
632
|
+
return value || undefined;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
/** @inheritdoc */
|
|
636
|
+
setAcoustIdFingerprint(fingerprint: string): void {
|
|
637
|
+
this.setProperty("ACOUSTID_FINGERPRINT", fingerprint);
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
/** @inheritdoc */
|
|
641
|
+
getAcoustIdId(): string | undefined {
|
|
642
|
+
const value = this.getProperty("ACOUSTID_ID");
|
|
643
|
+
return value || undefined;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/** @inheritdoc */
|
|
647
|
+
setAcoustIdId(id: string): void {
|
|
648
|
+
this.setProperty("ACOUSTID_ID", id);
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
/** @inheritdoc */
|
|
652
|
+
getReplayGainTrackGain(): string | undefined {
|
|
653
|
+
const value = this.getProperty("REPLAYGAIN_TRACK_GAIN");
|
|
654
|
+
return value || undefined;
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
/** @inheritdoc */
|
|
658
|
+
setReplayGainTrackGain(gain: string): void {
|
|
659
|
+
this.setProperty("REPLAYGAIN_TRACK_GAIN", gain);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
/** @inheritdoc */
|
|
663
|
+
getReplayGainTrackPeak(): string | undefined {
|
|
664
|
+
const value = this.getProperty("REPLAYGAIN_TRACK_PEAK");
|
|
665
|
+
return value || undefined;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/** @inheritdoc */
|
|
669
|
+
setReplayGainTrackPeak(peak: string): void {
|
|
670
|
+
this.setProperty("REPLAYGAIN_TRACK_PEAK", peak);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
/** @inheritdoc */
|
|
674
|
+
getReplayGainAlbumGain(): string | undefined {
|
|
675
|
+
const value = this.getProperty("REPLAYGAIN_ALBUM_GAIN");
|
|
676
|
+
return value || undefined;
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
/** @inheritdoc */
|
|
680
|
+
setReplayGainAlbumGain(gain: string): void {
|
|
681
|
+
this.setProperty("REPLAYGAIN_ALBUM_GAIN", gain);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
/** @inheritdoc */
|
|
685
|
+
getReplayGainAlbumPeak(): string | undefined {
|
|
686
|
+
const value = this.getProperty("REPLAYGAIN_ALBUM_PEAK");
|
|
687
|
+
return value || undefined;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/** @inheritdoc */
|
|
691
|
+
setReplayGainAlbumPeak(peak: string): void {
|
|
692
|
+
this.setProperty("REPLAYGAIN_ALBUM_PEAK", peak);
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
/** @inheritdoc */
|
|
696
|
+
getAppleSoundCheck(): string | undefined {
|
|
697
|
+
// Apple Sound Check is stored differently in MP4 files
|
|
698
|
+
if (this.isMP4()) {
|
|
699
|
+
return this.getMP4Item("iTunNORM");
|
|
700
|
+
}
|
|
701
|
+
// For other formats, it might be in properties
|
|
702
|
+
const value = this.getProperty("ITUNESOUNDCHECK");
|
|
703
|
+
return value || undefined;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/** @inheritdoc */
|
|
707
|
+
setAppleSoundCheck(data: string): void {
|
|
708
|
+
// Apple Sound Check is stored differently in MP4 files
|
|
709
|
+
if (this.isMP4()) {
|
|
710
|
+
this.setMP4Item("iTunNORM", data);
|
|
711
|
+
} else {
|
|
712
|
+
// For other formats, store in properties
|
|
713
|
+
this.setProperty("ITUNESOUNDCHECK", data);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
345
716
|
}
|
|
346
717
|
|
|
347
718
|
/**
|
|
@@ -359,7 +730,7 @@ export class AudioFileImpl implements AudioFile {
|
|
|
359
730
|
* const taglib = new TagLib(module);
|
|
360
731
|
*
|
|
361
732
|
* // Open and process a file
|
|
362
|
-
* const file = await taglib.
|
|
733
|
+
* const file = await taglib.open("song.mp3");
|
|
363
734
|
* const tag = file.tag();
|
|
364
735
|
* console.log(`Title: ${tag.title}`);
|
|
365
736
|
* file.dispose();
|
|
@@ -381,7 +752,7 @@ export class TagLib {
|
|
|
381
752
|
* @example
|
|
382
753
|
* ```typescript
|
|
383
754
|
* const taglib = await TagLib.initialize();
|
|
384
|
-
* const file = await taglib.
|
|
755
|
+
* const file = await taglib.open("song.mp3");
|
|
385
756
|
* ```
|
|
386
757
|
*/
|
|
387
758
|
static async initialize(): Promise<TagLib> {
|
|
@@ -392,35 +763,53 @@ export class TagLib {
|
|
|
392
763
|
}
|
|
393
764
|
|
|
394
765
|
/**
|
|
395
|
-
* Open an audio file from
|
|
766
|
+
* Open an audio file from various sources.
|
|
396
767
|
* Automatically detects the file format based on content.
|
|
397
768
|
*
|
|
398
|
-
* @param
|
|
769
|
+
* @param input - File path (string), ArrayBuffer, Uint8Array, or File object
|
|
399
770
|
* @returns Promise resolving to AudioFile instance
|
|
400
771
|
* @throws {Error} If the file format is invalid or unsupported
|
|
401
772
|
* @throws {Error} If the module is not properly initialized
|
|
402
773
|
*
|
|
403
774
|
* @example
|
|
404
775
|
* ```typescript
|
|
776
|
+
* // From file path
|
|
777
|
+
* const file = await taglib.open("song.mp3");
|
|
778
|
+
*
|
|
405
779
|
* // From ArrayBuffer
|
|
406
|
-
* const file = await taglib.
|
|
780
|
+
* const file = await taglib.open(arrayBuffer);
|
|
407
781
|
*
|
|
408
782
|
* // From Uint8Array
|
|
409
|
-
* const
|
|
410
|
-
*
|
|
783
|
+
* const file = await taglib.open(uint8Array);
|
|
784
|
+
*
|
|
785
|
+
* // From File object (browser)
|
|
786
|
+
* const file = await taglib.open(fileObject);
|
|
411
787
|
*
|
|
412
788
|
* // Remember to dispose when done
|
|
413
789
|
* file.dispose();
|
|
414
790
|
* ```
|
|
415
791
|
*/
|
|
416
|
-
async
|
|
792
|
+
async open(input: string | ArrayBuffer | Uint8Array | File): Promise<AudioFile> {
|
|
417
793
|
// Check if Embind is available
|
|
418
794
|
if (!this.module.createFileHandle) {
|
|
419
|
-
throw new
|
|
420
|
-
"TagLib module not properly initialized
|
|
795
|
+
throw new TagLibInitializationError(
|
|
796
|
+
"TagLib module not properly initialized: createFileHandle not found. " +
|
|
797
|
+
"Make sure the module is fully loaded before calling open."
|
|
421
798
|
);
|
|
422
799
|
}
|
|
423
800
|
|
|
801
|
+
// Track the source path if input is a string
|
|
802
|
+
const sourcePath = typeof input === "string" ? input : undefined;
|
|
803
|
+
|
|
804
|
+
// Read file data if input is a path or File object
|
|
805
|
+
const audioData = await readFileData(input);
|
|
806
|
+
|
|
807
|
+
// Ensure we pass the correct slice of the buffer
|
|
808
|
+
const buffer = audioData.buffer.slice(
|
|
809
|
+
audioData.byteOffset,
|
|
810
|
+
audioData.byteOffset + audioData.byteLength
|
|
811
|
+
);
|
|
812
|
+
|
|
424
813
|
// Convert ArrayBuffer to Uint8Array for Embind
|
|
425
814
|
const uint8Array = new Uint8Array(buffer);
|
|
426
815
|
|
|
@@ -430,10 +819,92 @@ export class TagLib {
|
|
|
430
819
|
// Load the buffer - Embind should handle Uint8Array conversion
|
|
431
820
|
const success = fileHandle.loadFromBuffer(uint8Array);
|
|
432
821
|
if (!success) {
|
|
433
|
-
throw new
|
|
822
|
+
throw new InvalidFormatError(
|
|
823
|
+
"Failed to load audio file. File may be corrupted or in an unsupported format",
|
|
824
|
+
buffer.byteLength
|
|
825
|
+
);
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
return new AudioFileImpl(this.module, fileHandle, sourcePath);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
/**
|
|
832
|
+
* Update tags in a file and save changes to disk in one operation.
|
|
833
|
+
* This is a convenience method that opens, modifies, saves, and closes the file.
|
|
834
|
+
*
|
|
835
|
+
* @param path - File path to update
|
|
836
|
+
* @param tags - Object containing tags to update
|
|
837
|
+
* @throws {Error} If file operations fail
|
|
838
|
+
*
|
|
839
|
+
* @example
|
|
840
|
+
* ```typescript
|
|
841
|
+
* await taglib.updateFile("song.mp3", {
|
|
842
|
+
* title: "New Title",
|
|
843
|
+
* artist: "New Artist"
|
|
844
|
+
* });
|
|
845
|
+
* ```
|
|
846
|
+
*/
|
|
847
|
+
async updateFile(path: string, tags: Partial<BasicTag>): Promise<void> {
|
|
848
|
+
const file = await this.open(path);
|
|
849
|
+
try {
|
|
850
|
+
const tag = file.tag();
|
|
851
|
+
|
|
852
|
+
// Apply tag updates
|
|
853
|
+
if (tags.title !== undefined) tag.setTitle(tags.title);
|
|
854
|
+
if (tags.artist !== undefined) tag.setArtist(tags.artist);
|
|
855
|
+
if (tags.album !== undefined) tag.setAlbum(tags.album);
|
|
856
|
+
if (tags.year !== undefined) tag.setYear(tags.year);
|
|
857
|
+
if (tags.track !== undefined) tag.setTrack(tags.track);
|
|
858
|
+
if (tags.genre !== undefined) tag.setGenre(tags.genre);
|
|
859
|
+
if (tags.comment !== undefined) tag.setComment(tags.comment);
|
|
860
|
+
|
|
861
|
+
// Save to file
|
|
862
|
+
await file.saveToFile();
|
|
863
|
+
} finally {
|
|
864
|
+
file.dispose();
|
|
434
865
|
}
|
|
866
|
+
}
|
|
435
867
|
|
|
436
|
-
|
|
868
|
+
/**
|
|
869
|
+
* Copy a file with new tags.
|
|
870
|
+
* Opens the source file, applies new tags, and saves to a new location.
|
|
871
|
+
*
|
|
872
|
+
* @param sourcePath - Source file path
|
|
873
|
+
* @param destPath - Destination file path
|
|
874
|
+
* @param tags - Object containing tags to apply
|
|
875
|
+
* @throws {Error} If file operations fail
|
|
876
|
+
*
|
|
877
|
+
* @example
|
|
878
|
+
* ```typescript
|
|
879
|
+
* await taglib.copyWithTags("original.mp3", "copy.mp3", {
|
|
880
|
+
* title: "Copy of Song",
|
|
881
|
+
* comment: "This is a copy"
|
|
882
|
+
* });
|
|
883
|
+
* ```
|
|
884
|
+
*/
|
|
885
|
+
async copyWithTags(
|
|
886
|
+
sourcePath: string,
|
|
887
|
+
destPath: string,
|
|
888
|
+
tags: Partial<BasicTag>
|
|
889
|
+
): Promise<void> {
|
|
890
|
+
const file = await this.open(sourcePath);
|
|
891
|
+
try {
|
|
892
|
+
const tag = file.tag();
|
|
893
|
+
|
|
894
|
+
// Apply tag updates
|
|
895
|
+
if (tags.title !== undefined) tag.setTitle(tags.title);
|
|
896
|
+
if (tags.artist !== undefined) tag.setArtist(tags.artist);
|
|
897
|
+
if (tags.album !== undefined) tag.setAlbum(tags.album);
|
|
898
|
+
if (tags.year !== undefined) tag.setYear(tags.year);
|
|
899
|
+
if (tags.track !== undefined) tag.setTrack(tags.track);
|
|
900
|
+
if (tags.genre !== undefined) tag.setGenre(tags.genre);
|
|
901
|
+
if (tags.comment !== undefined) tag.setComment(tags.comment);
|
|
902
|
+
|
|
903
|
+
// Save to new location
|
|
904
|
+
await file.saveToFile(destPath);
|
|
905
|
+
} finally {
|
|
906
|
+
file.dispose();
|
|
907
|
+
}
|
|
437
908
|
}
|
|
438
909
|
|
|
439
910
|
/**
|
|
@@ -463,3 +934,26 @@ export class TagLib {
|
|
|
463
934
|
export async function createTagLib(module: WasmModule): Promise<TagLib> {
|
|
464
935
|
return new TagLib(module);
|
|
465
936
|
}
|
|
937
|
+
|
|
938
|
+
/**
|
|
939
|
+
* Re-export error types for convenient error handling
|
|
940
|
+
*/
|
|
941
|
+
export {
|
|
942
|
+
EnvironmentError,
|
|
943
|
+
FileOperationError,
|
|
944
|
+
InvalidFormatError,
|
|
945
|
+
isEnvironmentError,
|
|
946
|
+
isFileOperationError,
|
|
947
|
+
isInvalidFormatError,
|
|
948
|
+
isMemoryError,
|
|
949
|
+
isMetadataError,
|
|
950
|
+
isTagLibError,
|
|
951
|
+
isUnsupportedFormatError,
|
|
952
|
+
MemoryError,
|
|
953
|
+
MetadataError,
|
|
954
|
+
SUPPORTED_FORMATS,
|
|
955
|
+
TagLibError,
|
|
956
|
+
TagLibErrorCode,
|
|
957
|
+
TagLibInitializationError,
|
|
958
|
+
UnsupportedFormatError,
|
|
959
|
+
} from "./errors.ts";
|