taglib-wasm 1.5.3 → 1.6.1

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 (52) hide show
  1. package/LICENSE +12 -9
  2. package/README.md +6 -3
  3. package/dist/index.browser.js +402 -90
  4. package/dist/simple.browser.js +400 -89
  5. package/dist/src/constants/complex-properties.d.ts +18 -12
  6. package/dist/src/constants/complex-properties.d.ts.map +1 -1
  7. package/dist/src/constants/properties.d.ts +24 -0
  8. package/dist/src/constants/properties.d.ts.map +1 -1
  9. package/dist/src/constants/properties.js +34 -0
  10. package/dist/src/constants/specialized-properties.d.ts +23 -0
  11. package/dist/src/constants/specialized-properties.d.ts.map +1 -1
  12. package/dist/src/constants/specialized-properties.js +20 -0
  13. package/dist/src/constants/tags.d.ts.map +1 -1
  14. package/dist/src/constants/tags.js +2 -1
  15. package/dist/src/msgpack/encoder.d.ts.map +1 -1
  16. package/dist/src/msgpack/encoder.js +6 -4
  17. package/dist/src/runtime/detector.d.ts +0 -6
  18. package/dist/src/runtime/detector.d.ts.map +1 -1
  19. package/dist/src/runtime/detector.js +1 -14
  20. package/dist/src/runtime/wasi-adapter/file-handle.d.ts +2 -0
  21. package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
  22. package/dist/src/runtime/wasi-adapter/file-handle.js +73 -52
  23. package/dist/src/taglib/audio-file-base.d.ts.map +1 -1
  24. package/dist/src/taglib/audio-file-base.js +33 -77
  25. package/dist/src/taglib/load-audio-data.d.ts.map +1 -1
  26. package/dist/src/taglib/load-audio-data.js +19 -5
  27. package/dist/src/taglib/metadata-extent.d.ts +41 -0
  28. package/dist/src/taglib/metadata-extent.d.ts.map +1 -0
  29. package/dist/src/taglib/metadata-extent.js +128 -0
  30. package/dist/src/taglib/mp4-item-names.d.ts +12 -0
  31. package/dist/src/taglib/mp4-item-names.d.ts.map +1 -0
  32. package/dist/src/taglib/mp4-item-names.js +4 -0
  33. package/dist/src/taglib/mutable-tag-impl.d.ts +14 -0
  34. package/dist/src/taglib/mutable-tag-impl.d.ts.map +1 -0
  35. package/dist/src/taglib/mutable-tag-impl.js +88 -0
  36. package/dist/src/taglib/save-reconstruct.d.ts.map +1 -1
  37. package/dist/src/taglib/save-reconstruct.js +4 -0
  38. package/dist/src/types/metadata-mappings.d.ts.map +1 -1
  39. package/dist/src/types/metadata-mappings.js +14 -0
  40. package/dist/src/types/tags.d.ts +16 -0
  41. package/dist/src/types/tags.d.ts.map +1 -1
  42. package/dist/src/utils/mirror-fields.d.ts +79 -0
  43. package/dist/src/utils/mirror-fields.d.ts.map +1 -0
  44. package/dist/src/utils/mirror-fields.js +67 -0
  45. package/dist/src/utils/tag-mapping.d.ts.map +1 -1
  46. package/dist/src/utils/tag-mapping.js +47 -5
  47. package/dist/src/version.d.ts +1 -1
  48. package/dist/src/version.js +1 -1
  49. package/dist/taglib-wasi.wasm +0 -0
  50. package/dist/taglib-web.wasm +0 -0
  51. package/dist/taglib-wrapper.js +1 -1
  52. package/package.json +3 -3
@@ -1443,6 +1443,26 @@ var init_specialized_properties = __esm({
1443
1443
  vorbis: "ITUNNORM",
1444
1444
  mp4: "----:com.apple.iTunes:iTunNORM"
1445
1445
  }
1446
+ },
1447
+ /**
1448
+ * Apple gapless-playback data (encoder delay, padding, original sample count).
1449
+ *
1450
+ * Exists so the two Apple freeform atoms are presented consistently: without
1451
+ * it, `properties()` answered `appleSoundCheck` for iTunNORM but the raw
1452
+ * uppercased `ITUNSMPB` for its sibling, because only one had an alias. The
1453
+ * name mirrors `appleSoundCheck` — vendor-prefixed, feature-named rather than
1454
+ * atom-named (tuneup-ibo).
1455
+ */
1456
+ appleGaplessInfo: {
1457
+ key: "ITUNSMPB",
1458
+ description: "Apple gapless playback data (encoder delay and padding)",
1459
+ type: "string",
1460
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1461
+ mappings: {
1462
+ id3v2: { frame: "TXXX", description: "iTunSMPB" },
1463
+ vorbis: "ITUNSMPB",
1464
+ mp4: "----:com.apple.iTunes:iTunSMPB"
1465
+ }
1446
1466
  }
