taglib-wasm 1.5.3 → 1.6.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 (47) hide show
  1. package/LICENSE +12 -9
  2. package/README.md +6 -3
  3. package/dist/index.browser.js +255 -85
  4. package/dist/simple.browser.js +253 -84
  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 +61 -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/mp4-item-names.d.ts +12 -0
  26. package/dist/src/taglib/mp4-item-names.d.ts.map +1 -0
  27. package/dist/src/taglib/mp4-item-names.js +4 -0
  28. package/dist/src/taglib/mutable-tag-impl.d.ts +14 -0
  29. package/dist/src/taglib/mutable-tag-impl.d.ts.map +1 -0
  30. package/dist/src/taglib/mutable-tag-impl.js +88 -0
  31. package/dist/src/taglib/save-reconstruct.d.ts.map +1 -1
  32. package/dist/src/taglib/save-reconstruct.js +4 -0
  33. package/dist/src/types/metadata-mappings.d.ts.map +1 -1
  34. package/dist/src/types/metadata-mappings.js +14 -0
  35. package/dist/src/types/tags.d.ts +16 -0
  36. package/dist/src/types/tags.d.ts.map +1 -1
  37. package/dist/src/utils/mirror-fields.d.ts +79 -0
  38. package/dist/src/utils/mirror-fields.d.ts.map +1 -0
  39. package/dist/src/utils/mirror-fields.js +67 -0
  40. package/dist/src/utils/tag-mapping.d.ts.map +1 -1
  41. package/dist/src/utils/tag-mapping.js +47 -5
  42. package/dist/src/version.d.ts +1 -1
  43. package/dist/src/version.js +1 -1
  44. package/dist/taglib-wasi.wasm +0 -0
  45. package/dist/taglib-web.wasm +0 -0
  46. package/dist/taglib-wrapper.js +1 -1
  47. package/package.json +3 -3
@@ -1375,6 +1375,26 @@ var init_specialized_properties = __esm({
1375
1375
  vorbis: "ITUNNORM",
1376
1376
  mp4: "----:com.apple.iTunes:iTunNORM"
1377
1377
  }
1378
+ },
1379
+ /**
1380
+ * Apple gapless-playback data (encoder delay, padding, original sample count).
1381
+ *
1382
+ * Exists so the two Apple freeform atoms are presented consistently: without
1383
+ * it, `properties()` answered `appleSoundCheck` for iTunNORM but the raw
1384
+ * uppercased `ITUNSMPB` for its sibling, because only one had an alias. The
1385
+ * name mirrors `appleSoundCheck` — vendor-prefixed, feature-named rather than
1386
+ * atom-named (tuneup-ibo).
1387
+ */
1388
+ appleGaplessInfo: {
1389
+ key: "ITUNSMPB",
1390
+ description: "Apple gapless playback data (encoder delay and padding)",
1391
+ type: "string",
1392
+ supportedFormats: ["ID3v2", "MP4", "Vorbis"],
1393
+ mappings: {
1394
+ id3v2: { frame: "TXXX", description: "iTunSMPB" },
1395
+ vorbis: "ITUNSMPB",
1396
+ mp4: "----:com.apple.iTunes:iTunSMPB"
1397
+ }
1378
1398
  }
1379
1399
  };
1380
1400
  }
