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/simple.browser.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
|
@@ -2180,6 +2289,137 @@ var init_audio_file_impl = __esm({
|
|
|
2180
2289
|
}
|
|
2181
2290
|
});
|
|
2182
2291
|
|
|
2292
|
+
// src/taglib/metadata-extent.ts
|
|
2293
|
+
function be32(b, offset) {
|
|
2294
|
+
return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
|
|
2295
|
+
}
|
|
2296
|
+
function startsWith(bytes, magic, at = 0) {
|
|
2297
|
+
if (bytes.length < at + magic.length) return false;
|
|
2298
|
+
for (let i = 0; i < magic.length; i++) {
|
|
2299
|
+
if (bytes[at + i] !== magic.charCodeAt(i)) return false;
|
|
2300
|
+
}
|
|
2301
|
+
return true;
|
|
2302
|
+
}
|
|
2303
|
+
function id3v2End(bytes) {
|
|
2304
|
+
if (bytes.length < 10) return void 0;
|
|
2305
|
+
const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
|
|
2306
|
+
const hasFooter = (bytes[5] & 16) !== 0;
|
|
2307
|
+
return 10 + size + (hasFooter ? 10 : 0);
|
|
2308
|
+
}
|
|
2309
|
+
function flacEnd(bytes, limit, start) {
|
|
2310
|
+
let offset = start + 4;
|
|
2311
|
+
for (; ; ) {
|
|
2312
|
+
if (offset + 4 > bytes.length) return void 0;
|
|
2313
|
+
const isLast = (bytes[offset] & 128) !== 0;
|
|
2314
|
+
const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
2315
|
+
offset += 4 + length;
|
|
2316
|
+
if (offset > limit) return offset;
|
|
2317
|
+
if (isLast) return offset;
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
function mp4MoovEnd(bytes, limit, start) {
|
|
2321
|
+
let offset = start;
|
|
2322
|
+
const readable = Math.min(bytes.length, limit);
|
|
2323
|
+
while (offset + 8 <= readable) {
|
|
2324
|
+
const size = be32(bytes, offset);
|
|
2325
|
+
const type = String.fromCharCode(
|
|
2326
|
+
bytes[offset + 4],
|
|
2327
|
+
bytes[offset + 5],
|
|
2328
|
+
bytes[offset + 6],
|
|
2329
|
+
bytes[offset + 7]
|
|
2330
|
+
);
|
|
2331
|
+
if (size < 8) return void 0;
|
|
2332
|
+
if (type === "moov") return offset + size;
|
|
2333
|
+
offset += size;
|
|
2334
|
+
}
|
|
2335
|
+
return void 0;
|
|
2336
|
+
}
|
|
2337
|
+
function oggMetadataEnd(bytes, limit, start) {
|
|
2338
|
+
let offset = start;
|
|
2339
|
+
let packetsCompleted = 0;
|
|
2340
|
+
let first = true;
|
|
2341
|
+
for (; ; ) {
|
|
2342
|
+
if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
|
|
2343
|
+
return void 0;
|
|
2344
|
+
}
|
|
2345
|
+
const segmentCount = bytes[offset + 26];
|
|
2346
|
+
const tableStart = offset + 27;
|
|
2347
|
+
if (tableStart + segmentCount > bytes.length) return void 0;
|
|
2348
|
+
let payload = 0;
|
|
2349
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
2350
|
+
const segment = bytes[tableStart + i];
|
|
2351
|
+
payload += segment;
|
|
2352
|
+
if (segment < 255) packetsCompleted++;
|
|
2353
|
+
}
|
|
2354
|
+
if (first) {
|
|
2355
|
+
const payloadStart = tableStart + segmentCount;
|
|
2356
|
+
if (payloadStart + 5 > bytes.length) return void 0;
|
|
2357
|
+
if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
|
|
2358
|
+
return void 0;
|
|
2359
|
+
}
|
|
2360
|
+
first = false;
|
|
2361
|
+
}
|
|
2362
|
+
offset = tableStart + segmentCount + payload;
|
|
2363
|
+
if (packetsCompleted >= 2 || offset > limit) return offset;
|
|
2364
|
+
}
|
|
2365
|
+
}
|
|
2366
|
+
function isMpegFrameSync(bytes, at = 0) {
|
|
2367
|
+
if (bytes.length < at + 4) return false;
|
|
2368
|
+
if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return false;
|
|
2369
|
+
const version = bytes[at + 1] >> 3 & 3;
|
|
2370
|
+
const layer = bytes[at + 1] >> 1 & 3;
|
|
2371
|
+
if (version === 1 || layer === 0) return false;
|
|
2372
|
+
const bitrateIndex = bytes[at + 2] >> 4 & 15;
|
|
2373
|
+
const sampleRateIndex = bytes[at + 2] >> 2 & 3;
|
|
2374
|
+
return bitrateIndex !== 0 && bitrateIndex !== 15 && sampleRateIndex !== 3;
|
|
2375
|
+
}
|
|
2376
|
+
function containerEnd(bytes, at, limit) {
|
|
2377
|
+
if (at >= bytes.length) return bytes.length < limit ? null : void 0;
|
|
2378
|
+
if (at + 8 > bytes.length) return void 0;
|
|
2379
|
+
if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
|
|
2380
|
+
if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
|
|
2381
|
+
if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
|
|
2382
|
+
return null;
|
|
2383
|
+
}
|
|
2384
|
+
function metadataFitsInHeader(header, limit) {
|
|
2385
|
+
let end;
|
|
2386
|
+
if (startsWith(header, "ID3")) {
|
|
2387
|
+
const tagEnd = id3v2End(header);
|
|
2388
|
+
if (tagEnd === void 0) return false;
|
|
2389
|
+
end = tagEnd;
|
|
2390
|
+
if (tagEnd <= limit) {
|
|
2391
|
+
const behind = containerEnd(header, tagEnd, limit);
|
|
2392
|
+
if (behind === void 0) return false;
|
|
2393
|
+
if (behind !== null) end = behind;
|
|
2394
|
+
}
|
|
2395
|
+
} else {
|
|
2396
|
+
const container = containerEnd(header, 0, limit);
|
|
2397
|
+
if (container === null) {
|
|
2398
|
+
end = isMpegFrameSync(header) ? 0 : void 0;
|
|
2399
|
+
} else {
|
|
2400
|
+
end = container;
|
|
2401
|
+
}
|
|
2402
|
+
}
|
|
2403
|
+
if (end === void 0 || end > header.length) return false;
|
|
2404
|
+
return end <= limit;
|
|
2405
|
+
}
|
|
2406
|
+
function trailerFitsInFooter(tail, footerSize) {
|
|
2407
|
+
if (tail.length < Math.min(footerSize, 32)) return false;
|
|
2408
|
+
for (const offsetFromEnd of [32, 32 + 128]) {
|
|
2409
|
+
const at = tail.length - offsetFromEnd;
|
|
2410
|
+
if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
|
|
2411
|
+
const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
|
|
2412
|
+
const trailing = offsetFromEnd === 32 ? 0 : 128;
|
|
2413
|
+
if (size + 32 + trailing > footerSize) return false;
|
|
2414
|
+
}
|
|
2415
|
+
return true;
|
|
2416
|
+
}
|
|
2417
|
+
var init_metadata_extent = __esm({
|
|
2418
|
+
"src/taglib/metadata-extent.ts"() {
|
|
2419
|
+
"use strict";
|
|
2420
|
+
}
|
|
2421
|
+
});
|
|
2422
|
+
|
|
2183
2423
|
// src/taglib/load-audio-data.ts
|
|
2184
2424
|
async function loadAudioData(input, opts) {
|
|
2185
2425
|
if (opts.partial && typeof File !== "undefined" && input instanceof File) {
|
|
@@ -2188,12 +2428,20 @@ async function loadAudioData(input, opts) {
|
|
|
2188
2428
|
if (input.size <= headerSize + footerSize) {
|
|
2189
2429
|
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
2190
2430
|
}
|
|
2191
|
-
const header =
|
|
2431
|
+
const header = new Uint8Array(
|
|
2432
|
+
await input.slice(0, headerSize).arrayBuffer()
|
|
2433
|
+
);
|
|
2434
|
+
if (!metadataFitsInHeader(header, headerSize)) {
|
|
2435
|
+
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
2436
|
+
}
|
|
2192
2437
|
const footerStart = Math.max(0, input.size - footerSize);
|
|
2193
|
-
const footer = await input.slice(footerStart).arrayBuffer();
|
|
2438
|
+
const footer = new Uint8Array(await input.slice(footerStart).arrayBuffer());
|
|
2439
|
+
if (!trailerFitsInFooter(footer, footerSize)) {
|
|
2440
|
+
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
2441
|
+
}
|
|
2194
2442
|
const combined = new Uint8Array(header.byteLength + footer.byteLength);
|
|
2195
|
-
combined.set(
|
|
2196
|
-
combined.set(
|
|
2443
|
+
combined.set(header, 0);
|
|
2444
|
+
combined.set(footer, header.byteLength);
|
|
2197
2445
|
return { data: combined, isPartiallyLoaded: true };
|
|
2198
2446
|
}
|
|
2199
2447
|
if (opts.partial && typeof input === "string") {
|
|
@@ -2204,7 +2452,9 @@ async function loadAudioData(input, opts) {
|
|
|
2204
2452
|
opts.maxHeaderSize,
|
|
2205
2453
|
opts.maxFooterSize
|
|
2206
2454
|
);
|
|
2207
|
-
|
|
2455
|
+
if (metadataFitsInHeader(data, opts.maxHeaderSize) && trailerFitsInFooter(data, opts.maxFooterSize)) {
|
|
2456
|
+
return { data, isPartiallyLoaded: true };
|
|
2457
|
+
}
|
|
2208
2458
|
}
|
|
2209
2459
|
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
2210
2460
|
}
|
|
@@ -2214,14 +2464,29 @@ var init_load_audio_data = __esm({
|
|
|
2214
2464
|
"src/taglib/load-audio-data.ts"() {
|
|
2215
2465
|
"use strict";
|
|
2216
2466
|
init_file();
|
|
2467
|
+
init_metadata_extent();
|
|
2217
2468
|
}
|
|
2218
2469
|
});
|
|
2219
2470
|
|
|
2220
|
-
// src/utils/
|
|
2221
|
-
function
|
|
2222
|
-
const parsed = Number.parseInt(
|
|
2471
|
+
// src/utils/mirror-fields.ts
|
|
2472
|
+
function parseLeadingInt(raw) {
|
|
2473
|
+
const parsed = Number.parseInt(raw, 10);
|
|
2223
2474
|
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
2224
2475
|
}
|
|
2476
|
+
var MIRROR_FIELDS, DATE_MIRROR, TRACK_MIRROR;
|
|
2477
|
+
var init_mirror_fields = __esm({
|
|
2478
|
+
"src/utils/mirror-fields.ts"() {
|
|
2479
|
+
"use strict";
|
|
2480
|
+
MIRROR_FIELDS = [
|
|
2481
|
+
{ raw: "date", numeric: "year" },
|
|
2482
|
+
{ raw: "trackNumber", numeric: "track" }
|
|
2483
|
+
];
|
|
2484
|
+
DATE_MIRROR = MIRROR_FIELDS[0];
|
|
2485
|
+
TRACK_MIRROR = MIRROR_FIELDS[1];
|
|
2486
|
+
}
|
|
2487
|
+
});
|
|
2488
|
+
|
|
2489
|
+
// src/utils/tag-mapping.ts
|
|
2225
2490
|
function readExtendedTag(audioFile) {
|
|
2226
2491
|
const tag = mapPropertiesToExtendedTag(audioFile.properties());
|
|
2227
2492
|
const pictures = audioFile.getPictures();
|
|
@@ -2246,21 +2511,35 @@ function mapPropertiesToExtendedTag(props) {
|
|
|
2246
2511
|
const values = props[propKey];
|
|
2247
2512
|
if (!values || values.length === 0) continue;
|
|
2248
2513
|
if (tagField === "year" || tagField === "track") {
|
|
2249
|
-
const num =
|
|
2514
|
+
const num = parseLeadingInt(values[0]);
|
|
2250
2515
|
if (num !== void 0) tag[tagField] = num;
|
|
2251
2516
|
if (propKey === "date") {
|
|
2252
2517
|
tag.date = values.length === 1 ? values[0] : values;
|
|
2253
2518
|
}
|
|
2519
|
+
if (propKey === "trackNumber") {
|
|
2520
|
+
tag.trackNumber = values.length === 1 ? values[0] : values;
|
|
2521
|
+
}
|
|
2254
2522
|
} else {
|
|
2255
2523
|
tag[tagField] = values;
|
|
2256
2524
|
}
|
|
2257
2525
|
}
|
|
2526
|
+
for (const [propKey, totalField] of [
|
|
2527
|
+
["trackNumber", "totalTracks"],
|
|
2528
|
+
["discNumber", "totalDiscs"]
|
|
2529
|
+
]) {
|
|
2530
|
+
if (tag[totalField] !== void 0) continue;
|
|
2531
|
+
const raw = props[propKey]?.[0];
|
|
2532
|
+
const slash = raw?.indexOf("/") ?? -1;
|
|
2533
|
+
if (raw === void 0 || slash === -1) continue;
|
|
2534
|
+
const total = parseLeadingInt(raw.slice(slash + 1));
|
|
2535
|
+
if (total !== void 0) tag[totalField] = total;
|
|
2536
|
+
}
|
|
2258
2537
|
for (const [key, values] of Object.entries(props)) {
|
|
2259
2538
|
if (BASIC_PROPERTY_KEYS[key]) continue;
|
|
2260
2539
|
if (!values || values.length === 0) continue;
|
|
2261
2540
|
const camelKey = fromTagLibKey(key);
|
|
2262
2541
|
if (NUMERIC_FIELDS.has(camelKey)) {
|
|
2263
|
-
const num =
|
|
2542
|
+
const num = parseLeadingInt(values[0]);
|
|
2264
2543
|
if (num !== void 0) tag[camelKey] = num;
|
|
2265
2544
|
} else if (camelKey === "compilation") {
|
|
2266
2545
|
tag[camelKey] = values[0] === "1";
|
|
@@ -2273,8 +2552,29 @@ function mapPropertiesToExtendedTag(props) {
|
|
|
2273
2552
|
function mergeTagUpdates(file, tags) {
|
|
2274
2553
|
const currentProps = file.properties();
|
|
2275
2554
|
const newProps = normalizeTagInput(tags);
|
|
2555
|
+
reconcilePairFields(currentProps, newProps, tags);
|
|
2276
2556
|
file.setProperties({ ...currentProps, ...newProps });
|
|
2277
2557
|
}
|
|
2558
|
+
function reconcilePairFields(currentProps, newProps, input) {
|
|
2559
|
+
const pairs = [
|
|
2560
|
+
["trackNumber", "totalTracks", "track"],
|
|
2561
|
+
["discNumber", "totalDiscs", "discNumber"]
|
|
2562
|
+
];
|
|
2563
|
+
for (const [rawKey, totalKey, numericKey] of pairs) {
|
|
2564
|
+
const incoming = newProps[rawKey]?.[0];
|
|
2565
|
+
if (incoming?.includes("/")) {
|
|
2566
|
+
newProps[totalKey] = [];
|
|
2567
|
+
continue;
|
|
2568
|
+
}
|
|
2569
|
+
const numericOnly = input[numericKey] !== void 0 && input[rawKey] === void 0;
|
|
2570
|
+
if (!numericOnly || incoming === void 0) continue;
|
|
2571
|
+
const existing = currentProps[rawKey]?.[0];
|
|
2572
|
+
const slash = existing?.indexOf("/") ?? -1;
|
|
2573
|
+
if (existing !== void 0 && slash !== -1) {
|
|
2574
|
+
newProps[rawKey] = [`${incoming}${existing.slice(slash)}`];
|
|
2575
|
+
}
|
|
2576
|
+
}
|
|
2577
|
+
}
|
|
2278
2578
|
function normalizeTagInput(input) {
|
|
2279
2579
|
const props = {};
|
|
2280
2580
|
for (const field of [
|
|
@@ -2297,6 +2597,9 @@ function normalizeTagInput(input) {
|
|
|
2297
2597
|
if (input.track !== void 0) {
|
|
2298
2598
|
props.trackNumber = [String(input.track)];
|
|
2299
2599
|
}
|
|
2600
|
+
if (input.trackNumber !== void 0) {
|
|
2601
|
+
props.trackNumber = Array.isArray(input.trackNumber) ? input.trackNumber : [input.trackNumber];
|
|
2602
|
+
}
|
|
2300
2603
|
for (const [field, val] of Object.entries(input)) {
|
|
2301
2604
|
if (BASIC_FIELDS.has(field) || val === void 0) continue;
|
|
2302
2605
|
if (field === "compilation") {
|
|
@@ -2309,6 +2612,12 @@ function normalizeTagInput(input) {
|
|
|
2309
2612
|
props[field] = val;
|
|
2310
2613
|
}
|
|
2311
2614
|
}
|
|
2615
|
+
for (const [rawField, totalField] of [
|
|
2616
|
+
["trackNumber", "totalTracks"],
|
|
2617
|
+
["discNumber", "totalDiscs"]
|
|
2618
|
+
]) {
|
|
2619
|
+
if (props[rawField]?.[0]?.includes("/")) delete props[totalField];
|
|
2620
|
+
}
|
|
2312
2621
|
return props;
|
|
2313
2622
|
}
|
|
2314
2623
|
var BASIC_PROPERTY_KEYS, BASIC_FIELDS, NUMERIC_FIELDS;
|
|
@@ -2316,6 +2625,7 @@ var init_tag_mapping = __esm({
|
|
|
2316
2625
|
"src/utils/tag-mapping.ts"() {
|
|
2317
2626
|
"use strict";
|
|
2318
2627
|
init_properties();
|
|
2628
|
+
init_mirror_fields();
|
|
2319
2629
|
BASIC_PROPERTY_KEYS = {
|
|
2320
2630
|
title: "title",
|
|
2321
2631
|
artist: "artist",
|
|
@@ -2333,7 +2643,8 @@ var init_tag_mapping = __esm({
|
|
|
2333
2643
|
"genre",
|
|
2334
2644
|
"year",
|
|
2335
2645
|
"date",
|
|
2336
|
-
"track"
|
|
2646
|
+
"track",
|
|
2647
|
+
"trackNumber"
|
|
2337
2648
|
]);
|
|
2338
2649
|
NUMERIC_FIELDS = /* @__PURE__ */ new Set([
|
|
2339
2650
|
"year",
|
|
@@ -2351,7 +2662,7 @@ var VERSION;
|
|
|
2351
2662
|
var init_version = __esm({
|
|
2352
2663
|
"src/version.ts"() {
|
|
2353
2664
|
"use strict";
|
|
2354
|
-
VERSION = "1.
|
|
2665
|
+
VERSION = "1.6.1";
|
|
2355
2666
|
}
|
|
2356
2667
|
});
|
|
2357
2668
|
|