taglib-wasm 1.1.1 → 1.2.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 +512 -38
  2. package/dist/index.browser.js +366 -56
  3. package/dist/index.d.ts +2 -1
  4. package/dist/index.d.ts.map +1 -1
  5. package/dist/index.js +2 -0
  6. package/dist/simple.browser.js +366 -56
  7. package/dist/src/bwf/bext.d.ts +28 -0
  8. package/dist/src/bwf/bext.d.ts.map +1 -0
  9. package/dist/src/bwf/bext.js +126 -0
  10. package/dist/src/msgpack/encoder.d.ts.map +1 -1
  11. package/dist/src/msgpack/encoder.js +9 -1
  12. package/dist/src/runtime/unified-loader/module-loading.js +1 -1
  13. package/dist/src/runtime/wasi-adapter/adapter.d.ts +0 -2
  14. package/dist/src/runtime/wasi-adapter/adapter.d.ts.map +1 -1
  15. package/dist/src/runtime/wasi-adapter/adapter.js +0 -12
  16. package/dist/src/runtime/wasi-adapter/file-handle.d.ts +12 -4
  17. package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
  18. package/dist/src/runtime/wasi-adapter/file-handle.js +73 -54
  19. package/dist/src/runtime/wasi-host-loader.d.ts +1 -1
  20. package/dist/src/runtime/wasi-host-loader.js +1 -1
  21. package/dist/src/taglib/audio-file-base.d.ts.map +1 -1
  22. package/dist/src/taglib/audio-file-base.js +25 -42
  23. package/dist/src/taglib/audio-file-bwf.d.ts +14 -0
  24. package/dist/src/taglib/audio-file-bwf.d.ts.map +1 -0
  25. package/dist/src/taglib/audio-file-bwf.js +41 -0
  26. package/dist/src/taglib/audio-file-impl.d.ts +10 -0
  27. package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
  28. package/dist/src/taglib/audio-file-impl.js +69 -13
  29. package/dist/src/taglib/audio-file-interface.d.ts +27 -0
  30. package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
  31. package/dist/src/taglib/embind-adapter.d.ts +53 -0
  32. package/dist/src/taglib/embind-adapter.d.ts.map +1 -0
  33. package/dist/src/taglib/embind-adapter.js +82 -0
  34. package/dist/src/taglib/taglib-class.d.ts.map +1 -1
  35. package/dist/src/taglib/taglib-class.js +3 -1
  36. package/dist/src/types/audio-formats.d.ts +20 -0
  37. package/dist/src/types/audio-formats.d.ts.map +1 -1
  38. package/dist/src/types/bwf.d.ts +43 -0
  39. package/dist/src/types/bwf.d.ts.map +1 -0
  40. package/dist/src/types/bwf.js +0 -0
  41. package/dist/src/types/chapters.d.ts +47 -0
  42. package/dist/src/types/chapters.d.ts.map +1 -0
  43. package/dist/src/types/chapters.js +0 -0
  44. package/dist/src/types/index.d.ts +2 -0
  45. package/dist/src/types/index.d.ts.map +1 -1
  46. package/dist/src/types/index.js +2 -0
  47. package/dist/src/types/metadata-mappings.d.ts +1 -1
  48. package/dist/src/types/metadata-mappings.d.ts.map +1 -1
  49. package/dist/src/types/tags.d.ts +25 -7
  50. package/dist/src/types/tags.d.ts.map +1 -1
  51. package/dist/src/version.d.ts +1 -1
  52. package/dist/src/version.js +1 -1
  53. package/dist/src/wasm.d.ts +17 -39
  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/taglib-wrapper.d.ts +0 -2
  58. package/dist/taglib-wrapper.js +1 -1
  59. package/package.json +5 -5
  60. package/dist/taglib_wasi.wasm +0 -0
@@ -88,6 +88,20 @@ var init_pictures = __esm({
88
88
  }
89
89
  });
90
90
 
