taglib-wasm 2.2.3 → 2.3.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.
Files changed (60) hide show
  1. package/README.md +192 -13
  2. package/dist/index.browser.d.ts +24 -4
  3. package/dist/index.browser.d.ts.map +1 -1
  4. package/dist/index.browser.js +1123 -208
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +2 -0
  8. package/dist/simple.browser.js +527 -202
  9. package/dist/src/errors/base.d.ts +17 -2
  10. package/dist/src/errors/base.d.ts.map +1 -1
  11. package/dist/src/errors/base.js +18 -0
  12. package/dist/src/runtime/wasi-adapter/adapter.d.ts +13 -1
  13. package/dist/src/runtime/wasi-adapter/adapter.d.ts.map +1 -1
  14. package/dist/src/runtime/wasi-adapter/adapter.js +17 -0
  15. package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +4 -1
  16. package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -1
  17. package/dist/src/runtime/wasi-adapter/audio-properties.js +23 -6
  18. package/dist/src/runtime/wasi-adapter/path-open.d.ts +68 -0
  19. package/dist/src/runtime/wasi-adapter/path-open.d.ts.map +1 -0
  20. package/dist/src/runtime/wasi-adapter/path-open.js +76 -0
  21. package/dist/src/runtime/wasi-adapter/wasm-io.d.ts.map +1 -1
  22. package/dist/src/runtime/wasi-adapter/wasm-io.js +36 -28
  23. package/dist/src/runtime/wasi-host-loader.js +8 -3
  24. package/dist/src/runtime/wasmer-sdk-loader/types.d.ts +13 -0
  25. package/dist/src/runtime/wasmer-sdk-loader/types.d.ts.map +1 -1
  26. package/dist/src/simple/checksum-operations.d.ts +25 -0
  27. package/dist/src/simple/checksum-operations.d.ts.map +1 -0
  28. package/dist/src/simple/checksum-operations.js +7 -0
  29. package/dist/src/simple/index.d.ts +2 -0
  30. package/dist/src/simple/index.d.ts.map +1 -1
  31. package/dist/src/simple/index.js +2 -0
  32. package/dist/src/taglib/audio-file-checksum.d.ts +47 -0
  33. package/dist/src/taglib/audio-file-checksum.d.ts.map +1 -0
  34. package/dist/src/taglib/audio-file-checksum.js +77 -0
  35. package/dist/src/taglib/audio-file-impl.d.ts +4 -0
  36. package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
  37. package/dist/src/taglib/audio-file-impl.js +32 -0
  38. package/dist/src/taglib/audio-file-interface.d.ts +17 -0
  39. package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
  40. package/dist/src/taglib/media-ranges.d.ts +85 -0
  41. package/dist/src/taglib/media-ranges.d.ts.map +1 -0
  42. package/dist/src/taglib/media-ranges.js +192 -0
  43. package/dist/src/taglib/metadata-extent.d.ts +55 -0
  44. package/dist/src/taglib/metadata-extent.d.ts.map +1 -1
  45. package/dist/src/taglib/metadata-extent.js +24 -6
  46. package/dist/src/taglib/taglib-class.d.ts.map +1 -1
  47. package/dist/src/taglib/taglib-class.js +13 -24
  48. package/dist/src/utils/path.d.ts +12 -0
  49. package/dist/src/utils/path.d.ts.map +1 -1
  50. package/dist/src/utils/path.js +19 -0
  51. package/dist/src/version.d.ts +1 -1
  52. package/dist/src/version.js +1 -1
  53. package/dist/src/wasm.d.ts +9 -0
  54. package/dist/src/wasm.d.ts.map +1 -1
  55. package/dist/taglib-wasi.wasm +0 -0
  56. package/dist/taglib-web.wasm +0 -0
  57. package/dist/web.browser.d.ts +13 -0
  58. package/dist/web.browser.d.ts.map +1 -0
  59. package/dist/web.browser.js +3630 -0
  60. package/package.json +12 -4
