taglib-wasm 2.2.2 → 2.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +197 -16
- package/dist/index.browser.d.ts +24 -4
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +1139 -217
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/simple.browser.js +539 -209
- package/dist/src/constants/properties.d.ts +2 -2
- package/dist/src/constants/specialized-properties.d.ts +2 -2
- package/dist/src/constants/specialized-properties.js +2 -2
- package/dist/src/errors/base.d.ts +17 -2
- package/dist/src/errors/base.d.ts.map +1 -1
- package/dist/src/errors/base.js +18 -0
- package/dist/src/runtime/detector.d.ts +16 -1
- package/dist/src/runtime/detector.d.ts.map +1 -1
- package/dist/src/runtime/detector.js +58 -52
- package/dist/src/runtime/platform-io.d.ts +8 -0
- package/dist/src/runtime/platform-io.d.ts.map +1 -1
- package/dist/src/runtime/platform-io.js +11 -11
- package/dist/src/runtime/unified-loader/module-selection.d.ts +2 -2
- package/dist/src/runtime/unified-loader/module-selection.d.ts.map +1 -1
- package/dist/src/runtime/unified-loader/module-selection.js +3 -5
- package/dist/src/runtime/wasi-adapter/adapter.d.ts +13 -1
- package/dist/src/runtime/wasi-adapter/adapter.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/adapter.js +17 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +4 -1
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/audio-properties.js +24 -6
- package/dist/src/runtime/wasi-adapter/path-open.d.ts +68 -0
- package/dist/src/runtime/wasi-adapter/path-open.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/path-open.js +76 -0
- package/dist/src/runtime/wasi-adapter/wasm-io.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/wasm-io.js +36 -28
- package/dist/src/runtime/wasi-host-loader.js +8 -3
- package/dist/src/runtime/wasmer-sdk-loader/types.d.ts +13 -0
- package/dist/src/runtime/wasmer-sdk-loader/types.d.ts.map +1 -1
- package/dist/src/simple/checksum-operations.d.ts +25 -0
- package/dist/src/simple/checksum-operations.d.ts.map +1 -0
- package/dist/src/simple/checksum-operations.js +7 -0
- package/dist/src/simple/index.d.ts +2 -0
- package/dist/src/simple/index.d.ts.map +1 -1
- package/dist/src/simple/index.js +2 -0
- package/dist/src/taglib/audio-file-checksum.d.ts +47 -0
- package/dist/src/taglib/audio-file-checksum.d.ts.map +1 -0
- package/dist/src/taglib/audio-file-checksum.js +77 -0
- package/dist/src/taglib/audio-file-impl.d.ts +4 -0
- package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-impl.js +34 -2
- package/dist/src/taglib/audio-file-interface.d.ts +17 -0
- package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
- package/dist/src/taglib/embind-adapter.d.ts.map +1 -1
- package/dist/src/taglib/embind-adapter.js +4 -1
- package/dist/src/taglib/id3v2-frames.d.ts.map +1 -1
- package/dist/src/taglib/id3v2-frames.js +2 -2
- package/dist/src/taglib/media-ranges.d.ts +85 -0
- package/dist/src/taglib/media-ranges.d.ts.map +1 -0
- package/dist/src/taglib/media-ranges.js +192 -0
- package/dist/src/taglib/metadata-extent.d.ts +55 -0
- package/dist/src/taglib/metadata-extent.d.ts.map +1 -1
- package/dist/src/taglib/metadata-extent.js +24 -6
- package/dist/src/taglib/taglib-class.d.ts.map +1 -1
- package/dist/src/taglib/taglib-class.js +13 -24
- package/dist/src/types/audio-formats.d.ts +5 -5
- package/dist/src/types/audio-formats.d.ts.map +1 -1
- package/dist/src/types/format-property-keys.d.ts +1 -1
- package/dist/src/types/format-property-keys.d.ts.map +1 -1
- package/dist/src/types/tags.d.ts +8 -1
- package/dist/src/types/tags.d.ts.map +1 -1
- package/dist/src/utils/path.d.ts +12 -0
- package/dist/src/utils/path.d.ts.map +1 -1
- package/dist/src/utils/path.js +19 -0
- package/dist/src/utils/tag-mapping.d.ts.map +1 -1
- package/dist/src/utils/tag-mapping.js +2 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/wasm.d.ts +9 -0
- package/dist/src/wasm.d.ts.map +1 -1
- package/dist/src/web-utils/data-url.d.ts.map +1 -1
- package/dist/src/web-utils/data-url.js +4 -2
- package/dist/taglib-wasi.wasm +0 -0
- package/dist/taglib-web.wasm +0 -0
- package/dist/web.browser.d.ts +13 -0
- package/dist/web.browser.d.ts.map +1 -0
- package/dist/web.browser.js +3630 -0
- package/package.json +19 -17
package/dist/simple.browser.js
CHANGED
|
@@ -281,11 +281,29 @@ var init_base = __esm({
|
|
|
281
281
|
"use strict";
|
|
282
282
|
SUPPORTED_FORMATS = [
|
|
283
283
|
"MP3",
|
|
284
|
+
"AAC",
|
|
284
285
|
"MP4",
|
|
285
286
|
"M4A",
|
|
286
287
|
"FLAC",
|
|
287
288
|
"OGG",
|
|
289
|
+
"OPUS",
|
|
290
|
+
"OggFLAC",
|
|
291
|
+
"SPEEX",
|
|
288
292
|
"WAV",
|
|
293
|
+
"AIFF",
|
|
294
|
+
"ASF",
|
|
295
|
+
"APE",
|
|
296
|
+
"DSF",
|
|
297
|
+
"DSDIFF",
|
|
298
|
+
"WV",
|
|
299
|
+
"MPC",
|
|
300
|
+
"TTA",
|
|
301
|
+
"SHN",
|
|
302
|
+
"MOD",
|
|
303
|
+
"S3M",
|
|
304
|
+
"IT",
|
|
305
|
+
"XM",
|
|
306
|
+
"MATROSKA",
|
|
289
307
|
"MKA"
|
|
290
308
|
];
|
|
291
309
|
TagLibError = class _TagLibError extends Error {
|
|
@@ -562,8 +580,8 @@ var init_audio_file_bwf = __esm({
|
|
|
562
580
|
|
|
563
581
|
// src/taglib/id3v2-frames.ts
|
|
564
582
|
function assertMp3(format) {
|
|
565
|
-
if (format !== "MP3") {
|
|
566
|
-
throw new UnsupportedFormatError(format, ["MP3"], {
|
|
583
|
+
if (format !== "MP3" && format !== "AAC") {
|
|
584
|
+
throw new UnsupportedFormatError(format, ["MP3", "AAC"], {
|
|
567
585
|
operation: "id3v2Frames"
|
|
568
586
|
});
|
|
569
587
|
}
|
|
@@ -1303,7 +1321,7 @@ var init_specialized_properties = __esm({
|
|
|
1303
1321
|
},
|
|
1304
1322
|
releaseType: {
|
|
1305
1323
|
key: "RELEASETYPE",
|
|
1306
|
-
description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.
|
|
1324
|
+
description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.2 translates the RELEASETYPE key per format: ID3v2 TXXX 'MUSICBRAINZ ALBUM TYPE' (TagLib's description casing), MP4 freeform atom, APEv2 MUSICBRAINZ_ALBUMTYPE, ASF 'MusicBrainz/Album Type', Vorbis/Matroska raw RELEASETYPE",
|
|
1307
1325
|
type: "string",
|
|
1308
1326
|
supportedFormats: [
|
|
1309
1327
|
"ID3v2",
|
|
@@ -1321,7 +1339,7 @@ var init_specialized_properties = __esm({
|
|
|
1321
1339
|
},
|
|
1322
1340
|
releaseCountry: {
|
|
1323
1341
|
key: "RELEASECOUNTRY",
|
|
1324
|
-
description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.
|
|
1342
|
+
description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.2 translates the RELEASECOUNTRY key per format: ID3v2 TXXX 'MUSICBRAINZ ALBUM RELEASE COUNTRY' (TagLib's description casing; an uppercase TXXX:RELEASECOUNTRY description also reads back), MP4 freeform atom 'MusicBrainz Album Release Country', APEv2 raw RELEASECOUNTRY, ASF 'MusicBrainz/Album Release Country', WAV ICNT, Vorbis/Matroska raw RELEASECOUNTRY",
|
|
1325
1343
|
type: "string",
|
|
1326
1344
|
supportedFormats: [
|
|
1327
1345
|
"ID3v2",
|
|
@@ -1875,6 +1893,463 @@ var init_audio_file_base = __esm({
|
|
|
1875
1893
|
}
|
|
1876
1894
|
});
|
|
1877
1895
|
|
|
1896
|
+
// src/taglib/metadata-extent.ts
|
|
1897
|
+
function be32(b, offset) {
|
|
1898
|
+
return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
|
|
1899
|
+
}
|
|
1900
|
+
function startsWith(bytes, magic, at = 0) {
|
|
1901
|
+
if (bytes.length < at + magic.length) return false;
|
|
1902
|
+
for (let i = 0; i < magic.length; i++) {
|
|
1903
|
+
if (bytes[at + i] !== magic.charCodeAt(i)) return false;
|
|
1904
|
+
}
|
|
1905
|
+
return true;
|
|
1906
|
+
}
|
|
1907
|
+
function id3v2End(bytes) {
|
|
1908
|
+
if (!startsWith(bytes, "ID3") || bytes.length < 10) return void 0;
|
|
1909
|
+
const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
|
|
1910
|
+
const hasFooter = (bytes[5] & 16) !== 0;
|
|
1911
|
+
return 10 + size + (hasFooter ? 10 : 0);
|
|
1912
|
+
}
|
|
1913
|
+
function flacEnd(bytes, limit, start) {
|
|
1914
|
+
let offset = start + 4;
|
|
1915
|
+
for (; ; ) {
|
|
1916
|
+
if (offset + 4 > bytes.length) return void 0;
|
|
1917
|
+
const isLast = (bytes[offset] & 128) !== 0;
|
|
1918
|
+
const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
1919
|
+
offset += 4 + length;
|
|
1920
|
+
if (offset > limit) return offset;
|
|
1921
|
+
if (isLast) return offset;
|
|
1922
|
+
}
|
|
1923
|
+
}
|
|
1924
|
+
function flacMarkerOffset(bytes) {
|
|
1925
|
+
const at = startsWith(bytes, "ID3") ? id3v2End(bytes) : 0;
|
|
1926
|
+
if (at === void 0) return void 0;
|
|
1927
|
+
return startsWith(bytes, "fLaC", at) ? at : void 0;
|
|
1928
|
+
}
|
|
1929
|
+
function flacAudioStart(bytes) {
|
|
1930
|
+
const at = flacMarkerOffset(bytes);
|
|
1931
|
+
if (at === void 0) return void 0;
|
|
1932
|
+
return flacEnd(bytes, bytes.length, at);
|
|
1933
|
+
}
|
|
1934
|
+
function mp4MoovEnd(bytes, limit, start) {
|
|
1935
|
+
let offset = start;
|
|
1936
|
+
const readable = Math.min(bytes.length, limit);
|
|
1937
|
+
while (offset + 8 <= readable) {
|
|
1938
|
+
const size = be32(bytes, offset);
|
|
1939
|
+
const type = String.fromCharCode(
|
|
1940
|
+
bytes[offset + 4],
|
|
1941
|
+
bytes[offset + 5],
|
|
1942
|
+
bytes[offset + 6],
|
|
1943
|
+
bytes[offset + 7]
|
|
1944
|
+
);
|
|
1945
|
+
if (size < 8) return void 0;
|
|
1946
|
+
if (type === "moov") return offset + size;
|
|
1947
|
+
offset += size;
|
|
1948
|
+
}
|
|
1949
|
+
return void 0;
|
|
1950
|
+
}
|
|
1951
|
+
function oggMetadataEnd(bytes, limit, start) {
|
|
1952
|
+
let offset = start;
|
|
1953
|
+
let packetsCompleted = 0;
|
|
1954
|
+
let first = true;
|
|
1955
|
+
for (; ; ) {
|
|
1956
|
+
if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
|
|
1957
|
+
return void 0;
|
|
1958
|
+
}
|
|
1959
|
+
const segmentCount = bytes[offset + 26];
|
|
1960
|
+
const tableStart = offset + 27;
|
|
1961
|
+
if (tableStart + segmentCount > bytes.length) return void 0;
|
|
1962
|
+
let payload = 0;
|
|
1963
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
1964
|
+
const segment = bytes[tableStart + i];
|
|
1965
|
+
payload += segment;
|
|
1966
|
+
if (segment < 255) packetsCompleted++;
|
|
1967
|
+
}
|
|
1968
|
+
if (first) {
|
|
1969
|
+
const payloadStart = tableStart + segmentCount;
|
|
1970
|
+
if (payloadStart + 5 > bytes.length) return void 0;
|
|
1971
|
+
if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
|
|
1972
|
+
return void 0;
|
|
1973
|
+
}
|
|
1974
|
+
first = false;
|
|
1975
|
+
}
|
|
1976
|
+
offset = tableStart + segmentCount + payload;
|
|
1977
|
+
if (packetsCompleted >= 2 || offset > limit) return offset;
|
|
1978
|
+
}
|
|
1979
|
+
}
|
|
1980
|
+
function mpegFrameLength(bytes, at) {
|
|
1981
|
+
if (bytes.length < at + 4) return 0;
|
|
1982
|
+
if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return 0;
|
|
1983
|
+
const version = bytes[at + 1] >> 3 & 3;
|
|
1984
|
+
const layer = bytes[at + 1] >> 1 & 3;
|
|
1985
|
+
if (version === 1 || layer === 0) return 0;
|
|
1986
|
+
const bitrateIndex = bytes[at + 2] >> 4 & 15;
|
|
1987
|
+
const sampleRateIndex = bytes[at + 2] >> 2 & 3;
|
|
1988
|
+
if (bitrateIndex === 0 || bitrateIndex === 15 || sampleRateIndex === 3) {
|
|
1989
|
+
return 0;
|
|
1990
|
+
}
|
|
1991
|
+
const mpeg1 = version === 3;
|
|
1992
|
+
const layerIndex = layer ^ 3;
|
|
1993
|
+
const bitrate = MPEG_BITRATES[mpeg1 ? 0 : 1][layerIndex][bitrateIndex];
|
|
1994
|
+
const sampleRateTable = mpeg1 ? 0 : version === 2 ? 1 : 2;
|
|
1995
|
+
const sampleRate = MPEG_SAMPLE_RATES[sampleRateTable][sampleRateIndex];
|
|
1996
|
+
const samplesPerFrame = layerIndex === 2 ? mpeg1 ? 1152 : 576 : layerIndex === 1 ? 1152 : 384;
|
|
1997
|
+
let frameLength = Math.floor(samplesPerFrame * bitrate * 125 / sampleRate);
|
|
1998
|
+
if ((bytes[at + 2] & 2) !== 0) frameLength += layerIndex === 0 ? 4 : 1;
|
|
1999
|
+
return frameLength === 0 ? 0 : frameLength;
|
|
2000
|
+
}
|
|
2001
|
+
function isMpegFrameSync(bytes, at = 0) {
|
|
2002
|
+
const frameLength = mpegFrameLength(bytes, at);
|
|
2003
|
+
if (frameLength === 0) return false;
|
|
2004
|
+
const next = at + frameLength;
|
|
2005
|
+
if (bytes.length < next + 4) return false;
|
|
2006
|
+
const mask = 4294839296;
|
|
2007
|
+
const header = ((bytes[at] << 24 | bytes[at + 1] << 16 | bytes[at + 2] << 8 | bytes[at + 3]) >>> 0 & mask) >>> 0;
|
|
2008
|
+
const nextHeader = ((bytes[next] << 24 | bytes[next + 1] << 16 | bytes[next + 2] << 8 | bytes[next + 3]) >>> 0 & mask) >>> 0;
|
|
2009
|
+
return header === nextHeader;
|
|
2010
|
+
}
|
|
2011
|
+
function containerEnd(bytes, at, limit) {
|
|
2012
|
+
if (at >= bytes.length) return bytes.length < limit ? null : void 0;
|
|
2013
|
+
if (at + 8 > bytes.length) return void 0;
|
|
2014
|
+
if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
|
|
2015
|
+
if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
|
|
2016
|
+
if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
|
|
2017
|
+
return null;
|
|
2018
|
+
}
|
|
2019
|
+
function metadataFitsInHeader(header, limit) {
|
|
2020
|
+
let end;
|
|
2021
|
+
if (startsWith(header, "ID3")) {
|
|
2022
|
+
const tagEnd = id3v2End(header);
|
|
2023
|
+
if (tagEnd === void 0) return false;
|
|
2024
|
+
end = tagEnd;
|
|
2025
|
+
if (tagEnd <= limit) {
|
|
2026
|
+
const behind = containerEnd(header, tagEnd, limit);
|
|
2027
|
+
if (behind === void 0) return false;
|
|
2028
|
+
if (behind !== null) end = behind;
|
|
2029
|
+
}
|
|
2030
|
+
} else {
|
|
2031
|
+
const container = containerEnd(header, 0, limit);
|
|
2032
|
+
if (container === null) {
|
|
2033
|
+
end = isMpegFrameSync(header) ? 0 : void 0;
|
|
2034
|
+
} else {
|
|
2035
|
+
end = container;
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
if (end === void 0 || end > header.length) return false;
|
|
2039
|
+
return end <= limit;
|
|
2040
|
+
}
|
|
2041
|
+
function trailerFitsInFooter(tail, footerSize) {
|
|
2042
|
+
if (tail.length < Math.min(footerSize, 32)) return false;
|
|
2043
|
+
for (const offsetFromEnd of [32, 32 + 128]) {
|
|
2044
|
+
const at = tail.length - offsetFromEnd;
|
|
2045
|
+
if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
|
|
2046
|
+
const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
|
|
2047
|
+
const trailing = offsetFromEnd === 32 ? 0 : 128;
|
|
2048
|
+
if (size + 32 + trailing > footerSize) return false;
|
|
2049
|
+
}
|
|
2050
|
+
return true;
|
|
2051
|
+
}
|
|
2052
|
+
var MPEG_BITRATES, MPEG_SAMPLE_RATES;
|
|
2053
|
+
var init_metadata_extent = __esm({
|
|
2054
|
+
"src/taglib/metadata-extent.ts"() {
|
|
2055
|
+
"use strict";
|
|
2056
|
+
MPEG_BITRATES = [
|
|
2057
|
+
[
|
|
2058
|
+
[0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
2059
|
+
// Layer I
|
|
2060
|
+
[0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
2061
|
+
// Layer II
|
|
2062
|
+
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0]
|
|
2063
|
+
// Layer III
|
|
2064
|
+
],
|
|
2065
|
+
[
|
|
2066
|
+
[0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
|
2067
|
+
// Layer I
|
|
2068
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
2069
|
+
// Layer II
|
|
2070
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
2071
|
+
// Layer III
|
|
2072
|
+
]
|
|
2073
|
+
];
|
|
2074
|
+
MPEG_SAMPLE_RATES = [
|
|
2075
|
+
[44100, 48e3, 32e3],
|
|
2076
|
+
// MPEG-1
|
|
2077
|
+
[22050, 24e3, 16e3],
|
|
2078
|
+
// MPEG-2
|
|
2079
|
+
[11025, 12e3, 8e3]
|
|
2080
|
+
// MPEG-2.5
|
|
2081
|
+
];
|
|
2082
|
+
}
|
|
2083
|
+
});
|
|
2084
|
+
|
|
2085
|
+
// src/taglib/media-ranges.ts
|
|
2086
|
+
function adtsFrameLength(bytes, at) {
|
|
2087
|
+
if (bytes.length < at + 7) return 0;
|
|
2088
|
+
if (bytes[at] !== 255 || (bytes[at + 1] & 246) !== 240) return 0;
|
|
2089
|
+
const length = (bytes[at + 3] & 3) << 11 | bytes[at + 4] << 3 | bytes[at + 5] >> 5;
|
|
2090
|
+
return length >= 7 ? length : 0;
|
|
2091
|
+
}
|
|
2092
|
+
function trailingTagStart(bytes, end = bytes.length) {
|
|
2093
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2094
|
+
let at = Math.max(0, Math.min(end, bytes.length));
|
|
2095
|
+
for (; ; ) {
|
|
2096
|
+
if (at >= 128 && text(bytes, at - 128, 3) === "TAG") {
|
|
2097
|
+
at -= 128;
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
if (at >= 32 && text(bytes, at - 32, 8) === "APETAGEX") {
|
|
2101
|
+
const size = view.getUint32(at - 32 + 12, true);
|
|
2102
|
+
const flags = view.getUint32(at - 32 + 20, true);
|
|
2103
|
+
const start = at - size - ((flags & 2147483648) !== 0 ? 32 : 0);
|
|
2104
|
+
if (start < 0 || start >= at) return at;
|
|
2105
|
+
at = start;
|
|
2106
|
+
continue;
|
|
2107
|
+
}
|
|
2108
|
+
return at;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
function firstFrame(bytes) {
|
|
2112
|
+
const from = id3v2End(bytes) ?? 0;
|
|
2113
|
+
const limit = Math.min(bytes.length - 6, from + MPEG_SCAN_LIMIT);
|
|
2114
|
+
for (let o = from; o < limit; o++) {
|
|
2115
|
+
const len = frameLengthAt(bytes, o);
|
|
2116
|
+
if (len > 0 && frameLengthAt(bytes, o + len) > 0) return o;
|
|
2117
|
+
}
|
|
2118
|
+
return -1;
|
|
2119
|
+
}
|
|
2120
|
+
function forwardLastFrame(bytes, from, end) {
|
|
2121
|
+
let o = from;
|
|
2122
|
+
let start = -1;
|
|
2123
|
+
let length = 0;
|
|
2124
|
+
let frames = 0;
|
|
2125
|
+
while (o + 6 <= end) {
|
|
2126
|
+
const len = frameLengthAt(bytes, o);
|
|
2127
|
+
if (len === 0 || o + len > end) break;
|
|
2128
|
+
start = o;
|
|
2129
|
+
length = len;
|
|
2130
|
+
frames++;
|
|
2131
|
+
o += len;
|
|
2132
|
+
}
|
|
2133
|
+
return { start, length, frames };
|
|
2134
|
+
}
|
|
2135
|
+
function walkMpeg(bytes) {
|
|
2136
|
+
const from = firstFrame(bytes);
|
|
2137
|
+
if (from < 0) return fallback("no valid first frame");
|
|
2138
|
+
const end = trailingTagStart(bytes);
|
|
2139
|
+
const last = forwardLastFrame(bytes, from, end);
|
|
2140
|
+
if (last.start < 0) return fallback("no valid frames");
|
|
2141
|
+
return {
|
|
2142
|
+
kind: "ranges",
|
|
2143
|
+
ranges: [{ offset: from, length: last.start + last.length - from }],
|
|
2144
|
+
detail: `first=${from} last=${last.start}+${last.length} frames=${last.frames}`
|
|
2145
|
+
};
|
|
2146
|
+
}
|
|
2147
|
+
function flacStreamInfoMd5(bytes) {
|
|
2148
|
+
const marker = flacMarkerOffset(bytes);
|
|
2149
|
+
if (marker === void 0) return void 0;
|
|
2150
|
+
const header = marker + 4;
|
|
2151
|
+
if (header + 4 > bytes.length) return void 0;
|
|
2152
|
+
if ((bytes[header] & 127) !== 0) return void 0;
|
|
2153
|
+
const blockLength = bytes[header + 1] << 16 | bytes[header + 2] << 8 | bytes[header + 3];
|
|
2154
|
+
if (blockLength < 34) return void 0;
|
|
2155
|
+
const payload = header + 4;
|
|
2156
|
+
if (payload + 34 > bytes.length) return void 0;
|
|
2157
|
+
let hex = "";
|
|
2158
|
+
for (const byte of bytes.subarray(payload + 18, payload + 34)) {
|
|
2159
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
2160
|
+
}
|
|
2161
|
+
return hex;
|
|
2162
|
+
}
|
|
2163
|
+
function walkFlac(bytes) {
|
|
2164
|
+
const start = flacAudioStart(bytes);
|
|
2165
|
+
if (start === void 0) return fallback("no fLaC marker");
|
|
2166
|
+
const end = trailingTagStart(bytes);
|
|
2167
|
+
if (end <= start) return fallback("no audio bytes");
|
|
2168
|
+
return {
|
|
2169
|
+
kind: "ranges",
|
|
2170
|
+
ranges: [{ offset: start, length: end - start }],
|
|
2171
|
+
detail: `audioStart=${start} end=${end}`
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2174
|
+
function atomLength(bytes, view, o) {
|
|
2175
|
+
const size = view.getUint32(o, false);
|
|
2176
|
+
if (size === 0) return { length: bytes.length - o, headerSize: 8 };
|
|
2177
|
+
if (size === 1) {
|
|
2178
|
+
if (o + 16 > bytes.length) return void 0;
|
|
2179
|
+
const big = view.getBigUint64(o + 8, false);
|
|
2180
|
+
if (big > BigInt(Number.MAX_SAFE_INTEGER)) return void 0;
|
|
2181
|
+
return { length: Number(big), headerSize: 16 };
|
|
2182
|
+
}
|
|
2183
|
+
return { length: size, headerSize: 8 };
|
|
2184
|
+
}
|
|
2185
|
+
function walkMp4(bytes) {
|
|
2186
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2187
|
+
const ranges = [];
|
|
2188
|
+
let o = 0;
|
|
2189
|
+
while (o + 8 <= bytes.length) {
|
|
2190
|
+
const type = text(bytes, o + 4, 4);
|
|
2191
|
+
const atom = atomLength(bytes, view, o);
|
|
2192
|
+
if (atom === void 0) return fallback(`malformed ${type} size`);
|
|
2193
|
+
if (atom.length < atom.headerSize) {
|
|
2194
|
+
return fallback(`malformed ${type} size`);
|
|
2195
|
+
}
|
|
2196
|
+
if (o + atom.length > bytes.length) return fallback(`oversized ${type}`);
|
|
2197
|
+
if (type === "mdat" && atom.length > atom.headerSize) {
|
|
2198
|
+
ranges.push({
|
|
2199
|
+
offset: o + atom.headerSize,
|
|
2200
|
+
length: atom.length - atom.headerSize
|
|
2201
|
+
});
|
|
2202
|
+
}
|
|
2203
|
+
o += atom.length;
|
|
2204
|
+
}
|
|
2205
|
+
if (o !== bytes.length) return fallback("trailing bytes after the last atom");
|
|
2206
|
+
if (ranges.length === 0) return fallback("no non-empty mdat");
|
|
2207
|
+
return { kind: "ranges", ranges, detail: `${ranges.length} mdat(s)` };
|
|
2208
|
+
}
|
|
2209
|
+
function walkWav(bytes) {
|
|
2210
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2211
|
+
if (text(bytes, 0, 4) !== "RIFF") return fallback("not RIFF");
|
|
2212
|
+
if (text(bytes, 8, 4) !== "WAVE") return fallback("not WAVE");
|
|
2213
|
+
const ranges = [];
|
|
2214
|
+
let o = 12;
|
|
2215
|
+
while (o + 8 <= bytes.length) {
|
|
2216
|
+
const id = text(bytes, o, 4);
|
|
2217
|
+
const size = view.getUint32(o + 4, true);
|
|
2218
|
+
const dataStart = o + 8;
|
|
2219
|
+
if (dataStart + size > bytes.length) return fallback(`truncated ${id}`);
|
|
2220
|
+
if (id === "data" && size > 0) {
|
|
2221
|
+
ranges.push({ offset: dataStart, length: size });
|
|
2222
|
+
}
|
|
2223
|
+
o = dataStart + size + size % 2;
|
|
2224
|
+
}
|
|
2225
|
+
if (ranges.length === 0) return fallback("no data chunk");
|
|
2226
|
+
return { kind: "ranges", ranges, detail: `${ranges.length} data chunk(s)` };
|
|
2227
|
+
}
|
|
2228
|
+
function mediaRanges(format, bytes) {
|
|
2229
|
+
const walked = (() => {
|
|
2230
|
+
switch (format.toUpperCase()) {
|
|
2231
|
+
case "MP3":
|
|
2232
|
+
case "AAC":
|
|
2233
|
+
return walkMpeg(bytes);
|
|
2234
|
+
case "FLAC":
|
|
2235
|
+
return walkFlac(bytes);
|
|
2236
|
+
case "MP4":
|
|
2237
|
+
case "M4A":
|
|
2238
|
+
return walkMp4(bytes);
|
|
2239
|
+
case "WAV":
|
|
2240
|
+
return walkWav(bytes);
|
|
2241
|
+
default:
|
|
2242
|
+
return { kind: "fallback", ranges: [], detail: "format not covered" };
|
|
2243
|
+
}
|
|
2244
|
+
})();
|
|
2245
|
+
if (walked.kind === "fallback") {
|
|
2246
|
+
return {
|
|
2247
|
+
kind: "fallback",
|
|
2248
|
+
ranges: [{ offset: 0, length: bytes.length }],
|
|
2249
|
+
detail: `${walked.detail} \u2192 whole file`
|
|
2250
|
+
};
|
|
2251
|
+
}
|
|
2252
|
+
return walked;
|
|
2253
|
+
}
|
|
2254
|
+
var decoder, text, fallback, frameLengthAt, MPEG_SCAN_LIMIT;
|
|
2255
|
+
var init_media_ranges = __esm({
|
|
2256
|
+
"src/taglib/media-ranges.ts"() {
|
|
2257
|
+
"use strict";
|
|
2258
|
+
init_metadata_extent();
|
|
2259
|
+
decoder = new TextDecoder();
|
|
2260
|
+
text = (b, o, n) => decoder.decode(b.subarray(o, o + n));
|
|
2261
|
+
fallback = (detail) => ({
|
|
2262
|
+
kind: "fallback",
|
|
2263
|
+
ranges: [],
|
|
2264
|
+
detail
|
|
2265
|
+
});
|
|
2266
|
+
frameLengthAt = (bytes, at) => mpegFrameLength(bytes, at) || adtsFrameLength(bytes, at);
|
|
2267
|
+
MPEG_SCAN_LIMIT = 65536;
|
|
2268
|
+
}
|
|
2269
|
+
});
|
|
2270
|
+
|
|
2271
|
+
// src/taglib/audio-file-checksum.ts
|
|
2272
|
+
async function fileBytes(source, io) {
|
|
2273
|
+
if (source.path) return await io.readFile(source.path);
|
|
2274
|
+
if (source.blob) return new Uint8Array(await source.blob.arrayBuffer());
|
|
2275
|
+
if (!source.partiallyLoaded && source.bytes.length > 0) return source.bytes;
|
|
2276
|
+
throw new MetadataError(
|
|
2277
|
+
"read",
|
|
2278
|
+
"the handle holds no file bytes and has no readable source; a partial handle that has been saved drops its source \u2014 checksum the saved path instead"
|
|
2279
|
+
);
|
|
2280
|
+
}
|
|
2281
|
+
function joinRanges(bytes, ranges) {
|
|
2282
|
+
const total = ranges.reduce((n, r) => n + r.length, 0);
|
|
2283
|
+
const overrun = ranges.find((r) => r.offset + r.length > bytes.length);
|
|
2284
|
+
if (overrun || total > bytes.length) {
|
|
2285
|
+
throw new MetadataError(
|
|
2286
|
+
"read",
|
|
2287
|
+
`range walk overran the buffer (${overrun ? `range ${overrun.offset}+${overrun.length}` : `${total} bytes`} past the ${bytes.length}-byte buffer)`
|
|
2288
|
+
);
|
|
2289
|
+
}
|
|
2290
|
+
if (ranges.length === 1) {
|
|
2291
|
+
const only = ranges[0];
|
|
2292
|
+
const view = bytes.subarray(only.offset, only.offset + only.length);
|
|
2293
|
+
return view.buffer instanceof ArrayBuffer ? view : new Uint8Array(view);
|
|
2294
|
+
}
|
|
2295
|
+
const out = new Uint8Array(total);
|
|
2296
|
+
let at = 0;
|
|
2297
|
+
for (const r of ranges) {
|
|
2298
|
+
out.set(bytes.subarray(r.offset, r.offset + r.length), at);
|
|
2299
|
+
at += r.length;
|
|
2300
|
+
}
|
|
2301
|
+
return out;
|
|
2302
|
+
}
|
|
2303
|
+
async function mediaChecksum(source, format, io, options = {}) {
|
|
2304
|
+
if (options.basis === "pcm") {
|
|
2305
|
+
if (format.toUpperCase() !== "FLAC") {
|
|
2306
|
+
throw new UnsupportedFormatError(format, ["FLAC"], {
|
|
2307
|
+
operation: "media checksum (basis: pcm)"
|
|
2308
|
+
});
|
|
2309
|
+
}
|
|
2310
|
+
const windowed = source.bytes.length > 0 ? source.bytes : source.path && io.readPartial ? await io.readPartial(source.path, STREAMINFO_WINDOW, 0) : void 0;
|
|
2311
|
+
let md5 = windowed === void 0 ? void 0 : flacStreamInfoMd5(windowed);
|
|
2312
|
+
if (md5 === void 0 && windowed !== source.bytes) {
|
|
2313
|
+
md5 = flacStreamInfoMd5(await fileBytes(source, io));
|
|
2314
|
+
}
|
|
2315
|
+
if (md5 === void 0) {
|
|
2316
|
+
throw new MetadataError(
|
|
2317
|
+
"read",
|
|
2318
|
+
"STREAMINFO digest unavailable"
|
|
2319
|
+
);
|
|
2320
|
+
}
|
|
2321
|
+
return {
|
|
2322
|
+
source: "flac-streaminfo-md5",
|
|
2323
|
+
algorithm: "md5",
|
|
2324
|
+
hex: md5,
|
|
2325
|
+
bytesHashed: 16
|
|
2326
|
+
};
|
|
2327
|
+
}
|
|
2328
|
+
const bytes = await fileBytes(source, io);
|
|
2329
|
+
const walk = mediaRanges(format, bytes);
|
|
2330
|
+
const payload = joinRanges(bytes, walk.ranges);
|
|
2331
|
+
const digest = new Uint8Array(
|
|
2332
|
+
await crypto.subtle.digest("SHA-256", payload)
|
|
2333
|
+
);
|
|
2334
|
+
let hex = "";
|
|
2335
|
+
for (const byte of digest) hex += byte.toString(16).padStart(2, "0");
|
|
2336
|
+
return {
|
|
2337
|
+
source: walk.kind === "ranges" ? "audio-payload" : "file",
|
|
2338
|
+
algorithm: "sha256",
|
|
2339
|
+
hex,
|
|
2340
|
+
bytesHashed: payload.length
|
|
2341
|
+
};
|
|
2342
|
+
}
|
|
2343
|
+
var STREAMINFO_WINDOW;
|
|
2344
|
+
var init_audio_file_checksum = __esm({
|
|
2345
|
+
"src/taglib/audio-file-checksum.ts"() {
|
|
2346
|
+
"use strict";
|
|
2347
|
+
init_media_ranges();
|
|
2348
|
+
init_errors2();
|
|
2349
|
+
STREAMINFO_WINDOW = 65536;
|
|
2350
|
+
}
|
|
2351
|
+
});
|
|
2352
|
+
|
|
1878
2353
|
// src/utils/file.ts
|
|
1879
2354
|
async function readFileData(file) {
|
|
1880
2355
|
if (file instanceof Uint8Array) return file;
|
|
@@ -1982,8 +2457,8 @@ function wrapEmbindHandle(raw) {
|
|
|
1982
2457
|
// Text only — language/description are not surfaced by the PropertyMap.
|
|
1983
2458
|
getLyrics() {
|
|
1984
2459
|
const props = raw.getProperties();
|
|
1985
|
-
return (props["LYRICS"] ?? []).map((
|
|
1986
|
-
text,
|
|
2460
|
+
return (props["LYRICS"] ?? []).map((text2) => ({
|
|
2461
|
+
text: text2,
|
|
1987
2462
|
description: "",
|
|
1988
2463
|
language: ""
|
|
1989
2464
|
}));
|
|
@@ -2053,7 +2528,10 @@ function wrapEmbindHandle(raw) {
|
|
|
2053
2528
|
...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: pw.isEncrypted() } : {},
|
|
2054
2529
|
...formatVersion > 0 ? { formatVersion } : {},
|
|
2055
2530
|
...isValidBitrateMode(bitrateMode) ? { bitrateMode } : {},
|
|
2056
|
-
|
|
2531
|
+
// OpusHead gain exists only in an Ogg Opus stream. Gating on codec
|
|
2532
|
+
// alone fabricated outputGainDb: 0 for MP4 tracks once the MP4 codec
|
|
2533
|
+
// enum was mapped (the WASI side never emitted it there) — taglib-kswf.
|
|
2534
|
+
...containerFormat === "OGG" && codec === "Opus" ? { outputGainDb: pw.outputGainDb() } : {}
|
|
2057
2535
|
};
|
|
2058
2536
|
}
|
|
2059
2537
|
};
|
|
@@ -2233,6 +2711,8 @@ var init_audio_file_impl = __esm({
|
|
|
2233
2711
|
init_rating();
|
|
2234
2712
|
init_detector();
|
|
2235
2713
|
init_audio_file_base();
|
|
2714
|
+
init_audio_file_checksum();
|
|
2715
|
+
init_platform_io_browser_stub();
|
|
2236
2716
|
init_save_reconstruct();
|
|
2237
2717
|
AudioFileImpl = class extends BaseAudioFileImpl {
|
|
2238
2718
|
constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
|
|
@@ -2245,6 +2725,9 @@ var init_audio_file_impl = __esm({
|
|
|
2245
2725
|
partialLoadOptions
|
|
2246
2726
|
);
|
|
2247
2727
|
__publicField(this, "pathModeBuffer", null);
|
|
2728
|
+
/** How the handle was loaded — `isPartiallyLoaded` is mutated by saveToFile(). */
|
|
2729
|
+
__publicField(this, "partiallyLoadedAtConstruction");
|
|
2730
|
+
this.partiallyLoadedAtConstruction = isPartiallyLoaded;
|
|
2248
2731
|
}
|
|
2249
2732
|
save() {
|
|
2250
2733
|
if (this.isPartiallyLoaded && this.originalSource) {
|
|
@@ -2280,6 +2763,31 @@ var init_audio_file_impl = __esm({
|
|
|
2280
2763
|
"No file data available: in-memory buffer is empty and no source path is set"
|
|
2281
2764
|
);
|
|
2282
2765
|
}
|
|
2766
|
+
mediaChecksum(options) {
|
|
2767
|
+
return mediaChecksum(
|
|
2768
|
+
{
|
|
2769
|
+
// The cheap, no-IO accessor — `getFileBuffer()` would read the whole
|
|
2770
|
+
// file from disk in path mode (and cache it), which the source rule is
|
|
2771
|
+
// about to read for itself.
|
|
2772
|
+
bytes: this.handle.getBuffer(),
|
|
2773
|
+
// empty in path mode: the data is on disk
|
|
2774
|
+
// Both optional fields are conditional: they are `undefined` far more
|
|
2775
|
+
// often than not, and `exactOptionalPropertyTypes` refuses an explicit
|
|
2776
|
+
// `undefined` on an optional property.
|
|
2777
|
+
...this.sourcePath !== void 0 ? { path: this.sourcePath } : {},
|
|
2778
|
+
// originalSource is the File when the caller passed one. A partial File
|
|
2779
|
+
// has no path, so this is the only way back to the real bytes.
|
|
2780
|
+
...this.originalSource instanceof Blob ? { blob: this.originalSource } : {},
|
|
2781
|
+
// Captured at construction, NOT this.isPartiallyLoaded: saveToFile() sets
|
|
2782
|
+
// that flag false (and clears originalSource) while the handle still holds
|
|
2783
|
+
// the spliced image, which would make the source rule hash the image.
|
|
2784
|
+
partiallyLoaded: this.partiallyLoadedAtConstruction
|
|
2785
|
+
},
|
|
2786
|
+
this.getFormat(),
|
|
2787
|
+
getPlatformIO(),
|
|
2788
|
+
options
|
|
2789
|
+
);
|
|
2790
|
+
}
|
|
2283
2791
|
async saveToFile(path) {
|
|
2284
2792
|
const targetPath = path ?? this.sourcePath;
|
|
2285
2793
|
if (!targetPath) {
|
|
@@ -2369,8 +2877,8 @@ var init_audio_file_impl = __esm({
|
|
|
2369
2877
|
}
|
|
2370
2878
|
setChapters(chapters, options) {
|
|
2371
2879
|
const fmt = this.getFormat();
|
|
2372
|
-
if (fmt !== "MP3" && fmt !== "MP4") {
|
|
2373
|
-
throw new UnsupportedFormatError(fmt, ["MP3", "MP4"], {
|
|
2880
|
+
if (fmt !== "MP3" && fmt !== "AAC" && fmt !== "MP4") {
|
|
2881
|
+
throw new UnsupportedFormatError(fmt, ["MP3", "AAC", "MP4"], {
|
|
2374
2882
|
operation: "setChapters"
|
|
2375
2883
|
});
|
|
2376
2884
|
}
|
|
@@ -2477,181 +2985,6 @@ var init_audio_file_impl = __esm({
|
|
|
2477
2985
|
}
|
|
2478
2986
|
});
|
|
2479
2987
|
|
|
2480
|
-
// src/taglib/metadata-extent.ts
|
|
2481
|
-
function be32(b, offset) {
|
|
2482
|
-
return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
|
|
2483
|
-
}
|
|
2484
|
-
function startsWith(bytes, magic, at = 0) {
|
|
2485
|
-
if (bytes.length < at + magic.length) return false;
|
|
2486
|
-
for (let i = 0; i < magic.length; i++) {
|
|
2487
|
-
if (bytes[at + i] !== magic.charCodeAt(i)) return false;
|
|
2488
|
-
}
|
|
2489
|
-
return true;
|
|
2490
|
-
}
|
|
2491
|
-
function id3v2End(bytes) {
|
|
2492
|
-
if (bytes.length < 10) return void 0;
|
|
2493
|
-
const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
|
|
2494
|
-
const hasFooter = (bytes[5] & 16) !== 0;
|
|
2495
|
-
return 10 + size + (hasFooter ? 10 : 0);
|
|
2496
|
-
}
|
|
2497
|
-
function flacEnd(bytes, limit, start) {
|
|
2498
|
-
let offset = start + 4;
|
|
2499
|
-
for (; ; ) {
|
|
2500
|
-
if (offset + 4 > bytes.length) return void 0;
|
|
2501
|
-
const isLast = (bytes[offset] & 128) !== 0;
|
|
2502
|
-
const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
2503
|
-
offset += 4 + length;
|
|
2504
|
-
if (offset > limit) return offset;
|
|
2505
|
-
if (isLast) return offset;
|
|
2506
|
-
}
|
|
2507
|
-
}
|
|
2508
|
-
function mp4MoovEnd(bytes, limit, start) {
|
|
2509
|
-
let offset = start;
|
|
2510
|
-
const readable = Math.min(bytes.length, limit);
|
|
2511
|
-
while (offset + 8 <= readable) {
|
|
2512
|
-
const size = be32(bytes, offset);
|
|
2513
|
-
const type = String.fromCharCode(
|
|
2514
|
-
bytes[offset + 4],
|
|
2515
|
-
bytes[offset + 5],
|
|
2516
|
-
bytes[offset + 6],
|
|
2517
|
-
bytes[offset + 7]
|
|
2518
|
-
);
|
|
2519
|
-
if (size < 8) return void 0;
|
|
2520
|
-
if (type === "moov") return offset + size;
|
|
2521
|
-
offset += size;
|
|
2522
|
-
}
|
|
2523
|
-
return void 0;
|
|
2524
|
-
}
|
|
2525
|
-
function oggMetadataEnd(bytes, limit, start) {
|
|
2526
|
-
let offset = start;
|
|
2527
|
-
let packetsCompleted = 0;
|
|
2528
|
-
let first = true;
|
|
2529
|
-
for (; ; ) {
|
|
2530
|
-
if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
|
|
2531
|
-
return void 0;
|
|
2532
|
-
}
|
|
2533
|
-
const segmentCount = bytes[offset + 26];
|
|
2534
|
-
const tableStart = offset + 27;
|
|
2535
|
-
if (tableStart + segmentCount > bytes.length) return void 0;
|
|
2536
|
-
let payload = 0;
|
|
2537
|
-
for (let i = 0; i < segmentCount; i++) {
|
|
2538
|
-
const segment = bytes[tableStart + i];
|
|
2539
|
-
payload += segment;
|
|
2540
|
-
if (segment < 255) packetsCompleted++;
|
|
2541
|
-
}
|
|
2542
|
-
if (first) {
|
|
2543
|
-
const payloadStart = tableStart + segmentCount;
|
|
2544
|
-
if (payloadStart + 5 > bytes.length) return void 0;
|
|
2545
|
-
if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
|
|
2546
|
-
return void 0;
|
|
2547
|
-
}
|
|
2548
|
-
first = false;
|
|
2549
|
-
}
|
|
2550
|
-
offset = tableStart + segmentCount + payload;
|
|
2551
|
-
if (packetsCompleted >= 2 || offset > limit) return offset;
|
|
2552
|
-
}
|
|
2553
|
-
}
|
|
2554
|
-
function isMpegFrameSync(bytes, at = 0) {
|
|
2555
|
-
if (bytes.length < at + 4) return false;
|
|
2556
|
-
if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return false;
|
|
2557
|
-
const version = bytes[at + 1] >> 3 & 3;
|
|
2558
|
-
const layer = bytes[at + 1] >> 1 & 3;
|
|
2559
|
-
if (version === 1 || layer === 0) return false;
|
|
2560
|
-
const bitrateIndex = bytes[at + 2] >> 4 & 15;
|
|
2561
|
-
const sampleRateIndex = bytes[at + 2] >> 2 & 3;
|
|
2562
|
-
if (bitrateIndex === 0 || bitrateIndex === 15 || sampleRateIndex === 3) {
|
|
2563
|
-
return false;
|
|
2564
|
-
}
|
|
2565
|
-
const mpeg1 = version === 3;
|
|
2566
|
-
const layerIndex = layer ^ 3;
|
|
2567
|
-
const bitrate = MPEG_BITRATES[mpeg1 ? 0 : 1][layerIndex][bitrateIndex];
|
|
2568
|
-
const sampleRateTable = mpeg1 ? 0 : version === 2 ? 1 : 2;
|
|
2569
|
-
const sampleRate = MPEG_SAMPLE_RATES[sampleRateTable][sampleRateIndex];
|
|
2570
|
-
const samplesPerFrame = layerIndex === 2 ? mpeg1 ? 1152 : 576 : layerIndex === 1 ? 1152 : 384;
|
|
2571
|
-
let frameLength = Math.floor(samplesPerFrame * bitrate * 125 / sampleRate);
|
|
2572
|
-
if ((bytes[at + 2] & 2) !== 0) frameLength += layerIndex === 0 ? 4 : 1;
|
|
2573
|
-
if (frameLength === 0) return false;
|
|
2574
|
-
const next = at + frameLength;
|
|
2575
|
-
if (bytes.length < next + 4) return false;
|
|
2576
|
-
const mask = 4294839296;
|
|
2577
|
-
const header = ((bytes[at] << 24 | bytes[at + 1] << 16 | bytes[at + 2] << 8 | bytes[at + 3]) >>> 0 & mask) >>> 0;
|
|
2578
|
-
const nextHeader = ((bytes[next] << 24 | bytes[next + 1] << 16 | bytes[next + 2] << 8 | bytes[next + 3]) >>> 0 & mask) >>> 0;
|
|
2579
|
-
return header === nextHeader;
|
|
2580
|
-
}
|
|
2581
|
-
function containerEnd(bytes, at, limit) {
|
|
2582
|
-
if (at >= bytes.length) return bytes.length < limit ? null : void 0;
|
|
2583
|
-
if (at + 8 > bytes.length) return void 0;
|
|
2584
|
-
if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
|
|
2585
|
-
if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
|
|
2586
|
-
if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
|
|
2587
|
-
return null;
|
|
2588
|
-
}
|
|
2589
|
-
function metadataFitsInHeader(header, limit) {
|
|
2590
|
-
let end;
|
|
2591
|
-
if (startsWith(header, "ID3")) {
|
|
2592
|
-
const tagEnd = id3v2End(header);
|
|
2593
|
-
if (tagEnd === void 0) return false;
|
|
2594
|
-
end = tagEnd;
|
|
2595
|
-
if (tagEnd <= limit) {
|
|
2596
|
-
const behind = containerEnd(header, tagEnd, limit);
|
|
2597
|
-
if (behind === void 0) return false;
|
|
2598
|
-
if (behind !== null) end = behind;
|
|
2599
|
-
}
|
|
2600
|
-
} else {
|
|
2601
|
-
const container = containerEnd(header, 0, limit);
|
|
2602
|
-
if (container === null) {
|
|
2603
|
-
end = isMpegFrameSync(header) ? 0 : void 0;
|
|
2604
|
-
} else {
|
|
2605
|
-
end = container;
|
|
2606
|
-
}
|
|
2607
|
-
}
|
|
2608
|
-
if (end === void 0 || end > header.length) return false;
|
|
2609
|
-
return end <= limit;
|
|
2610
|
-
}
|
|
2611
|
-
function trailerFitsInFooter(tail, footerSize) {
|
|
2612
|
-
if (tail.length < Math.min(footerSize, 32)) return false;
|
|
2613
|
-
for (const offsetFromEnd of [32, 32 + 128]) {
|
|
2614
|
-
const at = tail.length - offsetFromEnd;
|
|
2615
|
-
if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
|
|
2616
|
-
const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
|
|
2617
|
-
const trailing = offsetFromEnd === 32 ? 0 : 128;
|
|
2618
|
-
if (size + 32 + trailing > footerSize) return false;
|
|
2619
|
-
}
|
|
2620
|
-
return true;
|
|
2621
|
-
}
|
|
2622
|
-
var MPEG_BITRATES, MPEG_SAMPLE_RATES;
|
|
2623
|
-
var init_metadata_extent = __esm({
|
|
2624
|
-
"src/taglib/metadata-extent.ts"() {
|
|
2625
|
-
"use strict";
|
|
2626
|
-
MPEG_BITRATES = [
|
|
2627
|
-
[
|
|
2628
|
-
[0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
2629
|
-
// Layer I
|
|
2630
|
-
[0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
2631
|
-
// Layer II
|
|
2632
|
-
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0]
|
|
2633
|
-
// Layer III
|
|
2634
|
-
],
|
|
2635
|
-
[
|
|
2636
|
-
[0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
|
2637
|
-
// Layer I
|
|
2638
|
-
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
2639
|
-
// Layer II
|
|
2640
|
-
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
2641
|
-
// Layer III
|
|
2642
|
-
]
|
|
2643
|
-
];
|
|
2644
|
-
MPEG_SAMPLE_RATES = [
|
|
2645
|
-
[44100, 48e3, 32e3],
|
|
2646
|
-
// MPEG-1
|
|
2647
|
-
[22050, 24e3, 16e3],
|
|
2648
|
-
// MPEG-2
|
|
2649
|
-
[11025, 12e3, 8e3]
|
|
2650
|
-
// MPEG-2.5
|
|
2651
|
-
];
|
|
2652
|
-
}
|
|
2653
|
-
});
|
|
2654
|
-
|
|
2655
2988
|
// src/taglib/load-audio-data.ts
|
|
2656
2989
|
async function loadAudioData(input, opts) {
|
|
2657
2990
|
if (opts.partial && typeof File !== "undefined" && input instanceof File) {
|
|
@@ -2876,6 +3209,8 @@ function normalizeTagInput(input) {
|
|
|
2876
3209
|
if (val === "explicit") props.itunesAdvisory = ["1"];
|
|
2877
3210
|
else if (val === "clean") props.itunesAdvisory = ["2"];
|
|
2878
3211
|
else if (val === "unspecified") props.itunesAdvisory = [];
|
|
3212
|
+
} else if (field === "bpm") {
|
|
3213
|
+
props[field] = [String(Math.trunc(val))];
|
|
2879
3214
|
} else if (NUMERIC_FIELDS.has(field)) {
|
|
2880
3215
|
props[field] = [String(val)];
|
|
2881
3216
|
} else if (field === "r128TrackGain" || field === "r128AlbumGain") {
|
|
@@ -2936,7 +3271,7 @@ var VERSION;
|
|
|
2936
3271
|
var init_version = __esm({
|
|
2937
3272
|
"src/version.ts"() {
|
|
2938
3273
|
"use strict";
|
|
2939
|
-
VERSION = "2.
|
|
3274
|
+
VERSION = "2.3.0";
|
|
2940
3275
|
}
|
|
2941
3276
|
});
|
|
2942
3277
|
|
|
@@ -3058,30 +3393,19 @@ var init_taglib_class = __esm({
|
|
|
3058
3393
|
const actualInput = isNamedAudioInput(input) ? input.data : input;
|
|
3059
3394
|
const sourcePath = typeof actualInput === "string" ? actualInput : void 0;
|
|
3060
3395
|
if (typeof actualInput === "string" && this.module.isWasi) {
|
|
3061
|
-
const
|
|
3062
|
-
|
|
3063
|
-
const
|
|
3064
|
-
|
|
3065
|
-
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
fileHandle2,
|
|
3075
|
-
sourcePath,
|
|
3076
|
-
actualInput,
|
|
3077
|
-
false
|
|
3078
|
-
);
|
|
3079
|
-
}
|
|
3080
|
-
} catch (error) {
|
|
3081
|
-
if (typeof fileHandle2.destroy === "function") {
|
|
3082
|
-
fileHandle2.destroy();
|
|
3083
|
-
}
|
|
3084
|
-
throw error;
|
|
3396
|
+
const wasiPath = toWasiPath(actualInput);
|
|
3397
|
+
if (this.module.loadAudioPath) {
|
|
3398
|
+
const fileHandle2 = await this.module.loadAudioPath(
|
|
3399
|
+
wasiPath,
|
|
3400
|
+
actualInput
|
|
3401
|
+
);
|
|
3402
|
+
return new AudioFileImpl(
|
|
3403
|
+
this.module,
|
|
3404
|
+
fileHandle2,
|
|
3405
|
+
sourcePath,
|
|
3406
|
+
actualInput,
|
|
3407
|
+
false
|
|
3408
|
+
);
|
|
3085
3409
|
}
|
|
3086
3410
|
}
|
|
3087
3411
|
const opts = {
|
|
@@ -3595,6 +3919,11 @@ async function editTagsBatch(files, mutator, options = {}) {
|
|
|
3595
3919
|
);
|
|
3596
3920
|
}
|
|
3597
3921
|
|
|
3922
|
+
// src/simple/checksum-operations.ts
|
|
3923
|
+
async function readMediaChecksum(file, options) {
|
|
3924
|
+
return withAudioFile(file, (audioFile) => audioFile.mediaChecksum(options));
|
|
3925
|
+
}
|
|
3926
|
+
|
|
3598
3927
|
// src/simple/index.ts
|
|
3599
3928
|
init_types2();
|
|
3600
3929
|
export {
|
|
@@ -3613,6 +3942,7 @@ export {
|
|
|
3613
3942
|
isValidAudioFile,
|
|
3614
3943
|
readCoverArt,
|
|
3615
3944
|
readFormat,
|
|
3945
|
+
readMediaChecksum,
|
|
3616
3946
|
readMetadata,
|
|
3617
3947
|
readMetadataBatch,
|
|
3618
3948
|
readPictureMetadata,
|