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.
- package/LICENSE +12 -9
- package/README.md +6 -3
- package/dist/index.browser.js +402 -90
- package/dist/simple.browser.js +400 -89
- package/dist/src/constants/complex-properties.d.ts +18 -12
- package/dist/src/constants/complex-properties.d.ts.map +1 -1
- package/dist/src/constants/properties.d.ts +24 -0
- package/dist/src/constants/properties.d.ts.map +1 -1
- package/dist/src/constants/properties.js +34 -0
- package/dist/src/constants/specialized-properties.d.ts +23 -0
- package/dist/src/constants/specialized-properties.d.ts.map +1 -1
- package/dist/src/constants/specialized-properties.js +20 -0
- package/dist/src/constants/tags.d.ts.map +1 -1
- package/dist/src/constants/tags.js +2 -1
- package/dist/src/msgpack/encoder.d.ts.map +1 -1
- package/dist/src/msgpack/encoder.js +6 -4
- package/dist/src/runtime/detector.d.ts +0 -6
- package/dist/src/runtime/detector.d.ts.map +1 -1
- package/dist/src/runtime/detector.js +1 -14
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts +2 -0
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/file-handle.js +73 -52
- package/dist/src/taglib/audio-file-base.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-base.js +33 -77
- package/dist/src/taglib/load-audio-data.d.ts.map +1 -1
- package/dist/src/taglib/load-audio-data.js +19 -5
- package/dist/src/taglib/metadata-extent.d.ts +41 -0
- package/dist/src/taglib/metadata-extent.d.ts.map +1 -0
- package/dist/src/taglib/metadata-extent.js +128 -0
- package/dist/src/taglib/mp4-item-names.d.ts +12 -0
- package/dist/src/taglib/mp4-item-names.d.ts.map +1 -0
- package/dist/src/taglib/mp4-item-names.js +4 -0
- package/dist/src/taglib/mutable-tag-impl.d.ts +14 -0
- package/dist/src/taglib/mutable-tag-impl.d.ts.map +1 -0
- package/dist/src/taglib/mutable-tag-impl.js +88 -0
- package/dist/src/taglib/save-reconstruct.d.ts.map +1 -1
- package/dist/src/taglib/save-reconstruct.js +4 -0
- package/dist/src/types/metadata-mappings.d.ts.map +1 -1
- package/dist/src/types/metadata-mappings.js +14 -0
- package/dist/src/types/tags.d.ts +16 -0
- package/dist/src/types/tags.d.ts.map +1 -1
- package/dist/src/utils/mirror-fields.d.ts +79 -0
- package/dist/src/utils/mirror-fields.d.ts.map +1 -0
- package/dist/src/utils/mirror-fields.js +67 -0
- package/dist/src/utils/tag-mapping.d.ts.map +1 -1
- package/dist/src/utils/tag-mapping.js +47 -5
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/taglib-wasi.wasm +0 -0
- package/dist/taglib-web.wasm +0 -0
- package/dist/taglib-wrapper.js +1 -1
- package/package.json +3 -3
package/dist/index.browser.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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(
|
|
2264
|
-
combined.set(
|
|
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
|
-
|
|
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/
|
|
2289
|
-
function
|
|
2290
|
-
const parsed = Number.parseInt(
|
|
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 =
|
|
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 =
|
|
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.
|
|
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
|
|
3417
|
+
return VALID_TAG_NAMES.has(name);
|
|
3106
3418
|
}
|
|
3107
3419
|
function getAllTagNames() {
|
|
3108
3420
|
return Object.values(Tags);
|