@@ -0,0 +1,3630 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
4
+ var __esm = (fn, res, err) => function __init() {
5
+ if (err) throw err[0];
6
+ try {
7
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
8
+ } catch (e) {
9
+ throw err = [e], e;
10
+ }
11
+ };
12
+ var __export = (target, all) => {
13
+ for (var name in all)
14
+ __defProp(target, name, { get: all[name], enumerable: true });
15
+ };
16
+ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
17
+
18
+ // src/errors/base.ts
19
+ var SUPPORTED_FORMATS, TagLibError;
20
+ var init_base = __esm({
21
+ "src/errors/base.ts"() {
22
+ "use strict";
23
+ SUPPORTED_FORMATS = [
24
+ "MP3",
25
+ "AAC",
26
+ "MP4",
27
+ "M4A",
28
+ "FLAC",
29
+ "OGG",
30
+ "OPUS",
31
+ "OggFLAC",
32
+ "SPEEX",
33
+ "WAV",
34
+ "AIFF",
35
+ "ASF",
36
+ "APE",
37
+ "DSF",
38
+ "DSDIFF",
39
+ "WV",
40
+ "MPC",
41
+ "TTA",
42
+ "SHN",
43
+ "MOD",
44
+ "S3M",
45
+ "IT",
46
+ "XM",
47
+ "MATROSKA",
48
+ "MKA"
49
+ ];
50
+ TagLibError = class _TagLibError extends Error {
51
+ /**
52
+ * Creates a new TagLibError
53
+ * @param code - Error code for programmatic handling
54
+ * @param message - Human-readable error message
55
+ * @param details - Additional context about the error
56
+ */
57
+ constructor(code, message, details) {
58
+ super(message);
59
+ __publicField(this, "code", code);
60
+ __publicField(this, "details", details);
61
+ this.name = "TagLibError";
62
+ Object.setPrototypeOf(this, _TagLibError.prototype);
63
+ }
64
+ };
65
+ }
66
+ });
67
+
68
+ // src/errors/classes.ts
69
+ function errorMessage(error) {
70
+ return error instanceof Error ? error.message : String(error);
71
+ }
72
+ function createErrorMessage(prefix, details) {
73
+ return `${prefix}: ${details}`;
74
+ }
75
+ function formatFileSize(bytes) {
76
+ if (bytes < 1024) return `${bytes} bytes`;
77
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
78
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
79
+ }
80
+ var TagLibInitializationError, InvalidFormatError, UnsupportedFormatError, FileOperationError, MetadataError, MemoryError, EnvironmentError;
81
+ var init_classes = __esm({
82
+ "src/errors/classes.ts"() {
83
+ "use strict";
84
+ init_base();
85
+ TagLibInitializationError = class _TagLibInitializationError extends TagLibError {
86
+ /**
87
+ * Creates a new TagLibInitializationError
88
+ * @param message - Description of the initialization failure
89
+ * @param details - Additional context about the error
90
+ */
91
+ constructor(message, details) {
92
+ super(
93
+ "INITIALIZATION",
94
+ createErrorMessage("Failed to initialize TagLib Wasm module", message),
95
+ details
96
+ );
97
+ this.name = "TagLibInitializationError";
98
+ Object.setPrototypeOf(this, _TagLibInitializationError.prototype);
99
+ }
100
+ };
101
+ InvalidFormatError = class _InvalidFormatError extends TagLibError {
102
+ /**
103
+ * Creates a new InvalidFormatError
104
+ * @param message - Description of the format error
105
+ * @param bufferSize - Size of the audio buffer in bytes
106
+ * @param details - Additional context about the error
107
+ */
108
+ constructor(message, bufferSize, details) {
109
+ const errorDetails = [`Invalid audio file format: ${message}`];
110
+ if (bufferSize !== void 0) {
111
+ errorDetails.push(`Buffer size: ${formatFileSize(bufferSize)}`);
112
+ if (bufferSize < 1024) {
113
+ errorDetails.push(
114
+ "Audio files must be at least 1KB to contain valid headers."
115
+ );
116
+ }
117
+ }
118
+ super(
119
+ "INVALID_FORMAT",
120
+ errorDetails.join(". "),
121
+ { ...details, bufferSize }
122
+ );
123
+ __publicField(this, "bufferSize", bufferSize);
124
+ this.name = "InvalidFormatError";
125
+ Object.setPrototypeOf(this, _InvalidFormatError.prototype);
126
+ }
127
+ };
128
+ UnsupportedFormatError = class _UnsupportedFormatError extends TagLibError {
129
+ /**
130
+ * Creates a new UnsupportedFormatError
131
+ * @param format - The unsupported format that was encountered
132
+ * @param supportedFormats - List of formats that are supported
133
+ * @param details - Additional context about the error
134
+ */
135
+ constructor(format, supportedFormats = SUPPORTED_FORMATS, details) {
136
+ super(
137
+ "UNSUPPORTED_FORMAT",
138
+ `Unsupported audio format: ${format}. Supported formats: ${supportedFormats.join(", ")}`,
139
+ { ...details, format, supportedFormats }
140
+ );
141
+ __publicField(this, "format", format);
142
+ __publicField(this, "supportedFormats", supportedFormats);
143
+ this.name = "UnsupportedFormatError";
144
+ Object.setPrototypeOf(this, _UnsupportedFormatError.prototype);
145
+ }
146
+ };
147
+ FileOperationError = class _FileOperationError extends TagLibError {
148
+ /**
149
+ * Creates a new FileOperationError
150
+ * @param operation - The file operation that failed
151
+ * @param message - Description of the failure
152
+ * @param path - File path involved in the operation
153
+ * @param details - Additional context about the error
154
+ */
155
+ constructor(operation, message, path, details) {
156
+ const errorDetails = [`Failed to ${operation} file`];
157
+ if (path) {
158
+ errorDetails.push(`Path: ${path}`);
159
+ }
160
+ errorDetails.push(message);
161
+ super(
162
+ "FILE_OPERATION",
163
+ errorDetails.join(". "),
164
+ { ...details, operation, path }
165
+ );
166
+ __publicField(this, "operation", operation);
167
+ __publicField(this, "path", path);
168
+ this.name = "FileOperationError";
169
+ Object.setPrototypeOf(this, _FileOperationError.prototype);
170
+ }
171
+ };
172
+ MetadataError = class _MetadataError extends TagLibError {
173
+ /**
174
+ * Creates a new MetadataError
175
+ * @param operation - The metadata operation that failed
176
+ * @param message - Description of the failure
177
+ * @param field - The metadata field involved
178
+ * @param details - Additional context about the error
179
+ */
180
+ constructor(operation, message, field, details) {
181
+ const errorDetails = [`Failed to ${operation} metadata`];
182
+ if (field) {
183
+ errorDetails.push(`Field: ${field}`);
184
+ }
185
+ errorDetails.push(message);
186
+ super(
187
+ "METADATA",
188
+ errorDetails.join(". "),
189
+ { ...details, operation, field }
190
+ );
191
+ __publicField(this, "operation", operation);
192
+ __publicField(this, "field", field);
193
+ this.name = "MetadataError";
194
+ Object.setPrototypeOf(this, _MetadataError.prototype);
195
+ }
196
+ };
197
+ MemoryError = class _MemoryError extends TagLibError {
198
+ /**
199
+ * Creates a new MemoryError
200
+ * @param message - Description of the memory failure
201
+ * @param details - Additional context about the error
202
+ */
203
+ constructor(message, details) {
204
+ super(
205
+ "MEMORY",
206
+ createErrorMessage("Memory allocation failed", message),
207
+ details
208
+ );
209
+ this.name = "MemoryError";
210
+ Object.setPrototypeOf(this, _MemoryError.prototype);
211
+ }
212
+ };
213
+ EnvironmentError = class _EnvironmentError extends TagLibError {
214
+ /**
215
+ * Creates a new EnvironmentError
216
+ * @param environment - The runtime environment name
217
+ * @param reason - Why the environment is incompatible
218
+ * @param requiredFeature - The feature that is missing
219
+ */
220
+ constructor(environment, reason, requiredFeature) {
221
+ const message = requiredFeature ? `Environment '${environment}' ${reason}. Required feature: ${requiredFeature}.` : `Environment '${environment}' ${reason}.`;
222
+ super("ENVIRONMENT", message);
223
+ __publicField(this, "environment", environment);
224
+ __publicField(this, "reason", reason);
225
+ __publicField(this, "requiredFeature", requiredFeature);
226
+ this.name = "EnvironmentError";
227
+ Object.setPrototypeOf(this, _EnvironmentError.prototype);
228
+ }
229
+ };
230
+ }
231
+ });
232
+
233
+ // src/types/audio-formats.ts
234
+ function isNamedAudioInput(input) {
235
+ return typeof input === "object" && input !== null && "name" in input && "data" in input && !(input instanceof File) && !(input instanceof Uint8Array) && !(input instanceof ArrayBuffer);
236
+ }
237
+ var init_audio_formats = __esm({
238
+ "src/types/audio-formats.ts"() {
239
+ "use strict";
240
+ }
241
+ });
242
+
243
+ // src/types/tags.ts
244
+ var init_tags = __esm({
245
+ "src/types/tags.ts"() {
246
+ "use strict";
247
+ }
248
+ });
249
+
250
+ // src/types/metadata-mappings.ts
251
+ var init_metadata_mappings = __esm({
252
+ "src/types/metadata-mappings.ts"() {
253
+ "use strict";
254
+ }
255
+ });
256
+
257
+ // src/types/pictures.ts
258
+ var PICTURE_TYPE_VALUES, PICTURE_TYPE_NAMES;
259
+ var init_pictures = __esm({
260
+ "src/types/pictures.ts"() {
261
+ "use strict";
262
+ PICTURE_TYPE_VALUES = {
263
+ Other: 0,
264
+ FileIcon: 1,
265
+ OtherFileIcon: 2,
266
+ FrontCover: 3,
267
+ BackCover: 4,
268
+ LeafletPage: 5,
269
+ Media: 6,
270
+ LeadArtist: 7,
271
+ Artist: 8,
272
+ Conductor: 9,
273
+ Band: 10,
274
+ Composer: 11,
275
+ Lyricist: 12,
276
+ RecordingLocation: 13,
277
+ DuringRecording: 14,
278
+ DuringPerformance: 15,
279
+ MovieScreenCapture: 16,
280
+ ColouredFish: 17,
281
+ Illustration: 18,
282
+ BandLogo: 19,
283
+ PublisherLogo: 20
284
+ };
285
+ PICTURE_TYPE_NAMES = {
286
+ 0: "Other",
287
+ 1: "FileIcon",
288
+ 2: "OtherFileIcon",
289
+ 3: "FrontCover",
290
+ 4: "BackCover",
291
+ 5: "LeafletPage",
292
+ 6: "Media",
293
+ 7: "LeadArtist",
294
+ 8: "Artist",
295
+ 9: "Conductor",
296
+ 10: "Band",
297
+ 11: "Composer",
298
+ 12: "Lyricist",
299
+ 13: "RecordingLocation",
300
+ 14: "DuringRecording",
301
+ 15: "DuringPerformance",
302
+ 16: "MovieScreenCapture",
303
+ 17: "ColouredFish",
304
+ 18: "Illustration",
305
+ 19: "BandLogo",
306
+ 20: "PublisherLogo"
307
+ };
308
+ }
309
+ });
310
+
311
+ // src/types/chapters.ts
312
+ var init_chapters = __esm({
313
+ "src/types/chapters.ts"() {
314
+ "use strict";
315
+ }
316
+ });
317
+
318
+ // src/types/bwf.ts
319
+ var init_bwf = __esm({
320
+ "src/types/bwf.ts"() {
321
+ "use strict";
322
+ }
323
+ });
324
+
325
+ // src/types/config.ts
326
+ var init_config = __esm({
327
+ "src/types/config.ts"() {
328
+ "use strict";
329
+ }
330
+ });
331
+
332
+ // src/types/format-property-keys.ts
333
+ var init_format_property_keys = __esm({
334
+ "src/types/format-property-keys.ts"() {
335
+ "use strict";
336
+ }
337
+ });
338
+
339
+ // src/types/index.ts
340
+ var init_types = __esm({
341
+ "src/types/index.ts"() {
342
+ "use strict";
343
+ init_audio_formats();
344
+ init_tags();
345
+ init_metadata_mappings();
346
+ init_pictures();
347
+ init_chapters();
348
+ init_bwf();
349
+ init_config();
350
+ init_format_property_keys();
351
+ }
352
+ });
353
+
354
+ // src/types.ts
355
+ var init_types2 = __esm({
356
+ "src/types.ts"() {
357
+ "use strict";
358
+ init_types();
359
+ }
360
+ });
361
+
362
+ // src/bwf/bext.ts
363
+ function readFixedString(bytes, offset, len) {
364
+ let end = offset;
365
+ const max = Math.min(offset + len, bytes.length);
366
+ while (end < max && bytes[end] !== 0) end++;
367
+ let s = "";
368
+ for (let i = offset; i < end; i++) s += String.fromCharCode(bytes[i]);
369
+ return s;
370
+ }
371
+ function writeFixedString(bytes, offset, len, value) {
372
+ for (let i = 0; i < len; i++) {
373
+ bytes[offset + i] = i < value.length ? value.charCodeAt(i) & 255 : 0;
374
+ }
375
+ }
376
+ function clampInt16(n) {
377
+ return Math.max(-32768, Math.min(32767, Math.round(n)));
378
+ }
379
+ function loudnessRaw(v) {
380
+ return v === void 0 ? LOUDNESS_UNSET : clampInt16(v * 100);
381
+ }
382
+ function loudnessFromRaw(raw) {
383
+ return raw === LOUDNESS_UNSET ? void 0 : raw / 100;
384
+ }
385
+ function bytesToHex(bytes) {
386
+ let s = "";
387
+ for (const b of bytes) s += b.toString(16).padStart(2, "0");
388
+ return s;
389
+ }
390
+ function hexToBytes(hex, outLen) {
391
+ const clean = hex.replace(/[^0-9a-fA-F]/g, "");
392
+ const out = new Uint8Array(outLen);
393
+ for (let i = 0; i + 1 < clean.length + 1 && i / 2 < outLen; i += 2) {
394
+ const pair = clean.slice(i, i + 2);
395
+ if (pair.length === 2) out[i / 2] = parseInt(pair, 16);
396
+ }
397
+ return out;
398
+ }
399
+ function decodeBext(bytes) {
400
+ if (bytes.length < VERSION_OFFSET + 2) return void 0;
401
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
402
+ const timeRefLow = dv.getUint32(338, true);
403
+ const timeRefHigh = dv.getUint32(342, true);
404
+ const version = dv.getUint16(VERSION_OFFSET, true);
405
+ const result = {
406
+ description: readFixedString(bytes, 0, 256),
407
+ originator: readFixedString(bytes, 256, 32),
408
+ originatorReference: readFixedString(bytes, 288, 32),
409
+ originationDate: readFixedString(bytes, 320, 10),
410
+ originationTime: readFixedString(bytes, 330, 8),
411
+ timeReferenceSamples: BigInt(timeRefHigh) << 32n | BigInt(timeRefLow),
412
+ version,
413
+ codingHistory: bytes.length > FIXED_PREFIX_LEN ? readFixedString(
414
+ bytes,
415
+ FIXED_PREFIX_LEN,
416
+ bytes.length - FIXED_PREFIX_LEN
417
+ ) : ""
418
+ };
419
+ if (version >= 1 && bytes.length >= UMID_OFFSET + UMID_LEN) {
420
+ result.umid = bytesToHex(
421
+ bytes.subarray(UMID_OFFSET, UMID_OFFSET + UMID_LEN)
422
+ );
423
+ }
424
+ if (version >= 2 && bytes.length >= LOUDNESS_OFFSET + 10) {
425
+ const lv = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET, true));
426
+ const lr = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 2, true));
427
+ const mtp = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 4, true));
428
+ const mml = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 6, true));
429
+ const msl = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 8, true));
430
+ if (lv !== void 0) result.loudnessValueDb = lv;
431
+ if (lr !== void 0) result.loudnessRangeDb = lr;
432
+ if (mtp !== void 0) result.maxTruePeakLevelDbtp = mtp;
433
+ if (mml !== void 0) result.maxMomentaryLoudnessDb = mml;
434
+ if (msl !== void 0) result.maxShortTermLoudnessDb = msl;
435
+ }
436
+ return result;
437
+ }
438
+ function encodeBext(b) {
439
+ const codingHistory = b.codingHistory ?? "";
440
+ const bytes = new Uint8Array(FIXED_PREFIX_LEN + codingHistory.length);
441
+ const dv = new DataView(bytes.buffer);
442
+ const hasLoudness = b.loudnessValueDb !== void 0 || b.loudnessRangeDb !== void 0 || b.maxTruePeakLevelDbtp !== void 0 || b.maxMomentaryLoudnessDb !== void 0 || b.maxShortTermLoudnessDb !== void 0;
443
+ const version = Number.isInteger(b.version) ? b.version : hasLoudness ? 2 : b.umid ? 1 : 0;
444
+ writeFixedString(bytes, 0, 256, b.description ?? "");
445
+ writeFixedString(bytes, 256, 32, b.originator ?? "");
446
+ writeFixedString(bytes, 288, 32, b.originatorReference ?? "");
447
+ writeFixedString(bytes, 320, 10, b.originationDate ?? "");
448
+ writeFixedString(bytes, 330, 8, b.originationTime ?? "");
449
+ const tr = b.timeReferenceSamples ?? 0n;
450
+ dv.setUint32(338, Number(tr & 0xffffffffn), true);
451
+ dv.setUint32(342, Number(tr >> 32n & 0xffffffffn), true);
452
+ dv.setUint16(VERSION_OFFSET, version, true);
453
+ if (version >= 1 && b.umid) {
454
+ bytes.set(hexToBytes(b.umid, UMID_LEN), UMID_OFFSET);
455
+ }
456
+ if (version >= 2) {
457
+ dv.setInt16(LOUDNESS_OFFSET, loudnessRaw(b.loudnessValueDb), true);
458
+ dv.setInt16(LOUDNESS_OFFSET + 2, loudnessRaw(b.loudnessRangeDb), true);
459
+ dv.setInt16(LOUDNESS_OFFSET + 4, loudnessRaw(b.maxTruePeakLevelDbtp), true);
460
+ dv.setInt16(
461
+ LOUDNESS_OFFSET + 6,
462
+ loudnessRaw(b.maxMomentaryLoudnessDb),
463
+ true
464
+ );
465
+ dv.setInt16(
466
+ LOUDNESS_OFFSET + 8,
467
+ loudnessRaw(b.maxShortTermLoudnessDb),
468
+ true
469
+ );
470
+ }
471
+ writeFixedString(
472
+ bytes,
473
+ FIXED_PREFIX_LEN,
474
+ codingHistory.length,
475
+ codingHistory
476
+ );
477
+ return bytes;
478
+ }
479
+ var FIXED_PREFIX_LEN, VERSION_OFFSET, UMID_OFFSET, UMID_LEN, LOUDNESS_OFFSET, LOUDNESS_UNSET;
480
+ var init_bext = __esm({
481
+ "src/bwf/bext.ts"() {
482
+ "use strict";
483
+ FIXED_PREFIX_LEN = 602;
484
+ VERSION_OFFSET = 346;
485
+ UMID_OFFSET = 348;
486
+ UMID_LEN = 64;
487
+ LOUDNESS_OFFSET = 412;
488
+ LOUDNESS_UNSET = 32767;
489
+ }
490
+ });
491
+
492
+ // src/errors/guards.ts
493
+ function isTagLibError(error) {
494
+ return error instanceof TagLibError;
495
+ }
496
+ function isInvalidFormatError(error) {
497
+ return error instanceof InvalidFormatError;
498
+ }
499
+ function isUnsupportedFormatError(error) {
500
+ return error instanceof UnsupportedFormatError;
501
+ }
502
+ function isFileOperationError(error) {
503
+ return error instanceof FileOperationError;
504
+ }
505
+ function isMetadataError(error) {
506
+ return error instanceof MetadataError;
507
+ }
508
+ function isMemoryError(error) {
509
+ return error instanceof MemoryError;
510
+ }
511
+ function isEnvironmentError(error) {
512
+ return error instanceof EnvironmentError;
513
+ }
514
+ var init_guards = __esm({
515
+ "src/errors/guards.ts"() {
516
+ "use strict";
517
+ init_base();
518
+ init_classes();
519
+ }
520
+ });
521
+
522
+ // src/errors/index.ts
523
+ var init_errors = __esm({
524
+ "src/errors/index.ts"() {
525
+ "use strict";
526
+ init_base();
527
+ init_classes();
528
+ init_guards();
529
+ }
530
+ });
531
+
532
+ // src/errors.ts
533
+ var init_errors2 = __esm({
534
+ "src/errors.ts"() {
535
+ "use strict";
536
+ init_errors();
537
+ }
538
+ });
539
+
540
+ // src/taglib/audio-file-bwf.ts
541
+ function requireBwf(format) {
542
+ if (!BWF_FORMATS.has(format)) {
543
+ throw new UnsupportedFormatError(format, ["WAV", "FLAC"], {
544
+ operation: "BWF metadata"
545
+ });
546
+ }
547
+ }
548
+ function getBextData(handle) {
549
+ const raw = handle.getBextData();
550
+ return raw && raw.length > 0 ? raw : void 0;
551
+ }
552
+ function setBextData(handle, format, data) {
553
+ requireBwf(format);
554
+ handle.setBextData(data);
555
+ }
556
+ function getBext(handle) {
557
+ const raw = handle.getBextData();
558
+ return raw && raw.length > 0 ? decodeBext(raw) : void 0;
559
+ }
560
+ function setBext(handle, format, bext) {
561
+ requireBwf(format);
562
+ handle.setBextData(encodeBext(bext));
563
+ }
564
+ function getIxml(handle) {
565
+ return handle.getIxml();
566
+ }
567
+ function setIxml(handle, format, data) {
568
+ requireBwf(format);
569
+ handle.setIxml(data);
570
+ }
571
+ var BWF_FORMATS;
572
+ var init_audio_file_bwf = __esm({
573
+ "src/taglib/audio-file-bwf.ts"() {
574
+ "use strict";
575
+ init_bext();
576
+ init_errors2();
577
+ BWF_FORMATS = /* @__PURE__ */ new Set(["WAV", "FLAC"]);
578
+ }
579
+ });
580
+
581
+ // src/taglib/id3v2-frames.ts
582
+ function assertMp3(format) {
583
+ if (format !== "MP3" && format !== "AAC") {
584
+ throw new UnsupportedFormatError(format, ["MP3", "AAC"], {
585
+ operation: "id3v2Frames"
586
+ });
587
+ }
588
+ }
589
+ function assertFrameId(id, operation) {
590
+ if (!FRAME_ID_PATTERN.test(id)) {
591
+ throw new MetadataError(
592
+ operation,
593
+ `Invalid ID3v2 frame ID: "${id}". Frame IDs must match [A-Z0-9]{4}`,
594
+ id
595
+ );
596
+ }
597
+ }
598
+ function assertFrameBodies(id, data) {
599
+ for (const body of data) {
600
+ if (body.length === 0) {
601
+ throw new MetadataError(
602
+ "write",
603
+ "Frame data must not be empty: zero-size ID3v2 frames are invalid",
604
+ id
605
+ );
606
+ }
607
+ }
608
+ }
609
+ function toPublicFrame(frame) {
610
+ return {
611
+ id: frame.id,
612
+ data: frame.data,
613
+ ...frame.flags ? { flags: frame.flags } : {}
614
+ };
615
+ }
616
+ var FRAME_ID_PATTERN;
617
+ var init_id3v2_frames = __esm({
618
+ "src/taglib/id3v2-frames.ts"() {
619
+ "use strict";
620
+ init_classes();
621
+ FRAME_ID_PATTERN = /^[A-Z0-9]{4}$/;
622
+ }
623
+ });
624
+
625
+ // browser-stub:platform-io-browser-stub
626
+ function getPlatformIO() {
627
+ throw new Error("Filesystem operations are not available in the browser.");
628
+ }
629
+ var init_platform_io_browser_stub = __esm({
630
+ "browser-stub:platform-io-browser-stub"() {
631
+ }
632
+ });
633
+
634
+ // src/utils/write.ts
635
+ async function writeFileData(path, data) {
636
+ try {
637
+ await getPlatformIO().writeFile(path, data);
638
+ } catch (error) {
639
+ if (error instanceof EnvironmentError) throw error;
640
+ throw new FileOperationError(
641
+ "write",
642
+ error.message,
643
+ path
644
+ );
645
+ }
646
+ }
647
+ var init_write = __esm({
648
+ "src/utils/write.ts"() {
649
+ "use strict";
650
+ init_errors2();
651
+ init_platform_io_browser_stub();
652
+ }
653
+ });
654
+
655
+ // browser-stub:node-fs-browser-stub
656
+ function getNodeFsSync() {
657
+ return null;
658
+ }
659
+ var init_node_fs_browser_stub = __esm({
660
+ "browser-stub:node-fs-browser-stub"() {
661
+ }
662
+ });
663
+
664
+ // src/utils/rating.ts
665
+ function normalized(value) {
666
+ return value;
667
+ }
668
+ var init_rating = __esm({
669
+ "src/utils/rating.ts"() {
670
+ "use strict";
671
+ }
672
+ });
673
+
674
+ // src/runtime/detector.ts
675
+ function isDeno() {
676
+ return g.Deno !== void 0;
677
+ }
678
+ var g, EXNREF_PROBE;
679
+ var init_detector = __esm({
680
+ "src/runtime/detector.ts"() {
681
+ "use strict";
682
+ g = globalThis;
683
+ EXNREF_PROBE = new Uint8Array([
684
+ 0,
685
+ 97,
686
+ 115,
687
+ 109,
688
+ 1,
689
+ 0,
690
+ 0,
691
+ 0,
692
+ // Wasm header
693
+ 6,
694
+ 6,
695
+ // Global section, 6 bytes
696
+ 1,
697
+ // 1 global
698
+ 105,
699
+ 0,
700
+ // exnref, const
701
+ 208,
702
+ 105,
703
+ 11
704
+ // ref.null exnref, end
705
+ ]);
706
+ }
707
+ });
708
+
709
+ // src/constants/additional-properties.ts
710
+ var ADDITIONAL_PROPERTIES;
711
+ var init_additional_properties = __esm({
712
+ "src/constants/additional-properties.ts"() {
713
+ "use strict";
714
+ ADDITIONAL_PROPERTIES = {
715
+ albumArtistSort: {
716
+ key: "ALBUMARTISTSORT",
717
+ description: "Sort name for album artist (for alphabetization)",
718
+ type: "string",
719
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
720
+ mappings: {
721
+ id3v2: { frame: "TSO2" },
722
+ vorbis: "ALBUMARTISTSORT",
723
+ mp4: "soaa"
724
+ }
725
+ },
726
+ composerSort: {
727
+ key: "COMPOSERSORT",
728
+ description: "Sort name for composer (for alphabetization)",
729
+ type: "string",
730
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
731
+ mappings: {
732
+ id3v2: { frame: "TSOC" },
733
+ vorbis: "COMPOSERSORT",
734
+ mp4: "soco"
735
+ }
736
+ },
737
+ subtitle: {
738
+ key: "SUBTITLE",
739
+ description: "Subtitle or description refinement",
740
+ type: "string",
741
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
742
+ mappings: {
743
+ id3v2: { frame: "TIT3" },
744
+ vorbis: "SUBTITLE",
745
+ mp4: "----:com.apple.iTunes:SUBTITLE"
746
+ }
747
+ },
748
+ label: {
749
+ key: "LABEL",
750
+ description: "Record label name",
751
+ type: "string",
752
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
753
+ mappings: {
754
+ id3v2: { frame: "TPUB" },
755
+ vorbis: "LABEL",
756
+ mp4: "----:com.apple.iTunes:LABEL",
757
+ wav: "IPUB"
758
+ }
759
+ },
760
+ producer: {
761
+ key: "PRODUCER",
762
+ description: "Producer of the recording",
763
+ type: "string",
764
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
765
+ mappings: {
766
+ id3v2: { frame: "TXXX", description: "PRODUCER" },
767
+ vorbis: "PRODUCER",
768
+ mp4: "----:com.apple.iTunes:PRODUCER"
769
+ }
770
+ },
771
+ radioStationOwner: {
772
+ key: "RADIOSTATIONOWNER",
773
+ description: "Owner of the radio station",
774
+ type: "string",
775
+ supportedFormats: ["ID3v2"],
776
+ mappings: {
777
+ id3v2: { frame: "TRSO" }
778
+ }
779
+ },
780
+ asin: {
781
+ key: "ASIN",
782
+ description: "Amazon Standard Identification Number",
783
+ type: "string",
784
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
785
+ mappings: {
786
+ id3v2: { frame: "TXXX", description: "ASIN" },
787
+ vorbis: "ASIN",
788
+ mp4: "----:com.apple.iTunes:ASIN"
789
+ }
790
+ },
791
+ musicbrainzReleaseArtistId: {
792
+ key: "MUSICBRAINZ_ALBUMARTISTID",
793
+ description: "MusicBrainz Release Artist ID (UUID)",
794
+ type: "string",
795
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
796
+ mappings: {
797
+ id3v2: {
798
+ frame: "TXXX",
799
+ description: "MusicBrainz Album Artist Id"
800
+ },
801
+ vorbis: "MUSICBRAINZ_ALBUMARTISTID",
802
+ mp4: "----:com.apple.iTunes:MusicBrainz Album Artist Id"
803
+ }
804
+ },
805
+ musicbrainzWorkId: {
806
+ key: "MUSICBRAINZ_WORKID",
807
+ description: "MusicBrainz Work ID (UUID)",
808
+ type: "string",
809
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
810
+ mappings: {
811
+ id3v2: { frame: "TXXX", description: "MusicBrainz Work Id" },
812
+ vorbis: "MUSICBRAINZ_WORKID",
813
+ mp4: "----:com.apple.iTunes:MusicBrainz Work Id"
814
+ }
815
+ },
816
+ musicbrainzReleaseTrackId: {
817
+ key: "MUSICBRAINZ_RELEASETRACKID",
818
+ description: "MusicBrainz Release Track ID (UUID)",
819
+ type: "string",
820
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
821
+ mappings: {
822
+ id3v2: {
823
+ frame: "TXXX",
824
+ description: "MusicBrainz Release Track Id"
825
+ },
826
+ vorbis: "MUSICBRAINZ_RELEASETRACKID",
827
+ mp4: "----:com.apple.iTunes:MusicBrainz Release Track Id"
828
+ }
829
+ },
830
+ podcastId: {
831
+ key: "PODCASTID",
832
+ description: "Podcast episode identifier",
833
+ type: "string",
834
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
835
+ mappings: {
836
+ id3v2: { frame: "TGID" },
837
+ vorbis: "PODCASTID",
838
+ mp4: "egid"
839
+ }
840
+ },
841
+ podcastUrl: {
842
+ key: "PODCASTURL",
843
+ description: "Podcast feed URL",
844
+ type: "string",
845
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
846
+ mappings: {
847
+ id3v2: { frame: "WFED" },
848
+ vorbis: "PODCASTURL",
849
+ mp4: "purl"
850
+ }
851
+ },
852
+ originalArtist: {
853
+ key: "ORIGINALARTIST",
854
+ description: "Original artist of a cover or remix",
855
+ type: "string",
856
+ supportedFormats: ["ID3v2", "Vorbis"],
857
+ mappings: {
858
+ id3v2: { frame: "TOPE" },
859
+ vorbis: "ORIGINALARTIST"
860
+ }
861
+ },
862
+ originalAlbum: {
863
+ key: "ORIGINALALBUM",
864
+ description: "Original album of a cover or remix",
865
+ type: "string",
866
+ supportedFormats: ["ID3v2", "Vorbis"],
867
+ mappings: {
868
+ id3v2: { frame: "TOAL" },
869
+ vorbis: "ORIGINALALBUM"
870
+ }
871
+ },
872
+ originalDate: {
873
+ key: "ORIGINALDATE",
874
+ description: "Original release date",
875
+ type: "string",
876
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
877
+ mappings: {
878
+ id3v2: { frame: "TDOR" },
879
+ vorbis: "ORIGINALDATE",
880
+ mp4: "----:com.apple.iTunes:ORIGINALDATE"
881
+ }
882
+ },
883
+ script: {
884
+ key: "SCRIPT",
885
+ description: "Writing script used for text (e.g., Latn, Jpan)",
886
+ type: "string",
887
+ supportedFormats: ["MP4", "Vorbis"],
888
+ mappings: {
889
+ vorbis: "SCRIPT",
890
+ mp4: "----:com.apple.iTunes:SCRIPT"
891
+ }
892
+ },
893
+ involvedPeople: {
894
+ key: "INVOLVEDPEOPLE",
895
+ description: "List of involved people and their roles",
896
+ type: "string",
897
+ supportedFormats: ["ID3v2"],
898
+ mappings: {
899
+ id3v2: { frame: "TIPL" }
900
+ }
901
+ },
902
+ encoding: {
903
+ key: "ENCODING",
904
+ description: "Encoding software or settings",
905
+ type: "string",
906
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
907
+ mappings: {
908
+ id3v2: { frame: "TSSE" },
909
+ vorbis: "ENCODING",
910
+ mp4: "\xA9too",
911
+ wav: "ISFT"
912
+ }
913
+ }
914
+ };
915
+ }
916
+ });
917
+
918
+ // src/constants/basic-properties.ts
919
+ var BASIC_PROPERTIES;
920
+ var init_basic_properties = __esm({
921
+ "src/constants/basic-properties.ts"() {
922
+ "use strict";
923
+ BASIC_PROPERTIES = {
924
+ title: {
925
+ key: "TITLE",
926
+ description: "The title of the track",
927
+ type: "string",
928
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
929
+ mappings: {
930
+ id3v2: { frame: "TIT2" },
931
+ vorbis: "TITLE",
932
+ mp4: "\xA9nam",
933
+ wav: "INAM"
934
+ }
935
+ },
936
+ artist: {
937
+ key: "ARTIST",
938
+ description: "The primary performer(s) of the track",
939
+ type: "string",
940
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
941
+ mappings: {
942
+ id3v2: { frame: "TPE1" },
943
+ vorbis: "ARTIST",
944
+ mp4: "\xA9ART",
945
+ wav: "IART"
946
+ }
947
+ },
948
+ album: {
949
+ key: "ALBUM",
950
+ description: "The album/collection name",
951
+ type: "string",
952
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
953
+ mappings: {
954
+ id3v2: { frame: "TALB" },
955
+ vorbis: "ALBUM",
956
+ mp4: "\xA9alb",
957
+ wav: "IPRD"
958
+ }
959
+ },
960
+ date: {
961
+ key: "DATE",
962
+ description: "The date of recording (typically year)",
963
+ type: "string",
964
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
965
+ mappings: {
966
+ id3v2: { frame: "TDRC" },
967
+ vorbis: "DATE",
968
+ mp4: "\xA9day",
969
+ wav: "ICRD"
970
+ }
971
+ },
972
+ trackNumber: {
973
+ key: "TRACKNUMBER",
974
+ description: "The track number within the album",
975
+ type: "string",
976
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
977
+ mappings: {
978
+ id3v2: { frame: "TRCK" },
979
+ vorbis: "TRACKNUMBER",
980
+ mp4: "trkn",
981
+ wav: "ITRK"
982
+ }
983
+ },
984
+ genre: {
985
+ key: "GENRE",
986
+ description: "The musical genre",
987
+ type: "string",
988
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
989
+ mappings: {
990
+ id3v2: { frame: "TCON" },
991
+ vorbis: "GENRE",
992
+ mp4: "\xA9gen",
993
+ wav: "IGNR"
994
+ }
995
+ },
996
+ comment: {
997
+ key: "COMMENT",
998
+ description: "Comments or notes about the track",
999
+ type: "string",
1000
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
1001
+ mappings: {
1002
+ id3v2: { frame: "COMM" },
1003
+ vorbis: "COMMENT",
1004
+ mp4: "\xA9cmt",
1005
+ wav: "ICMT"
1006
+ }
1007
+ }
1008
+ };
1009
+ }
1010
+ });
1011
+
1012
+ // src/constants/general-extended-properties.ts
1013
+ var GENERAL_EXTENDED_PROPERTIES;
1014
+ var init_general_extended_properties = __esm({
1015
+ "src/constants/general-extended-properties.ts"() {
1016
+ "use strict";
1017
+ GENERAL_EXTENDED_PROPERTIES = {
1018
+ albumArtist: {
1019
+ key: "ALBUMARTIST",
1020
+ description: "The album artist (band/orchestra/ensemble)",
1021
+ type: "string",
1022
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1023
+ mappings: {
1024
+ id3v2: { frame: "TPE2" },
1025
+ vorbis: "ALBUMARTIST",
1026
+ mp4: "aART"
1027
+ }
1028
+ },
1029
+ composer: {
1030
+ key: "COMPOSER",
1031
+ description: "The original composer(s) of the track",
1032
+ type: "string",
1033
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1034
+ mappings: {
1035
+ id3v2: { frame: "TCOM" },
1036
+ vorbis: "COMPOSER",
1037
+ mp4: "\xA9wrt"
1038
+ }
1039
+ },
1040
+ copyright: {
1041
+ key: "COPYRIGHT",
1042
+ description: "Copyright information",
1043
+ type: "string",
1044
+ supportedFormats: ["ID3v2", "Vorbis"],
1045
+ mappings: {
1046
+ vorbis: "COPYRIGHT"
1047
+ }
1048
+ },
1049
+ encodedBy: {
1050
+ key: "ENCODEDBY",
1051
+ description: "The encoding software or person",
1052
+ type: "string",
1053
+ supportedFormats: ["ID3v2", "Vorbis"],
1054
+ mappings: {
1055
+ vorbis: "ENCODEDBY"
1056
+ }
1057
+ },
1058
+ discNumber: {
1059
+ key: "DISCNUMBER",
1060
+ description: "The disc number for multi-disc sets",
1061
+ type: "string",
1062
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1063
+ mappings: {
1064
+ id3v2: { frame: "TPOS" },
1065
+ vorbis: "DISCNUMBER",
1066
+ mp4: "disk"
1067
+ }
1068
+ },
1069
+ bpm: {
1070
+ key: "BPM",
1071
+ description: "Beats per minute",
1072
+ type: "string",
1073
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1074
+ mappings: {
1075
+ id3v2: { frame: "TBPM" },
1076
+ vorbis: "BPM",
1077
+ mp4: "tmpo"
1078
+ }
1079
+ },
1080
+ totalTracks: {
1081
+ key: "TRACKTOTAL",
1082
+ description: "Total number of tracks on the album",
1083
+ type: "string",
1084
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1085
+ mappings: {
1086
+ id3v2: { frame: "TRCK" },
1087
+ vorbis: "TRACKTOTAL",
1088
+ mp4: "trkn"
1089
+ }
1090
+ },
1091
+ totalDiscs: {
1092
+ key: "DISCTOTAL",
1093
+ description: "Total number of discs in the set",
1094
+ type: "string",
1095
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1096
+ mappings: {
1097
+ id3v2: { frame: "TPOS" },
1098
+ vorbis: "DISCTOTAL",
1099
+ mp4: "disk"
1100
+ }
1101
+ },
1102
+ compilation: {
1103
+ key: "COMPILATION",
1104
+ description: "Whether the album is a compilation (various artists)",
1105
+ type: "boolean",
1106
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1107
+ mappings: {
1108
+ id3v2: { frame: "TCMP" },
1109
+ vorbis: "COMPILATION",
1110
+ mp4: "cpil"
1111
+ }
1112
+ },
1113
+ // Sorting Properties
1114
+ titleSort: {
1115
+ key: "TITLESORT",
1116
+ description: "Sort name for title (for alphabetization)",
1117
+ type: "string",
1118
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1119
+ mappings: {
1120
+ id3v2: { frame: "TSOT" },
1121
+ vorbis: "TITLESORT",
1122
+ mp4: "sonm"
1123
+ }
1124
+ },
1125
+ artistSort: {
1126
+ key: "ARTISTSORT",
1127
+ description: "Sort name for artist (for alphabetization)",
1128
+ type: "string",
1129
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1130
+ mappings: {
1131
+ id3v2: { frame: "TSOP" },
1132
+ vorbis: "ARTISTSORT",
1133
+ mp4: "soar"
1134
+ }
1135
+ },
1136
+ albumSort: {
1137
+ key: "ALBUMSORT",
1138
+ description: "Sort name for album (for alphabetization)",
1139
+ type: "string",
1140
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1141
+ mappings: {
1142
+ id3v2: { frame: "TSOA" },
1143
+ vorbis: "ALBUMSORT",
1144
+ mp4: "soal"
1145
+ }
1146
+ },
1147
+ // Additional common properties
1148
+ lyricist: {
1149
+ key: "LYRICIST",
1150
+ description: "The lyrics/text writer(s)",
1151
+ type: "string",
1152
+ supportedFormats: ["Vorbis"],
1153
+ mappings: {
1154
+ vorbis: "LYRICIST"
1155
+ }
1156
+ },
1157
+ conductor: {
1158
+ key: "CONDUCTOR",
1159
+ description: "The conductor",
1160
+ type: "string",
1161
+ supportedFormats: ["Vorbis"],
1162
+ mappings: {
1163
+ vorbis: "CONDUCTOR"
1164
+ }
1165
+ },
1166
+ remixedBy: {
1167
+ key: "REMIXER",
1168
+ description: "Person who remixed the track",
1169
+ type: "string",
1170
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1171
+ mappings: {
1172
+ id3v2: { frame: "TPE4" },
1173
+ vorbis: "REMIXER",
1174
+ mp4: "----:com.apple.iTunes:REMIXER"
1175
+ }
1176
+ },
1177
+ language: {
1178
+ key: "LANGUAGE",
1179
+ description: "Language of vocals/lyrics",
1180
+ type: "string",
1181
+ supportedFormats: ["Vorbis"],
1182
+ mappings: {
1183
+ vorbis: "LANGUAGE"
1184
+ }
1185
+ },
1186
+ publisher: {
1187
+ key: "PUBLISHER",
1188
+ description: "The publisher",
1189
+ type: "string",
1190
+ supportedFormats: ["Vorbis"],
1191
+ mappings: {
1192
+ vorbis: "PUBLISHER"
1193
+ }
1194
+ },
1195
+ mood: {
1196
+ key: "MOOD",
1197
+ description: "The mood/atmosphere of the track",
1198
+ type: "string",
1199
+ supportedFormats: ["Vorbis"],
1200
+ mappings: {
1201
+ vorbis: "MOOD"
1202
+ }
1203
+ },
1204
+ media: {
1205
+ key: "MEDIA",
1206
+ description: "Media type (CD, vinyl, etc.)",
1207
+ type: "string",
1208
+ supportedFormats: ["Vorbis"],
1209
+ mappings: {
1210
+ vorbis: "MEDIA"
1211
+ }
1212
+ },
1213
+ grouping: {
1214
+ key: "GROUPING",
1215
+ description: "Content group/work",
1216
+ type: "string",
1217
+ supportedFormats: ["Vorbis"],
1218
+ mappings: {
1219
+ vorbis: "GROUPING"
1220
+ }
1221
+ },
1222
+ work: {
1223
+ key: "WORK",
1224
+ description: "Work name",
1225
+ type: "string",
1226
+ supportedFormats: ["Vorbis"],
1227
+ mappings: {
1228
+ vorbis: "WORK"
1229
+ }
1230
+ },
1231
+ lyrics: {
1232
+ key: "LYRICS",
1233
+ description: "Lyrics content",
1234
+ type: "string",
1235
+ supportedFormats: ["Vorbis"],
1236
+ mappings: {
1237
+ vorbis: "LYRICS"
1238
+ }
1239
+ },
1240
+ isrc: {
1241
+ key: "ISRC",
1242
+ description: "International Standard Recording Code",
1243
+ type: "string",
1244
+ supportedFormats: ["Vorbis"],
1245
+ mappings: {
1246
+ vorbis: "ISRC"
1247
+ }
1248
+ },
1249
+ catalogNumber: {
1250
+ key: "CATALOGNUMBER",
1251
+ description: "Catalog number",
1252
+ type: "string",
1253
+ supportedFormats: ["Vorbis"],
1254
+ mappings: {
1255
+ vorbis: "CATALOGNUMBER"
1256
+ }
1257
+ },
1258
+ barcode: {
1259
+ key: "BARCODE",
1260
+ description: "Barcode (EAN/UPC)",
1261
+ type: "string",
1262
+ supportedFormats: ["Vorbis"],
1263
+ mappings: {
1264
+ vorbis: "BARCODE"
1265
+ }
1266
+ }
1267
+ };
1268
+ }
1269
+ });
1270
+
1271
+ // src/constants/specialized-properties.ts
1272
+ var SPECIALIZED_PROPERTIES;
1273
+ var init_specialized_properties = __esm({
1274
+ "src/constants/specialized-properties.ts"() {
1275
+ "use strict";
1276
+ SPECIALIZED_PROPERTIES = {
1277
+ // MusicBrainz Identifiers
1278
+ musicbrainzArtistId: {
1279
+ key: "MUSICBRAINZ_ARTISTID",
1280
+ description: "MusicBrainz Artist ID (UUID)",
1281
+ type: "string",
1282
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1283
+ mappings: {
1284
+ id3v2: { frame: "TXXX", description: "MusicBrainz Artist Id" },
1285
+ vorbis: "MUSICBRAINZ_ARTISTID",
1286
+ mp4: "----:com.apple.iTunes:MusicBrainz Artist Id"
1287
+ }
1288
+ },
1289
+ musicbrainzReleaseId: {
1290
+ key: "MUSICBRAINZ_ALBUMID",
1291
+ description: "MusicBrainz Release ID (UUID)",
1292
+ type: "string",
1293
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1294
+ mappings: {
1295
+ id3v2: { frame: "TXXX", description: "MusicBrainz Album Id" },
1296
+ vorbis: "MUSICBRAINZ_ALBUMID",
1297
+ mp4: "----:com.apple.iTunes:MusicBrainz Album Id"
1298
+ }
1299
+ },
1300
+ musicbrainzTrackId: {
1301
+ key: "MUSICBRAINZ_TRACKID",
1302
+ description: "MusicBrainz Recording ID (UUID)",
1303
+ type: "string",
1304
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1305
+ mappings: {
1306
+ id3v2: { frame: "UFID", description: "http://musicbrainz.org" },
1307
+ vorbis: "MUSICBRAINZ_TRACKID",
1308
+ mp4: "----:com.apple.iTunes:MusicBrainz Track Id"
1309
+ }
1310
+ },
1311
+ musicbrainzReleaseGroupId: {
1312
+ key: "MUSICBRAINZ_RELEASEGROUPID",
1313
+ description: "MusicBrainz Release Group ID (UUID)",
1314
+ type: "string",
1315
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1316
+ mappings: {
1317
+ id3v2: { frame: "TXXX", description: "MusicBrainz Release Group Id" },
1318
+ vorbis: "MUSICBRAINZ_RELEASEGROUPID",
1319
+ mp4: "----:com.apple.iTunes:MusicBrainz Release Group Id"
1320
+ }
1321
+ },
1322
+ releaseType: {
1323
+ key: "RELEASETYPE",
1324
+ description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.2 translates the RELEASETYPE key per format: ID3v2 TXXX 'MUSICBRAINZ ALBUM TYPE' (TagLib's description casing), MP4 freeform atom, APEv2 MUSICBRAINZ_ALBUMTYPE, ASF 'MusicBrainz/Album Type', Vorbis/Matroska raw RELEASETYPE",
1325
+ type: "string",
1326
+ supportedFormats: [
1327
+ "ID3v2",
1328
+ "MP4",
1329
+ "Vorbis",
1330
+ "APE",
1331
+ "ASF",
1332
+ "Matroska"
1333
+ ],
1334
+ mappings: {
1335
+ id3v2: { frame: "TXXX", description: "MusicBrainz Album Type" },
1336
+ vorbis: "RELEASETYPE",
1337
+ mp4: "----:com.apple.iTunes:MusicBrainz Album Type"
1338
+ }
1339
+ },
1340
+ releaseCountry: {
1341
+ key: "RELEASECOUNTRY",
1342
+ description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.2 translates the RELEASECOUNTRY key per format: ID3v2 TXXX 'MUSICBRAINZ ALBUM RELEASE COUNTRY' (TagLib's description casing; an uppercase TXXX:RELEASECOUNTRY description also reads back), MP4 freeform atom 'MusicBrainz Album Release Country', APEv2 raw RELEASECOUNTRY, ASF 'MusicBrainz/Album Release Country', WAV ICNT, Vorbis/Matroska raw RELEASECOUNTRY",
1343
+ type: "string",
1344
+ supportedFormats: [
1345
+ "ID3v2",
1346
+ "MP4",
1347
+ "Vorbis",
1348
+ "APE",
1349
+ "ASF",
1350
+ "Matroska",
1351
+ "WAV"
1352
+ ],
1353
+ mappings: {
1354
+ id3v2: {
1355
+ frame: "TXXX",
1356
+ description: "MusicBrainz Album Release Country"
1357
+ },
1358
+ vorbis: "RELEASECOUNTRY",
1359
+ mp4: "----:com.apple.iTunes:MusicBrainz Album Release Country"
1360
+ }
1361
+ },
1362
+ itunesAdvisory: {
1363
+ key: "ITUNESADVISORY",
1364
+ description: "Content advisory in the ITUNESADVISORY convention ('1' = explicit, '2' = clean, '0' = unspecified). On MP4 the value is stored in the native rtng atom and surfaced here. The typed tri-state lives on ExtendedTag.advisory; aliases CONTENTADVISORY and EXPLICIT resolve here",
1365
+ type: "string",
1366
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "APE", "ASF"],
1367
+ mappings: {
1368
+ id3v2: { frame: "TXXX", description: "ITUNESADVISORY" },
1369
+ vorbis: "ITUNESADVISORY",
1370
+ mp4: "----:com.apple.iTunes:ITUNESADVISORY"
1371
+ }
1372
+ },
1373
+ // ReplayGain Properties
1374
+ replayGainTrackGain: {
1375
+ key: "REPLAYGAIN_TRACK_GAIN",
1376
+ description: "ReplayGain track gain in dB (e.g., '-6.54 dB')",
1377
+ type: "string",
1378
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1379
+ mappings: {
1380
+ id3v2: { frame: "TXXX", description: "ReplayGain_Track_Gain" },
1381
+ vorbis: "REPLAYGAIN_TRACK_GAIN",
1382
+ mp4: "----:com.apple.iTunes:replaygain_track_gain"
1383
+ }
1384
+ },
1385
+ replayGainTrackPeak: {
1386
+ key: "REPLAYGAIN_TRACK_PEAK",
1387
+ description: "ReplayGain track peak value (0.0-1.0)",
1388
+ type: "string",
1389
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1390
+ mappings: {
1391
+ id3v2: { frame: "TXXX", description: "ReplayGain_Track_Peak" },
1392
+ vorbis: "REPLAYGAIN_TRACK_PEAK",
1393
+ mp4: "----:com.apple.iTunes:replaygain_track_peak"
1394
+ }
1395
+ },
1396
+ replayGainAlbumGain: {
1397
+ key: "REPLAYGAIN_ALBUM_GAIN",
1398
+ description: "ReplayGain album gain in dB",
1399
+ type: "string",
1400
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1401
+ mappings: {
1402
+ id3v2: { frame: "TXXX", description: "ReplayGain_Album_Gain" },
1403
+ vorbis: "REPLAYGAIN_ALBUM_GAIN",
1404
+ mp4: "----:com.apple.iTunes:replaygain_album_gain"
1405
+ }
1406
+ },
1407
+ replayGainAlbumPeak: {
1408
+ key: "REPLAYGAIN_ALBUM_PEAK",
1409
+ description: "ReplayGain album peak value (0.0-1.0)",
1410
+ type: "string",
1411
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1412
+ mappings: {
1413
+ id3v2: { frame: "TXXX", description: "ReplayGain_Album_Peak" },
1414
+ vorbis: "REPLAYGAIN_ALBUM_PEAK",
1415
+ mp4: "----:com.apple.iTunes:replaygain_album_peak"
1416
+ }
1417
+ },
1418
+ r128TrackGain: {
1419
+ key: "R128_TRACK_GAIN",
1420
+ description: "EBU R128 track loudness gain (RFC 7845), raw wire value: signed Q7.8 integer (dB x 256, e.g. '-573' = -2.23828125 dB). readTags() converts to a decibel number (see ExtendedTag.r128TrackGain)",
1421
+ type: "string",
1422
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "ASF"],
1423
+ mappings: {
1424
+ id3v2: { frame: "TXXX", description: "R128_TRACK_GAIN" },
1425
+ vorbis: "R128_TRACK_GAIN",
1426
+ mp4: "----:com.apple.iTunes:R128_TRACK_GAIN"
1427
+ }
1428
+ },
1429
+ r128AlbumGain: {
1430
+ key: "R128_ALBUM_GAIN",
1431
+ description: "EBU R128 album loudness gain (RFC 7845), raw wire value: signed Q7.8 integer (dB x 256). readTags() converts to a decibel number",
1432
+ type: "string",
1433
+ supportedFormats: ["ID3v2", "MP4", "Vorbis", "ASF"],
1434
+ mappings: {
1435
+ id3v2: { frame: "TXXX", description: "R128_ALBUM_GAIN" },
1436
+ vorbis: "R128_ALBUM_GAIN",
1437
+ mp4: "----:com.apple.iTunes:R128_ALBUM_GAIN"
1438
+ }
1439
+ },
1440
+ // AcoustID Properties
1441
+ acoustidFingerprint: {
1442
+ key: "ACOUSTID_FINGERPRINT",
1443
+ description: "AcoustID fingerprint (Chromaprint)",
1444
+ type: "string",
1445
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1446
+ mappings: {
1447
+ id3v2: { frame: "TXXX", description: "Acoustid Fingerprint" },
1448
+ vorbis: "ACOUSTID_FINGERPRINT",
1449
+ mp4: "----:com.apple.iTunes:Acoustid Fingerprint"
1450
+ }
1451
+ },
1452
+ acoustidId: {
1453
+ key: "ACOUSTID_ID",
1454
+ description: "AcoustID UUID",
1455
+ type: "string",
1456
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1457
+ mappings: {
1458
+ id3v2: { frame: "TXXX", description: "Acoustid Id" },
1459
+ vorbis: "ACOUSTID_ID",
1460
+ mp4: "----:com.apple.iTunes:Acoustid Id"
1461
+ }
1462
+ },
1463
+ // Apple Sound Check
1464
+ appleSoundCheck: {
1465
+ key: "ITUNNORM",
1466
+ description: "Apple Sound Check normalization data",
1467
+ type: "string",
1468
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1469
+ mappings: {
1470
+ id3v2: { frame: "TXXX", description: "iTunNORM" },
1471
+ vorbis: "ITUNNORM",
1472
+ mp4: "----:com.apple.iTunes:iTunNORM"
1473
+ }
1474
+ },
1475
+ /**
1476
+ * Apple gapless-playback data (encoder delay, padding, original sample count).
1477
+ *
1478
+ * Exists so the two Apple freeform atoms are presented consistently: without
1479
+ * it, `properties()` answered `appleSoundCheck` for iTunNORM but the raw
1480
+ * uppercased `ITUNSMPB` for its sibling, because only one had an alias. The
1481
+ * name mirrors `appleSoundCheck` — vendor-prefixed, feature-named rather than
1482
+ * atom-named (tuneup-ibo).
1483
+ */
1484
+ appleGaplessInfo: {
1485
+ key: "ITUNSMPB",
1486
+ description: "Apple gapless playback data (encoder delay and padding)",
1487
+ type: "string",
1488
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1489
+ mappings: {
1490
+ id3v2: { frame: "TXXX", description: "iTunSMPB" },
1491
+ vorbis: "ITUNSMPB",
1492
+ mp4: "----:com.apple.iTunes:iTunSMPB"
1493
+ }
1494
+ }
1495
+ };
1496
+ }
1497
+ });
1498
+
1499
+ // src/constants/properties.ts
1500
+ function toTagLibKey(key) {
1501
+ return _toTagLib[key] ?? key;
1502
+ }
1503
+ function mp4AtomWireKey(atom) {
1504
+ return _mp4AtomToWireKey[atom];
1505
+ }
1506
+ function mp4FreeformAtomNames(wireKeys) {
1507
+ const names = [];
1508
+ for (const key of wireKeys) {
1509
+ const atom = _mp4FreeformAtoms[key];
1510
+ if (atom !== void 0) names.push(atom);
1511
+ }
1512
+ return names;
1513
+ }
1514
+ function fromTagLibKey(key) {
1515
+ return _fromTagLib[key] ?? key;
1516
+ }
1517
+ function remapKeysFromTagLib(obj) {
1518
+ const result = {};
1519
+ for (const [key, value] of Object.entries(obj)) {
1520
+ const canonical = READ_ALIASES[key];
1521
+ if (canonical !== void 0 && canonical in obj) continue;
1522
+ result[fromTagLibKey(key)] = value;
1523
+ }
1524
+ return result;
1525
+ }
1526
+ var PROPERTIES, _toTagLib, _fromTagLib, READ_ALIASES, _mp4FreeformAtoms, _mp4AtomToWireKey;
1527
+ var init_properties = __esm({
1528
+ "src/constants/properties.ts"() {
1529
+ "use strict";
1530
+ init_additional_properties();
1531
+ init_basic_properties();
1532
+ init_general_extended_properties();
1533
+ init_specialized_properties();
1534
+ PROPERTIES = {
1535
+ ...BASIC_PROPERTIES,
1536
+ ...GENERAL_EXTENDED_PROPERTIES,
1537
+ ...SPECIALIZED_PROPERTIES,
1538
+ ...ADDITIONAL_PROPERTIES
1539
+ };
1540
+ _toTagLib = {};
1541
+ _fromTagLib = {};
1542
+ for (const [camelKey, meta] of Object.entries(PROPERTIES)) {
1543
+ const wireKey = meta.key;
1544
+ _toTagLib[camelKey] = wireKey;
1545
+ _fromTagLib[wireKey] = camelKey;
1546
+ }
1547
+ _toTagLib["year"] = "DATE";
1548
+ _toTagLib["track"] = "TRACKNUMBER";
1549
+ _fromTagLib["disc"] = "discNumber";
1550
+ READ_ALIASES = {
1551
+ "ALBUM ARTIST": "ALBUMARTIST",
1552
+ ALBUM_ARTIST: "ALBUMARTIST",
1553
+ ORGANIZATION: "LABEL",
1554
+ PUBLISHER: "LABEL",
1555
+ UPC: "BARCODE",
1556
+ EAN: "BARCODE",
1557
+ GTIN: "BARCODE",
1558
+ TOTALTRACKS: "TRACKTOTAL",
1559
+ TOTALDISCS: "DISCTOTAL",
1560
+ MUSICBRAINZ_ALBUMTYPE: "RELEASETYPE",
1561
+ "MusicBrainz Album Type": "RELEASETYPE",
1562
+ CONTENTADVISORY: "ITUNESADVISORY",
1563
+ EXPLICIT: "ITUNESADVISORY"
1564
+ };
1565
+ for (const [alias, canonical] of Object.entries(READ_ALIASES)) {
1566
+ if (_fromTagLib[alias] === void 0) {
1567
+ _fromTagLib[alias] = _fromTagLib[canonical];
1568
+ }
1569
+ }
1570
+ _mp4FreeformAtoms = {};
1571
+ for (const meta of Object.values(PROPERTIES)) {
1572
+ const { key, mappings } = meta;
1573
+ const atom = mappings?.mp4;
1574
+ if (typeof atom !== "string" || !atom.startsWith("----:")) continue;
1575
+ const bare = atom.slice(atom.lastIndexOf(":") + 1);
1576
+ if (bare === bare.toUpperCase()) continue;
1577
+ _mp4FreeformAtoms[key] = atom;
1578
+ const readKey = bare.toUpperCase();
1579
+ const camel = _fromTagLib[key];
1580
+ if (camel !== void 0 && _fromTagLib[readKey] === void 0) {
1581
+ _fromTagLib[readKey] = camel;
1582
+ }
1583
+ }
1584
+ _mp4AtomToWireKey = {};
1585
+ for (const meta of Object.values(PROPERTIES)) {
1586
+ const { key, mappings } = meta;
1587
+ const atom = mappings?.mp4;
1588
+ if (typeof atom !== "string" || atom.startsWith("----:")) continue;
1589
+ if (_mp4AtomToWireKey[atom] === void 0) _mp4AtomToWireKey[atom] = key;
1590
+ }
1591
+ }
1592
+ });
1593
+
1594
+ // src/taglib/mp4-item-names.ts
1595
+ var MP4_ITEM_NAMES_KEY;
1596
+ var init_mp4_item_names = __esm({
1597
+ "src/taglib/mp4-item-names.ts"() {
1598
+ "use strict";
1599
+ MP4_ITEM_NAMES_KEY = "_mp4ItemNames";
1600
+ }
1601
+ });
1602
+
1603
+ // src/taglib/mutable-tag-impl.ts
1604
+ function buildMutableTag(handle) {
1605
+ let data = handle.getTagData();
1606
+ const tag = {
1607
+ get title() {
1608
+ return data.title;
1609
+ },
1610
+ get artist() {
1611
+ return data.artist;
1612
+ },
1613
+ get album() {
1614
+ return data.album;
1615
+ },
1616
+ get comment() {
1617
+ return data.comment;
1618
+ },
1619
+ get genre() {
1620
+ return data.genre;
1621
+ },
1622
+ get year() {
1623
+ return data.year;
1624
+ },
1625
+ get track() {
1626
+ return data.track;
1627
+ },
1628
+ get date() {
1629
+ return handle.getProperty("DATE") || void 0;
1630
+ },
1631
+ setTitle: (value) => {
1632
+ handle.setTagData({ title: value });
1633
+ data = handle.getTagData();
1634
+ return tag;
1635
+ },
1636
+ setArtist: (value) => {
1637
+ handle.setTagData({ artist: value });
1638
+ data = handle.getTagData();
1639
+ return tag;
1640
+ },
1641
+ setAlbum: (value) => {
1642
+ handle.setTagData({ album: value });
1643
+ data = handle.getTagData();
1644
+ return tag;
1645
+ },
1646
+ setComment: (value) => {
1647
+ handle.setTagData({ comment: value });
1648
+ data = handle.getTagData();
1649
+ return tag;
1650
+ },
1651
+ setGenre: (value) => {
1652
+ handle.setTagData({ genre: value });
1653
+ data = handle.getTagData();
1654
+ return tag;
1655
+ },
1656
+ setYear: (value) => {
1657
+ handle.setTagData({ year: value });
1658
+ data = handle.getTagData();
1659
+ return tag;
1660
+ },
1661
+ setTrack: (value) => {
1662
+ const existing = value > 0 ? handle.getProperty(RAW_TRACK_WIRE_KEY) : "";
1663
+ const slash = existing.indexOf("/");
1664
+ if (slash !== -1) {
1665
+ handle.setProperty(
1666
+ RAW_TRACK_WIRE_KEY,
1667
+ `${value}${existing.slice(slash)}`
1668
+ );
1669
+ } else {
1670
+ handle.setTagData({ track: value });
1671
+ }
1672
+ data = handle.getTagData();
1673
+ return tag;
1674
+ },
1675
+ setDate: (value) => {
1676
+ if (value === "") {
1677
+ handle.setTagData({ year: 0 });
1678
+ } else {
1679
+ handle.setProperty("DATE", value);
1680
+ }
1681
+ data = handle.getTagData();
1682
+ return tag;
1683
+ }
1684
+ };
1685
+ return tag;
1686
+ }
1687
+ var RAW_TRACK_WIRE_KEY;
1688
+ var init_mutable_tag_impl = __esm({
1689
+ "src/taglib/mutable-tag-impl.ts"() {
1690
+ "use strict";
1691
+ init_properties();
1692
+ RAW_TRACK_WIRE_KEY = toTagLibKey("trackNumber");
1693
+ }
1694
+ });
1695
+
1696
+ // src/taglib/auto-dispose.ts
1697
+ function registerAutoDispose(target, handle) {
1698
+ registry.register(target, handle, target);
1699
+ }
1700
+ function unregisterAutoDispose(target) {
1701
+ registry.unregister(target);
1702
+ }
1703
+ var registry;
1704
+ var init_auto_dispose = __esm({
1705
+ "src/taglib/auto-dispose.ts"() {
1706
+ "use strict";
1707
+ registry = new FinalizationRegistry((handle) => {
1708
+ handle.destroy();
1709
+ });
1710
+ }
1711
+ });
1712
+
1713
+ // src/taglib/audio-file-base.ts
1714
+ var LYRICS_PROPERTY_KEY, LYRICS_WIRE_KEY, EMPTY_KEY_SET, BaseAudioFileImpl;
1715
+ var init_audio_file_base = __esm({
1716
+ "src/taglib/audio-file-base.ts"() {
1717
+ "use strict";
1718
+ init_properties();
1719
+ init_errors2();
1720
+ init_mp4_item_names();
1721
+ init_mutable_tag_impl();
1722
+ init_auto_dispose();
1723
+ LYRICS_PROPERTY_KEY = "lyrics";
1724
+ LYRICS_WIRE_KEY = toTagLibKey(LYRICS_PROPERTY_KEY);
1725
+ EMPTY_KEY_SET = /* @__PURE__ */ new Set();
1726
+ BaseAudioFileImpl = class {
1727
+ constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
1728
+ __publicField(this, "module", module);
1729
+ __publicField(this, "fileHandle");
1730
+ __publicField(this, "cachedAudioProperties", null);
1731
+ __publicField(this, "sourcePath");
1732
+ __publicField(this, "originalSource");
1733
+ __publicField(this, "isPartiallyLoaded", false);
1734
+ __publicField(this, "partialLoadOptions");
1735
+ /** Text-property wire keys in the partial header at load; undefined if full. */
1736
+ __publicField(this, "partialKeysAtLoad");
1737
+ this.fileHandle = fileHandle;
1738
+ this.sourcePath = sourcePath;
1739
+ this.originalSource = originalSource;
1740
+ this.isPartiallyLoaded = isPartiallyLoaded;
1741
+ this.partialLoadOptions = partialLoadOptions;
1742
+ this.partialKeysAtLoad = isPartiallyLoaded ? new Set(Object.keys(fileHandle.getProperties())) : void 0;
1743
+ registerAutoDispose(this, fileHandle);
1744
+ }
1745
+ /**
1746
+ * Wire-key text properties present in the partial header at load but now
1747
+ * absent — the user deleted them. The partial-load reconstruct subtracts these
1748
+ * from its preserve-the-original merge so a deletion persists without wiping
1749
+ * frames beyond the loaded header (taglib-d14). Lyrics are excluded (they ride
1750
+ * their own reconstruct path); empty for a full load.
1751
+ */
1752
+ partialDeletedPropertyKeys() {
1753
+ if (!this.partialKeysAtLoad) return EMPTY_KEY_SET;
1754
+ const current = new Set(Object.keys(this.handle.getProperties()));
1755
+ const deleted = /* @__PURE__ */ new Set();
1756
+ for (const key of this.partialKeysAtLoad) {
1757
+ if (key !== LYRICS_WIRE_KEY && !current.has(key)) deleted.add(key);
1758
+ }
1759
+ return deleted;
1760
+ }
1761
+ get handle() {
1762
+ if (!this.fileHandle) {
1763
+ throw new MetadataError("read", "File handle has been disposed");
1764
+ }
1765
+ return this.fileHandle;
1766
+ }
1767
+ getFormat() {
1768
+ return this.handle.getFormat();
1769
+ }
1770
+ isFormat(format) {
1771
+ return this.getFormat() === format;
1772
+ }
1773
+ tag() {
1774
+ return buildMutableTag(this.handle);
1775
+ }
1776
+ audioProperties() {
1777
+ if (!this.cachedAudioProperties) {
1778
+ this.cachedAudioProperties = this.handle.getAudioProperties() ?? null;
1779
+ }
1780
+ return this.cachedAudioProperties ?? void 0;
1781
+ }
1782
+ properties() {
1783
+ const remapped = remapKeysFromTagLib(this.handle.getProperties());
1784
+ delete remapped[LYRICS_PROPERTY_KEY];
1785
+ return remapped;
1786
+ }
1787
+ setProperties(properties) {
1788
+ const translated = {};
1789
+ for (const [key, values] of Object.entries(properties)) {
1790
+ if (values !== void 0) {
1791
+ translated[toTagLibKey(fromTagLibKey(key))] = values;
1792
+ }
1793
+ }
1794
+ if (!(LYRICS_WIRE_KEY in translated)) {
1795
+ const existing = this.handle.getProperties()[LYRICS_WIRE_KEY];
1796
+ if (existing !== void 0) translated[LYRICS_WIRE_KEY] = existing;
1797
+ }
1798
+ const atomNames = mp4FreeformAtomNames(Object.keys(translated));
1799
+ if (atomNames.length > 0) translated[MP4_ITEM_NAMES_KEY] = atomNames;
1800
+ this.handle.setProperties(translated);
1801
+ }
1802
+ getProperty(key) {
1803
+ const wireKey = toTagLibKey(fromTagLibKey(key));
1804
+ const values = this.handle.getProperties()[wireKey]?.filter(
1805
+ (v) => v !== ""
1806
+ );
1807
+ if (values !== void 0 && values.length > 0) return values;
1808
+ if (!this.isMP4()) return void 0;
1809
+ const remapped = this.properties()[key]?.filter((v) => v !== "");
1810
+ return remapped !== void 0 && remapped.length > 0 ? remapped : void 0;
1811
+ }
1812
+ setProperty(key, value) {
1813
+ const wireKey = toTagLibKey(fromTagLibKey(key));
1814
+ const atomNames = mp4FreeformAtomNames([wireKey]);
1815
+ if (atomNames.length > 0 && this.isMP4()) {
1816
+ if (value === "") {
1817
+ const prior = this.handle.getMp4ItemRemovals?.() ?? [];
1818
+ this.handle.setMp4ItemRemovals?.([
1819
+ .../* @__PURE__ */ new Set([...prior, ...atomNames])
1820
+ ]);
1821
+ if (this.module.isWasi) {
1822
+ this.handle.setProperties({
1823
+ ...this.handle.getProperties(),
1824
+ [wireKey]: []
1825
+ });
1826
+ } else {
1827
+ const rest = { ...this.handle.getProperties() };
1828
+ delete rest[wireKey];
1829
+ this.handle.setProperties(rest);
1830
+ }
1831
+ return;
1832
+ }
1833
+ this.handle.setProperties({
1834
+ ...this.handle.getProperties(),
1835
+ [wireKey]: [value],
1836
+ [MP4_ITEM_NAMES_KEY]: atomNames
1837
+ });
1838
+ return;
1839
+ }
1840
+ this.handle.setProperty(wireKey, value);
1841
+ }
1842
+ /**
1843
+ * The empty-string clearing contract, named (taglib-qyw2): both backends
1844
+ * remove the property when the written value is empty, and setProperty's
1845
+ * wire-key normalization + MP4 freeform atom-name handling apply unchanged.
1846
+ */
1847
+ removeProperty(key) {
1848
+ this.setProperty(key, "");
1849
+ }
1850
+ isMP4() {
1851
+ return this.handle.isMP4();
1852
+ }
1853
+ getMP4Item(key) {
1854
+ if (!this.isMP4()) {
1855
+ throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1856
+ }
1857
+ const wireKey = mp4AtomWireKey(key);
1858
+ const value = wireKey !== void 0 ? this.handle.getProperty(wireKey) : this.handle.getMP4Item(key);
1859
+ return value === "" ? void 0 : value;
1860
+ }
1861
+ setMP4Item(key, value) {
1862
+ if (!this.isMP4()) {
1863
+ throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1864
+ }
1865
+ const wireKey = mp4AtomWireKey(key);
1866
+ if (wireKey !== void 0) {
1867
+ this.handle.setProperty(wireKey, value);
1868
+ return;
1869
+ }
1870
+ this.handle.setMP4Item(key, value);
1871
+ }
1872
+ removeMP4Item(key) {
1873
+ if (!this.isMP4()) {
1874
+ throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1875
+ }
1876
+ this.handle.removeMP4Item(key);
1877
+ }
1878
+ isValid() {
1879
+ return this.handle.isValid();
1880
+ }
1881
+ dispose() {
1882
+ unregisterAutoDispose(this);
1883
+ if (this.fileHandle) {
1884
+ this.fileHandle.destroy();
1885
+ this.fileHandle = null;
1886
+ this.cachedAudioProperties = null;
1887
+ }
1888
+ }
1889
+ [Symbol.dispose]() {
1890
+ this.dispose();
1891
+ }
1892
+ };
1893
+ }
1894
+ });
1895
+
1896
+ // src/taglib/metadata-extent.ts
1897
+ function be32(b, offset) {
1898
+ return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
1899
+ }
1900
+ function startsWith(bytes, magic, at = 0) {
1901
+ if (bytes.length < at + magic.length) return false;
1902
+ for (let i = 0; i < magic.length; i++) {
1903
+ if (bytes[at + i] !== magic.charCodeAt(i)) return false;
1904
+ }
1905
+ return true;
1906
+ }
1907
+ function id3v2End(bytes) {
1908
+ if (!startsWith(bytes, "ID3") || bytes.length < 10) return void 0;
1909
+ const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
1910
+ const hasFooter = (bytes[5] & 16) !== 0;
1911
+ return 10 + size + (hasFooter ? 10 : 0);
1912
+ }
1913
+ function flacEnd(bytes, limit, start) {
1914
+ let offset = start + 4;
1915
+ for (; ; ) {
1916
+ if (offset + 4 > bytes.length) return void 0;
1917
+ const isLast = (bytes[offset] & 128) !== 0;
1918
+ const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
1919
+ offset += 4 + length;
1920
+ if (offset > limit) return offset;
1921
+ if (isLast) return offset;
1922
+ }
1923
+ }
1924
+ function flacMarkerOffset(bytes) {
1925
+ const at = startsWith(bytes, "ID3") ? id3v2End(bytes) : 0;
1926
+ if (at === void 0) return void 0;
1927
+ return startsWith(bytes, "fLaC", at) ? at : void 0;
1928
+ }
1929
+ function flacAudioStart(bytes) {
1930
+ const at = flacMarkerOffset(bytes);
1931
+ if (at === void 0) return void 0;
1932
+ return flacEnd(bytes, bytes.length, at);
1933
+ }
1934
+ function mp4MoovEnd(bytes, limit, start) {
1935
+ let offset = start;
1936
+ const readable = Math.min(bytes.length, limit);
1937
+ while (offset + 8 <= readable) {
1938
+ const size = be32(bytes, offset);
1939
+ const type = String.fromCharCode(
1940
+ bytes[offset + 4],
1941
+ bytes[offset + 5],
1942
+ bytes[offset + 6],
1943
+ bytes[offset + 7]
1944
+ );
1945
+ if (size < 8) return void 0;
1946
+ if (type === "moov") return offset + size;
1947
+ offset += size;
1948
+ }
1949
+ return void 0;
1950
+ }
1951
+ function oggMetadataEnd(bytes, limit, start) {
1952
+ let offset = start;
1953
+ let packetsCompleted = 0;
1954
+ let first = true;
1955
+ for (; ; ) {
1956
+ if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
1957
+ return void 0;
1958
+ }
1959
+ const segmentCount = bytes[offset + 26];
1960
+ const tableStart = offset + 27;
1961
+ if (tableStart + segmentCount > bytes.length) return void 0;
1962
+ let payload = 0;
1963
+ for (let i = 0; i < segmentCount; i++) {
1964
+ const segment = bytes[tableStart + i];
1965
+ payload += segment;
1966
+ if (segment < 255) packetsCompleted++;
1967
+ }
1968
+ if (first) {
1969
+ const payloadStart = tableStart + segmentCount;
1970
+ if (payloadStart + 5 > bytes.length) return void 0;
1971
+ if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
1972
+ return void 0;
1973
+ }
1974
+ first = false;
1975
+ }
1976
+ offset = tableStart + segmentCount + payload;
1977
+ if (packetsCompleted >= 2 || offset > limit) return offset;
1978
+ }
1979
+ }
1980
+ function mpegFrameLength(bytes, at) {
1981
+ if (bytes.length < at + 4) return 0;
1982
+ if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return 0;
1983
+ const version = bytes[at + 1] >> 3 & 3;
1984
+ const layer = bytes[at + 1] >> 1 & 3;
1985
+ if (version === 1 || layer === 0) return 0;
1986
+ const bitrateIndex = bytes[at + 2] >> 4 & 15;
1987
+ const sampleRateIndex = bytes[at + 2] >> 2 & 3;
1988
+ if (bitrateIndex === 0 || bitrateIndex === 15 || sampleRateIndex === 3) {
1989
+ return 0;
1990
+ }
1991
+ const mpeg1 = version === 3;
1992
+ const layerIndex = layer ^ 3;
1993
+ const bitrate = MPEG_BITRATES[mpeg1 ? 0 : 1][layerIndex][bitrateIndex];
1994
+ const sampleRateTable = mpeg1 ? 0 : version === 2 ? 1 : 2;
1995
+ const sampleRate = MPEG_SAMPLE_RATES[sampleRateTable][sampleRateIndex];
1996
+ const samplesPerFrame = layerIndex === 2 ? mpeg1 ? 1152 : 576 : layerIndex === 1 ? 1152 : 384;
1997
+ let frameLength = Math.floor(samplesPerFrame * bitrate * 125 / sampleRate);
1998
+ if ((bytes[at + 2] & 2) !== 0) frameLength += layerIndex === 0 ? 4 : 1;
1999
+ return frameLength === 0 ? 0 : frameLength;
2000
+ }
2001
+ function isMpegFrameSync(bytes, at = 0) {
2002
+ const frameLength = mpegFrameLength(bytes, at);
2003
+ if (frameLength === 0) return false;
2004
+ const next = at + frameLength;
2005
+ if (bytes.length < next + 4) return false;
2006
+ const mask = 4294839296;
2007
+ const header = ((bytes[at] << 24 | bytes[at + 1] << 16 | bytes[at + 2] << 8 | bytes[at + 3]) >>> 0 & mask) >>> 0;
2008
+ const nextHeader = ((bytes[next] << 24 | bytes[next + 1] << 16 | bytes[next + 2] << 8 | bytes[next + 3]) >>> 0 & mask) >>> 0;
2009
+ return header === nextHeader;
2010
+ }
2011
+ function containerEnd(bytes, at, limit) {
2012
+ if (at >= bytes.length) return bytes.length < limit ? null : void 0;
2013
+ if (at + 8 > bytes.length) return void 0;
2014
+ if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
2015
+ if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
2016
+ if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
2017
+ return null;
2018
+ }
2019
+ function metadataFitsInHeader(header, limit) {
2020
+ let end;
2021
+ if (startsWith(header, "ID3")) {
2022
+ const tagEnd = id3v2End(header);
2023
+ if (tagEnd === void 0) return false;
2024
+ end = tagEnd;
2025
+ if (tagEnd <= limit) {
2026
+ const behind = containerEnd(header, tagEnd, limit);
2027
+ if (behind === void 0) return false;
2028
+ if (behind !== null) end = behind;
2029
+ }
2030
+ } else {
2031
+ const container = containerEnd(header, 0, limit);
2032
+ if (container === null) {
2033
+ end = isMpegFrameSync(header) ? 0 : void 0;
2034
+ } else {
2035
+ end = container;
2036
+ }
2037
+ }
2038
+ if (end === void 0 || end > header.length) return false;
2039
+ return end <= limit;
2040
+ }
2041
+ function trailerFitsInFooter(tail, footerSize) {
2042
+ if (tail.length < Math.min(footerSize, 32)) return false;
2043
+ for (const offsetFromEnd of [32, 32 + 128]) {
2044
+ const at = tail.length - offsetFromEnd;
2045
+ if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
2046
+ const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
2047
+ const trailing = offsetFromEnd === 32 ? 0 : 128;
2048
+ if (size + 32 + trailing > footerSize) return false;
2049
+ }
2050
+ return true;
2051
+ }
2052
+ var MPEG_BITRATES, MPEG_SAMPLE_RATES;
2053
+ var init_metadata_extent = __esm({
2054
+ "src/taglib/metadata-extent.ts"() {
2055
+ "use strict";
2056
+ MPEG_BITRATES = [
2057
+ [
2058
+ [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
2059
+ // Layer I
2060
+ [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
2061
+ // Layer II
2062
+ [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0]
2063
+ // Layer III
2064
+ ],
2065
+ [
2066
+ [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
2067
+ // Layer I
2068
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
2069
+ // Layer II
2070
+ [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
2071
+ // Layer III
2072
+ ]
2073
+ ];
2074
+ MPEG_SAMPLE_RATES = [
2075
+ [44100, 48e3, 32e3],
2076
+ // MPEG-1
2077
+ [22050, 24e3, 16e3],
2078
+ // MPEG-2
2079
+ [11025, 12e3, 8e3]
2080
+ // MPEG-2.5
2081
+ ];
2082
+ }
2083
+ });
2084
+
2085
+ // src/taglib/media-ranges.ts
2086
+ function adtsFrameLength(bytes, at) {
2087
+ if (bytes.length < at + 7) return 0;
2088
+ if (bytes[at] !== 255 || (bytes[at + 1] & 246) !== 240) return 0;
2089
+ const length = (bytes[at + 3] & 3) << 11 | bytes[at + 4] << 3 | bytes[at + 5] >> 5;
2090
+ return length >= 7 ? length : 0;
2091
+ }
2092
+ function trailingTagStart(bytes, end = bytes.length) {
2093
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2094
+ let at = Math.max(0, Math.min(end, bytes.length));
2095
+ for (; ; ) {
2096
+ if (at >= 128 && text(bytes, at - 128, 3) === "TAG") {
2097
+ at -= 128;
2098
+ continue;
2099
+ }
2100
+ if (at >= 32 && text(bytes, at - 32, 8) === "APETAGEX") {
2101
+ const size = view.getUint32(at - 32 + 12, true);
2102
+ const flags = view.getUint32(at - 32 + 20, true);
2103
+ const start = at - size - ((flags & 2147483648) !== 0 ? 32 : 0);
2104
+ if (start < 0 || start >= at) return at;
2105
+ at = start;
2106
+ continue;
2107
+ }
2108
+ return at;
2109
+ }
2110
+ }
2111
+ function firstFrame(bytes) {
2112
+ const from = id3v2End(bytes) ?? 0;
2113
+ const limit = Math.min(bytes.length - 6, from + MPEG_SCAN_LIMIT);
2114
+ for (let o = from; o < limit; o++) {
2115
+ const len = frameLengthAt(bytes, o);
2116
+ if (len > 0 && frameLengthAt(bytes, o + len) > 0) return o;
2117
+ }
2118
+ return -1;
2119
+ }
2120
+ function forwardLastFrame(bytes, from, end) {
2121
+ let o = from;
2122
+ let start = -1;
2123
+ let length = 0;
2124
+ let frames = 0;
2125
+ while (o + 6 <= end) {
2126
+ const len = frameLengthAt(bytes, o);
2127
+ if (len === 0 || o + len > end) break;
2128
+ start = o;
2129
+ length = len;
2130
+ frames++;
2131
+ o += len;
2132
+ }
2133
+ return { start, length, frames };
2134
+ }
2135
+ function walkMpeg(bytes) {
2136
+ const from = firstFrame(bytes);
2137
+ if (from < 0) return fallback("no valid first frame");
2138
+ const end = trailingTagStart(bytes);
2139
+ const last = forwardLastFrame(bytes, from, end);
2140
+ if (last.start < 0) return fallback("no valid frames");
2141
+ return {
2142
+ kind: "ranges",
2143
+ ranges: [{ offset: from, length: last.start + last.length - from }],
2144
+ detail: `first=${from} last=${last.start}+${last.length} frames=${last.frames}`
2145
+ };
2146
+ }
2147
+ function flacStreamInfoMd5(bytes) {
2148
+ const marker = flacMarkerOffset(bytes);
2149
+ if (marker === void 0) return void 0;
2150
+ const header = marker + 4;
2151
+ if (header + 4 > bytes.length) return void 0;
2152
+ if ((bytes[header] & 127) !== 0) return void 0;
2153
+ const blockLength = bytes[header + 1] << 16 | bytes[header + 2] << 8 | bytes[header + 3];
2154
+ if (blockLength < 34) return void 0;
2155
+ const payload = header + 4;
2156
+ if (payload + 34 > bytes.length) return void 0;
2157
+ let hex = "";
2158
+ for (const byte of bytes.subarray(payload + 18, payload + 34)) {
2159
+ hex += byte.toString(16).padStart(2, "0");
2160
+ }
2161
+ return hex;
2162
+ }
2163
+ function walkFlac(bytes) {
2164
+ const start = flacAudioStart(bytes);
2165
+ if (start === void 0) return fallback("no fLaC marker");
2166
+ const end = trailingTagStart(bytes);
2167
+ if (end <= start) return fallback("no audio bytes");
2168
+ return {
2169
+ kind: "ranges",
2170
+ ranges: [{ offset: start, length: end - start }],
2171
+ detail: `audioStart=${start} end=${end}`
2172
+ };
2173
+ }
2174
+ function atomLength(bytes, view, o) {
2175
+ const size = view.getUint32(o, false);
2176
+ if (size === 0) return { length: bytes.length - o, headerSize: 8 };
2177
+ if (size === 1) {
2178
+ if (o + 16 > bytes.length) return void 0;
2179
+ const big = view.getBigUint64(o + 8, false);
2180
+ if (big > BigInt(Number.MAX_SAFE_INTEGER)) return void 0;
2181
+ return { length: Number(big), headerSize: 16 };
2182
+ }
2183
+ return { length: size, headerSize: 8 };
2184
+ }
2185
+ function walkMp4(bytes) {
2186
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2187
+ const ranges = [];
2188
+ let o = 0;
2189
+ while (o + 8 <= bytes.length) {
2190
+ const type = text(bytes, o + 4, 4);
2191
+ const atom = atomLength(bytes, view, o);
2192
+ if (atom === void 0) return fallback(`malformed ${type} size`);
2193
+ if (atom.length < atom.headerSize) {
2194
+ return fallback(`malformed ${type} size`);
2195
+ }
2196
+ if (o + atom.length > bytes.length) return fallback(`oversized ${type}`);
2197
+ if (type === "mdat" && atom.length > atom.headerSize) {
2198
+ ranges.push({
2199
+ offset: o + atom.headerSize,
2200
+ length: atom.length - atom.headerSize
2201
+ });
2202
+ }
2203
+ o += atom.length;
2204
+ }
2205
+ if (o !== bytes.length) return fallback("trailing bytes after the last atom");
2206
+ if (ranges.length === 0) return fallback("no non-empty mdat");
2207
+ return { kind: "ranges", ranges, detail: `${ranges.length} mdat(s)` };
2208
+ }
2209
+ function walkWav(bytes) {
2210
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
2211
+ if (text(bytes, 0, 4) !== "RIFF") return fallback("not RIFF");
2212
+ if (text(bytes, 8, 4) !== "WAVE") return fallback("not WAVE");
2213
+ const ranges = [];
2214
+ let o = 12;
2215
+ while (o + 8 <= bytes.length) {
2216
+ const id = text(bytes, o, 4);
2217
+ const size = view.getUint32(o + 4, true);
2218
+ const dataStart = o + 8;
2219
+ if (dataStart + size > bytes.length) return fallback(`truncated ${id}`);
2220
+ if (id === "data" && size > 0) {
2221
+ ranges.push({ offset: dataStart, length: size });
2222
+ }
2223
+ o = dataStart + size + size % 2;
2224
+ }
2225
+ if (ranges.length === 0) return fallback("no data chunk");
2226
+ return { kind: "ranges", ranges, detail: `${ranges.length} data chunk(s)` };
2227
+ }
2228
+ function mediaRanges(format, bytes) {
2229
+ const walked = (() => {
2230
+ switch (format.toUpperCase()) {
2231
+ case "MP3":
2232
+ case "AAC":
2233
+ return walkMpeg(bytes);
2234
+ case "FLAC":
2235
+ return walkFlac(bytes);
2236
+ case "MP4":
2237
+ case "M4A":
2238
+ return walkMp4(bytes);
2239
+ case "WAV":
2240
+ return walkWav(bytes);
2241
+ default:
2242
+ return { kind: "fallback", ranges: [], detail: "format not covered" };
2243
+ }
2244
+ })();
2245
+ if (walked.kind === "fallback") {
2246
+ return {
2247
+ kind: "fallback",
2248
+ ranges: [{ offset: 0, length: bytes.length }],
2249
+ detail: `${walked.detail} \u2192 whole file`
2250
+ };
2251
+ }
2252
+ return walked;
2253
+ }
2254
+ var decoder, text, fallback, frameLengthAt, MPEG_SCAN_LIMIT;
2255
+ var init_media_ranges = __esm({
2256
+ "src/taglib/media-ranges.ts"() {
2257
+ "use strict";
2258
+ init_metadata_extent();
2259
+ decoder = new TextDecoder();
2260
+ text = (b, o, n) => decoder.decode(b.subarray(o, o + n));
2261
+ fallback = (detail) => ({
2262
+ kind: "fallback",
2263
+ ranges: [],
2264
+ detail
2265
+ });
2266
+ frameLengthAt = (bytes, at) => mpegFrameLength(bytes, at) || adtsFrameLength(bytes, at);
2267
+ MPEG_SCAN_LIMIT = 65536;
2268
+ }
2269
+ });
2270
+
2271
+ // src/taglib/audio-file-checksum.ts
2272
+ async function fileBytes(source, io) {
2273
+ if (source.path) return await io.readFile(source.path);
2274
+ if (source.blob) return new Uint8Array(await source.blob.arrayBuffer());
2275
+ if (!source.partiallyLoaded && source.bytes.length > 0) return source.bytes;
2276
+ throw new MetadataError(
2277
+ "read",
2278
+ "the handle holds no file bytes and has no readable source; a partial handle that has been saved drops its source \u2014 checksum the saved path instead"
2279
+ );
2280
+ }
2281
+ function joinRanges(bytes, ranges) {
2282
+ const total = ranges.reduce((n, r) => n + r.length, 0);
2283
+ const overrun = ranges.find((r) => r.offset + r.length > bytes.length);
2284
+ if (overrun || total > bytes.length) {
2285
+ throw new MetadataError(
2286
+ "read",
2287
+ `range walk overran the buffer (${overrun ? `range ${overrun.offset}+${overrun.length}` : `${total} bytes`} past the ${bytes.length}-byte buffer)`
2288
+ );
2289
+ }
2290
+ if (ranges.length === 1) {
2291
+ const only = ranges[0];
2292
+ const view = bytes.subarray(only.offset, only.offset + only.length);
2293
+ return view.buffer instanceof ArrayBuffer ? view : new Uint8Array(view);
2294
+ }
2295
+ const out = new Uint8Array(total);
2296
+ let at = 0;
2297
+ for (const r of ranges) {
2298
+ out.set(bytes.subarray(r.offset, r.offset + r.length), at);
2299
+ at += r.length;
2300
+ }
2301
+ return out;
2302
+ }
2303
+ async function mediaChecksum(source, format, io, options = {}) {
2304
+ if (options.basis === "pcm") {
2305
+ if (format.toUpperCase() !== "FLAC") {
2306
+ throw new UnsupportedFormatError(format, ["FLAC"], {
2307
+ operation: "media checksum (basis: pcm)"
2308
+ });
2309
+ }
2310
+ const windowed = source.bytes.length > 0 ? source.bytes : source.path && io.readPartial ? await io.readPartial(source.path, STREAMINFO_WINDOW, 0) : void 0;
2311
+ let md5 = windowed === void 0 ? void 0 : flacStreamInfoMd5(windowed);
2312
+ if (md5 === void 0 && windowed !== source.bytes) {
2313
+ md5 = flacStreamInfoMd5(await fileBytes(source, io));
2314
+ }
2315
+ if (md5 === void 0) {
2316
+ throw new MetadataError(
2317
+ "read",
2318
+ "STREAMINFO digest unavailable"
2319
+ );
2320
+ }
2321
+ return {
2322
+ source: "flac-streaminfo-md5",
2323
+ algorithm: "md5",
2324
+ hex: md5,
2325
+ bytesHashed: 16
2326
+ };
2327
+ }
2328
+ const bytes = await fileBytes(source, io);
2329
+ const walk = mediaRanges(format, bytes);
2330
+ const payload = joinRanges(bytes, walk.ranges);
2331
+ const digest = new Uint8Array(
2332
+ await crypto.subtle.digest("SHA-256", payload)
2333
+ );
2334
+ let hex = "";
2335
+ for (const byte of digest) hex += byte.toString(16).padStart(2, "0");
2336
+ return {
2337
+ source: walk.kind === "ranges" ? "audio-payload" : "file",
2338
+ algorithm: "sha256",
2339
+ hex,
2340
+ bytesHashed: payload.length
2341
+ };
2342
+ }
2343
+ var STREAMINFO_WINDOW;
2344
+ var init_audio_file_checksum = __esm({
2345
+ "src/taglib/audio-file-checksum.ts"() {
2346
+ "use strict";
2347
+ init_media_ranges();
2348
+ init_errors2();
2349
+ STREAMINFO_WINDOW = 65536;
2350
+ }
2351
+ });
2352
+
2353
+ // src/utils/file.ts
2354
+ async function readFileData(file) {
2355
+ if (file instanceof Uint8Array) return file;
2356
+ if (file instanceof ArrayBuffer) return new Uint8Array(file);
2357
+ if (typeof File !== "undefined" && file instanceof File) {
2358
+ return new Uint8Array(await file.arrayBuffer());
2359
+ }
2360
+ if (typeof file === "string") {
2361
+ try {
2362
+ return await getPlatformIO().readFile(file);
2363
+ } catch (error) {
2364
+ if (error instanceof EnvironmentError) throw error;
2365
+ throw new FileOperationError("read", error.message, file);
2366
+ }
2367
+ }
2368
+ const inputType = Object.prototype.toString.call(file);
2369
+ throw new FileOperationError(
2370
+ "read",
2371
+ `Invalid file input type: ${inputType}. Expected string path, Uint8Array, ArrayBuffer, or File object.`
2372
+ );
2373
+ }
2374
+ async function getFileSize(path) {
2375
+ try {
2376
+ const s = await getPlatformIO().stat(path);
2377
+ return s.size;
2378
+ } catch (error) {
2379
+ if (error instanceof EnvironmentError) throw error;
2380
+ throw new FileOperationError("stat", error.message, path);
2381
+ }
2382
+ }
2383
+ async function readPartialFileData(path, headerSize, footerSize) {
2384
+ const io = getPlatformIO();
2385
+ if (!io.readPartial) {
2386
+ throw new EnvironmentError(
2387
+ "current runtime",
2388
+ "does not support partial file reading",
2389
+ "filesystem access with seek support"
2390
+ );
2391
+ }
2392
+ try {
2393
+ return await io.readPartial(path, headerSize, footerSize);
2394
+ } catch (error) {
2395
+ if (error instanceof EnvironmentError) throw error;
2396
+ throw new FileOperationError("read", error.message, path);
2397
+ }
2398
+ }
2399
+ var init_file = __esm({
2400
+ "src/utils/file.ts"() {
2401
+ "use strict";
2402
+ init_errors2();
2403
+ init_platform_io_browser_stub();
2404
+ }
2405
+ });
2406
+
2407
+ // src/taglib/embind-adapter.ts
2408
+ function isValidBitrateMode(value) {
2409
+ return value === "CBR" || value === "VBR" || value === "ABR";
2410
+ }
2411
+ function wrapEmbindHandle(raw) {
2412
+ const stagedId3v2Frames = {};
2413
+ let destroyed = false;
2414
+ const overrides = {
2415
+ getTagData() {
2416
+ const tw = raw.getTag();
2417
+ return {
2418
+ title: tw.title(),
2419
+ artist: tw.artist(),
2420
+ album: tw.album(),
2421
+ comment: tw.comment(),
2422
+ genre: tw.genre(),
2423
+ year: tw.year(),
2424
+ track: tw.track()
2425
+ };
2426
+ },
2427
+ setTagData(data) {
2428
+ const tw = raw.getTag();
2429
+ if (data.title !== void 0) tw.setTitle(data.title);
2430
+ if (data.artist !== void 0) tw.setArtist(data.artist);
2431
+ if (data.album !== void 0) tw.setAlbum(data.album);
2432
+ if (data.comment !== void 0) tw.setComment(data.comment);
2433
+ if (data.genre !== void 0) tw.setGenre(data.genre);
2434
+ if (data.year !== void 0) tw.setYear(data.year);
2435
+ if (data.track !== void 0) tw.setTrack(data.track);
2436
+ },
2437
+ getBextData() {
2438
+ const v = raw.getBextData();
2439
+ if (v === void 0 || v === null) return void 0;
2440
+ const u8 = v instanceof Uint8Array ? v : new Uint8Array(v);
2441
+ return u8.length > 0 ? u8 : void 0;
2442
+ },
2443
+ setBextData(data) {
2444
+ raw.setBextData(data ?? null);
2445
+ },
2446
+ getIxml() {
2447
+ const v = raw.getIxml();
2448
+ return typeof v === "string" && v.length > 0 ? v : void 0;
2449
+ },
2450
+ setIxml(data) {
2451
+ raw.setIxml(
2452
+ data ?? null
2453
+ );
2454
+ },
2455
+ // Emscripten exposes USLT only through the PropertyMap "LYRICS" key (no
2456
+ // dedicated Embind binding), so bridge get/setLyrics to get/setProperties.
2457
+ // Text only — language/description are not surfaced by the PropertyMap.
2458
+ getLyrics() {
2459
+ const props = raw.getProperties();
2460
+ return (props["LYRICS"] ?? []).map((text2) => ({
2461
+ text: text2,
2462
+ description: "",
2463
+ language: ""
2464
+ }));
2465
+ },
2466
+ setLyrics(lyrics) {
2467
+ const handle = raw;
2468
+ const props = handle.getProperties();
2469
+ if (lyrics.length > 0) props["LYRICS"] = lyrics.map((l) => l.text);
2470
+ else delete props["LYRICS"];
2471
+ handle.setProperties(props);
2472
+ },
2473
+ setId3v2Frames(id, data) {
2474
+ stagedId3v2Frames[id] = data.map((d) => new Uint8Array(d));
2475
+ raw.setId3v2Frames(id, data);
2476
+ },
2477
+ removeId3v2Frames(id) {
2478
+ stagedId3v2Frames[id] = [];
2479
+ raw.removeId3v2Frames(id);
2480
+ },
2481
+ getStagedId3v2Frames() {
2482
+ return Object.keys(stagedId3v2Frames).length > 0 ? { ...stagedId3v2Frames } : void 0;
2483
+ },
2484
+ hasId3Tags() {
2485
+ const v = raw.hasId3Tags();
2486
+ if (!v || typeof v !== "object") return { v1: false, v2: false };
2487
+ const o = v;
2488
+ return { v1: o.v1 === true, v2: o.v2 === true };
2489
+ },
2490
+ stripId3Tags(opts) {
2491
+ raw.stripId3Tags(opts);
2492
+ },
2493
+ destroy() {
2494
+ if (destroyed) return;
2495
+ destroyed = true;
2496
+ raw.destroy();
2497
+ },
2498
+ get destroyed() {
2499
+ return destroyed;
2500
+ },
2501
+ // taglib-qyw2: the PropertyMap erase pass computes freeform item names
2502
+ // from the property table (upper-cased) and misses the restored exact-case
2503
+ // atom (taglib-bnhl), so clears queue the exact name here; the imperative
2504
+ // Embind model applies removeItem immediately, before setProperties.
2505
+ setMp4ItemRemovals(removals) {
2506
+ const handle = raw;
2507
+ for (const name of removals) handle.removeMP4Item(name);
2508
+ },
2509
+ getAudioProperties() {
2510
+ const pw = raw.getAudioProperties();
2511
+ if (!pw) return null;
2512
+ const containerFormat = pw.containerFormat() || "unknown";
2513
+ const codec = pw.codec() || "unknown";
2514
+ const mpegVersion = pw.mpegVersion();
2515
+ const formatVersion = pw.formatVersion();
2516
+ const bitrateMode = pw.bitrateMode();
2517
+ return {
2518
+ duration: pw.lengthInSeconds(),
2519
+ durationMs: pw.lengthInMilliseconds(),
2520
+ bitrate: pw.bitrate(),
2521
+ sampleRate: pw.sampleRate(),
2522
+ channels: pw.channels(),
2523
+ bitsPerSample: pw.bitsPerSample(),
2524
+ codec,
2525
+ containerFormat,
2526
+ isLossless: pw.isLossless(),
2527
+ ...mpegVersion > 0 ? { mpegVersion, mpegLayer: pw.mpegLayer() } : {},
2528
+ ...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: pw.isEncrypted() } : {},
2529
+ ...formatVersion > 0 ? { formatVersion } : {},
2530
+ ...isValidBitrateMode(bitrateMode) ? { bitrateMode } : {},
2531
+ // OpusHead gain exists only in an Ogg Opus stream. Gating on codec
2532
+ // alone fabricated outputGainDb: 0 for MP4 tracks once the MP4 codec
2533
+ // enum was mapped (the WASI side never emitted it there) — taglib-kswf.
2534
+ ...containerFormat === "OGG" && codec === "Opus" ? { outputGainDb: pw.outputGainDb() } : {}
2535
+ };
2536
+ }
2537
+ };
2538
+ return new Proxy(raw, {
2539
+ get(target, prop, receiver) {
2540
+ if (prop in overrides) return overrides[prop];
2541
+ const value = Reflect.get(target, prop, receiver);
2542
+ return typeof value === "function" ? value.bind(target) : value;
2543
+ }
2544
+ });
2545
+ }
2546
+ var init_embind_adapter = __esm({
2547
+ "src/taglib/embind-adapter.ts"() {
2548
+ "use strict";
2549
+ }
2550
+ });
2551
+
2552
+ // src/taglib/extra-state-registry.ts
2553
+ function chapterStyle(chapters) {
2554
+ const s = chapters[0]?.source;
2555
+ return s === "nero" || s === "both" ? s : "quicktime";
2556
+ }
2557
+ function copyExtraState(target, source, sourceComplete) {
2558
+ for (const field of EXTRA_FIELDS) {
2559
+ field.copy(target, source, sourceComplete);
2560
+ }
2561
+ }
2562
+ var EXTRA_FIELDS;
2563
+ var init_extra_state_registry = __esm({
2564
+ "src/taglib/extra-state-registry.ts"() {
2565
+ "use strict";
2566
+ EXTRA_FIELDS = [
2567
+ {
2568
+ name: "pictures",
2569
+ copy(target, source, complete) {
2570
+ const v = source.getPictures();
2571
+ if (complete || v.length > 0) target.setPictures(v);
2572
+ }
2573
+ },
2574
+ {
2575
+ name: "ratings",
2576
+ copy(target, source, complete) {
2577
+ const v = source.getRatings();
2578
+ if (complete || v.length > 0) target.setRatings(v);
2579
+ }
2580
+ },
2581
+ {
2582
+ name: "lyrics",
2583
+ copy(target, source, complete) {
2584
+ const v = source.getLyrics();
2585
+ if (complete || v.length > 0) target.setLyrics(v);
2586
+ }
2587
+ },
2588
+ {
2589
+ name: "chapters",
2590
+ copy(target, source, complete) {
2591
+ const v = source.getChapters();
2592
+ if (complete || v.length > 0) target.setChapters(v, chapterStyle(v));
2593
+ }
2594
+ },
2595
+ {
2596
+ name: "bextData",
2597
+ copy(target, source, complete) {
2598
+ const v = source.getBextData();
2599
+ if (v !== void 0) target.setBextData(v);
2600
+ else if (complete) target.setBextData(null);
2601
+ }
2602
+ },
2603
+ {
2604
+ name: "ixml",
2605
+ copy(target, source, complete) {
2606
+ const v = source.getIxml();
2607
+ if (v !== void 0) target.setIxml(v);
2608
+ else if (complete) target.setIxml(null);
2609
+ }
2610
+ },
2611
+ {
2612
+ name: "_mp4ItemRemovals",
2613
+ copy(target, source, complete) {
2614
+ const v = source.getMp4ItemRemovals?.();
2615
+ if (complete || v !== void 0 && v.length > 0) {
2616
+ target.setMp4ItemRemovals?.(v ?? []);
2617
+ }
2618
+ }
2619
+ },
2620
+ {
2621
+ name: "id3v2Frames",
2622
+ copy(target, source) {
2623
+ const staged = source.getStagedId3v2Frames?.();
2624
+ if (!staged) return;
2625
+ for (const [id, bodies] of Object.entries(staged)) {
2626
+ target.setId3v2Frames(id, bodies);
2627
+ }
2628
+ }
2629
+ }
2630
+ ];
2631
+ }
2632
+ });
2633
+
2634
+ // src/taglib/save-reconstruct.ts
2635
+ function copyEditedState(target, source, sourceComplete, deletedKeys) {
2636
+ target.setTagData(source.getTagData());
2637
+ const merged = sourceComplete ? source.getProperties() : { ...target.getProperties(), ...source.getProperties() };
2638
+ for (const key of deletedKeys) delete merged[key];
2639
+ const atomNames = mp4FreeformAtomNames(Object.keys(merged));
2640
+ if (atomNames.length > 0) merged[MP4_ITEM_NAMES_KEY] = atomNames;
2641
+ target.setProperties(merged);
2642
+ copyExtraState(target, source, sourceComplete);
2643
+ }
2644
+ async function saveViaFreshHandle(module, editing, source, targetPath, sourceComplete, deletedKeys = /* @__PURE__ */ new Set()) {
2645
+ const rawFullHandle = module.createFileHandle();
2646
+ const fullFileHandle = module.isWasi ? rawFullHandle : wrapEmbindHandle(rawFullHandle);
2647
+ try {
2648
+ {
2649
+ const data = await readFileData(source);
2650
+ if (!fullFileHandle.loadFromBuffer(data)) {
2651
+ throw new InvalidFormatError(
2652
+ "Failed to load full audio file for saving"
2653
+ );
2654
+ }
2655
+ }
2656
+ copyEditedState(fullFileHandle, editing, sourceComplete, deletedKeys);
2657
+ if (!fullFileHandle.save()) {
2658
+ throw new FileOperationError(
2659
+ "save",
2660
+ "Failed to save changes to full file"
2661
+ );
2662
+ }
2663
+ await writeFileData(targetPath, fullFileHandle.getBuffer());
2664
+ } finally {
2665
+ fullFileHandle.destroy();
2666
+ }
2667
+ }
2668
+ var init_save_reconstruct = __esm({
2669
+ "src/taglib/save-reconstruct.ts"() {
2670
+ "use strict";
2671
+ init_errors2();
2672
+ init_file();
2673
+ init_write();
2674
+ init_embind_adapter();
2675
+ init_extra_state_registry();
2676
+ init_properties();
2677
+ init_mp4_item_names();
2678
+ }
2679
+ });
2680
+
2681
+ // src/taglib/audio-file-impl.ts
2682
+ function sortChapters(list) {
2683
+ return [...list].sort((a, b) => a.startTimeMs - b.startTimeMs);
2684
+ }
2685
+ function inferEndTimeMs(sorted, index, trackEndMs) {
2686
+ const own = sorted[index].endTimeMs;
2687
+ if (own !== void 0) return own;
2688
+ const next = sorted[index + 1];
2689
+ return next ? next.startTimeMs : trackEndMs;
2690
+ }
2691
+ function readFileSync(path) {
2692
+ if (isDeno()) return Deno.readFileSync(path);
2693
+ const fs = getNodeFsSync();
2694
+ if (fs) return new Uint8Array(fs.readFileSync(path));
2695
+ throw new FileOperationError(
2696
+ "read",
2697
+ "node:fs is unavailable in this runtime: cannot read file data from disk",
2698
+ path
2699
+ );
2700
+ }
2701
+ var AudioFileImpl;
2702
+ var init_audio_file_impl = __esm({
2703
+ "src/taglib/audio-file-impl.ts"() {
2704
+ "use strict";
2705
+ init_types2();
2706
+ init_audio_file_bwf();
2707
+ init_id3v2_frames();
2708
+ init_errors2();
2709
+ init_write();
2710
+ init_node_fs_browser_stub();
2711
+ init_rating();
2712
+ init_detector();
2713
+ init_audio_file_base();
2714
+ init_audio_file_checksum();
2715
+ init_platform_io_browser_stub();
2716
+ init_save_reconstruct();
2717
+ AudioFileImpl = class extends BaseAudioFileImpl {
2718
+ constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
2719
+ super(
2720
+ module,
2721
+ fileHandle,
2722
+ sourcePath,
2723
+ originalSource,
2724
+ isPartiallyLoaded,
2725
+ partialLoadOptions
2726
+ );
2727
+ __publicField(this, "pathModeBuffer", null);
2728
+ /** How the handle was loaded — `isPartiallyLoaded` is mutated by saveToFile(). */
2729
+ __publicField(this, "partiallyLoadedAtConstruction");
2730
+ this.partiallyLoadedAtConstruction = isPartiallyLoaded;
2731
+ }
2732
+ save() {
2733
+ if (this.isPartiallyLoaded && this.originalSource) {
2734
+ throw new FileOperationError(
2735
+ "save",
2736
+ "Cannot save partially loaded file directly. Use saveToFile() instead"
2737
+ );
2738
+ }
2739
+ this.cachedAudioProperties = null;
2740
+ this.pathModeBuffer = null;
2741
+ return this.handle.save();
2742
+ }
2743
+ getFileBuffer() {
2744
+ const buffer = this.handle.getBuffer();
2745
+ if (buffer.length > 0) return buffer;
2746
+ if (this.pathModeBuffer) return this.pathModeBuffer;
2747
+ if (this.sourcePath) {
2748
+ try {
2749
+ this.pathModeBuffer = readFileSync(this.sourcePath);
2750
+ return this.pathModeBuffer;
2751
+ } catch (error) {
2752
+ if (error instanceof FileOperationError) throw error;
2753
+ throw new FileOperationError(
2754
+ "read",
2755
+ `Cannot return file data: ${errorMessage(error)}`,
2756
+ this.sourcePath,
2757
+ { cause: error }
2758
+ );
2759
+ }
2760
+ }
2761
+ throw new FileOperationError(
2762
+ "read",
2763
+ "No file data available: in-memory buffer is empty and no source path is set"
2764
+ );
2765
+ }
2766
+ mediaChecksum(options) {
2767
+ return mediaChecksum(
2768
+ {
2769
+ // The cheap, no-IO accessor — `getFileBuffer()` would read the whole
2770
+ // file from disk in path mode (and cache it), which the source rule is
2771
+ // about to read for itself.
2772
+ bytes: this.handle.getBuffer(),
2773
+ // empty in path mode: the data is on disk
2774
+ // Both optional fields are conditional: they are `undefined` far more
2775
+ // often than not, and `exactOptionalPropertyTypes` refuses an explicit
2776
+ // `undefined` on an optional property.
2777
+ ...this.sourcePath !== void 0 ? { path: this.sourcePath } : {},
2778
+ // originalSource is the File when the caller passed one. A partial File
2779
+ // has no path, so this is the only way back to the real bytes.
2780
+ ...this.originalSource instanceof Blob ? { blob: this.originalSource } : {},
2781
+ // Captured at construction, NOT this.isPartiallyLoaded: saveToFile() sets
2782
+ // that flag false (and clears originalSource) while the handle still holds
2783
+ // the spliced image, which would make the source rule hash the image.
2784
+ partiallyLoaded: this.partiallyLoadedAtConstruction
2785
+ },
2786
+ this.getFormat(),
2787
+ getPlatformIO(),
2788
+ options
2789
+ );
2790
+ }
2791
+ async saveToFile(path) {
2792
+ const targetPath = path ?? this.sourcePath;
2793
+ if (!targetPath) {
2794
+ throw new FileOperationError(
2795
+ "save",
2796
+ "No file path available. Provide a path or open the file from a path"
2797
+ );
2798
+ }
2799
+ if (this.isPartiallyLoaded && this.originalSource) {
2800
+ await saveViaFreshHandle(
2801
+ this.module,
2802
+ this.handle,
2803
+ this.originalSource,
2804
+ targetPath,
2805
+ false,
2806
+ this.partialDeletedPropertyKeys()
2807
+ );
2808
+ this.isPartiallyLoaded = false;
2809
+ this.originalSource = void 0;
2810
+ } else if (this.module.isWasi && this.sourcePath && targetPath !== this.sourcePath) {
2811
+ await saveViaFreshHandle(
2812
+ this.module,
2813
+ this.handle,
2814
+ this.sourcePath,
2815
+ targetPath,
2816
+ true
2817
+ );
2818
+ } else {
2819
+ if (!this.save()) {
2820
+ throw new FileOperationError(
2821
+ "save",
2822
+ "Failed to save changes to in-memory buffer"
2823
+ );
2824
+ }
2825
+ const buffer = this.handle.getBuffer();
2826
+ if (buffer.length > 0) {
2827
+ await writeFileData(targetPath, buffer);
2828
+ }
2829
+ }
2830
+ this.pathModeBuffer = null;
2831
+ }
2832
+ getPictures() {
2833
+ const picturesArray = this.handle.getPictures();
2834
+ return picturesArray.map((pic) => ({
2835
+ mimeType: pic.mimeType,
2836
+ data: pic.data,
2837
+ type: PICTURE_TYPE_NAMES[pic.type] ?? "Other",
2838
+ ...pic.description !== void 0 ? { description: pic.description } : {}
2839
+ }));
2840
+ }
2841
+ setPictures(pictures) {
2842
+ this.handle.setPictures(pictures.map((pic) => ({
2843
+ mimeType: pic.mimeType,
2844
+ data: pic.data,
2845
+ type: PICTURE_TYPE_VALUES[pic.type] ?? 0,
2846
+ description: pic.description ?? ""
2847
+ })));
2848
+ }
2849
+ addPicture(picture) {
2850
+ this.handle.addPicture({
2851
+ mimeType: picture.mimeType,
2852
+ data: picture.data,
2853
+ type: PICTURE_TYPE_VALUES[picture.type] ?? 0,
2854
+ description: picture.description ?? ""
2855
+ });
2856
+ }
2857
+ removePictures() {
2858
+ this.handle.removePictures();
2859
+ }
2860
+ getChapters() {
2861
+ const sorted = sortChapters(this.handle.getChapters());
2862
+ const trackEndMs = this.audioProperties()?.durationMs;
2863
+ return sorted.map((c, i) => {
2864
+ const endTimeMs = inferEndTimeMs(sorted, i, trackEndMs);
2865
+ return {
2866
+ startTimeMs: c.startTimeMs,
2867
+ ...endTimeMs !== void 0 ? { endTimeMs } : {},
2868
+ // Truthiness, not !== undefined: an empty CHAP title/id must read back
2869
+ // as ABSENT (the old `c.title || undefined` contract; the Embind
2870
+ // reader emits "" unconditionally, WASI omits — coalescing keeps the
2871
+ // backends agreeing).
2872
+ ...c.title ? { title: c.title } : {},
2873
+ ...c.id ? { id: c.id } : {},
2874
+ ...c.source !== void 0 ? { source: c.source } : {}
2875
+ };
2876
+ });
2877
+ }
2878
+ setChapters(chapters, options) {
2879
+ const fmt = this.getFormat();
2880
+ if (fmt !== "MP3" && fmt !== "AAC" && fmt !== "MP4") {
2881
+ throw new UnsupportedFormatError(fmt, ["MP3", "AAC", "MP4"], {
2882
+ operation: "setChapters"
2883
+ });
2884
+ }
2885
+ const sorted = sortChapters(chapters);
2886
+ const trackEndMs = this.audioProperties()?.durationMs;
2887
+ const style = options?.mp4ChapterStyle ?? "quicktime";
2888
+ const source = fmt === "MP4" ? style : "id3";
2889
+ const raw = sorted.map((c, i) => ({
2890
+ ...c.id !== void 0 ? { id: c.id } : {},
2891
+ startTimeMs: c.startTimeMs,
2892
+ endTimeMs: inferEndTimeMs(sorted, i, trackEndMs) ?? c.startTimeMs,
2893
+ ...c.title !== void 0 ? { title: c.title } : {},
2894
+ source
2895
+ }));
2896
+ this.handle.setChapters(raw, style);
2897
+ }
2898
+ getBext() {
2899
+ return getBext(this.handle);
2900
+ }
2901
+ setBext(bext) {
2902
+ setBext(this.handle, this.getFormat(), bext);
2903
+ }
2904
+ getBextData() {
2905
+ return getBextData(this.handle);
2906
+ }
2907
+ setBextData(data) {
2908
+ setBextData(this.handle, this.getFormat(), data);
2909
+ }
2910
+ getIxml() {
2911
+ return getIxml(this.handle);
2912
+ }
2913
+ setIxml(data) {
2914
+ setIxml(this.handle, this.getFormat(), data);
2915
+ }
2916
+ getRatings() {
2917
+ return this.handle.getRatings().map(
2918
+ (r) => ({
2919
+ rating: r.rating,
2920
+ ...r.email !== void 0 && r.email !== "" ? { email: r.email } : {},
2921
+ // 0 must read back as ABSENT (old `r.counter || undefined` contract).
2922
+ ...r.counter !== 0 ? { counter: r.counter } : {}
2923
+ })
2924
+ );
2925
+ }
2926
+ setRatings(ratings) {
2927
+ this.handle.setRatings(ratings.map((r) => ({
2928
+ rating: r.rating,
2929
+ email: r.email ?? "",
2930
+ counter: r.counter ?? 0
2931
+ })));
2932
+ }
2933
+ getLyrics() {
2934
+ return this.handle.getLyrics().map((l) => {
2935
+ const out = { text: l.text };
2936
+ if (l.description) out.description = l.description;
2937
+ if (l.language) out.language = l.language;
2938
+ return out;
2939
+ });
2940
+ }
2941
+ setLyrics(lyrics) {
2942
+ this.handle.setLyrics(lyrics.map((l) => ({
2943
+ text: l.text,
2944
+ description: l.description ?? "",
2945
+ language: l.language ?? ""
2946
+ })));
2947
+ }
2948
+ getRating() {
2949
+ const ratings = this.getRatings();
2950
+ return ratings.length > 0 ? normalized(ratings[0].rating) : void 0;
2951
+ }
2952
+ setRating(rating, email) {
2953
+ this.setRatings([{
2954
+ rating,
2955
+ ...email !== void 0 ? { email } : {},
2956
+ counter: 0
2957
+ }]);
2958
+ }
2959
+ getId3v2Frames(id) {
2960
+ assertMp3(this.getFormat());
2961
+ if (id !== void 0) assertFrameId(id, "read");
2962
+ return this.handle.getId3v2Frames(id ?? "").map(toPublicFrame);
2963
+ }
2964
+ setId3v2Frames(id, data) {
2965
+ assertMp3(this.getFormat());
2966
+ assertFrameId(id, "write");
2967
+ assertFrameBodies(id, data);
2968
+ this.handle.setId3v2Frames(id, data);
2969
+ }
2970
+ removeId3v2Frames(id) {
2971
+ assertMp3(this.getFormat());
2972
+ assertFrameId(id, "write");
2973
+ this.handle.removeId3v2Frames(id);
2974
+ }
2975
+ hasId3Tags() {
2976
+ return this.handle.hasId3Tags();
2977
+ }
2978
+ stripId3Tags(opts) {
2979
+ this.handle.stripId3Tags({
2980
+ v1: opts?.v1 ?? true,
2981
+ v2: opts?.v2 ?? true
2982
+ });
2983
+ }
2984
+ };
2985
+ }
2986
+ });
2987
+
2988
+ // src/taglib/load-audio-data.ts
2989
+ async function loadAudioData(input, opts) {
2990
+ if (opts.partial && typeof File !== "undefined" && input instanceof File) {
2991
+ const headerSize = Math.min(opts.maxHeaderSize, input.size);
2992
+ const footerSize = Math.min(opts.maxFooterSize, input.size);
2993
+ if (input.size <= headerSize + footerSize) {
2994
+ return { data: await readFileData(input), isPartiallyLoaded: false };
2995
+ }
2996
+ const header = new Uint8Array(
2997
+ await input.slice(0, headerSize).arrayBuffer()
2998
+ );
2999
+ if (!metadataFitsInHeader(header, headerSize)) {
3000
+ return { data: await readFileData(input), isPartiallyLoaded: false };
3001
+ }
3002
+ const footerStart = Math.max(0, input.size - footerSize);
3003
+ const footer = new Uint8Array(await input.slice(footerStart).arrayBuffer());
3004
+ if (!trailerFitsInFooter(footer, footerSize)) {
3005
+ return { data: await readFileData(input), isPartiallyLoaded: false };
3006
+ }
3007
+ const combined = new Uint8Array(header.byteLength + footer.byteLength);
3008
+ combined.set(header, 0);
3009
+ combined.set(footer, header.byteLength);
3010
+ return { data: combined, isPartiallyLoaded: true };
3011
+ }
3012
+ if (opts.partial && typeof input === "string") {
3013
+ const fileSize = await getFileSize(input);
3014
+ if (fileSize > opts.maxHeaderSize + opts.maxFooterSize) {
3015
+ const data = await readPartialFileData(
3016
+ input,
3017
+ opts.maxHeaderSize,
3018
+ opts.maxFooterSize
3019
+ );
3020
+ if (metadataFitsInHeader(data, opts.maxHeaderSize) && trailerFitsInFooter(data, opts.maxFooterSize)) {
3021
+ return { data, isPartiallyLoaded: true };
3022
+ }
3023
+ }
3024
+ return { data: await readFileData(input), isPartiallyLoaded: false };
3025
+ }
3026
+ return { data: await readFileData(input), isPartiallyLoaded: false };
3027
+ }
3028
+ var init_load_audio_data = __esm({
3029
+ "src/taglib/load-audio-data.ts"() {
3030
+ "use strict";
3031
+ init_file();
3032
+ init_metadata_extent();
3033
+ }
3034
+ });
3035
+
3036
+ // src/utils/tag-mapping.ts
3037
+ function mergeTagUpdatesInto(currentProps, tags) {
3038
+ const newProps = normalizeTagInput(tags);
3039
+ reconcilePairFields(currentProps, newProps, tags);
3040
+ if (newProps.date !== void 0 || newProps.year !== void 0) {
3041
+ currentProps.YEAR = [];
3042
+ }
3043
+ return { ...currentProps, ...newProps };
3044
+ }
3045
+ function mergeTagUpdates(file, tags) {
3046
+ file.setProperties(mergeTagUpdatesInto(file.properties(), tags));
3047
+ }
3048
+ function reconcilePairFields(currentProps, newProps, input) {
3049
+ const pairs = [
3050
+ ["trackNumber", "totalTracks", "track"],
3051
+ ["discNumber", "totalDiscs", "discNumber"]
3052
+ ];
3053
+ for (const [rawKey, totalKey, numericKey] of pairs) {
3054
+ const incoming = newProps[rawKey]?.[0];
3055
+ if (incoming?.includes("/")) {
3056
+ newProps[totalKey] = [];
3057
+ continue;
3058
+ }
3059
+ const numericOnly = input[numericKey] !== void 0 && input[rawKey] === void 0;
3060
+ if (!numericOnly || incoming === void 0) continue;
3061
+ const existing = currentProps[rawKey]?.[0];
3062
+ const slash = existing?.indexOf("/") ?? -1;
3063
+ if (existing !== void 0 && slash !== -1) {
3064
+ newProps[rawKey] = [`${incoming}${existing.slice(slash)}`];
3065
+ }
3066
+ }
3067
+ }
3068
+ function normalizeTagInput(input) {
3069
+ const props = {};
3070
+ for (const field of [
3071
+ "title",
3072
+ "artist",
3073
+ "album",
3074
+ "comment",
3075
+ "genre"
3076
+ ]) {
3077
+ const val = input[field];
3078
+ if (val === void 0) continue;
3079
+ props[field] = Array.isArray(val) ? val : [val];
3080
+ }
3081
+ if (input.year !== void 0) {
3082
+ props.date = [String(input.year)];
3083
+ }
3084
+ if (input.date !== void 0) {
3085
+ props.date = Array.isArray(input.date) ? input.date : [input.date];
3086
+ }
3087
+ if (input.track !== void 0) {
3088
+ props.trackNumber = [String(input.track)];
3089
+ }
3090
+ if (input.trackNumber !== void 0) {
3091
+ props.trackNumber = Array.isArray(input.trackNumber) ? input.trackNumber : [input.trackNumber];
3092
+ }
3093
+ for (const [field, val] of Object.entries(input)) {
3094
+ if (BASIC_FIELDS.has(field) || val === void 0) continue;
3095
+ if (field === "extraProperties") continue;
3096
+ if (field === "compilation") {
3097
+ props[field] = [val ? "1" : "0"];
3098
+ } else if (field === "advisory") {
3099
+ if (val === "explicit") props.itunesAdvisory = ["1"];
3100
+ else if (val === "clean") props.itunesAdvisory = ["2"];
3101
+ else if (val === "unspecified") props.itunesAdvisory = [];
3102
+ } else if (field === "bpm") {
3103
+ props[field] = [String(Math.trunc(val))];
3104
+ } else if (NUMERIC_FIELDS.has(field)) {
3105
+ props[field] = [String(val)];
3106
+ } else if (field === "r128TrackGain" || field === "r128AlbumGain") {
3107
+ props[field] = Array.isArray(val) ? val : [String(Math.round(val * 256))];
3108
+ } else if (typeof val === "string") {
3109
+ props[field] = [val];
3110
+ } else if (Array.isArray(val)) {
3111
+ props[field] = val;
3112
+ }
3113
+ }
3114
+ for (const [rawField, totalField] of [
3115
+ ["trackNumber", "totalTracks"],
3116
+ ["discNumber", "totalDiscs"]
3117
+ ]) {
3118
+ if (props[rawField]?.[0]?.includes("/")) delete props[totalField];
3119
+ }
3120
+ return props;
3121
+ }
3122
+ var BASIC_FIELDS, NUMERIC_FIELDS;
3123
+ var init_tag_mapping = __esm({
3124
+ "src/utils/tag-mapping.ts"() {
3125
+ "use strict";
3126
+ BASIC_FIELDS = /* @__PURE__ */ new Set([
3127
+ "title",
3128
+ "artist",
3129
+ "album",
3130
+ "comment",
3131
+ "genre",
3132
+ "year",
3133
+ "date",
3134
+ "track",
3135
+ "trackNumber"
3136
+ ]);
3137
+ NUMERIC_FIELDS = /* @__PURE__ */ new Set([
3138
+ "year",
3139
+ "track",
3140
+ "discNumber",
3141
+ "totalTracks",
3142
+ "totalDiscs",
3143
+ "bpm"
3144
+ ]);
3145
+ }
3146
+ });
3147
+
3148
+ // src/version.ts
3149
+ var VERSION;
3150
+ var init_version = __esm({
3151
+ "src/version.ts"() {
3152
+ "use strict";
3153
+ VERSION = "2.3.0";
3154
+ }
3155
+ });
3156
+
3157
+ // src/runtime/module-loader-browser.ts
3158
+ var module_loader_browser_exports = {};
3159
+ __export(module_loader_browser_exports, {
3160
+ loadTagLibModule: () => loadTagLibModule
3161
+ });
3162
+ async function loadTagLibModule(options) {
3163
+ let createTagLibModule;
3164
+ try {
3165
+ const module2 = await import("./taglib-wrapper.js");
3166
+ createTagLibModule = module2.default;
3167
+ } catch {
3168
+ try {
3169
+ const module2 = await import("./taglib-wrapper.js");
3170
+ createTagLibModule = module2.default;
3171
+ } catch {
3172
+ throw new TagLibInitializationError(
3173
+ "Could not load taglib-wrapper.js. Ensure it is co-located with the browser bundle."
3174
+ );
3175
+ }
3176
+ }
3177
+ const moduleConfig = {};
3178
+ if (options?.wasmBinary) {
3179
+ moduleConfig.wasmBinary = options.wasmBinary;
3180
+ }
3181
+ if (options?.wasmUrl) {
3182
+ moduleConfig.locateFile = (path) => {
3183
+ if (path.endsWith(".wasm")) {
3184
+ return options.wasmUrl;
3185
+ }
3186
+ return path;
3187
+ };
3188
+ } else if (!options?.wasmBinary) {
3189
+ const wasmUrl = new URL("./taglib-web.wasm", import.meta.url);
3190
+ moduleConfig.locateFile = (path) => path.endsWith(".wasm") ? wasmUrl.href : path;
3191
+ }
3192
+ const module = await createTagLibModule(moduleConfig);
3193
+ return module;
3194
+ }
3195
+ var init_module_loader_browser = __esm({
3196
+ "src/runtime/module-loader-browser.ts"() {
3197
+ init_classes();
3198
+ }
3199
+ });
3200
+
3201
+ // src/taglib/taglib-class.ts
3202
+ function toWasiPath(osPath) {
3203
+ if (osPath.startsWith("\\\\") || osPath.startsWith("//")) {
3204
+ throw new FileOperationError(
3205
+ "read",
3206
+ `UNC paths are not supported. Path: ${osPath}`
3207
+ );
3208
+ }
3209
+ let p = osPath;
3210
+ if (!p.startsWith("/") && !/^[A-Za-z]:/.test(p)) {
3211
+ const g2 = globalThis;
3212
+ const cwd = isDeno() ? Deno.cwd() : g2.process?.cwd?.();
3213
+ if (cwd) {
3214
+ p = cwd.replace(/[/\\]+$/, "") + "/" + p;
3215
+ }
3216
+ }
3217
+ p = p.replaceAll("\\", "/");
3218
+ const driveMatch = p.match(/^([A-Za-z]):\//);
3219
+ if (driveMatch) {
3220
+ p = `/${driveMatch[1].toUpperCase()}${p.slice(2)}`;
3221
+ }
3222
+ p = p.replace(/\/\.\//g, "/").replace(/\/+/g, "/");
3223
+ if (!p.startsWith("/")) p = "/" + p;
3224
+ return p;
3225
+ }
3226
+ async function createTagLib(module) {
3227
+ return new TagLib(module);
3228
+ }
3229
+ var TagLib;
3230
+ var init_taglib_class = __esm({
3231
+ "src/taglib/taglib-class.ts"() {
3232
+ "use strict";
3233
+ init_audio_formats();
3234
+ init_errors2();
3235
+ init_detector();
3236
+ init_audio_file_impl();
3237
+ init_load_audio_data();
3238
+ init_tag_mapping();
3239
+ init_errors2();
3240
+ init_version();
3241
+ init_embind_adapter();
3242
+ TagLib = class _TagLib {
3243
+ constructor(module) {
3244
+ __publicField(this, "module");
3245
+ this.module = module;
3246
+ }
3247
+ /**
3248
+ * Initialize the TagLib Wasm module and return a ready-to-use instance.
3249
+ * @param options - Wasm loading configuration (binary, URL, backend selection).
3250
+ * @returns A configured TagLib instance.
3251
+ * @throws {TagLibInitializationError} If the Wasm module fails to load.
3252
+ */
3253
+ static async initialize(options) {
3254
+ const { loadTagLibModule: loadTagLibModule2 } = await Promise.resolve().then(() => (init_module_loader_browser(), module_loader_browser_exports));
3255
+ const module = await loadTagLibModule2(options);
3256
+ return new _TagLib(module);
3257
+ }
3258
+ /**
3259
+ * Open an audio file for reading and writing metadata.
3260
+ * @param input - File path, Uint8Array, ArrayBuffer, File object, or NamedAudioInput.
3261
+ * @param options - Partial-loading options for large files.
3262
+ * @returns An AudioFile instance (use `using` for automatic cleanup).
3263
+ * @throws {TagLibInitializationError} If the module is not properly initialized.
3264
+ * @throws {InvalidFormatError} If the file is corrupted or unsupported.
3265
+ */
3266
+ async open(input, options) {
3267
+ if (!this.module.createFileHandle) {
3268
+ throw new TagLibInitializationError(
3269
+ "TagLib module not properly initialized: createFileHandle not found. Make sure the module is fully loaded before calling open."
3270
+ );
3271
+ }
3272
+ const actualInput = isNamedAudioInput(input) ? input.data : input;
3273
+ const sourcePath = typeof actualInput === "string" ? actualInput : void 0;
3274
+ if (typeof actualInput === "string" && this.module.isWasi) {
3275
+ const wasiPath = toWasiPath(actualInput);
3276
+ if (this.module.loadAudioPath) {
3277
+ const fileHandle2 = await this.module.loadAudioPath(
3278
+ wasiPath,
3279
+ actualInput
3280
+ );
3281
+ return new AudioFileImpl(
3282
+ this.module,
3283
+ fileHandle2,
3284
+ sourcePath,
3285
+ actualInput,
3286
+ false
3287
+ );
3288
+ }
3289
+ }
3290
+ const opts = {
3291
+ partial: true,
3292
+ maxHeaderSize: 1024 * 1024,
3293
+ maxFooterSize: 128 * 1024,
3294
+ ...options
3295
+ };
3296
+ const { data: audioData, isPartiallyLoaded } = await loadAudioData(
3297
+ actualInput,
3298
+ opts
3299
+ );
3300
+ const uint8Array = actualInput instanceof Uint8Array && audioData.buffer === actualInput.buffer ? new Uint8Array(
3301
+ audioData.buffer.slice(
3302
+ audioData.byteOffset,
3303
+ audioData.byteOffset + audioData.byteLength
3304
+ )
3305
+ ) : audioData;
3306
+ const rawHandle = this.module.createFileHandle();
3307
+ const fileHandle = this.module.isWasi ? rawHandle : wrapEmbindHandle(rawHandle);
3308
+ try {
3309
+ const success = fileHandle.loadFromBuffer(uint8Array);
3310
+ if (!success) {
3311
+ throw new InvalidFormatError(
3312
+ "Failed to load audio file. File may be corrupted or in an unsupported format",
3313
+ uint8Array.byteLength
3314
+ );
3315
+ }
3316
+ return new AudioFileImpl(
3317
+ this.module,
3318
+ fileHandle,
3319
+ sourcePath,
3320
+ actualInput,
3321
+ isPartiallyLoaded,
3322
+ opts
3323
+ );
3324
+ } catch (error) {
3325
+ if (typeof fileHandle.destroy === "function") {
3326
+ fileHandle.destroy();
3327
+ }
3328
+ throw error;
3329
+ }
3330
+ }
3331
+ async edit(input, fn) {
3332
+ const file = await this.open(input);
3333
+ try {
3334
+ await fn(file);
3335
+ if (typeof input === "string") {
3336
+ await file.saveToFile();
3337
+ } else {
3338
+ file.save();
3339
+ return file.getFileBuffer();
3340
+ }
3341
+ } finally {
3342
+ file.dispose();
3343
+ }
3344
+ }
3345
+ /**
3346
+ * Update tags in a file and save to disk in one operation.
3347
+ * @param path - Path to the audio file.
3348
+ * @param tags - Tag fields to update (partial update supported).
3349
+ * @throws {InvalidFormatError} If the file is corrupted or unsupported.
3350
+ * @throws {FileOperationError} If saving to disk fails.
3351
+ */
3352
+ async updateFile(path, tags) {
3353
+ const file = await this.open(path);
3354
+ try {
3355
+ mergeTagUpdates(file, tags);
3356
+ await file.saveToFile();
3357
+ } finally {
3358
+ file.dispose();
3359
+ }
3360
+ }
3361
+ /**
3362
+ * Copy an audio file to a new path with updated tags.
3363
+ * @param sourcePath - Path to the source audio file.
3364
+ * @param destPath - Destination path for the tagged copy.
3365
+ * @param tags - Tag fields to set on the copy.
3366
+ * @throws {InvalidFormatError} If the source file is corrupted or unsupported.
3367
+ * @throws {FileOperationError} If reading or writing fails.
3368
+ */
3369
+ async copyWithTags(sourcePath, destPath, tags) {
3370
+ const file = await this.open(sourcePath);
3371
+ try {
3372
+ mergeTagUpdates(file, tags);
3373
+ await file.saveToFile(destPath);
3374
+ } finally {
3375
+ file.dispose();
3376
+ }
3377
+ }
3378
+ /** Returns the taglib-wasm version with embedded TagLib version. */
3379
+ version() {
3380
+ return `${VERSION} (TagLib ${this.taglibVersion()})`;
3381
+ }
3382
+ taglibVersion() {
3383
+ if (this.module.getVersion) {
3384
+ return this.module.getVersion();
3385
+ }
3386
+ if (this.module.version) {
3387
+ return this.module.version();
3388
+ }
3389
+ return "unknown";
3390
+ }
3391
+ };
3392
+ }
3393
+ });
3394
+
3395
+ // src/taglib/index.ts
3396
+ var init_taglib = __esm({
3397
+ "src/taglib/index.ts"() {
3398
+ "use strict";
3399
+ init_audio_file_impl();
3400
+ init_taglib_class();
3401
+ init_errors2();
3402
+ }
3403
+ });
3404
+
3405
+ // src/taglib.ts
3406
+ var taglib_exports = {};
3407
+ __export(taglib_exports, {
3408
+ AudioFileImpl: () => AudioFileImpl,
3409
+ EnvironmentError: () => EnvironmentError,
3410
+ FileOperationError: () => FileOperationError,
3411
+ InvalidFormatError: () => InvalidFormatError,
3412
+ MemoryError: () => MemoryError,
3413
+ MetadataError: () => MetadataError,
3414
+ SUPPORTED_FORMATS: () => SUPPORTED_FORMATS,
3415
+ TagLib: () => TagLib,
3416
+ TagLibError: () => TagLibError,
3417
+ TagLibInitializationError: () => TagLibInitializationError,
3418
+ UnsupportedFormatError: () => UnsupportedFormatError,
3419
+ createTagLib: () => createTagLib,
3420
+ isEnvironmentError: () => isEnvironmentError,
3421
+ isFileOperationError: () => isFileOperationError,
3422
+ isInvalidFormatError: () => isInvalidFormatError,
3423
+ isMemoryError: () => isMemoryError,
3424
+ isMetadataError: () => isMetadataError,
3425
+ isTagLibError: () => isTagLibError,
3426
+ isUnsupportedFormatError: () => isUnsupportedFormatError
3427
+ });
3428
+ var init_taglib2 = __esm({
3429
+ "src/taglib.ts"() {
3430
+ "use strict";
3431
+ init_taglib();
3432
+ }
3433
+ });
3434
+
3435
+ // src/web-utils/data-url.ts
3436
+ init_classes();
3437
+ function pictureToDataURL(picture) {
3438
+ const data = picture.data;
3439
+ const CHUNK = 32768;
3440
+ let binary = "";
3441
+ for (let i = 0; i < data.length; i += CHUNK) {
3442
+ binary += String.fromCharCode(...data.subarray(i, i + CHUNK));
3443
+ }
3444
+ const base64 = btoa(binary);
3445
+ return `data:${picture.mimeType};base64,${base64}`;
3446
+ }
3447
+ function dataURLToPicture(dataURL, type = "FrontCover", description) {
3448
+ const regex = /^data:([^;]+);base64,(.+)$/;
3449
+ const matches = regex.exec(dataURL);
3450
+ if (!matches) {
3451
+ throw new InvalidFormatError("Invalid data URL format");
3452
+ }
3453
+ const [, mimeType, base64] = matches;
3454
+ const binaryString = atob(base64);
3455
+ const data = new Uint8Array(binaryString.length);
3456
+ for (let i = 0; i < binaryString.length; i++) {
3457
+ data[i] = binaryString.codePointAt(i);
3458
+ }
3459
+ return {
3460
+ mimeType,
3461
+ data,
3462
+ type,
3463
+ ...description !== void 0 ? { description } : {}
3464
+ };
3465
+ }
3466
+
3467
+ // src/simple/config.ts
3468
+ var cachedTagLib = null;
3469
+ var bufferModeEnabled = false;
3470
+ async function getTagLib() {
3471
+ if (!cachedTagLib) {
3472
+ const { TagLib: TagLib2 } = await Promise.resolve().then(() => (init_taglib2(), taglib_exports));
3473
+ const initOptions = bufferModeEnabled ? { forceWasmType: "emscripten" } : void 0;
3474
+ cachedTagLib = await TagLib2.initialize(initOptions);
3475
+ }
3476
+ return cachedTagLib;
3477
+ }
3478
+
3479
+ // src/simple/with-audio-file.ts
3480
+ init_errors2();
3481
+ async function withAudioFile(file, fn, options) {
3482
+ const taglib = await getTagLib();
3483
+ const audioFile = await taglib.open(file, options);
3484
+ try {
3485
+ if (!audioFile.isValid()) {
3486
+ throw new InvalidFormatError(
3487
+ "File may be corrupted or in an unsupported format"
3488
+ );
3489
+ }
3490
+ return await fn(audioFile);
3491
+ } finally {
3492
+ audioFile.dispose();
3493
+ }
3494
+ }
3495
+ async function withAudioFileSave(file, fn) {
3496
+ return withAudioFile(file, (audioFile) => {
3497
+ fn(audioFile);
3498
+ if (!audioFile.save()) {
3499
+ throw new FileOperationError(
3500
+ "save",
3501
+ "Failed to save metadata changes. The file may be read-only or corrupted."
3502
+ );
3503
+ }
3504
+ return audioFile.getFileBuffer();
3505
+ }, { partial: false });
3506
+ }
3507
+
3508
+ // src/simple/picture-operations.ts
3509
+ async function readPictures(file) {
3510
+ return withAudioFile(file, (audioFile) => audioFile.getPictures());
3511
+ }
3512
+ async function applyPictures(file, pictures) {
3513
+ return withAudioFileSave(file, (audioFile) => {
3514
+ audioFile.setPictures(pictures);
3515
+ });
3516
+ }
3517
+
3518
+ // src/web-utils/canvas.ts
3519
+ async function setCoverArtFromCanvas(file, canvas, options = {}) {
3520
+ const {
3521
+ format = "image/jpeg",
3522
+ quality = 0.92,
3523
+ type = "FrontCover",
3524
+ description = "Front Cover"
3525
+ } = options;
3526
+ const dataURL = canvas.toDataURL(format, quality);
3527
+ const picture = dataURLToPicture(dataURL, type, description);
3528
+ return applyPictures(file, [picture]);
3529
+ }
3530
+ async function canvasToPicture(canvas, options = {}) {
3531
+ const {
3532
+ format = "image/jpeg",
3533
+ quality = 0.92,
3534
+ type = "FrontCover",
3535
+ description
3536
+ } = options;
3537
+ return new Promise((resolve, reject) => {
3538
+ canvas.toBlob(
3539
+ (blob) => {
3540
+ if (!blob) {
3541
+ reject(new Error("Failed to convert canvas to blob"));
3542
+ return;
3543
+ }
3544
+ blob.arrayBuffer().then(
3545
+ (arrayBuffer) => {
3546
+ const data = new Uint8Array(arrayBuffer);
3547
+ resolve({
3548
+ mimeType: format,
3549
+ data,
3550
+ type,
3551
+ ...description !== void 0 ? { description } : {}
3552
+ });
3553
+ },
3554
+ reject
3555
+ );
3556
+ },
3557
+ format,
3558
+ quality
3559
+ );
3560
+ });
3561
+ }
3562
+ async function imageFileToPicture(file, type = "FrontCover", description) {
3563
+ const arrayBuffer = await file.arrayBuffer();
3564
+ const data = new Uint8Array(arrayBuffer);
3565
+ return {
3566
+ mimeType: file.type,
3567
+ data,
3568
+ type,
3569
+ description: description ?? file.name
3570
+ };
3571
+ }
3572
+
3573
+ // src/web-utils/dom-integration.ts
3574
+ function ensureArrayBufferBacked(data) {
3575
+ if (data.buffer instanceof ArrayBuffer) {
3576
+ return data;
3577
+ }
3578
+ const copy = new Uint8Array(data.length);
3579
+ copy.set(data);
3580
+ return copy;
3581
+ }
3582
+ function displayPicture(picture, imgElement) {
3583
+ if (imgElement.src.startsWith("blob:")) {
3584
+ URL.revokeObjectURL(imgElement.src);
3585
+ }
3586
+ const data = ensureArrayBufferBacked(picture.data);
3587
+ const blob = new Blob([data], { type: picture.mimeType });
3588
+ const objectURL = URL.createObjectURL(blob);
3589
+ imgElement.src = objectURL;
3590
+ imgElement.addEventListener("load", () => {
3591
+ setTimeout(() => URL.revokeObjectURL(objectURL), 100);
3592
+ }, { once: true });
3593
+ }
3594
+ function createPictureDownloadURL(picture, filename = "cover") {
3595
+ const data = ensureArrayBufferBacked(picture.data);
3596
+ const blob = new Blob([data], { type: picture.mimeType });
3597
+ return URL.createObjectURL(blob);
3598
+ }
3599
+ async function createPictureGallery(file, container, options = {}) {
3600
+ const pictures = await readPictures(file);
3601
+ container.innerHTML = "";
3602
+ pictures.forEach((picture, index) => {
3603
+ const wrapper = document.createElement("div");
3604
+ wrapper.className = options.className ?? "picture-item";
3605
+ const img = document.createElement("img");
3606
+ displayPicture(picture, img);
3607
+ img.alt = picture.description ?? `Picture ${index + 1}`;
3608
+ if (options.onClick) {
3609
+ img.style.cursor = "pointer";
3610
+ img.addEventListener("click", () => options.onClick(picture, index));
3611
+ }
3612
+ wrapper.appendChild(img);
3613
+ if (options.includeDescription && picture.description) {
3614
+ const caption = document.createElement("p");
3615
+ caption.textContent = picture.description;
3616
+ wrapper.appendChild(caption);
3617
+ }
3618
+ container.appendChild(wrapper);
3619
+ });
3620
+ }
3621
+ export {
3622
+ canvasToPicture,
3623
+ createPictureDownloadURL,
3624
+ createPictureGallery,
3625
+ dataURLToPicture,
3626
+ displayPicture,
3627
+ imageFileToPicture,
3628
+ pictureToDataURL,
3629
+ setCoverArtFromCanvas
3630
+ };