taglib-wasm 1.2.1 → 1.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 (42) hide show
  1. package/dist/index.browser.js +232 -102
  2. package/dist/simple.browser.js +232 -102
  3. package/dist/src/constants/complex-properties.d.ts +0 -14
  4. package/dist/src/constants/complex-properties.d.ts.map +1 -1
  5. package/dist/src/errors/base.js +5 -2
  6. package/dist/src/errors/classes.js +13 -10
  7. package/dist/src/msgpack/encoder.d.ts +6 -0
  8. package/dist/src/msgpack/encoder.d.ts.map +1 -1
  9. package/dist/src/msgpack/encoder.js +15 -0
  10. package/dist/src/runtime/unified-loader/types.js +4 -1
  11. package/dist/src/runtime/wasi-adapter/file-handle.d.ts +3 -1
  12. package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
  13. package/dist/src/runtime/wasi-adapter/file-handle.js +41 -4
  14. package/dist/src/runtime/wasi-memory.d.ts +0 -5
  15. package/dist/src/runtime/wasi-memory.d.ts.map +1 -1
  16. package/dist/src/runtime/wasi-memory.js +6 -10
  17. package/dist/src/taglib/audio-file-base.d.ts.map +1 -1
  18. package/dist/src/taglib/audio-file-base.js +13 -1
  19. package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
  20. package/dist/src/taglib/audio-file-impl.js +22 -39
  21. package/dist/src/taglib/embind-adapter.d.ts.map +1 -1
  22. package/dist/src/taglib/embind-adapter.js +18 -0
  23. package/dist/src/taglib/extra-state-registry.d.ts +26 -0
  24. package/dist/src/taglib/extra-state-registry.d.ts.map +1 -0
  25. package/dist/src/taglib/extra-state-registry.js +59 -0
  26. package/dist/src/taglib/mutable-tag.d.ts +7 -0
  27. package/dist/src/taglib/mutable-tag.d.ts.map +1 -1
  28. package/dist/src/taglib/save-reconstruct.d.ts +13 -0
  29. package/dist/src/taglib/save-reconstruct.d.ts.map +1 -0
  30. package/dist/src/taglib/save-reconstruct.js +39 -0
  31. package/dist/src/types/metadata-mappings.d.ts.map +1 -1
  32. package/dist/src/types/metadata-mappings.js +8 -0
  33. package/dist/src/types/tags.d.ts +10 -0
  34. package/dist/src/types/tags.d.ts.map +1 -1
  35. package/dist/src/utils/tag-mapping.d.ts.map +1 -1
  36. package/dist/src/utils/tag-mapping.js +7 -0
  37. package/dist/src/version.d.ts +1 -1
  38. package/dist/src/version.js +1 -1
  39. package/dist/src/wasm.d.ts +14 -0
  40. package/dist/src/wasm.d.ts.map +1 -1
  41. package/dist/taglib-wasi.wasm +0 -0
  42. package/package.json +7 -8