91
+ // src/types/chapters.ts
92
+ var init_chapters = __esm({
93
+ "src/types/chapters.ts"() {
94
+ "use strict";
95
+ }
96
+ });
97
+
98
+ // src/types/bwf.ts
99
+ var init_bwf = __esm({
100
+ "src/types/bwf.ts"() {
101
+ "use strict";
102
+ }
103
+ });
104
+
91
105
  // src/types/config.ts
92
106
  var init_config = __esm({
93
107
  "src/types/config.ts"() {
@@ -110,6 +124,8 @@ var init_types = __esm({
110
124
  init_tags();
111
125
  init_metadata_mappings();
112
126
  init_pictures();
127
+ init_chapters();
128
+ init_bwf();
113
129
  init_config();
114
130
  init_format_property_keys();
115
131
  }
@@ -123,6 +139,136 @@ var init_types2 = __esm({
123
139
  }
124
140
  });
125
141
 
142
+ // src/bwf/bext.ts
143
+ function readFixedString(bytes, offset, len) {
144
+ let end = offset;
145
+ const max = Math.min(offset + len, bytes.length);
146
+ while (end < max && bytes[end] !== 0) end++;
147
+ let s = "";
148
+ for (let i = offset; i < end; i++) s += String.fromCharCode(bytes[i]);
149
+ return s;
150
+ }
151
+ function writeFixedString(bytes, offset, len, value) {
152
+ for (let i = 0; i < len; i++) {
153
+ bytes[offset + i] = i < value.length ? value.charCodeAt(i) & 255 : 0;
154
+ }
155
+ }
156
+ function clampInt16(n) {
157
+ return Math.max(-32768, Math.min(32767, Math.round(n)));
158
+ }
159
+ function loudnessRaw(v) {
160
+ return v === void 0 ? LOUDNESS_UNSET : clampInt16(v * 100);
161
+ }
162
+ function loudnessFromRaw(raw) {
163
+ return raw === LOUDNESS_UNSET ? void 0 : raw / 100;
164
+ }
165
+ function bytesToHex(bytes) {
166
+ let s = "";
167
+ for (const b of bytes) s += b.toString(16).padStart(2, "0");
168
+ return s;
169
+ }
170
+ function hexToBytes(hex, outLen) {
171
+ const clean = hex.replace(/[^0-9a-fA-F]/g, "");
172
+ const out = new Uint8Array(outLen);
173
+ for (let i = 0; i + 1 < clean.length + 1 && i / 2 < outLen; i += 2) {
174
+ const pair = clean.slice(i, i + 2);
175
+ if (pair.length === 2) out[i / 2] = parseInt(pair, 16);
176
+ }
177
+ return out;
178
+ }
179
+ function decodeBext(bytes) {
180
+ if (bytes.length < VERSION_OFFSET + 2) return void 0;
181
+ const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
182
+ const timeRefLow = dv.getUint32(338, true);
183
+ const timeRefHigh = dv.getUint32(342, true);
184
+ const version = dv.getUint16(VERSION_OFFSET, true);
185
+ const result = {
186
+ description: readFixedString(bytes, 0, 256),
187
+ originator: readFixedString(bytes, 256, 32),
188
+ originatorReference: readFixedString(bytes, 288, 32),
189
+ originationDate: readFixedString(bytes, 320, 10),
190
+ originationTime: readFixedString(bytes, 330, 8),
191
+ timeReferenceSamples: BigInt(timeRefHigh) << 32n | BigInt(timeRefLow),
192
+ version,
193
+ codingHistory: bytes.length > FIXED_PREFIX_LEN ? readFixedString(
194
+ bytes,
195
+ FIXED_PREFIX_LEN,
196
+ bytes.length - FIXED_PREFIX_LEN
197
+ ) : ""
198
+ };
199
+ if (version >= 1 && bytes.length >= UMID_OFFSET + UMID_LEN) {
200
+ result.umid = bytesToHex(
201
+ bytes.subarray(UMID_OFFSET, UMID_OFFSET + UMID_LEN)
202
+ );
203
+ }
204
+ if (version >= 2 && bytes.length >= LOUDNESS_OFFSET + 10) {
205
+ const lv = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET, true));
206
+ const lr = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 2, true));
207
+ const mtp = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 4, true));
208
+ const mml = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 6, true));
209
+ const msl = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 8, true));
210
+ if (lv !== void 0) result.loudnessValueDb = lv;
211
+ if (lr !== void 0) result.loudnessRangeDb = lr;
212
+ if (mtp !== void 0) result.maxTruePeakLevelDbtp = mtp;
213
+ if (mml !== void 0) result.maxMomentaryLoudnessDb = mml;
214
+ if (msl !== void 0) result.maxShortTermLoudnessDb = msl;
215
+ }
216
+ return result;
217
+ }
218
+ function encodeBext(b) {
219
+ const codingHistory = b.codingHistory ?? "";
220
+ const bytes = new Uint8Array(FIXED_PREFIX_LEN + codingHistory.length);
221
+ const dv = new DataView(bytes.buffer);
222
+ const hasLoudness = b.loudnessValueDb !== void 0 || b.loudnessRangeDb !== void 0 || b.maxTruePeakLevelDbtp !== void 0 || b.maxMomentaryLoudnessDb !== void 0 || b.maxShortTermLoudnessDb !== void 0;
223
+ const version = Number.isInteger(b.version) ? b.version : hasLoudness ? 2 : b.umid ? 1 : 0;
224
+ writeFixedString(bytes, 0, 256, b.description ?? "");
225
+ writeFixedString(bytes, 256, 32, b.originator ?? "");
226
+ writeFixedString(bytes, 288, 32, b.originatorReference ?? "");
227
+ writeFixedString(bytes, 320, 10, b.originationDate ?? "");
228
+ writeFixedString(bytes, 330, 8, b.originationTime ?? "");
229
+ const tr = b.timeReferenceSamples ?? 0n;
230
+ dv.setUint32(338, Number(tr & 0xffffffffn), true);
231
+ dv.setUint32(342, Number(tr >> 32n & 0xffffffffn), true);
232
+ dv.setUint16(VERSION_OFFSET, version, true);
233
+ if (version >= 1 && b.umid) {
234
+ bytes.set(hexToBytes(b.umid, UMID_LEN), UMID_OFFSET);
235
+ }
236
+ if (version >= 2) {
237
+ dv.setInt16(LOUDNESS_OFFSET, loudnessRaw(b.loudnessValueDb), true);
238
+ dv.setInt16(LOUDNESS_OFFSET + 2, loudnessRaw(b.loudnessRangeDb), true);
239
+ dv.setInt16(LOUDNESS_OFFSET + 4, loudnessRaw(b.maxTruePeakLevelDbtp), true);
240
+ dv.setInt16(
241
+ LOUDNESS_OFFSET + 6,
242
+ loudnessRaw(b.maxMomentaryLoudnessDb),
243
+ true
244
+ );
245
+ dv.setInt16(
246
+ LOUDNESS_OFFSET + 8,
247
+ loudnessRaw(b.maxShortTermLoudnessDb),
248
+ true
249
+ );
250
+ }
251
+ writeFixedString(
252
+ bytes,
253
+ FIXED_PREFIX_LEN,
254
+ codingHistory.length,
255
+ codingHistory
256
+ );
257
+ return bytes;
258
+ }
259
+ var FIXED_PREFIX_LEN, VERSION_OFFSET, UMID_OFFSET, UMID_LEN, LOUDNESS_OFFSET, LOUDNESS_UNSET;
260
+ var init_bext = __esm({
261
+ "src/bwf/bext.ts"() {
262
+ "use strict";
263
+ FIXED_PREFIX_LEN = 602;
264
+ VERSION_OFFSET = 346;
265
+ UMID_OFFSET = 348;
266
+ UMID_LEN = 64;
267
+ LOUDNESS_OFFSET = 412;
268
+ LOUDNESS_UNSET = 32767;
269
+ }
270
+ });
271
+
126
272
  // src/errors/base.ts