1447
1467
  };
1448
1468
  }
@@ -1452,6 +1472,17 @@ var init_specialized_properties = __esm({
1452
1472
  function toTagLibKey(key) {
1453
1473
  return _toTagLib[key] ?? key;
1454
1474
  }
1475
+ function mp4AtomWireKey(atom) {
1476
+ return _mp4AtomToWireKey[atom];
1477
+ }
1478
+ function mp4FreeformAtomNames(wireKeys) {
1479
+ const names = [];
1480
+ for (const key of wireKeys) {
1481
+ const atom = _mp4FreeformAtoms[key];
1482
+ if (atom !== void 0) names.push(atom);
1483
+ }
1484
+ return names;
1485
+ }
1455
1486
  function fromTagLibKey(key) {
1456
1487
  return _fromTagLib[key] ?? key;
1457
1488
  }
@@ -1462,7 +1493,7 @@ function remapKeysFromTagLib(obj) {
1462
1493
  }
1463
1494
  return result;
1464
1495
  }
1465
- var PROPERTIES, _toTagLib, _fromTagLib;
1496
+ var PROPERTIES, _toTagLib, _fromTagLib, _mp4FreeformAtoms, _mp4AtomToWireKey;
1466
1497
  var init_properties = __esm({
1467
1498
  "src/constants/properties.ts"() {
1468
1499
  "use strict";
@@ -1486,6 +1517,129 @@ var init_properties = __esm({
1486
1517
  _toTagLib["year"] = "DATE";
1487
1518
  _toTagLib["track"] = "TRACKNUMBER";
1488
1519
  _fromTagLib["disc"] = "discNumber";
1520
+ _mp4FreeformAtoms = {};
1521
+ for (const meta of Object.values(PROPERTIES)) {
1522
+ const { key, mappings } = meta;
1523
+ const atom = mappings?.mp4;
1524
+ if (typeof atom !== "string" || !atom.startsWith("----:")) continue;
1525
+ const bare = atom.slice(atom.lastIndexOf(":") + 1);
1526
+ if (bare === bare.toUpperCase()) continue;
1527
+ _mp4FreeformAtoms[key] = atom;
1528
+ const readKey = bare.toUpperCase();
1529
+ const camel = _fromTagLib[key];
1530
+ if (camel !== void 0 && _fromTagLib[readKey] === void 0) {
1531
+ _fromTagLib[readKey] = camel;
1532
+ }
1533
+ }
1534
+ _mp4AtomToWireKey = {};
1535
+ for (const meta of Object.values(PROPERTIES)) {
1536
+ const { key, mappings } = meta;
1537
+ const atom = mappings?.mp4;
1538
+ if (typeof atom !== "string" || atom.startsWith("----:")) continue;
1539
+ if (_mp4AtomToWireKey[atom] === void 0) _mp4AtomToWireKey[atom] = key;
1540
+ }
1541
+ }
1542
+ });
1543
+
1544
+ // src/taglib/mp4-item-names.ts
1545
+ var MP4_ITEM_NAMES_KEY;
1546
+ var init_mp4_item_names = __esm({
1547
+ "src/taglib/mp4-item-names.ts"() {
1548
+ "use strict";
1549
+ MP4_ITEM_NAMES_KEY = "_mp4ItemNames";
1550
+ }
1551
+ });
1552
+
1553
+ // src/taglib/mutable-tag-impl.ts
1554
+ function buildMutableTag(handle) {
1555
+ let data = handle.getTagData();
1556
+ const tag = {
1557
+ get title() {
1558
+ return data.title;
1559
+ },
1560
+ get artist() {
1561
+ return data.artist;
1562
+ },
1563
+ get album() {
1564
+ return data.album;
1565
+ },
1566
+ get comment() {
1567
+ return data.comment;
1568
+ },
1569
+ get genre() {
1570
+ return data.genre;
1571
+ },
1572
+ get year() {
1573
+ return data.year;
1574
+ },
1575
+ get track() {
1576
+ return data.track;
1577
+ },
1578
+ get date() {
1579
+ return handle.getProperty("DATE") || void 0;
1580
+ },
1581
+ setTitle: (value) => {
1582
+ handle.setTagData({ title: value });
1583
+ data = handle.getTagData();
1584
+ return tag;
1585
+ },
1586
+ setArtist: (value) => {
1587
+ handle.setTagData({ artist: value });
1588
+ data = handle.getTagData();
1589
+ return tag;
1590
+ },
1591
+ setAlbum: (value) => {
1592
+ handle.setTagData({ album: value });
1593
+ data = handle.getTagData();
1594
+ return tag;
1595
+ },
1596
+ setComment: (value) => {
1597
+ handle.setTagData({ comment: value });
1598
+ data = handle.getTagData();
1599
+ return tag;
1600
+ },
1601
+ setGenre: (value) => {
1602
+ handle.setTagData({ genre: value });
1603
+ data = handle.getTagData();
1604
+ return tag;
1605
+ },
1606
+ setYear: (value) => {
1607
+ handle.setTagData({ year: value });
1608
+ data = handle.getTagData();
1609
+ return tag;
1610
+ },
1611
+ setTrack: (value) => {
1612
+ const existing = value > 0 ? handle.getProperty(RAW_TRACK_WIRE_KEY) : "";
1613
+ const slash = existing.indexOf("/");
1614
+ if (slash !== -1) {
1615
+ handle.setProperty(
1616
+ RAW_TRACK_WIRE_KEY,
1617
+ `${value}${existing.slice(slash)}`
1618
+ );
1619
+ } else {
1620
+ handle.setTagData({ track: value });
1621
+ }
1622
+ data = handle.getTagData();
1623
+ return tag;
1624
+ },
1625
+ setDate: (value) => {
1626
+ if (value === "") {
1627
+ handle.setTagData({ year: 0 });
1628
+ } else {
1629
+ handle.setProperty("DATE", value);
1630
+ }
1631
+ data = handle.getTagData();
1632
+ return tag;
1633
+ }
1634
+ };
1635
+ return tag;
1636
+ }
1637
+ var RAW_TRACK_WIRE_KEY;
1638
+ var init_mutable_tag_impl = __esm({
1639
+ "src/taglib/mutable-tag-impl.ts"() {
1640
+ "use strict";
1641
+ init_properties();
1642
+ RAW_TRACK_WIRE_KEY = toTagLibKey("trackNumber");
1489
1643
  }
1490
1644
  });
1491
1645
 
@@ -1496,6 +1650,8 @@ var init_audio_file_base = __esm({
1496
1650
  "use strict";
1497
1651
  init_properties();
1498
1652
  init_errors2();
1653
+ init_mp4_item_names();
1654
+ init_mutable_tag_impl();
1499
1655
  LYRICS_PROPERTY_KEY = "lyrics";
1500
1656
  LYRICS_WIRE_KEY = toTagLibKey(LYRICS_PROPERTY_KEY);
1501
1657
  EMPTY_KEY_SET = /* @__PURE__ */ new Set();
@@ -1546,79 +1702,7 @@ var init_audio_file_base = __esm({
1546
1702
  return this.getFormat() === format;
1547
1703
  }
1548
1704
  tag() {
1549
- const handle = this.handle;
1550
- let data = handle.getTagData();
1551
- const tag = {
1552
- get title() {
1553
- return data.title;
1554
- },
1555
- get artist() {
1556
- return data.artist;
1557
- },
1558
- get album() {
1559
- return data.album;
1560
- },
1561
- get comment() {
1562
- return data.comment;
1563
- },
1564
- get genre() {
1565
- return data.genre;
1566
- },
1567
- get year() {
1568
- return data.year;
1569
- },
1570
- get track() {
1571
- return data.track;
1572
- },
1573
- get date() {
1574
- return handle.getProperty("DATE") || void 0;
1575
- },
1576
- setTitle: (value) => {
1577
- handle.setTagData({ title: value });
1578
- data = handle.getTagData();
1579
- return tag;
1580
- },
1581
- setArtist: (value) => {
1582
- handle.setTagData({ artist: value });
1583
- data = handle.getTagData();
1584
- return tag;
1585
- },
1586
- setAlbum: (value) => {
1587
- handle.setTagData({ album: value });
1588
- data = handle.getTagData();
1589
- return tag;
1590
- },
1591
- setComment: (value) => {
1592
- handle.setTagData({ comment: value });
1593
- data = handle.getTagData();
1594
- return tag;
1595
- },
1596
- setGenre: (value) => {
1597
- handle.setTagData({ genre: value });
1598
- data = handle.getTagData();
1599
- return tag;
1600
- },
1601
- setYear: (value) => {
1602
- handle.setTagData({ year: value });
1603
- data = handle.getTagData();
1604
- return tag;
1605
- },
1606
- setTrack: (value) => {
1607
- handle.setTagData({ track: value });
1608
- data = handle.getTagData();
1609
- return tag;
1610
- },
1611
- setDate: (value) => {
1612
- if (value === "") {
1613
- handle.setTagData({ year: 0 });
1614
- } else {
1615
- handle.setProperty("DATE", value);
1616
- }
1617
- data = handle.getTagData();
1618
- return tag;
1619
- }
1620
- };
1621
- return tag;
1705
+ return buildMutableTag(this.handle);
1622
1706
  }
1623
1707
  audioProperties() {
1624
1708
  if (!this.cachedAudioProperties) {
@@ -1640,14 +1724,29 @@ var init_audio_file_base = __esm({
1640
1724
  const existing = this.handle.getProperties()[LYRICS_WIRE_KEY];
1641
1725
  if (existing !== void 0) translated[LYRICS_WIRE_KEY] = existing;
1642
1726
  }
1727
+ const atomNames = mp4FreeformAtomNames(Object.keys(translated));
1728
+ if (atomNames.length > 0) translated[MP4_ITEM_NAMES_KEY] = atomNames;
1643
1729
  this.handle.setProperties(translated);
1644
1730
  }
1645
1731
  getProperty(key) {
1646
1732
  const value = this.handle.getProperty(toTagLibKey(key));
1647
- return value === "" ? void 0 : value;
1733
+ if (value !== "") return value;
1734
+ if (!this.isMP4()) return void 0;
1735
+ const remapped = this.properties()[key]?.[0];
1736
+ return remapped === "" ? void 0 : remapped;
1648
1737
  }
1649
1738
  setProperty(key, value) {
1650
- this.handle.setProperty(toTagLibKey(key), value);
1739
+ const wireKey = toTagLibKey(key);
1740
+ const atomNames = mp4FreeformAtomNames([wireKey]);
1741
+ if (atomNames.length > 0 && this.isMP4()) {
1742
+ this.handle.setProperties({
1743
+ ...this.handle.getProperties(),
1744
+ [wireKey]: [value],
1745
+ [MP4_ITEM_NAMES_KEY]: atomNames
1746
+ });
1747
+ return;
1748
+ }
1749
+ this.handle.setProperty(wireKey, value);
1651
1750
  }
1652
1751
  isMP4() {
1653
1752
  return this.handle.isMP4();
@@ -1656,13 +1755,19 @@ var init_audio_file_base = __esm({
1656
1755
  if (!this.isMP4()) {
1657
1756
  throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1658
1757
  }
1659
- const value = this.handle.getMP4Item(key);
1758
+ const wireKey = mp4AtomWireKey(key);
1759
+ const value = wireKey !== void 0 ? this.handle.getProperty(wireKey) : this.handle.getMP4Item(key);
1660
1760
  return value === "" ? void 0 : value;
1661
1761
  }
1662
1762
  setMP4Item(key, value) {
1663
1763
  if (!this.isMP4()) {
1664
1764
  throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1665
1765
  }
1766
+ const wireKey = mp4AtomWireKey(key);
1767
+ if (wireKey !== void 0) {
1768
+ this.handle.setProperty(wireKey, value);
1769
+ return;
1770
+ }
1666
1771
  this.handle.setMP4Item(key, value);
1667
1772
  }
1668
1773
  removeMP4Item(key) {
@@ -1945,6 +2050,8 @@ function copyEditedState(target, source, sourceComplete, deletedKeys) {
1945
2050
  target.setTagData(source.getTagData());
1946
2051
  const merged = sourceComplete ? source.getProperties() : { ...target.getProperties(), ...source.getProperties() };
1947
2052
  for (const key of deletedKeys) delete merged[key];
2053
+ const atomNames = mp4FreeformAtomNames(Object.keys(merged));
2054
+ if (atomNames.length > 0) merged[MP4_ITEM_NAMES_KEY] = atomNames;
1948
2055
  target.setProperties(merged);
1949
2056
  copyExtraState(target, source, sourceComplete);
1950
2057
  }
@@ -1980,6 +2087,8 @@ var init_save_reconstruct = __esm({
1980
2087
  init_write();
1981
2088
  init_embind_adapter();
1982
2089
  init_extra_state_registry();
2090
+ init_properties();
2091
+ init_mp4_item_names();
1983
2092
  }
1984
2093
  });
1985
2094
 
@@ -2248,6 +2357,137 @@ var init_audio_file_impl = __esm({
2248
2357
  }
2249
2358
  });
2250
2359
 
2360
+ // src/taglib/metadata-extent.ts
2361
+ function be32(b, offset) {
2362
+ return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
2363
+ }
2364
+ function startsWith(bytes, magic, at = 0) {
2365
+ if (bytes.length < at + magic.length) return false;
2366
+ for (let i = 0; i < magic.length; i++) {
2367
+ if (bytes[at + i] !== magic.charCodeAt(i)) return false;
2368
+ }
2369
+ return true;
2370
+ }
2371
+ function id3v2End(bytes) {
2372
+ if (bytes.length < 10) return void 0;
2373
+ const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
2374
+ const hasFooter = (bytes[5] & 16) !== 0;
2375
+ return 10 + size + (hasFooter ? 10 : 0);
2376
+ }
2377
+ function flacEnd(bytes, limit, start) {
2378
+ let offset = start + 4;
2379
+ for (; ; ) {
2380
+ if (offset + 4 > bytes.length) return void 0;
2381
+ const isLast = (bytes[offset] & 128) !== 0;
2382
+ const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
2383
+ offset += 4 + length;
2384
+ if (offset > limit) return offset;
2385
+ if (isLast) return offset;
2386
+ }
2387
+ }
2388
+ function mp4MoovEnd(bytes, limit, start) {
2389
+ let offset = start;
2390
+ const readable = Math.min(bytes.length, limit);
2391
+ while (offset + 8 <= readable) {
2392
+ const size = be32(bytes, offset);
2393
+ const type = String.fromCharCode(
2394
+ bytes[offset + 4],
2395
+ bytes[offset + 5],
2396
+ bytes[offset + 6],
2397
+ bytes[offset + 7]
2398
+ );
2399
+ if (size < 8) return void 0;
2400
+ if (type === "moov") return offset + size;
2401
+ offset += size;
2402
+ }
2403
+ return void 0;
2404
+ }
2405
+ function oggMetadataEnd(bytes, limit, start) {
2406
+ let offset = start;
2407
+ let packetsCompleted = 0;
2408
+ let first = true;
2409
+ for (; ; ) {
2410
+ if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
2411
+ return void 0;
2412
+ }
2413
+ const segmentCount = bytes[offset + 26];
2414
+ const tableStart = offset + 27;
2415
+ if (tableStart + segmentCount > bytes.length) return void 0;
2416
+ let payload = 0;
2417
+ for (let i = 0; i < segmentCount; i++) {
2418
+ const segment = bytes[tableStart + i];
2419
+ payload += segment;
2420
+ if (segment < 255) packetsCompleted++;
2421
+ }
2422
+ if (first) {
2423
+ const payloadStart = tableStart + segmentCount;
2424
+ if (payloadStart + 5 > bytes.length) return void 0;
2425
+ if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
2426
+ return void 0;
2427
+ }
2428
+ first = false;
2429
+ }
2430
+ offset = tableStart + segmentCount + payload;
2431
+ if (packetsCompleted >= 2 || offset > limit) return offset;
2432
+ }
2433
+ }
2434
+ function isMpegFrameSync(bytes, at = 0) {
2435
+ if (bytes.length < at + 4) return false;
2436
+ if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return false;
2437
+ const version = bytes[at + 1] >> 3 & 3;
2438
+ const layer = bytes[at + 1] >> 1 & 3;
2439
+ if (version === 1 || layer === 0) return false;
2440
+ const bitrateIndex = bytes[at + 2] >> 4 & 15;
2441
+ const sampleRateIndex = bytes[at + 2] >> 2 & 3;
2442
+ return bitrateIndex !== 0 && bitrateIndex !== 15 && sampleRateIndex !== 3;
2443
+ }
2444
+ function containerEnd(bytes, at, limit) {
2445
+ if (at >= bytes.length) return bytes.length < limit ? null : void 0;
2446
+ if (at + 8 > bytes.length) return void 0;
2447
+ if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
2448
+ if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
2449
+ if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
2450
+ return null;
2451
+ }
2452
+ function metadataFitsInHeader(header, limit) {
2453
+ let end;
2454
+ if (startsWith(header, "ID3")) {
2455
+ const tagEnd = id3v2End(header);
2456
+ if (tagEnd === void 0) return false;
2457
+ end = tagEnd;
2458
+ if (tagEnd <= limit) {
2459
+ const behind = containerEnd(header, tagEnd, limit);
2460
+ if (behind === void 0) return false;
2461
+ if (behind !== null) end = behind;
2462
+ }
2463
+ } else {
2464
+ const container = containerEnd(header, 0, limit);
2465
+ if (container === null) {
2466
+ end = isMpegFrameSync(header) ? 0 : void 0;
2467
+ } else {
2468
+ end = container;
2469
+ }
2470
+ }
2471
+ if (end === void 0 || end > header.length) return false;
2472
+ return end <= limit;
2473
+ }
2474
+ function trailerFitsInFooter(tail, footerSize) {
2475
+ if (tail.length < Math.min(footerSize, 32)) return false;
2476
+ for (const offsetFromEnd of [32, 32 + 128]) {
2477
+ const at = tail.length - offsetFromEnd;
2478
+ if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
2479
+ const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
2480
+ const trailing = offsetFromEnd === 32 ? 0 : 128;
2481
+ if (size + 32 + trailing > footerSize) return false;
2482
+ }
2483
+ return true;
2484
+ }
2485
+ var init_metadata_extent = __esm({
2486
+ "src/taglib/metadata-extent.ts"() {
2487
+ "use strict";
2488
+ }
2489
+ });
2490
+
2251
2491
  // src/taglib/load-audio-data.ts
2252
2492
  async function loadAudioData(input, opts) {
2253
2493
  if (opts.partial && typeof File !== "undefined" && input instanceof File) {
@@ -2256,12 +2496,20 @@ async function loadAudioData(input, opts) {
2256
2496
  if (input.size <= headerSize + footerSize) {
2257
2497
  return { data: await readFileData(input), isPartiallyLoaded: false };
2258
2498
  }
2259
- const header = await input.slice(0, headerSize).arrayBuffer();
2499
+ const header = new Uint8Array(
2500
+ await input.slice(0, headerSize).arrayBuffer()
2501
+ );
2502
+ if (!metadataFitsInHeader(header, headerSize)) {
2503
+ return { data: await readFileData(input), isPartiallyLoaded: false };
2504
+ }
2260
2505
  const footerStart = Math.max(0, input.size - footerSize);
2261
- const footer = await input.slice(footerStart).arrayBuffer();
2506
+ const footer = new Uint8Array(await input.slice(footerStart).arrayBuffer());
2507
+ if (!trailerFitsInFooter(footer, footerSize)) {
2508
+ return { data: await readFileData(input), isPartiallyLoaded: false };
2509
+ }
2262
2510
  const combined = new Uint8Array(header.byteLength + footer.byteLength);
2263
- combined.set(new Uint8Array(header), 0);
2264
- combined.set(new Uint8Array(footer), header.byteLength);
2511
+ combined.set(header, 0);
2512
+ combined.set(footer, header.byteLength);
2265
2513
  return { data: combined, isPartiallyLoaded: true };
2266
2514
  }
2267
2515
  if (opts.partial && typeof input === "string") {
@@ -2272,7 +2520,9 @@ async function loadAudioData(input, opts) {
2272
2520
  opts.maxHeaderSize,
2273
2521
  opts.maxFooterSize
2274
2522
  );
2275
- return { data, isPartiallyLoaded: true };
2523
+ if (metadataFitsInHeader(data, opts.maxHeaderSize) && trailerFitsInFooter(data, opts.maxFooterSize)) {
2524
+ return { data, isPartiallyLoaded: true };
2525
+ }
2276
2526
  }
2277
2527
  return { data: await readFileData(input), isPartiallyLoaded: false };
2278
2528
  }
@@ -2282,14 +2532,29 @@ var init_load_audio_data = __esm({
2282
2532
  "src/taglib/load-audio-data.ts"() {
2283
2533
  "use strict";
2284
2534
  init_file();
2535
+ init_metadata_extent();
2285
2536
  }
2286
2537
  });
2287
2538
 
2288
- // src/utils/tag-mapping.ts
2289
- function parseNumeric(value) {
2290
- const parsed = Number.parseInt(value, 10);
2539
+ // src/utils/mirror-fields.ts
2540
+ function parseLeadingInt(raw) {
2541
+ const parsed = Number.parseInt(raw, 10);
2291
2542
  return Number.isNaN(parsed) ? void 0 : parsed;
2292
2543
  }
2544
+ var MIRROR_FIELDS, DATE_MIRROR, TRACK_MIRROR;
2545
+ var init_mirror_fields = __esm({
2546
+ "src/utils/mirror-fields.ts"() {
2547
+ "use strict";
2548
+ MIRROR_FIELDS = [
2549
+ { raw: "date", numeric: "year" },
2550
+ { raw: "trackNumber", numeric: "track" }
2551
+ ];
2552
+ DATE_MIRROR = MIRROR_FIELDS[0];
2553
+ TRACK_MIRROR = MIRROR_FIELDS[1];
2554
+ }
2555
+ });
2556
+
2557
+ // src/utils/tag-mapping.ts
2293
2558
  function readExtendedTag(audioFile) {
2294
2559
  const tag = mapPropertiesToExtendedTag(audioFile.properties());
2295
2560
  const pictures = audioFile.getPictures();
@@ -2314,21 +2579,35 @@ function mapPropertiesToExtendedTag(props) {
2314
2579
  const values = props[propKey];
2315
2580
  if (!values || values.length === 0) continue;
2316
2581
  if (tagField === "year" || tagField === "track") {
2317
- const num = parseNumeric(values[0]);
2582
+ const num = parseLeadingInt(values[0]);
2318
2583
  if (num !== void 0) tag[tagField] = num;
2319
2584
  if (propKey === "date") {
2320
2585
  tag.date = values.length === 1 ? values[0] : values;
2321
2586
  }
2587
+ if (propKey === "trackNumber") {
2588
+ tag.trackNumber = values.length === 1 ? values[0] : values;
2589
+ }
2322
2590
  } else {
2323
2591
  tag[tagField] = values;
2324
2592
  }
2325
2593
  }
2594
+ for (const [propKey, totalField] of [
2595
+ ["trackNumber", "totalTracks"],
2596
+ ["discNumber", "totalDiscs"]
2597
+ ]) {
2598
+ if (tag[totalField] !== void 0) continue;
2599
+ const raw = props[propKey]?.[0];
2600
+ const slash = raw?.indexOf("/") ?? -1;
2601
+ if (raw === void 0 || slash === -1) continue;
2602
+ const total = parseLeadingInt(raw.slice(slash + 1));
2603
+ if (total !== void 0) tag[totalField] = total;
2604
+ }
2326
2605
  for (const [key, values] of Object.entries(props)) {
2327
2606
  if (BASIC_PROPERTY_KEYS[key]) continue;
2328
2607
  if (!values || values.length === 0) continue;
2329
2608
  const camelKey = fromTagLibKey(key);
2330
2609
  if (NUMERIC_FIELDS.has(camelKey)) {
2331
- const num = parseNumeric(values[0]);
2610
+ const num = parseLeadingInt(values[0]);
2332
2611
  if (num !== void 0) tag[camelKey] = num;
2333
2612
  } else if (camelKey === "compilation") {
2334
2613
  tag[camelKey] = values[0] === "1";
@@ -2341,8 +2620,29 @@ function mapPropertiesToExtendedTag(props) {
2341
2620
  function mergeTagUpdates(file, tags) {
2342
2621
  const currentProps = file.properties();
2343
2622
  const newProps = normalizeTagInput(tags);
2623
+ reconcilePairFields(currentProps, newProps, tags);
2344
2624
  file.setProperties({ ...currentProps, ...newProps });
2345
2625
  }
2626
+ function reconcilePairFields(currentProps, newProps, input) {
2627
+ const pairs = [
2628
+ ["trackNumber", "totalTracks", "track"],
2629
+ ["discNumber", "totalDiscs", "discNumber"]
2630
+ ];
2631
+ for (const [rawKey, totalKey, numericKey] of pairs) {
2632
+ const incoming = newProps[rawKey]?.[0];
2633
+ if (incoming?.includes("/")) {
2634
+ newProps[totalKey] = [];
2635
+ continue;
2636
+ }
2637
+ const numericOnly = input[numericKey] !== void 0 && input[rawKey] === void 0;
2638
+ if (!numericOnly || incoming === void 0) continue;
2639
+ const existing = currentProps[rawKey]?.[0];
2640
+ const slash = existing?.indexOf("/") ?? -1;
2641
+ if (existing !== void 0 && slash !== -1) {
2642
+ newProps[rawKey] = [`${incoming}${existing.slice(slash)}`];
2643
+ }
2644
+ }
2645
+ }
2346
2646
  function normalizeTagInput(input) {
2347
2647
  const props = {};
2348
2648
  for (const field of [
@@ -2365,6 +2665,9 @@ function normalizeTagInput(input) {
2365
2665
  if (input.track !== void 0) {
2366
2666
  props.trackNumber = [String(input.track)];
2367
2667
  }
2668
+ if (input.trackNumber !== void 0) {
2669
+ props.trackNumber = Array.isArray(input.trackNumber) ? input.trackNumber : [input.trackNumber];
2670
+ }
2368
2671
  for (const [field, val] of Object.entries(input)) {
2369
2672
  if (BASIC_FIELDS.has(field) || val === void 0) continue;
2370
2673
  if (field === "compilation") {
@@ -2377,6 +2680,12 @@ function normalizeTagInput(input) {
2377
2680
  props[field] = val;
2378
2681
  }
2379
2682
  }
2683
+ for (const [rawField, totalField] of [
2684
+ ["trackNumber", "totalTracks"],
2685
+ ["discNumber", "totalDiscs"]
2686
+ ]) {
2687
+ if (props[rawField]?.[0]?.includes("/")) delete props[totalField];
2688
+ }
2380
2689
  return props;
2381
2690
  }
2382
2691
  var BASIC_PROPERTY_KEYS, BASIC_FIELDS, NUMERIC_FIELDS;
@@ -2384,6 +2693,7 @@ var init_tag_mapping = __esm({
2384
2693
  "src/utils/tag-mapping.ts"() {
2385
2694
  "use strict";
2386
2695
  init_properties();
2696
+ init_mirror_fields();
2387
2697
  BASIC_PROPERTY_KEYS = {
2388
2698
  title: "title",
2389
2699
  artist: "artist",
@@ -2401,7 +2711,8 @@ var init_tag_mapping = __esm({
2401
2711
  "genre",
2402
2712
  "year",
2403
2713
  "date",
2404
- "track"
2714
+ "track",
2715
+ "trackNumber"
2405
2716
  ]);
2406
2717
  NUMERIC_FIELDS = /* @__PURE__ */ new Set([
2407
2718
  "year",
@@ -2419,7 +2730,7 @@ var VERSION;
2419
2730
  var init_version = __esm({
2420
2731
  "src/version.ts"() {
2421
2732
  "use strict";
2422
- VERSION = "1.5.3";
2733
+ VERSION = "1.6.1";
2423
2734
  }
2424
2735
  });
2425
2736
 
@@ -3101,8 +3412,9 @@ var Tags = {
3101
3412
  InvolvedPeople: "involvedPeople",
3102
3413
  Encoding: "encoding"
3103
3414
  };
3415
+ var VALID_TAG_NAMES = new Set(Object.values(Tags));
3104
3416
  function isValidTagName(name) {
3105
- return Object.values(Tags).includes(name);
3417
+ return VALID_TAG_NAMES.has(name);
3106
3418
  }
3107
3419
  function getAllTagNames() {
3108
3420
  return Object.values(Tags);