@@ -304,8 +304,8 @@ var init_base = __esm({
304
304
  */
305
305
  constructor(code, message, details) {
306
306
  super(message);
307
- this.code = code;
308
- this.details = details;
307
+ __publicField(this, "code", code);
308
+ __publicField(this, "details", details);
309
309
  this.name = "TagLibError";
310
310
  Object.setPrototypeOf(this, _TagLibError.prototype);
311
311
  }
@@ -365,7 +365,7 @@ var init_classes = __esm({
365
365
  errorDetails.join(". "),
366
366
  { ...details, bufferSize }
367
367
  );
368
- this.bufferSize = bufferSize;
368
+ __publicField(this, "bufferSize", bufferSize);
369
369
  this.name = "InvalidFormatError";
370
370
  Object.setPrototypeOf(this, _InvalidFormatError.prototype);
371
371
  }
@@ -383,8 +383,8 @@ var init_classes = __esm({
383
383
  `Unsupported audio format: ${format}. Supported formats: ${supportedFormats.join(", ")}`,
384
384
  { ...details, format, supportedFormats }
385
385
  );
386
- this.format = format;
387
- this.supportedFormats = supportedFormats;
386
+ __publicField(this, "format", format);
387
+ __publicField(this, "supportedFormats", supportedFormats);
388
388
  this.name = "UnsupportedFormatError";
389
389
  Object.setPrototypeOf(this, _UnsupportedFormatError.prototype);
390
390
  }
@@ -408,8 +408,8 @@ var init_classes = __esm({
408
408
  errorDetails.join(". "),
409
409
  { ...details, operation, path }
410
410
  );
411
- this.operation = operation;
412
- this.path = path;
411
+ __publicField(this, "operation", operation);
412
+ __publicField(this, "path", path);
413
413
  this.name = "FileOperationError";
414
414
  Object.setPrototypeOf(this, _FileOperationError.prototype);
415
415
  }
@@ -433,8 +433,8 @@ var init_classes = __esm({
433
433
  errorDetails.join(". "),
434
434
  { ...details, operation, field }
435
435
  );
436
- this.operation = operation;
437
- this.field = field;
436
+ __publicField(this, "operation", operation);
437
+ __publicField(this, "field", field);
438
438
  this.name = "MetadataError";
439
439
  Object.setPrototypeOf(this, _MetadataError.prototype);
440
440
  }
@@ -465,9 +465,9 @@ var init_classes = __esm({
465
465
  constructor(environment, reason, requiredFeature) {
466
466
  const message = requiredFeature ? `Environment '${environment}' ${reason}. Required feature: ${requiredFeature}.` : `Environment '${environment}' ${reason}.`;
467
467
  super("ENVIRONMENT", message);
468
- this.environment = environment;
469
- this.reason = reason;
470
- this.requiredFeature = requiredFeature;
468
+ __publicField(this, "environment", environment);
469
+ __publicField(this, "reason", reason);
470
+ __publicField(this, "requiredFeature", requiredFeature);
471
471
  this.name = "EnvironmentError";
472
472
  Object.setPrototypeOf(this, _EnvironmentError.prototype);
473
473
  }
@@ -573,60 +573,6 @@ var init_platform_io_browser_stub = __esm({
573
573
  }
574
574
  });
575
575
 
576
- // src/utils/file.ts
577
- async function readFileData(file) {
578
- if (file instanceof Uint8Array) return file;
579
- if (file instanceof ArrayBuffer) return new Uint8Array(file);
580
- if (typeof File !== "undefined" && file instanceof File) {
581
- return new Uint8Array(await file.arrayBuffer());
582
- }
583
- if (typeof file === "string") {
584
- try {
585
- return await getPlatformIO().readFile(file);
586
- } catch (error) {
587
- if (error instanceof EnvironmentError) throw error;
588
- throw new FileOperationError("read", error.message, file);
589
- }
590
- }
591
- const inputType = Object.prototype.toString.call(file);
592
- throw new FileOperationError(
593
- "read",
594
- `Invalid file input type: ${inputType}. Expected string path, Uint8Array, ArrayBuffer, or File object.`
595
- );
596
- }
597
- async function getFileSize(path) {
598
- try {
599
- const s = await getPlatformIO().stat(path);
600
- return s.size;
601
- } catch (error) {
602
- if (error instanceof EnvironmentError) throw error;
603
- throw new FileOperationError("stat", error.message, path);
604
- }
605
- }
606
- async function readPartialFileData(path, headerSize, footerSize) {
607
- const io = getPlatformIO();
608
- if (!io.readPartial) {
609
- throw new EnvironmentError(
610
- "current runtime",
611
- "does not support partial file reading",
612
- "filesystem access with seek support"
613
- );
614
- }
615
- try {
616
- return await io.readPartial(path, headerSize, footerSize);
617
- } catch (error) {
618
- if (error instanceof EnvironmentError) throw error;
619
- throw new FileOperationError("read", error.message, path);
620
- }
621
- }
622
- var init_file = __esm({
623
- "src/utils/file.ts"() {
624
- "use strict";
625
- init_errors2();
626
- init_platform_io_browser_stub();
627
- }
628
- });
629
-
630
576
  // src/utils/write.ts
631
577
  async function writeFileData(path, data) {
632
578
  try {
@@ -1395,7 +1341,7 @@ var init_audio_file_base = __esm({
1395
1341
  init_errors2();
1396
1342
  BaseAudioFileImpl = class {
1397
1343
  constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
1398
- this.module = module;
1344
+ __publicField(this, "module", module);
1399
1345
  __publicField(this, "fileHandle");
1400
1346
  __publicField(this, "cachedAudioProperties", null);
1401
1347
  __publicField(this, "sourcePath");
@@ -1445,6 +1391,9 @@ var init_audio_file_base = __esm({
1445
1391
  get track() {
1446
1392
  return data.track;
1447
1393
  },
1394
+ get date() {
1395
+ return handle.getProperty("DATE") || void 0;
1396
+ },
1448
1397
  setTitle: (value) => {
1449
1398
  handle.setTagData({ title: value });
1450
1399
  data = handle.getTagData();
@@ -1479,6 +1428,15 @@ var init_audio_file_base = __esm({
1479
1428
  handle.setTagData({ track: value });
1480
1429
  data = handle.getTagData();
1481
1430
  return tag;
1431
+ },
1432
+ setDate: (value) => {
1433
+ if (value === "") {
1434
+ handle.setTagData({ year: 0 });
1435
+ } else {
1436
+ handle.setProperty("DATE", value);
1437
+ }
1438
+ data = handle.getTagData();
1439
+ return tag;
1482
1440
  }
1483
1441
  };
1484
1442
  return tag;
@@ -1545,6 +1503,60 @@ var init_audio_file_base = __esm({
1545
1503
  }
1546
1504
  });
1547
1505
 
1506
+ // src/utils/file.ts
1507
+ async function readFileData(file) {
1508
+ if (file instanceof Uint8Array) return file;
1509
+ if (file instanceof ArrayBuffer) return new Uint8Array(file);
1510
+ if (typeof File !== "undefined" && file instanceof File) {
1511
+ return new Uint8Array(await file.arrayBuffer());
1512
+ }
1513
+ if (typeof file === "string") {
1514
+ try {
1515
+ return await getPlatformIO().readFile(file);
1516
+ } catch (error) {
1517
+ if (error instanceof EnvironmentError) throw error;
1518
+ throw new FileOperationError("read", error.message, file);
1519
+ }
1520
+ }
1521
+ const inputType = Object.prototype.toString.call(file);
1522
+ throw new FileOperationError(
1523
+ "read",
1524
+ `Invalid file input type: ${inputType}. Expected string path, Uint8Array, ArrayBuffer, or File object.`
1525
+ );
1526
+ }
1527
+ async function getFileSize(path) {
1528
+ try {
1529
+ const s = await getPlatformIO().stat(path);
1530
+ return s.size;
1531
+ } catch (error) {
1532
+ if (error instanceof EnvironmentError) throw error;
1533
+ throw new FileOperationError("stat", error.message, path);
1534
+ }
1535
+ }
1536
+ async function readPartialFileData(path, headerSize, footerSize) {
1537
+ const io = getPlatformIO();
1538
+ if (!io.readPartial) {
1539
+ throw new EnvironmentError(
1540
+ "current runtime",
1541
+ "does not support partial file reading",
1542
+ "filesystem access with seek support"
1543
+ );
1544
+ }
1545
+ try {
1546
+ return await io.readPartial(path, headerSize, footerSize);
1547
+ } catch (error) {
1548
+ if (error instanceof EnvironmentError) throw error;
1549
+ throw new FileOperationError("read", error.message, path);
1550
+ }
1551
+ }
1552
+ var init_file = __esm({
1553
+ "src/utils/file.ts"() {
1554
+ "use strict";
1555
+ init_errors2();
1556
+ init_platform_io_browser_stub();
1557
+ }
1558
+ });
1559
+
1548
1560
  // src/taglib/embind-adapter.ts
1549
1561
  function isValidBitrateMode(value) {
1550
1562
  return value === "CBR" || value === "VBR" || value === "ABR";
@@ -1591,6 +1603,24 @@ function wrapEmbindHandle(raw) {
1591
1603
  data ?? null
1592
1604
  );
1593
1605
  },
1606
+ // Emscripten exposes USLT only through the PropertyMap "LYRICS" key (no
1607
+ // dedicated Embind binding), so bridge get/setLyrics to get/setProperties.
1608
+ // Text only — language/description are not surfaced by the PropertyMap.
1609
+ getLyrics() {
1610
+ const props = raw.getProperties();
1611
+ return (props["LYRICS"] ?? []).map((text) => ({
1612
+ text,
1613
+ description: "",
1614
+ language: ""
1615
+ }));
1616
+ },
1617
+ setLyrics(lyrics) {
1618
+ const handle = raw;
1619
+ const props = handle.getProperties();
1620
+ if (lyrics.length > 0) props["LYRICS"] = lyrics.map((l) => l.text);
1621
+ else delete props["LYRICS"];
1622
+ handle.setProperties(props);
1623
+ },
1594
1624
  hasId3Tags() {
1595
1625
  const v = raw.hasId3Tags();
1596
1626
  if (!v || typeof v !== "object") return { v1: false, v2: false };
@@ -1640,6 +1670,112 @@ var init_embind_adapter = __esm({
1640
1670
  }
1641
1671
  });
1642
1672
 
1673
+ // src/taglib/extra-state-registry.ts
1674
+ function chapterStyle(chapters) {
1675
+ const s = chapters[0]?.source;
1676
+ return s === "nero" || s === "both" ? s : "quicktime";
1677
+ }
1678
+ function copyExtraState(target, source, sourceComplete) {
1679
+ for (const field of EXTRA_FIELDS) {
1680
+ field.copy(target, source, sourceComplete);
1681
+ }
1682
+ }
1683
+ var EXTRA_FIELDS;
1684
+ var init_extra_state_registry = __esm({
1685
+ "src/taglib/extra-state-registry.ts"() {
1686
+ "use strict";
1687
+ EXTRA_FIELDS = [
1688
+ {
1689
+ name: "pictures",
1690
+ copy(target, source, complete) {
1691
+ const v = source.getPictures();
1692
+ if (complete || v.length > 0) target.setPictures(v);
1693
+ }
1694
+ },
1695
+ {
1696
+ name: "ratings",
1697
+ copy(target, source, complete) {
1698
+ const v = source.getRatings();
1699
+ if (complete || v.length > 0) target.setRatings(v);
1700
+ }
1701
+ },
1702
+ {
1703
+ name: "lyrics",
1704
+ copy(target, source, complete) {
1705
+ const v = source.getLyrics();
1706
+ if (complete || v.length > 0) target.setLyrics(v);
1707
+ }
1708
+ },
1709
+ {
1710
+ name: "chapters",
1711
+ copy(target, source, complete) {
1712
+ const v = source.getChapters();
1713
+ if (complete || v.length > 0) target.setChapters(v, chapterStyle(v));
1714
+ }
1715
+ },
1716
+ {
1717
+ name: "bextData",
1718
+ copy(target, source, complete) {
1719
+ const v = source.getBextData();
1720
+ if (v !== void 0) target.setBextData(v);
1721
+ else if (complete) target.setBextData(null);
1722
+ }
1723
+ },
1724
+ {
1725
+ name: "ixml",
1726
+ copy(target, source, complete) {
1727
+ const v = source.getIxml();
1728
+ if (v !== void 0) target.setIxml(v);
1729
+ else if (complete) target.setIxml(null);
1730
+ }
1731
+ }
1732
+ ];
1733
+ }
1734
+ });
1735
+
1736
+ // src/taglib/save-reconstruct.ts
1737
+ function copyEditedState(target, source, sourceComplete) {
1738
+ target.setTagData(source.getTagData());
1739
+ target.setProperties(
1740
+ sourceComplete ? source.getProperties() : { ...target.getProperties(), ...source.getProperties() }
1741
+ );
1742
+ copyExtraState(target, source, sourceComplete);
1743
+ }
1744
+ async function saveViaFreshHandle(module, editing, source, targetPath, sourceComplete) {
1745
+ const rawFullHandle = module.createFileHandle();
1746
+ const fullFileHandle = module.isWasi ? rawFullHandle : wrapEmbindHandle(rawFullHandle);
1747
+ try {
1748
+ {
1749
+ const data = await readFileData(source);
1750
+ if (!fullFileHandle.loadFromBuffer(data)) {
1751
+ throw new InvalidFormatError(
1752
+ "Failed to load full audio file for saving"
1753
+ );
1754
+ }
1755
+ }
1756
+ copyEditedState(fullFileHandle, editing, sourceComplete);
1757
+ if (!fullFileHandle.save()) {
1758
+ throw new FileOperationError(
1759
+ "save",
1760
+ "Failed to save changes to full file"
1761
+ );
1762
+ }
1763
+ await writeFileData(targetPath, fullFileHandle.getBuffer());
1764
+ } finally {
1765
+ fullFileHandle.destroy();
1766
+ }
1767
+ }
1768
+ var init_save_reconstruct = __esm({
1769
+ "src/taglib/save-reconstruct.ts"() {
1770
+ "use strict";
1771
+ init_errors2();
1772
+ init_file();
1773
+ init_write();
1774
+ init_embind_adapter();
1775
+ init_extra_state_registry();
1776
+ }
1777
+ });
1778
+
1643
1779
  // src/taglib/audio-file-impl.ts
1644
1780
  function sortChapters(list) {
1645
1781
  return [...list].sort((a, b) => a.startTimeMs - b.startTimeMs);
@@ -1669,10 +1805,9 @@ var init_audio_file_impl = __esm({
1669
1805
  init_types2();
1670
1806
  init_audio_file_bwf();
1671
1807
  init_errors2();
1672
- init_file();
1673
1808
  init_write();
1674
1809
  init_audio_file_base();
1675
- init_embind_adapter();
1810
+ init_save_reconstruct();
1676
1811
  AudioFileImpl = class extends BaseAudioFileImpl {
1677
1812
  constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
1678
1813
  super(
@@ -1718,38 +1853,23 @@ var init_audio_file_impl = __esm({
1718
1853
  );
1719
1854
  }
1720
1855
  if (this.isPartiallyLoaded && this.originalSource) {
1721
- const rawFullHandle = this.module.createFileHandle();
1722
- const fullFileHandle = this.module.isWasi ? rawFullHandle : wrapEmbindHandle(rawFullHandle);
1723
- try {
1724
- const success = await (async () => {
1725
- const data = await readFileData(this.originalSource);
1726
- return fullFileHandle.loadFromBuffer(data);
1727
- })();
1728
- if (!success) {
1729
- throw new InvalidFormatError(
1730
- "Failed to load full audio file for saving"
1731
- );
1732
- }
1733
- fullFileHandle.setTagData(this.handle.getTagData());
1734
- fullFileHandle.setProperties(this.handle.getProperties());
1735
- fullFileHandle.setPictures(this.handle.getPictures());
1736
- const bextBytes = this.handle.getBextData();
1737
- if (bextBytes !== void 0) fullFileHandle.setBextData(bextBytes);
1738
- const ixmlStr = this.handle.getIxml();
1739
- if (ixmlStr !== void 0) fullFileHandle.setIxml(ixmlStr);
1740
- if (!fullFileHandle.save()) {
1741
- throw new FileOperationError(
1742
- "save",
1743
- "Failed to save changes to full file"
1744
- );
1745
- }
1746
- const buffer = fullFileHandle.getBuffer();
1747
- await writeFileData(targetPath, buffer);
1748
- } finally {
1749
- fullFileHandle.destroy();
1750
- }
1856
+ await saveViaFreshHandle(
1857
+ this.module,
1858
+ this.handle,
1859
+ this.originalSource,
1860
+ targetPath,
1861
+ false
1862
+ );
1751
1863
  this.isPartiallyLoaded = false;
1752
1864
  this.originalSource = void 0;
1865
+ } else if (this.module.isWasi && this.sourcePath && targetPath !== this.sourcePath) {
1866
+ await saveViaFreshHandle(
1867
+ this.module,
1868
+ this.handle,
1869
+ this.sourcePath,
1870
+ targetPath,
1871
+ true
1872
+ );
1753
1873
  } else {
1754
1874
  if (!this.save()) {
1755
1875
  throw new FileOperationError(
@@ -1811,13 +1931,16 @@ var init_audio_file_impl = __esm({
1811
1931
  }
1812
1932
  const sorted = sortChapters(chapters);
1813
1933
  const trackEndMs = this.audioProperties()?.durationMs;
1934
+ const style = options?.mp4ChapterStyle ?? "quicktime";
1935
+ const source = fmt === "MP4" ? style : "id3";
1814
1936
  const raw = sorted.map((c, i) => ({
1815
1937
  id: c.id,
1816
1938
  startTimeMs: c.startTimeMs,
1817
1939
  endTimeMs: inferEndTimeMs(sorted, i, trackEndMs) ?? c.startTimeMs,
1818
- title: c.title
1940
+ title: c.title,
1941
+ source
1819
1942
  }));
1820
- this.handle.setChapters(raw, options?.mp4ChapterStyle ?? "quicktime");
1943
+ this.handle.setChapters(raw, style);
1821
1944
  }
1822
1945
  getBext() {
1823
1946
  return getBext(this.handle);
@@ -1923,6 +2046,9 @@ function mapPropertiesToExtendedTag(props) {
1923
2046
  if (tagField === "year" || tagField === "track") {
1924
2047
  const num = parseNumeric(values[0]);
1925
2048
  if (num !== void 0) tag[tagField] = num;
2049
+ if (propKey === "date") {
2050
+ tag.date = values.length === 1 ? values[0] : values;
2051
+ }
1926
2052
  } else {
1927
2053
  tag[tagField] = values;
1928
2054
  }
@@ -1963,6 +2089,9 @@ function normalizeTagInput(input) {
1963
2089
  if (input.year !== void 0) {
1964
2090
  props.date = [String(input.year)];
1965
2091
  }
2092
+ if (input.date !== void 0) {
2093
+ props.date = Array.isArray(input.date) ? input.date : [input.date];
2094
+ }
1966
2095
  if (input.track !== void 0) {
1967
2096
  props.trackNumber = [String(input.track)];
1968
2097
  }
@@ -2001,6 +2130,7 @@ var init_tag_mapping = __esm({
2001
2130
  "comment",
2002
2131
  "genre",
2003
2132
  "year",
2133
+ "date",
2004
2134
  "track"
2005
2135
  ]);
2006
2136
  NUMERIC_FIELDS = /* @__PURE__ */ new Set([
@@ -2019,7 +2149,7 @@ var VERSION;
2019
2149
  var init_version = __esm({
2020
2150
  "src/version.ts"() {
2021
2151
  "use strict";
2022
- VERSION = "1.2.1";
2152
+ VERSION = "1.3.0";
2023
2153
  }
2024
2154
  });
2025
2155