@@ -1384,6 +1404,17 @@ var init_specialized_properties = __esm({
1384
1404
  function toTagLibKey(key) {
1385
1405
  return _toTagLib[key] ?? key;
1386
1406
  }
1407
+ function mp4AtomWireKey(atom) {
1408
+ return _mp4AtomToWireKey[atom];
1409
+ }
1410
+ function mp4FreeformAtomNames(wireKeys) {
1411
+ const names = [];
1412
+ for (const key of wireKeys) {
1413
+ const atom = _mp4FreeformAtoms[key];
1414
+ if (atom !== void 0) names.push(atom);
1415
+ }
1416
+ return names;
1417
+ }
1387
1418
  function fromTagLibKey(key) {
1388
1419
  return _fromTagLib[key] ?? key;
1389
1420
  }
@@ -1394,7 +1425,7 @@ function remapKeysFromTagLib(obj) {
1394
1425
  }
1395
1426
  return result;
1396
1427
  }
1397
- var PROPERTIES, _toTagLib, _fromTagLib;
1428
+ var PROPERTIES, _toTagLib, _fromTagLib, _mp4FreeformAtoms, _mp4AtomToWireKey;
1398
1429
  var init_properties = __esm({
1399
1430
  "src/constants/properties.ts"() {
1400
1431
  "use strict";
@@ -1418,6 +1449,129 @@ var init_properties = __esm({
1418
1449
  _toTagLib["year"] = "DATE";
1419
1450
  _toTagLib["track"] = "TRACKNUMBER";
1420
1451
  _fromTagLib["disc"] = "discNumber";
1452
+ _mp4FreeformAtoms = {};
1453
+ for (const meta of Object.values(PROPERTIES)) {
1454
+ const { key, mappings } = meta;
1455
+ const atom = mappings?.mp4;
1456
+ if (typeof atom !== "string" || !atom.startsWith("----:")) continue;
1457
+ const bare = atom.slice(atom.lastIndexOf(":") + 1);
1458
+ if (bare === bare.toUpperCase()) continue;
1459
+ _mp4FreeformAtoms[key] = atom;
1460
+ const readKey = bare.toUpperCase();
1461
+ const camel = _fromTagLib[key];
1462
+ if (camel !== void 0 && _fromTagLib[readKey] === void 0) {
1463
+ _fromTagLib[readKey] = camel;
1464
+ }
1465
+ }
1466
+ _mp4AtomToWireKey = {};
1467
+ for (const meta of Object.values(PROPERTIES)) {
1468
+ const { key, mappings } = meta;
1469
+ const atom = mappings?.mp4;
1470
+ if (typeof atom !== "string" || atom.startsWith("----:")) continue;
1471
+ if (_mp4AtomToWireKey[atom] === void 0) _mp4AtomToWireKey[atom] = key;
1472
+ }
1473
+ }
1474
+ });
1475
+
1476
+ // src/taglib/mp4-item-names.ts
1477
+ var MP4_ITEM_NAMES_KEY;
1478
+ var init_mp4_item_names = __esm({
1479
+ "src/taglib/mp4-item-names.ts"() {
1480
+ "use strict";
1481
+ MP4_ITEM_NAMES_KEY = "_mp4ItemNames";
1482
+ }
1483
+ });
1484
+
1485
+ // src/taglib/mutable-tag-impl.ts
1486
+ function buildMutableTag(handle) {
1487
+ let data = handle.getTagData();
1488
+ const tag = {
1489
+ get title() {
1490
+ return data.title;
1491
+ },
1492
+ get artist() {
1493
+ return data.artist;
1494
+ },
1495
+ get album() {
1496
+ return data.album;
1497
+ },
1498
+ get comment() {
1499
+ return data.comment;
1500
+ },
1501
+ get genre() {
1502
+ return data.genre;
1503
+ },
1504
+ get year() {
1505
+ return data.year;
1506
+ },
1507
+ get track() {
1508
+ return data.track;
1509
+ },
1510
+ get date() {
1511
+ return handle.getProperty("DATE") || void 0;
1512
+ },
1513
+ setTitle: (value) => {
1514
+ handle.setTagData({ title: value });
1515
+ data = handle.getTagData();
1516
+ return tag;
1517
+ },
1518
+ setArtist: (value) => {
1519
+ handle.setTagData({ artist: value });
1520
+ data = handle.getTagData();
1521
+ return tag;
1522
+ },
1523
+ setAlbum: (value) => {
1524
+ handle.setTagData({ album: value });
1525
+ data = handle.getTagData();
1526
+ return tag;
1527
+ },
1528
+ setComment: (value) => {
1529
+ handle.setTagData({ comment: value });
1530
+ data = handle.getTagData();
1531
+ return tag;
1532
+ },
1533
+ setGenre: (value) => {
1534
+ handle.setTagData({ genre: value });
1535
+ data = handle.getTagData();
1536
+ return tag;
1537
+ },
1538
+ setYear: (value) => {
1539
+ handle.setTagData({ year: value });
1540
+ data = handle.getTagData();
1541
+ return tag;
1542
+ },
1543
+ setTrack: (value) => {
1544
+ const existing = value > 0 ? handle.getProperty(RAW_TRACK_WIRE_KEY) : "";
1545
+ const slash = existing.indexOf("/");
1546
+ if (slash !== -1) {
1547
+ handle.setProperty(
1548
+ RAW_TRACK_WIRE_KEY,
1549
+ `${value}${existing.slice(slash)}`
1550
+ );
1551
+ } else {
1552
+ handle.setTagData({ track: value });
1553
+ }
1554
+ data = handle.getTagData();
1555
+ return tag;
1556
+ },
1557
+ setDate: (value) => {
1558
+ if (value === "") {
1559
+ handle.setTagData({ year: 0 });
1560
+ } else {
1561
+ handle.setProperty("DATE", value);
1562
+ }
1563
+ data = handle.getTagData();
1564
+ return tag;
1565
+ }
1566
+ };
1567
+ return tag;
1568
+ }
1569
+ var RAW_TRACK_WIRE_KEY;
1570
+ var init_mutable_tag_impl = __esm({
1571
+ "src/taglib/mutable-tag-impl.ts"() {
1572
+ "use strict";
1573
+ init_properties();
1574
+ RAW_TRACK_WIRE_KEY = toTagLibKey("trackNumber");
1421
1575
  }
1422
1576
  });
1423
1577
 
@@ -1428,6 +1582,8 @@ var init_audio_file_base = __esm({
1428
1582
  "use strict";
1429
1583
  init_properties();
1430
1584
  init_errors2();
1585
+ init_mp4_item_names();
1586
+ init_mutable_tag_impl();
1431
1587
  LYRICS_PROPERTY_KEY = "lyrics";
1432
1588
  LYRICS_WIRE_KEY = toTagLibKey(LYRICS_PROPERTY_KEY);
1433
1589
  EMPTY_KEY_SET = /* @__PURE__ */ new Set();
@@ -1478,79 +1634,7 @@ var init_audio_file_base = __esm({
1478
1634
  return this.getFormat() === format;
1479
1635
  }
1480
1636
  tag() {
1481
- const handle = this.handle;
1482
- let data = handle.getTagData();
1483
- const tag = {
1484
- get title() {
1485
- return data.title;
1486
- },
1487
- get artist() {
1488
- return data.artist;
1489
- },
1490
- get album() {
1491
- return data.album;
1492
- },
1493
- get comment() {
1494
- return data.comment;
1495
- },
1496
- get genre() {
1497
- return data.genre;
1498
- },
1499
- get year() {
1500
- return data.year;
1501
- },
1502
- get track() {
1503
- return data.track;
1504
- },
1505
- get date() {
1506
- return handle.getProperty("DATE") || void 0;
1507
- },
1508
- setTitle: (value) => {
1509
- handle.setTagData({ title: value });
1510
- data = handle.getTagData();
1511
- return tag;
1512
- },
1513
- setArtist: (value) => {
1514
- handle.setTagData({ artist: value });
1515
- data = handle.getTagData();
1516
- return tag;
1517
- },
1518
- setAlbum: (value) => {
1519
- handle.setTagData({ album: value });
1520
- data = handle.getTagData();
1521
- return tag;
1522
- },
1523
- setComment: (value) => {
1524
- handle.setTagData({ comment: value });
1525
- data = handle.getTagData();
1526
- return tag;
1527
- },
1528
- setGenre: (value) => {
1529
- handle.setTagData({ genre: value });
1530
- data = handle.getTagData();
1531
- return tag;
1532
- },
1533
- setYear: (value) => {
1534
- handle.setTagData({ year: value });
1535
- data = handle.getTagData();
1536
- return tag;
1537
- },
1538
- setTrack: (value) => {
1539
- handle.setTagData({ track: value });
1540
- data = handle.getTagData();
1541
- return tag;
1542
- },
1543
- setDate: (value) => {
1544
- if (value === "") {
1545
- handle.setTagData({ year: 0 });
1546
- } else {
1547
- handle.setProperty("DATE", value);
1548
- }
1549
- data = handle.getTagData();
1550
- return tag;
1551
- }
1552
- };
1553
- return tag;
1637
+ return buildMutableTag(this.handle);
1554
1638
  }
1555
1639
  audioProperties() {
1556
1640
  if (!this.cachedAudioProperties) {
@@ -1572,14 +1656,29 @@ var init_audio_file_base = __esm({
1572
1656
  const existing = this.handle.getProperties()[LYRICS_WIRE_KEY];
1573
1657
  if (existing !== void 0) translated[LYRICS_WIRE_KEY] = existing;
1574
1658
  }
1659
+ const atomNames = mp4FreeformAtomNames(Object.keys(translated));
1660
+ if (atomNames.length > 0) translated[MP4_ITEM_NAMES_KEY] = atomNames;
1575
1661
  this.handle.setProperties(translated);
1576
1662
  }
1577
1663
  getProperty(key) {
1578
1664
  const value = this.handle.getProperty(toTagLibKey(key));
1579
- return value === "" ? void 0 : value;
1665
+ if (value !== "") return value;
1666
+ if (!this.isMP4()) return void 0;
1667
+ const remapped = this.properties()[key]?.[0];
1668
+ return remapped === "" ? void 0 : remapped;
1580
1669
  }
1581
1670
  setProperty(key, value) {
1582
- this.handle.setProperty(toTagLibKey(key), value);
1671
+ const wireKey = toTagLibKey(key);
1672
+ const atomNames = mp4FreeformAtomNames([wireKey]);
1673
+ if (atomNames.length > 0 && this.isMP4()) {
1674
+ this.handle.setProperties({
1675
+ ...this.handle.getProperties(),
1676
+ [wireKey]: [value],
1677
+ [MP4_ITEM_NAMES_KEY]: atomNames
1678
+ });
1679
+ return;
1680
+ }
1681
+ this.handle.setProperty(wireKey, value);
1583
1682
  }
1584
1683
  isMP4() {
1585
1684
  return this.handle.isMP4();
@@ -1588,13 +1687,19 @@ var init_audio_file_base = __esm({
1588
1687
  if (!this.isMP4()) {
1589
1688
  throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1590
1689
  }
1591
- const value = this.handle.getMP4Item(key);
1690
+ const wireKey = mp4AtomWireKey(key);
1691
+ const value = wireKey !== void 0 ? this.handle.getProperty(wireKey) : this.handle.getMP4Item(key);
1592
1692
  return value === "" ? void 0 : value;
1593
1693
  }
1594
1694
  setMP4Item(key, value) {
1595
1695
  if (!this.isMP4()) {
1596
1696
  throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
1597
1697
  }
1698
+ const wireKey = mp4AtomWireKey(key);
1699
+ if (wireKey !== void 0) {
1700
+ this.handle.setProperty(wireKey, value);
1701
+ return;
1702
+ }
1598
1703
  this.handle.setMP4Item(key, value);
1599
1704
  }
1600
1705
  removeMP4Item(key) {
@@ -1877,6 +1982,8 @@ function copyEditedState(target, source, sourceComplete, deletedKeys) {
1877
1982
  target.setTagData(source.getTagData());
1878
1983
  const merged = sourceComplete ? source.getProperties() : { ...target.getProperties(), ...source.getProperties() };
1879
1984
  for (const key of deletedKeys) delete merged[key];
1985
+ const atomNames = mp4FreeformAtomNames(Object.keys(merged));
1986
+ if (atomNames.length > 0) merged[MP4_ITEM_NAMES_KEY] = atomNames;
1880
1987
  target.setProperties(merged);
1881
1988
  copyExtraState(target, source, sourceComplete);
1882
1989
  }
@@ -1912,6 +2019,8 @@ var init_save_reconstruct = __esm({
1912
2019
  init_write();
1913
2020
  init_embind_adapter();
1914
2021
  init_extra_state_registry();
2022
+ init_properties();
2023
+ init_mp4_item_names();
1915
2024
  }
1916
2025
  });
1917
2026
 
@@ -2217,11 +2326,25 @@ var init_load_audio_data = __esm({
2217
2326
  }
2218
2327
  });
2219
2328
 
2220
- // src/utils/tag-mapping.ts
2221
- function parseNumeric(value) {
2222
- const parsed = Number.parseInt(value, 10);
2329
+ // src/utils/mirror-fields.ts
2330
+ function parseLeadingInt(raw) {
2331
+ const parsed = Number.parseInt(raw, 10);
2223
2332
  return Number.isNaN(parsed) ? void 0 : parsed;
2224
2333
  }
2334
+ var MIRROR_FIELDS, DATE_MIRROR, TRACK_MIRROR;
2335
+ var init_mirror_fields = __esm({
2336
+ "src/utils/mirror-fields.ts"() {
2337
+ "use strict";
2338
+ MIRROR_FIELDS = [
2339
+ { raw: "date", numeric: "year" },
2340
+ { raw: "trackNumber", numeric: "track" }
2341
+ ];
2342
+ DATE_MIRROR = MIRROR_FIELDS[0];
2343
+ TRACK_MIRROR = MIRROR_FIELDS[1];
2344
+ }
2345
+ });
2346
+
2347
+ // src/utils/tag-mapping.ts
2225
2348
  function readExtendedTag(audioFile) {
2226
2349
  const tag = mapPropertiesToExtendedTag(audioFile.properties());
2227
2350
  const pictures = audioFile.getPictures();
@@ -2246,21 +2369,35 @@ function mapPropertiesToExtendedTag(props) {
2246
2369
  const values = props[propKey];
2247
2370
  if (!values || values.length === 0) continue;
2248
2371
  if (tagField === "year" || tagField === "track") {
2249
- const num = parseNumeric(values[0]);
2372
+ const num = parseLeadingInt(values[0]);
2250
2373
  if (num !== void 0) tag[tagField] = num;
2251
2374
  if (propKey === "date") {
2252
2375
  tag.date = values.length === 1 ? values[0] : values;
2253
2376
  }
2377
+ if (propKey === "trackNumber") {
2378
+ tag.trackNumber = values.length === 1 ? values[0] : values;
2379
+ }
2254
2380
  } else {
2255
2381
  tag[tagField] = values;
2256
2382
  }
2257
2383
  }
2384
+ for (const [propKey, totalField] of [
2385
+ ["trackNumber", "totalTracks"],
2386
+ ["discNumber", "totalDiscs"]
2387
+ ]) {
2388
+ if (tag[totalField] !== void 0) continue;
2389
+ const raw = props[propKey]?.[0];
2390
+ const slash = raw?.indexOf("/") ?? -1;
2391
+ if (raw === void 0 || slash === -1) continue;
2392
+ const total = parseLeadingInt(raw.slice(slash + 1));
2393
+ if (total !== void 0) tag[totalField] = total;
2394
+ }
2258
2395
  for (const [key, values] of Object.entries(props)) {
2259
2396
  if (BASIC_PROPERTY_KEYS[key]) continue;
2260
2397
  if (!values || values.length === 0) continue;
2261
2398
  const camelKey = fromTagLibKey(key);
2262
2399
  if (NUMERIC_FIELDS.has(camelKey)) {
2263
- const num = parseNumeric(values[0]);
2400
+ const num = parseLeadingInt(values[0]);
2264
2401
  if (num !== void 0) tag[camelKey] = num;
2265
2402
  } else if (camelKey === "compilation") {
2266
2403
  tag[camelKey] = values[0] === "1";
@@ -2273,8 +2410,29 @@ function mapPropertiesToExtendedTag(props) {
2273
2410
  function mergeTagUpdates(file, tags) {
2274
2411
  const currentProps = file.properties();
2275
2412
  const newProps = normalizeTagInput(tags);
2413
+ reconcilePairFields(currentProps, newProps, tags);
2276
2414
  file.setProperties({ ...currentProps, ...newProps });
2277
2415
  }
2416
+ function reconcilePairFields(currentProps, newProps, input) {
2417
+ const pairs = [
2418
+ ["trackNumber", "totalTracks", "track"],
2419
+ ["discNumber", "totalDiscs", "discNumber"]
2420
+ ];
2421
+ for (const [rawKey, totalKey, numericKey] of pairs) {
2422
+ const incoming = newProps[rawKey]?.[0];
2423
+ if (incoming?.includes("/")) {
2424
+ newProps[totalKey] = [];
2425
+ continue;
2426
+ }
2427
+ const numericOnly = input[numericKey] !== void 0 && input[rawKey] === void 0;
2428
+ if (!numericOnly || incoming === void 0) continue;
2429
+ const existing = currentProps[rawKey]?.[0];
2430
+ const slash = existing?.indexOf("/") ?? -1;
2431
+ if (existing !== void 0 && slash !== -1) {
2432
+ newProps[rawKey] = [`${incoming}${existing.slice(slash)}`];
2433
+ }
2434
+ }
2435
+ }
2278
2436
  function normalizeTagInput(input) {
2279
2437
  const props = {};
2280
2438
  for (const field of [
@@ -2297,6 +2455,9 @@ function normalizeTagInput(input) {
2297
2455
  if (input.track !== void 0) {
2298
2456
  props.trackNumber = [String(input.track)];
2299
2457
  }
2458
+ if (input.trackNumber !== void 0) {
2459
+ props.trackNumber = Array.isArray(input.trackNumber) ? input.trackNumber : [input.trackNumber];
2460
+ }
2300
2461
  for (const [field, val] of Object.entries(input)) {
2301
2462
  if (BASIC_FIELDS.has(field) || val === void 0) continue;
2302
2463
  if (field === "compilation") {
@@ -2309,6 +2470,12 @@ function normalizeTagInput(input) {
2309
2470
  props[field] = val;
2310
2471
  }
2311
2472
  }
2473
+ for (const [rawField, totalField] of [
2474
+ ["trackNumber", "totalTracks"],
2475
+ ["discNumber", "totalDiscs"]
2476
+ ]) {
2477
+ if (props[rawField]?.[0]?.includes("/")) delete props[totalField];
2478
+ }
2312
2479
  return props;
2313
2480
  }
2314
2481
  var BASIC_PROPERTY_KEYS, BASIC_FIELDS, NUMERIC_FIELDS;
@@ -2316,6 +2483,7 @@ var init_tag_mapping = __esm({
2316
2483
  "src/utils/tag-mapping.ts"() {
2317
2484
  "use strict";
2318
2485
  init_properties();
2486
+ init_mirror_fields();
2319
2487
  BASIC_PROPERTY_KEYS = {
2320
2488
  title: "title",
2321
2489
  artist: "artist",
@@ -2333,7 +2501,8 @@ var init_tag_mapping = __esm({
2333
2501
  "genre",
2334
2502
  "year",
2335
2503
  "date",
2336
- "track"
2504
+ "track",
2505
+ "trackNumber"
2337
2506
  ]);
2338
2507
  NUMERIC_FIELDS = /* @__PURE__ */ new Set([
2339
2508
  "year",
@@ -2351,7 +2520,7 @@ var VERSION;
2351
2520
  var init_version = __esm({
2352
2521
  "src/version.ts"() {
2353
2522
  "use strict";
2354
- VERSION = "1.5.3";
2523
+ VERSION = "1.6.0";
2355
2524
  }
2356
2525
  });
2357
2526
 
@@ -3,18 +3,22 @@
3
3
  * These are properties that contain multiple fields and cannot be
4
4
  * represented as simple string values.
5
5
  *
6
+ * These constants describe the shape of each complex property. Values are read
7
+ * and written through dedicated typed accessors, not a generic property call.
8
+ *
6
9
  * @example
7
10
  * ```typescript
8
- * import { COMPLEX_PROPERTIES, COMPLEX_PROPERTY_KEY, Rating } from 'taglib-wasm';
11
+ * import type { Rating } from 'taglib-wasm';
9
12
  *
10
- * // Type-safe property access
11
- * const ratings = file.getComplexProperty(COMPLEX_PROPERTY_KEY.RATING);
13
+ * // Read via the typed accessor
14
+ * const ratings = file.getRatings();
12
15
  * console.log(ratings[0].rating); // 0.0-1.0 normalized
13
16
  *
14
17
  * // Set a rating
15
- * file.setComplexProperty(COMPLEX_PROPERTY_KEY.RATING, [
16
- * { rating: 0.8, email: "user@example.com" }
17
- * ]);
18
+ * file.setRating(0.8, "user@example.com");
19
+ *
20
+ * // Or replace the full list
21
+ * file.setRatings([{ rating: 0.8, email: "user@example.com" }]);
18
22
  * ```
19
23
  */
20
24
  import type { Picture } from "../types.js";
@@ -74,13 +78,15 @@ export interface UnsyncedLyrics {
74
78
  export type VariantMap = Record<string, unknown>;
75
79
  /**
76
80
  * Type map linking complex property keys to their value types.
77
- * Enables type-safe generic methods for getComplexProperty/setComplexProperty.
81
+ *
82
+ * This is a descriptive type map, not the accessor surface — each complex
83
+ * property is reached through its own typed method:
78
84
  *
79
85
  * @example
80
86
  * ```typescript
81
- * // TypeScript knows the return type based on key
82
- * const ratings = file.getComplexProperty("RATING"); // Rating[]
83
- * const pictures = file.getComplexProperty("PICTURE"); // Picture[]
87
+ * const ratings = file.getRatings(); // Rating[]
88
+ * const pictures = file.getPictures(); // Picture[]
89
+ * const chapters = file.getChapters(); // Chapter[]
84
90
  * ```
85
91
  *
86
92
  * Consumers can extend via module augmentation:
@@ -177,8 +183,8 @@ export type ComplexPropertyKeyMap = {
177
183
  *
178
184
  * @example
179
185
  * ```typescript
180
- * // Instead of: file.getComplexProperty(COMPLEX_PROPERTIES.RATING.key)
181
- * file.getComplexProperty(COMPLEX_PROPERTY_KEY.RATING);
186
+ * // Instead of: COMPLEX_PROPERTIES.RATING.key
187
+ * COMPLEX_PROPERTY_KEY.RATING; // "RATING"
182
188
  * ```
183
189
  */
184
190
  export declare const COMPLEX_PROPERTY_KEY: ComplexPropertyKeyMap;
@@ -1 +1 @@
1
- {"version":3,"file":"complex-properties.d.ts","sourceRoot":"","sources":["../../../src/constants/complex-properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM;IACrB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,IAAI,EAAE,UAAU,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,MAAM,WAAW,uBAAuB;IACtC,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,uBAAuB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CrB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,CAAC,IAAI,kBAAkB,GAAG,CAAC;CACtC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,qBAKzB,CAAC"}
1
+ {"version":3,"file":"complex-properties.d.ts","sourceRoot":"","sources":["../../../src/constants/complex-properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;;;;;;;GAQG;AACH,MAAM,WAAW,MAAM;IACrB,6DAA6D;IAC7D,MAAM,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,UAAU;IACzB,qEAAqE;IACrE,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,IAAI,EAAE,UAAU,CAAC;IACjB;;;;OAIG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;GAGG;AACH,MAAM,WAAW,cAAc;IAC7B,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,kCAAkC;IAClC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0DAA0D;IAC1D,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;GAGG;AACH,MAAM,MAAM,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,WAAW,uBAAuB;IACtC,oCAAoC;IACpC,OAAO,EAAE,OAAO,CAAC;IACjB,8CAA8C;IAC9C,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,MAAM,EAAE,cAAc,CAAC;IACvB,0CAA0C;IAC1C,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,kBAAkB,GAAG,MAAM,uBAAuB,CAAC;AAE/D;;;;;GAKG;AACH,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA4CrB,CAAC;AAEX;;GAEG;AACH,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,EAAE,CAAC,IAAI,kBAAkB,GAAG,CAAC;CACtC,CAAC;AAEF;;;;;;;;;GASG;AACH,eAAO,MAAM,oBAAoB,EAAE,qBAKzB,CAAC"}
@@ -395,6 +395,20 @@ export declare const PROPERTIES: {
395
395
  readonly mp4: "----:com.apple.iTunes:iTunNORM";
396
396
  };
397
397
  };
398
+ readonly appleGaplessInfo: {
399
+ readonly key: "ITUNSMPB";
400
+ readonly description: "Apple gapless playback data (encoder delay and padding)";
401
+ readonly type: "string";
402
+ readonly supportedFormats: readonly ["ID3v2", "MP4", "Vorbis"];
403
+ readonly mappings: {
404
+ readonly id3v2: {
405
+ readonly frame: "TXXX";
406
+ readonly description: "iTunSMPB";
407
+ };
408
+ readonly vorbis: "ITUNSMPB";
409
+ readonly mp4: "----:com.apple.iTunes:iTunSMPB";
410
+ };
411
+ };
398
412
  readonly albumArtist: {
399
413
  readonly key: "ALBUMARTIST";
400
414
  readonly description: "The album artist (band/orchestra/ensemble)";
@@ -775,6 +789,16 @@ export type PropertyKey = keyof typeof PROPERTIES;
775
789
  export type PropertyValue<K extends PropertyKey> = typeof PROPERTIES[K]["type"] extends "string" ? string : typeof PROPERTIES[K]["type"] extends "number" ? number : typeof PROPERTIES[K]["type"] extends "boolean" ? boolean : string;
776
790
  /** Translate a camelCase property key to TagLib's ALL_CAPS wire key. Unknown keys pass through. */
777
791
  export declare function toTagLibKey(key: string): string;
792
+ /**
793
+ * TagLib wire key for a standard MP4 atom name, or `undefined` for a freeform
794
+ * atom or one we do not model.
795
+ */
796
+ export declare function mp4AtomWireKey(atom: string): string | undefined;
797
+ /**
798
+ * Exact MP4 atom names for the given TagLib wire keys, for keys backed by a
799
+ * mixed-case freeform atom. Empty when none apply, which is the common case.
800
+ */
801
+ export declare function mp4FreeformAtomNames(wireKeys: Iterable<string>): string[];
778
802
  /** Translate a TagLib ALL_CAPS wire key to a camelCase property key. Unknown keys pass through. */
779
803
  export declare function fromTagLibKey(key: string): string;
780
804
  /** Remap all keys of an object from TagLib ALL_CAPS to camelCase. */
@@ -1 +1 @@
1
- {"version":3,"file":"properties.d.ts","sourceRoot":"","sources":["../../../src/constants/properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKb,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,UAAU,CAAC;AAElD;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,WAAW,IAC7C,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,QAAQ,GAAG,MAAM,GAClD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,QAAQ,GAAG,MAAM,GACtD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GAAG,OAAO,GACxD,MAAM,CAAC;AAmBb,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAED,mGAAmG;AACnG,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GACrB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAMnB;AAGD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"properties.d.ts","sourceRoot":"","sources":["../../../src/constants/properties.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH,eAAO,MAAM,UAAU;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAKb,CAAC;AAEX;;;GAGG;AACH,MAAM,MAAM,WAAW,GAAG,MAAM,OAAO,UAAU,CAAC;AAElD;;;GAGG;AACH,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,WAAW,IAC7C,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,QAAQ,GAAG,MAAM,GAClD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,QAAQ,GAAG,MAAM,GACtD,OAAO,UAAU,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,SAAS,SAAS,GAAG,OAAO,GACxD,MAAM,CAAC;AAmBb,mGAAmG;AACnG,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAE/C;AAsDD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAE/D;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,QAAQ,EAAE,QAAQ,CAAC,MAAM,CAAC,GAAG,MAAM,EAAE,CAOzE;AAED,mGAAmG;AACnG,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,qEAAqE;AACrE,wBAAgB,mBAAmB,CAAC,CAAC,EACnC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,GACrB,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAMnB;AAGD,YAAY,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
@@ -21,6 +21,38 @@ _fromTagLib["disc"] = "discNumber";
21
21
  function toTagLibKey(key) {
22
22
  return _toTagLib[key] ?? key;
23
23
  }
24
+ const _mp4FreeformAtoms = {};
25
+ for (const meta of Object.values(PROPERTIES)) {
26
+ const { key, mappings } = meta;
27
+ const atom = mappings?.mp4;
28
+ if (typeof atom !== "string" || !atom.startsWith("----:")) continue;
29
+ const bare = atom.slice(atom.lastIndexOf(":") + 1);
30
+ if (bare === bare.toUpperCase()) continue;
31
+ _mp4FreeformAtoms[key] = atom;
32
+ const readKey = bare.toUpperCase();
33
+ const camel = _fromTagLib[key];
34
+ if (camel !== void 0 && _fromTagLib[readKey] === void 0) {
35
+ _fromTagLib[readKey] = camel;
36
+ }
37
+ }
38
+ const _mp4AtomToWireKey = {};
39
+ for (const meta of Object.values(PROPERTIES)) {
40
+ const { key, mappings } = meta;
41
+ const atom = mappings?.mp4;
42
+ if (typeof atom !== "string" || atom.startsWith("----:")) continue;
43
+ if (_mp4AtomToWireKey[atom] === void 0) _mp4AtomToWireKey[atom] = key;
44
+ }
45
+ function mp4AtomWireKey(atom) {
46
+ return _mp4AtomToWireKey[atom];
47
+ }
48
+ function mp4FreeformAtomNames(wireKeys) {
49
+ const names = [];
50
+ for (const key of wireKeys) {
51
+ const atom = _mp4FreeformAtoms[key];
52
+ if (atom !== void 0) names.push(atom);
53
+ }
54
+ return names;
55
+ }
24
56
  function fromTagLibKey(key) {
25
57
  return _fromTagLib[key] ?? key;
26
58
  }
@@ -34,6 +66,8 @@ function remapKeysFromTagLib(obj) {
34
66
  export {
35
67
  PROPERTIES,
36
68
  fromTagLibKey,
69
+ mp4AtomWireKey,
70
+ mp4FreeformAtomNames,
37
71
  remapKeysFromTagLib,
38
72
  toTagLibKey
39
73
  };
@@ -157,5 +157,28 @@ export declare const SPECIALIZED_PROPERTIES: {
157
157
  readonly mp4: "----:com.apple.iTunes:iTunNORM";
158
158
  };
159
159
  };
160
+ /**
161
+ * Apple gapless-playback data (encoder delay, padding, original sample count).
162
+ *
163
+ * Exists so the two Apple freeform atoms are presented consistently: without
164
+ * it, `properties()` answered `appleSoundCheck` for iTunNORM but the raw
165
+ * uppercased `ITUNSMPB` for its sibling, because only one had an alias. The
166
+ * name mirrors `appleSoundCheck` — vendor-prefixed, feature-named rather than
167
+ * atom-named (tuneup-ibo).
168
+ */
169
+ readonly appleGaplessInfo: {
170
+ readonly key: "ITUNSMPB";
171
+ readonly description: "Apple gapless playback data (encoder delay and padding)";
172
+ readonly type: "string";
173
+ readonly supportedFormats: readonly ["ID3v2", "MP4", "Vorbis"];
174
+ readonly mappings: {
175
+ readonly id3v2: {
176
+ readonly frame: "TXXX";
177
+ readonly description: "iTunSMPB";
178
+ };
179
+ readonly vorbis: "ITUNSMPB";
180
+ readonly mp4: "----:com.apple.iTunes:iTunSMPB";
181
+ };
182
+ };
160
183
  };
161
184
  //# sourceMappingURL=specialized-properties.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"specialized-properties.d.ts","sourceRoot":"","sources":["../../../src/constants/specialized-properties.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiIzB,CAAC"}
1
+ {"version":3,"file":"specialized-properties.d.ts","sourceRoot":"","sources":["../../../src/constants/specialized-properties.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IAiIjC;;;;;;;;OAQG;;;;;;;;;;;;;;;CAYK,CAAC"}