127
273
  var SUPPORTED_FORMATS, TagLibError;
128
274
  var init_base = __esm({
@@ -365,6 +511,47 @@ var init_errors2 = __esm({
365
511
  }
366
512
  });
367
513
 
514
+ // src/taglib/audio-file-bwf.ts
515
+ function requireBwf(format) {
516
+ if (!BWF_FORMATS.has(format)) {
517
+ throw new UnsupportedFormatError(format, ["WAV", "FLAC"], {
518
+ operation: "BWF metadata"
519
+ });
520
+ }
521
+ }
522
+ function getBextData(handle) {
523
+ const raw = handle.getBextData();
524
+ return raw && raw.length > 0 ? raw : void 0;
525
+ }
526
+ function setBextData(handle, format, data) {
527
+ requireBwf(format);
528
+ handle.setBextData(data);
529
+ }
530
+ function getBext(handle) {
531
+ const raw = handle.getBextData();
532
+ return raw && raw.length > 0 ? decodeBext(raw) : void 0;
533
+ }
534
+ function setBext(handle, format, bext) {
535
+ requireBwf(format);
536
+ handle.setBextData(encodeBext(bext));
537
+ }
538
+ function getIxml(handle) {
539
+ return handle.getIxml();
540
+ }
541
+ function setIxml(handle, format, data) {
542
+ requireBwf(format);
543
+ handle.setIxml(data);
544
+ }
545
+ var BWF_FORMATS;
546
+ var init_audio_file_bwf = __esm({
547
+ "src/taglib/audio-file-bwf.ts"() {
548
+ "use strict";
549
+ init_bext();
550
+ init_errors2();
551
+ BWF_FORMATS = /* @__PURE__ */ new Set(["WAV", "FLAC"]);
552
+ }
553
+ });
554
+
368
555
  // browser-stub:platform-io-browser-stub
369
556
  function getPlatformIO() {
370
557
  throw new Error("Filesystem operations are not available in the browser.");
@@ -1222,61 +1409,63 @@ var init_audio_file_base = __esm({
1222
1409
  return this.getFormat() === format;
1223
1410
  }
1224
1411
  tag() {
1225
- const tagWrapper = this.handle.getTag();
1226
- if (!tagWrapper) {
1227
- throw new MetadataError(
1228
- "read",
1229
- "Tag may be corrupted or format not fully supported"
1230
- );
1231
- }
1412
+ const handle = this.handle;
1413
+ let data = handle.getTagData();
1232
1414
  const tag = {
1233
1415
  get title() {
1234
- return tagWrapper.title();
1416
+ return data.title;
1235
1417
  },
1236
1418
  get artist() {
1237
- return tagWrapper.artist();
1419
+ return data.artist;
1238
1420
  },
1239
1421
  get album() {
1240
- return tagWrapper.album();
1422
+ return data.album;
1241
1423
  },
1242
1424
  get comment() {
1243
- return tagWrapper.comment();
1425
+ return data.comment;
1244
1426
  },
1245
1427
  get genre() {
1246
- return tagWrapper.genre();
1428
+ return data.genre;
1247
1429
  },
1248
1430
  get year() {
1249
- return tagWrapper.year();
1431
+ return data.year;
1250
1432
  },
1251
1433
  get track() {
1252
- return tagWrapper.track();
1434
+ return data.track;
1253
1435
  },
1254
1436
  setTitle: (value) => {
1255
- tagWrapper.setTitle(value);
1437
+ handle.setTagData({ title: value });
1438
+ data = handle.getTagData();
1256
1439
  return tag;
1257
1440
  },
1258
1441
  setArtist: (value) => {
1259
- tagWrapper.setArtist(value);
1442
+ handle.setTagData({ artist: value });
1443
+ data = handle.getTagData();
1260
1444
  return tag;
1261
1445
  },
1262
1446
  setAlbum: (value) => {
1263
- tagWrapper.setAlbum(value);
1447
+ handle.setTagData({ album: value });
1448
+ data = handle.getTagData();
1264
1449
  return tag;
1265
1450
  },
1266
1451
  setComment: (value) => {
1267
- tagWrapper.setComment(value);
1452
+ handle.setTagData({ comment: value });
1453
+ data = handle.getTagData();
1268
1454
  return tag;
1269
1455
  },
1270
1456
  setGenre: (value) => {
1271
- tagWrapper.setGenre(value);
1457
+ handle.setTagData({ genre: value });
1458
+ data = handle.getTagData();
1272
1459
  return tag;
1273
1460
  },
1274
1461
  setYear: (value) => {
1275
- tagWrapper.setYear(value);
1462
+ handle.setTagData({ year: value });
1463
+ data = handle.getTagData();
1276
1464
  return tag;
1277
1465
  },
1278
1466
  setTrack: (value) => {
1279
- tagWrapper.setTrack(value);
1467
+ handle.setTagData({ track: value });
1468
+ data = handle.getTagData();
1280
1469
  return tag;
1281
1470
  }
1282
1471
  };
@@ -1284,28 +1473,9 @@ var init_audio_file_base = __esm({
1284
1473
  }
1285
1474
  audioProperties() {
1286
1475
  if (!this.cachedAudioProperties) {
1287
- const propsWrapper = this.handle.getAudioProperties();
1288
- if (!propsWrapper) {
1289
- return void 0;
1290
- }
1291
- const containerFormat = propsWrapper.containerFormat() || "unknown";
1292
- const mpegVersion = propsWrapper.mpegVersion();
1293
- const formatVersion = propsWrapper.formatVersion();
1294
- this.cachedAudioProperties = {
1295
- duration: propsWrapper.lengthInSeconds(),
1296
- bitrate: propsWrapper.bitrate(),
1297
- sampleRate: propsWrapper.sampleRate(),
1298
- channels: propsWrapper.channels(),
1299
- bitsPerSample: propsWrapper.bitsPerSample(),
1300
- codec: propsWrapper.codec() || "unknown",
1301
- containerFormat,
1302
- isLossless: propsWrapper.isLossless(),
1303
- ...mpegVersion > 0 ? { mpegVersion, mpegLayer: propsWrapper.mpegLayer() } : {},
1304
- ...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: propsWrapper.isEncrypted() } : {},
1305
- ...formatVersion > 0 ? { formatVersion } : {}
1306
- };
1476
+ this.cachedAudioProperties = this.handle.getAudioProperties() ?? null;
1307
1477
  }
1308
- return this.cachedAudioProperties;
1478
+ return this.cachedAudioProperties ?? void 0;
1309
1479
  }
1310
1480
  properties() {
1311
1481
  return remapKeysFromTagLib(this.handle.getProperties());
@@ -1363,7 +1533,102 @@ var init_audio_file_base = __esm({
1363
1533
  }
1364
1534
  });
1365
1535
 
1536
+ // src/taglib/embind-adapter.ts
1537
+ function isValidBitrateMode(value) {
1538
+ return value === "CBR" || value === "VBR" || value === "ABR";
1539
+ }
1540
+ function wrapEmbindHandle(raw) {
1541
+ const overrides = {
1542
+ getTagData() {
1543
+ const tw = raw.getTag();
1544
+ return {
1545
+ title: tw.title(),
1546
+ artist: tw.artist(),
1547
+ album: tw.album(),
1548
+ comment: tw.comment(),
1549
+ genre: tw.genre(),
1550
+ year: tw.year(),
1551
+ track: tw.track()
1552
+ };
1553
+ },
1554
+ setTagData(data) {
1555
+ const tw = raw.getTag();
1556
+ if (data.title !== void 0) tw.setTitle(data.title);
1557
+ if (data.artist !== void 0) tw.setArtist(data.artist);
1558
+ if (data.album !== void 0) tw.setAlbum(data.album);
1559
+ if (data.comment !== void 0) tw.setComment(data.comment);
1560
+ if (data.genre !== void 0) tw.setGenre(data.genre);
1561
+ if (data.year !== void 0) tw.setYear(data.year);
1562
+ if (data.track !== void 0) tw.setTrack(data.track);
1563
+ },
1564
+ getBextData() {
1565
+ const v = raw.getBextData();
1566
+ if (v === void 0 || v === null) return void 0;
1567
+ const u8 = v instanceof Uint8Array ? v : new Uint8Array(v);
1568
+ return u8.length > 0 ? u8 : void 0;
1569
+ },
1570
+ setBextData(data) {
1571
+ raw.setBextData(data ?? null);
1572
+ },
1573
+ getIxml() {
1574
+ const v = raw.getIxml();
1575
+ return typeof v === "string" && v.length > 0 ? v : void 0;
1576
+ },
1577
+ setIxml(data) {
1578
+ raw.setIxml(
1579
+ data ?? null
1580
+ );
1581
+ },
1582
+ getAudioProperties() {
1583
+ const pw = raw.getAudioProperties();
1584
+ if (!pw) return null;
1585
+ const containerFormat = pw.containerFormat() || "unknown";
1586
+ const codec = pw.codec() || "unknown";
1587
+ const mpegVersion = pw.mpegVersion();
1588
+ const formatVersion = pw.formatVersion();
1589
+ const bitrateMode = pw.bitrateMode();
1590
+ return {
1591
+ duration: pw.lengthInSeconds(),
1592
+ durationMs: pw.lengthInMilliseconds(),
1593
+ bitrate: pw.bitrate(),
1594
+ sampleRate: pw.sampleRate(),
1595
+ channels: pw.channels(),
1596
+ bitsPerSample: pw.bitsPerSample(),
1597
+ codec,
1598
+ containerFormat,
1599
+ isLossless: pw.isLossless(),
1600
+ ...mpegVersion > 0 ? { mpegVersion, mpegLayer: pw.mpegLayer() } : {},
1601
+ ...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: pw.isEncrypted() } : {},
1602
+ ...formatVersion > 0 ? { formatVersion } : {},
1603
+ ...isValidBitrateMode(bitrateMode) ? { bitrateMode } : {},
1604
+ ...codec === "Opus" ? { outputGainDb: pw.outputGainDb() } : {}
1605
+ };
1606
+ }
1607
+ };
1608
+ return new Proxy(raw, {
1609
+ get(target, prop, receiver) {
1610
+ if (prop in overrides) return overrides[prop];
1611
+ const value = Reflect.get(target, prop, receiver);
1612
+ return typeof value === "function" ? value.bind(target) : value;
1613
+ }
1614
+ });
1615
+ }
1616
+ var init_embind_adapter = __esm({
1617
+ "src/taglib/embind-adapter.ts"() {
1618
+ "use strict";
1619
+ }
1620
+ });
1621
+
1366
1622
  // src/taglib/audio-file-impl.ts
1623
+ function sortChapters(list) {
1624
+ return [...list].sort((a, b) => a.startTimeMs - b.startTimeMs);
1625
+ }
1626
+ function inferEndTimeMs(sorted, index, trackEndMs) {
1627
+ const own = sorted[index].endTimeMs;
1628
+ if (own !== void 0) return own;
1629
+ const next = sorted[index + 1];
1630
+ return next ? next.startTimeMs : trackEndMs;
1631
+ }
1367
1632
  function readFileSync(path) {
1368
1633
  if (typeof Deno !== "undefined") return Deno.readFileSync(path);
1369
1634
  if (_nodeFs === void 0) {
@@ -1381,10 +1646,12 @@ var init_audio_file_impl = __esm({
1381
1646
  "src/taglib/audio-file-impl.ts"() {
1382
1647
  "use strict";
1383
1648
  init_types2();
1649
+ init_audio_file_bwf();
1384
1650
  init_errors2();
1385
1651
  init_file();
1386
1652
  init_write();
1387
1653
  init_audio_file_base();
1654
+ init_embind_adapter();
1388
1655
  AudioFileImpl = class extends BaseAudioFileImpl {
1389
1656
  constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
1390
1657
  super(
@@ -1430,7 +1697,8 @@ var init_audio_file_impl = __esm({
1430
1697
  );
1431
1698
  }
1432
1699
  if (this.isPartiallyLoaded && this.originalSource) {
1433
- const fullFileHandle = this.module.createFileHandle();
1700
+ const rawFullHandle = this.module.createFileHandle();
1701
+ const fullFileHandle = this.module.isWasi ? rawFullHandle : wrapEmbindHandle(rawFullHandle);
1434
1702
  try {
1435
1703
  const success = await (async () => {
1436
1704
  const data = await readFileData(this.originalSource);
@@ -1441,19 +1709,13 @@ var init_audio_file_impl = __esm({
1441
1709
  "Failed to load full audio file for saving"
1442
1710
  );
1443
1711
  }
1444
- const partialTag = this.handle.getTag();
1445
- const fullTag = fullFileHandle.getTag();
1446
- if (partialTag && fullTag) {
1447
- fullTag.setTitle(partialTag.title());
1448
- fullTag.setArtist(partialTag.artist());
1449
- fullTag.setAlbum(partialTag.album());
1450
- fullTag.setComment(partialTag.comment());
1451
- fullTag.setGenre(partialTag.genre());
1452
- fullTag.setYear(partialTag.year());
1453
- fullTag.setTrack(partialTag.track());
1454
- }
1712
+ fullFileHandle.setTagData(this.handle.getTagData());
1455
1713
  fullFileHandle.setProperties(this.handle.getProperties());
1456
1714
  fullFileHandle.setPictures(this.handle.getPictures());
1715
+ const bextBytes = this.handle.getBextData();
1716
+ if (bextBytes !== void 0) fullFileHandle.setBextData(bextBytes);
1717
+ const ixmlStr = this.handle.getIxml();
1718
+ if (ixmlStr !== void 0) fullFileHandle.setIxml(ixmlStr);
1457
1719
  if (!fullFileHandle.save()) {
1458
1720
  throw new FileOperationError(
1459
1721
  "save",
@@ -1508,6 +1770,52 @@ var init_audio_file_impl = __esm({
1508
1770
  removePictures() {
1509
1771
  this.handle.removePictures();
1510
1772
  }
1773
+ getChapters() {
1774
+ const sorted = sortChapters(this.handle.getChapters());
1775
+ const trackEndMs = this.audioProperties()?.durationMs;
1776
+ return sorted.map((c, i) => ({
1777
+ startTimeMs: c.startTimeMs,
1778
+ endTimeMs: inferEndTimeMs(sorted, i, trackEndMs),
1779
+ title: c.title || void 0,
1780
+ id: c.id || void 0,
1781
+ source: c.source
1782
+ }));
1783
+ }
1784
+ setChapters(chapters, options) {
1785
+ const fmt = this.getFormat();
1786
+ if (fmt !== "MP3" && fmt !== "MP4") {
1787
+ throw new UnsupportedFormatError(fmt, ["MP3", "MP4"], {
1788
+ operation: "setChapters"
1789
+ });
1790
+ }
1791
+ const sorted = sortChapters(chapters);
1792
+ const trackEndMs = this.audioProperties()?.durationMs;
1793
+ const raw = sorted.map((c, i) => ({
1794
+ id: c.id,
1795
+ startTimeMs: c.startTimeMs,
1796
+ endTimeMs: inferEndTimeMs(sorted, i, trackEndMs) ?? c.startTimeMs,
1797
+ title: c.title
1798
+ }));
1799
+ this.handle.setChapters(raw, options?.mp4ChapterStyle ?? "quicktime");
1800
+ }
1801
+ getBext() {
1802
+ return getBext(this.handle);
1803
+ }
1804
+ setBext(bext) {
1805
+ setBext(this.handle, this.getFormat(), bext);
1806
+ }
1807
+ getBextData() {
1808
+ return getBextData(this.handle);
1809
+ }
1810
+ setBextData(data) {
1811
+ setBextData(this.handle, this.getFormat(), data);
1812
+ }
1813
+ getIxml() {
1814
+ return getIxml(this.handle);
1815
+ }
1816
+ setIxml(data) {
1817
+ setIxml(this.handle, this.getFormat(), data);
1818
+ }
1511
1819
  getRatings() {
1512
1820
  return this.handle.getRatings().map(
1513
1821
  (r) => ({
@@ -1681,7 +1989,7 @@ var VERSION;
1681
1989
  var init_version = __esm({
1682
1990
  "src/version.ts"() {
1683
1991
  "use strict";
1684
- VERSION = "1.1.1";
1992
+ VERSION = "1.2.0";
1685
1993
  }
1686
1994
  });
1687
1995
 
@@ -1768,6 +2076,7 @@ var init_taglib_class = __esm({
1768
2076
  init_tag_mapping();
1769
2077
  init_errors2();
1770
2078
  init_version();
2079
+ init_embind_adapter();
1771
2080
  TagLib = class _TagLib {
1772
2081
  constructor(module) {
1773
2082
  __publicField(this, "module");
@@ -1843,7 +2152,8 @@ var init_taglib_class = __esm({
1843
2152
  audioData.byteOffset + audioData.byteLength
1844
2153
  )
1845
2154
  ) : audioData;
1846
- const fileHandle = this.module.createFileHandle();
2155
+ const rawHandle = this.module.createFileHandle();
2156
+ const fileHandle = this.module.isWasi ? rawHandle : wrapEmbindHandle(rawHandle);
1847
2157
  try {
1848
2158
  const success = fileHandle.loadFromBuffer(uint8Array);
1849
2159
  if (!success) {
@@ -0,0 +1,28 @@
1
+ /**
2
+ * @fileoverview EBU Tech 3285 BWF `bext` chunk codec. Pure functions — no Wasm
3
+ * or TagLib dependency. The fixed prefix is 602 bytes for all versions; the
4
+ * `version` field selects which optional fields are meaningful (0 = none,
5
+ * 1 = UMID, 2 = UMID + loudness). Fixed-width string fields are NUL-padded
6
+ * ASCII; `codingHistory` is exposed verbatim (the spec uses CR/LF lines) and
7
+ * is not promised to preserve embedded NUL bytes. Loudness fields use the EBU
8
+ * "value not set" sentinel `0x7FFF`: a sentinel decodes to `undefined`, and an
9
+ * `undefined` loudness field on a v2 encode is written as the sentinel.
10
+ */
11
+ import type { BroadcastAudioExtension } from "../types/bwf.js";
12
+ /**
13
+ * Parse a raw `bext` chunk. Returns `undefined` only if `bytes` is shorter than
14
+ * 348 (cannot even read the `Version` field). Optional fields are populated per
15
+ * the chunk's declared version and the available length, so compact/legacy v0
16
+ * chunks parse into a partial struct.
17
+ */
18
+ export declare function decodeBext(bytes: Uint8Array): BroadcastAudioExtension | undefined;
19
+ /**
20
+ * Serialize a `bext` chunk. The result is always `602 + codingHistory.length`
21
+ * bytes. `version` defaults to 2 when any loudness field is present (else 1 if
22
+ * `umid` is set, else 0). Over-long string fields are truncated to their widths;
23
+ * loudness values are clamped to the Int16 range after ×100, with `undefined`
24
+ * written as the EBU `0x7FFF` "not set" sentinel; UMID is written only for
25
+ * version ≥ 1.
26
+ */
27
+ export declare function encodeBext(b: BroadcastAudioExtension): Uint8Array;
28
+ //# sourceMappingURL=bext.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bext.d.ts","sourceRoot":"","sources":["../../../src/bwf/bext.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AA6D/D;;;;;GAKG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,GAChB,uBAAuB,GAAG,SAAS,CAwCrC;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,uBAAuB,GAAG,UAAU,CA6CjE"}