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/index.browser.js
CHANGED
|
@@ -157,6 +157,11 @@ var init_types2 = __esm({
|
|
|
157
157
|
});
|
|
158
158
|
|
|
159
159
|
// src/bwf/bext.ts
|
|
160
|
+
var bext_exports = {};
|
|
161
|
+
__export(bext_exports, {
|
|
162
|
+
decodeBext: () => decodeBext,
|
|
163
|
+
encodeBext: () => encodeBext
|
|
164
|
+
});
|
|
160
165
|
function readFixedString(bytes, offset, len) {
|
|
161
166
|
let end = offset;
|
|
162
167
|
const max = Math.min(offset + len, bytes.length);
|
|
@@ -293,11 +298,29 @@ var init_base = __esm({
|
|
|
293
298
|
"use strict";
|
|
294
299
|
SUPPORTED_FORMATS = [
|
|
295
300
|
"MP3",
|
|
301
|
+
"AAC",
|
|
296
302
|
"MP4",
|
|
297
303
|
"M4A",
|
|
298
304
|
"FLAC",
|
|
299
305
|
"OGG",
|
|
306
|
+
"OPUS",
|
|
307
|
+
"OggFLAC",
|
|
308
|
+
"SPEEX",
|
|
300
309
|
"WAV",
|
|
310
|
+
"AIFF",
|
|
311
|
+
"ASF",
|
|
312
|
+
"APE",
|
|
313
|
+
"DSF",
|
|
314
|
+
"DSDIFF",
|
|
315
|
+
"WV",
|
|
316
|
+
"MPC",
|
|
317
|
+
"TTA",
|
|
318
|
+
"SHN",
|
|
319
|
+
"MOD",
|
|
320
|
+
"S3M",
|
|
321
|
+
"IT",
|
|
322
|
+
"XM",
|
|
323
|
+
"MATROSKA",
|
|
301
324
|
"MKA"
|
|
302
325
|
];
|
|
303
326
|
TagLibError = class _TagLibError extends Error {
|
|
@@ -574,8 +597,8 @@ var init_audio_file_bwf = __esm({
|
|
|
574
597
|
|
|
575
598
|
// src/taglib/id3v2-frames.ts
|
|
576
599
|
function assertMp3(format) {
|
|
577
|
-
if (format !== "MP3") {
|
|
578
|
-
throw new UnsupportedFormatError(format, ["MP3"], {
|
|
600
|
+
if (format !== "MP3" && format !== "AAC") {
|
|
601
|
+
throw new UnsupportedFormatError(format, ["MP3", "AAC"], {
|
|
579
602
|
operation: "id3v2Frames"
|
|
580
603
|
});
|
|
581
604
|
}
|
|
@@ -1371,7 +1394,7 @@ var init_specialized_properties = __esm({
|
|
|
1371
1394
|
},
|
|
1372
1395
|
releaseType: {
|
|
1373
1396
|
key: "RELEASETYPE",
|
|
1374
|
-
description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.
|
|
1397
|
+
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",
|
|
1375
1398
|
type: "string",
|
|
1376
1399
|
supportedFormats: [
|
|
1377
1400
|
"ID3v2",
|
|
@@ -1389,7 +1412,7 @@ var init_specialized_properties = __esm({
|
|
|
1389
1412
|
},
|
|
1390
1413
|
releaseCountry: {
|
|
1391
1414
|
key: "RELEASECOUNTRY",
|
|
1392
|
-
description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.
|
|
1415
|
+
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",
|
|
1393
1416
|
type: "string",
|
|
1394
1417
|
supportedFormats: [
|
|
1395
1418
|
"ID3v2",
|
|
@@ -1943,6 +1966,463 @@ var init_audio_file_base = __esm({
|
|
|
1943
1966
|
}
|
|
1944
1967
|
});
|
|
1945
1968
|
|
|
1969
|
+
// src/taglib/metadata-extent.ts
|
|
1970
|
+
function be32(b, offset) {
|
|
1971
|
+
return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
|
|
1972
|
+
}
|
|
1973
|
+
function startsWith(bytes, magic, at = 0) {
|
|
1974
|
+
if (bytes.length < at + magic.length) return false;
|
|
1975
|
+
for (let i = 0; i < magic.length; i++) {
|
|
1976
|
+
if (bytes[at + i] !== magic.charCodeAt(i)) return false;
|
|
1977
|
+
}
|
|
1978
|
+
return true;
|
|
1979
|
+
}
|
|
1980
|
+
function id3v2End(bytes) {
|
|
1981
|
+
if (!startsWith(bytes, "ID3") || bytes.length < 10) return void 0;
|
|
1982
|
+
const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
|
|
1983
|
+
const hasFooter = (bytes[5] & 16) !== 0;
|
|
1984
|
+
return 10 + size + (hasFooter ? 10 : 0);
|
|
1985
|
+
}
|
|
1986
|
+
function flacEnd(bytes, limit, start) {
|
|
1987
|
+
let offset = start + 4;
|
|
1988
|
+
for (; ; ) {
|
|
1989
|
+
if (offset + 4 > bytes.length) return void 0;
|
|
1990
|
+
const isLast = (bytes[offset] & 128) !== 0;
|
|
1991
|
+
const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
1992
|
+
offset += 4 + length;
|
|
1993
|
+
if (offset > limit) return offset;
|
|
1994
|
+
if (isLast) return offset;
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
function flacMarkerOffset(bytes) {
|
|
1998
|
+
const at = startsWith(bytes, "ID3") ? id3v2End(bytes) : 0;
|
|
1999
|
+
if (at === void 0) return void 0;
|
|
2000
|
+
return startsWith(bytes, "fLaC", at) ? at : void 0;
|
|
2001
|
+
}
|
|
2002
|
+
function flacAudioStart(bytes) {
|
|
2003
|
+
const at = flacMarkerOffset(bytes);
|
|
2004
|
+
if (at === void 0) return void 0;
|
|
2005
|
+
return flacEnd(bytes, bytes.length, at);
|
|
2006
|
+
}
|
|
2007
|
+
function mp4MoovEnd(bytes, limit, start) {
|
|
2008
|
+
let offset = start;
|
|
2009
|
+
const readable = Math.min(bytes.length, limit);
|
|
2010
|
+
while (offset + 8 <= readable) {
|
|
2011
|
+
const size = be32(bytes, offset);
|
|
2012
|
+
const type = String.fromCharCode(
|
|
2013
|
+
bytes[offset + 4],
|
|
2014
|
+
bytes[offset + 5],
|
|
2015
|
+
bytes[offset + 6],
|
|
2016
|
+
bytes[offset + 7]
|
|
2017
|
+
);
|
|
2018
|
+
if (size < 8) return void 0;
|
|
2019
|
+
if (type === "moov") return offset + size;
|
|
2020
|
+
offset += size;
|
|
2021
|
+
}
|
|
2022
|
+
return void 0;
|
|
2023
|
+
}
|
|
2024
|
+
function oggMetadataEnd(bytes, limit, start) {
|
|
2025
|
+
let offset = start;
|
|
2026
|
+
let packetsCompleted = 0;
|
|
2027
|
+
let first = true;
|
|
2028
|
+
for (; ; ) {
|
|
2029
|
+
if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
|
|
2030
|
+
return void 0;
|
|
2031
|
+
}
|
|
2032
|
+
const segmentCount = bytes[offset + 26];
|
|
2033
|
+
const tableStart = offset + 27;
|
|
2034
|
+
if (tableStart + segmentCount > bytes.length) return void 0;
|
|
2035
|
+
let payload = 0;
|
|
2036
|
+
for (let i = 0; i < segmentCount; i++) {
|
|
2037
|
+
const segment = bytes[tableStart + i];
|
|
2038
|
+
payload += segment;
|
|
2039
|
+
if (segment < 255) packetsCompleted++;
|
|
2040
|
+
}
|
|
2041
|
+
if (first) {
|
|
2042
|
+
const payloadStart = tableStart + segmentCount;
|
|
2043
|
+
if (payloadStart + 5 > bytes.length) return void 0;
|
|
2044
|
+
if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
|
|
2045
|
+
return void 0;
|
|
2046
|
+
}
|
|
2047
|
+
first = false;
|
|
2048
|
+
}
|
|
2049
|
+
offset = tableStart + segmentCount + payload;
|
|
2050
|
+
if (packetsCompleted >= 2 || offset > limit) return offset;
|
|
2051
|
+
}
|
|
2052
|
+
}
|
|
2053
|
+
function mpegFrameLength(bytes, at) {
|
|
2054
|
+
if (bytes.length < at + 4) return 0;
|
|
2055
|
+
if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return 0;
|
|
2056
|
+
const version = bytes[at + 1] >> 3 & 3;
|
|
2057
|
+
const layer = bytes[at + 1] >> 1 & 3;
|
|
2058
|
+
if (version === 1 || layer === 0) return 0;
|
|
2059
|
+
const bitrateIndex = bytes[at + 2] >> 4 & 15;
|
|
2060
|
+
const sampleRateIndex = bytes[at + 2] >> 2 & 3;
|
|
2061
|
+
if (bitrateIndex === 0 || bitrateIndex === 15 || sampleRateIndex === 3) {
|
|
2062
|
+
return 0;
|
|
2063
|
+
}
|
|
2064
|
+
const mpeg1 = version === 3;
|
|
2065
|
+
const layerIndex = layer ^ 3;
|
|
2066
|
+
const bitrate = MPEG_BITRATES[mpeg1 ? 0 : 1][layerIndex][bitrateIndex];
|
|
2067
|
+
const sampleRateTable = mpeg1 ? 0 : version === 2 ? 1 : 2;
|
|
2068
|
+
const sampleRate = MPEG_SAMPLE_RATES[sampleRateTable][sampleRateIndex];
|
|
2069
|
+
const samplesPerFrame = layerIndex === 2 ? mpeg1 ? 1152 : 576 : layerIndex === 1 ? 1152 : 384;
|
|
2070
|
+
let frameLength = Math.floor(samplesPerFrame * bitrate * 125 / sampleRate);
|
|
2071
|
+
if ((bytes[at + 2] & 2) !== 0) frameLength += layerIndex === 0 ? 4 : 1;
|
|
2072
|
+
return frameLength === 0 ? 0 : frameLength;
|
|
2073
|
+
}
|
|
2074
|
+
function isMpegFrameSync(bytes, at = 0) {
|
|
2075
|
+
const frameLength = mpegFrameLength(bytes, at);
|
|
2076
|
+
if (frameLength === 0) return false;
|
|
2077
|
+
const next = at + frameLength;
|
|
2078
|
+
if (bytes.length < next + 4) return false;
|
|
2079
|
+
const mask = 4294839296;
|
|
2080
|
+
const header = ((bytes[at] << 24 | bytes[at + 1] << 16 | bytes[at + 2] << 8 | bytes[at + 3]) >>> 0 & mask) >>> 0;
|
|
2081
|
+
const nextHeader = ((bytes[next] << 24 | bytes[next + 1] << 16 | bytes[next + 2] << 8 | bytes[next + 3]) >>> 0 & mask) >>> 0;
|
|
2082
|
+
return header === nextHeader;
|
|
2083
|
+
}
|
|
2084
|
+
function containerEnd(bytes, at, limit) {
|
|
2085
|
+
if (at >= bytes.length) return bytes.length < limit ? null : void 0;
|
|
2086
|
+
if (at + 8 > bytes.length) return void 0;
|
|
2087
|
+
if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
|
|
2088
|
+
if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
|
|
2089
|
+
if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
|
|
2090
|
+
return null;
|
|
2091
|
+
}
|
|
2092
|
+
function metadataFitsInHeader(header, limit) {
|
|
2093
|
+
let end;
|
|
2094
|
+
if (startsWith(header, "ID3")) {
|
|
2095
|
+
const tagEnd = id3v2End(header);
|
|
2096
|
+
if (tagEnd === void 0) return false;
|
|
2097
|
+
end = tagEnd;
|
|
2098
|
+
if (tagEnd <= limit) {
|
|
2099
|
+
const behind = containerEnd(header, tagEnd, limit);
|
|
2100
|
+
if (behind === void 0) return false;
|
|
2101
|
+
if (behind !== null) end = behind;
|
|
2102
|
+
}
|
|
2103
|
+
} else {
|
|
2104
|
+
const container = containerEnd(header, 0, limit);
|
|
2105
|
+
if (container === null) {
|
|
2106
|
+
end = isMpegFrameSync(header) ? 0 : void 0;
|
|
2107
|
+
} else {
|
|
2108
|
+
end = container;
|
|
2109
|
+
}
|
|
2110
|
+
}
|
|
2111
|
+
if (end === void 0 || end > header.length) return false;
|
|
2112
|
+
return end <= limit;
|
|
2113
|
+
}
|
|
2114
|
+
function trailerFitsInFooter(tail, footerSize) {
|
|
2115
|
+
if (tail.length < Math.min(footerSize, 32)) return false;
|
|
2116
|
+
for (const offsetFromEnd of [32, 32 + 128]) {
|
|
2117
|
+
const at = tail.length - offsetFromEnd;
|
|
2118
|
+
if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
|
|
2119
|
+
const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
|
|
2120
|
+
const trailing = offsetFromEnd === 32 ? 0 : 128;
|
|
2121
|
+
if (size + 32 + trailing > footerSize) return false;
|
|
2122
|
+
}
|
|
2123
|
+
return true;
|
|
2124
|
+
}
|
|
2125
|
+
var MPEG_BITRATES, MPEG_SAMPLE_RATES;
|
|
2126
|
+
var init_metadata_extent = __esm({
|
|
2127
|
+
"src/taglib/metadata-extent.ts"() {
|
|
2128
|
+
"use strict";
|
|
2129
|
+
MPEG_BITRATES = [
|
|
2130
|
+
[
|
|
2131
|
+
[0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
2132
|
+
// Layer I
|
|
2133
|
+
[0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
2134
|
+
// Layer II
|
|
2135
|
+
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0]
|
|
2136
|
+
// Layer III
|
|
2137
|
+
],
|
|
2138
|
+
[
|
|
2139
|
+
[0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
|
2140
|
+
// Layer I
|
|
2141
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
2142
|
+
// Layer II
|
|
2143
|
+
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
2144
|
+
// Layer III
|
|
2145
|
+
]
|
|
2146
|
+
];
|
|
2147
|
+
MPEG_SAMPLE_RATES = [
|
|
2148
|
+
[44100, 48e3, 32e3],
|
|
2149
|
+
// MPEG-1
|
|
2150
|
+
[22050, 24e3, 16e3],
|
|
2151
|
+
// MPEG-2
|
|
2152
|
+
[11025, 12e3, 8e3]
|
|
2153
|
+
// MPEG-2.5
|
|
2154
|
+
];
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2157
|
+
|
|
2158
|
+
// src/taglib/media-ranges.ts
|
|
2159
|
+
function adtsFrameLength(bytes, at) {
|
|
2160
|
+
if (bytes.length < at + 7) return 0;
|
|
2161
|
+
if (bytes[at] !== 255 || (bytes[at + 1] & 246) !== 240) return 0;
|
|
2162
|
+
const length = (bytes[at + 3] & 3) << 11 | bytes[at + 4] << 3 | bytes[at + 5] >> 5;
|
|
2163
|
+
return length >= 7 ? length : 0;
|
|
2164
|
+
}
|
|
2165
|
+
function trailingTagStart(bytes, end = bytes.length) {
|
|
2166
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2167
|
+
let at = Math.max(0, Math.min(end, bytes.length));
|
|
2168
|
+
for (; ; ) {
|
|
2169
|
+
if (at >= 128 && text(bytes, at - 128, 3) === "TAG") {
|
|
2170
|
+
at -= 128;
|
|
2171
|
+
continue;
|
|
2172
|
+
}
|
|
2173
|
+
if (at >= 32 && text(bytes, at - 32, 8) === "APETAGEX") {
|
|
2174
|
+
const size = view.getUint32(at - 32 + 12, true);
|
|
2175
|
+
const flags = view.getUint32(at - 32 + 20, true);
|
|
2176
|
+
const start = at - size - ((flags & 2147483648) !== 0 ? 32 : 0);
|
|
2177
|
+
if (start < 0 || start >= at) return at;
|
|
2178
|
+
at = start;
|
|
2179
|
+
continue;
|
|
2180
|
+
}
|
|
2181
|
+
return at;
|
|
2182
|
+
}
|
|
2183
|
+
}
|
|
2184
|
+
function firstFrame(bytes) {
|
|
2185
|
+
const from = id3v2End(bytes) ?? 0;
|
|
2186
|
+
const limit = Math.min(bytes.length - 6, from + MPEG_SCAN_LIMIT);
|
|
2187
|
+
for (let o = from; o < limit; o++) {
|
|
2188
|
+
const len = frameLengthAt(bytes, o);
|
|
2189
|
+
if (len > 0 && frameLengthAt(bytes, o + len) > 0) return o;
|
|
2190
|
+
}
|
|
2191
|
+
return -1;
|
|
2192
|
+
}
|
|
2193
|
+
function forwardLastFrame(bytes, from, end) {
|
|
2194
|
+
let o = from;
|
|
2195
|
+
let start = -1;
|
|
2196
|
+
let length = 0;
|
|
2197
|
+
let frames = 0;
|
|
2198
|
+
while (o + 6 <= end) {
|
|
2199
|
+
const len = frameLengthAt(bytes, o);
|
|
2200
|
+
if (len === 0 || o + len > end) break;
|
|
2201
|
+
start = o;
|
|
2202
|
+
length = len;
|
|
2203
|
+
frames++;
|
|
2204
|
+
o += len;
|
|
2205
|
+
}
|
|
2206
|
+
return { start, length, frames };
|
|
2207
|
+
}
|
|
2208
|
+
function walkMpeg(bytes) {
|
|
2209
|
+
const from = firstFrame(bytes);
|
|
2210
|
+
if (from < 0) return fallback("no valid first frame");
|
|
2211
|
+
const end = trailingTagStart(bytes);
|
|
2212
|
+
const last = forwardLastFrame(bytes, from, end);
|
|
2213
|
+
if (last.start < 0) return fallback("no valid frames");
|
|
2214
|
+
return {
|
|
2215
|
+
kind: "ranges",
|
|
2216
|
+
ranges: [{ offset: from, length: last.start + last.length - from }],
|
|
2217
|
+
detail: `first=${from} last=${last.start}+${last.length} frames=${last.frames}`
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
function flacStreamInfoMd5(bytes) {
|
|
2221
|
+
const marker = flacMarkerOffset(bytes);
|
|
2222
|
+
if (marker === void 0) return void 0;
|
|
2223
|
+
const header = marker + 4;
|
|
2224
|
+
if (header + 4 > bytes.length) return void 0;
|
|
2225
|
+
if ((bytes[header] & 127) !== 0) return void 0;
|
|
2226
|
+
const blockLength = bytes[header + 1] << 16 | bytes[header + 2] << 8 | bytes[header + 3];
|
|
2227
|
+
if (blockLength < 34) return void 0;
|
|
2228
|
+
const payload = header + 4;
|
|
2229
|
+
if (payload + 34 > bytes.length) return void 0;
|
|
2230
|
+
let hex = "";
|
|
2231
|
+
for (const byte of bytes.subarray(payload + 18, payload + 34)) {
|
|
2232
|
+
hex += byte.toString(16).padStart(2, "0");
|
|
2233
|
+
}
|
|
2234
|
+
return hex;
|
|
2235
|
+
}
|
|
2236
|
+
function walkFlac(bytes) {
|
|
2237
|
+
const start = flacAudioStart(bytes);
|
|
2238
|
+
if (start === void 0) return fallback("no fLaC marker");
|
|
2239
|
+
const end = trailingTagStart(bytes);
|
|
2240
|
+
if (end <= start) return fallback("no audio bytes");
|
|
2241
|
+
return {
|
|
2242
|
+
kind: "ranges",
|
|
2243
|
+
ranges: [{ offset: start, length: end - start }],
|
|
2244
|
+
detail: `audioStart=${start} end=${end}`
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
function atomLength(bytes, view, o) {
|
|
2248
|
+
const size = view.getUint32(o, false);
|
|
2249
|
+
if (size === 0) return { length: bytes.length - o, headerSize: 8 };
|
|
2250
|
+
if (size === 1) {
|
|
2251
|
+
if (o + 16 > bytes.length) return void 0;
|
|
2252
|
+
const big = view.getBigUint64(o + 8, false);
|
|
2253
|
+
if (big > BigInt(Number.MAX_SAFE_INTEGER)) return void 0;
|
|
2254
|
+
return { length: Number(big), headerSize: 16 };
|
|
2255
|
+
}
|
|
2256
|
+
return { length: size, headerSize: 8 };
|
|
2257
|
+
}
|
|
2258
|
+
function walkMp4(bytes) {
|
|
2259
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2260
|
+
const ranges = [];
|
|
2261
|
+
let o = 0;
|
|
2262
|
+
while (o + 8 <= bytes.length) {
|
|
2263
|
+
const type = text(bytes, o + 4, 4);
|
|
2264
|
+
const atom = atomLength(bytes, view, o);
|
|
2265
|
+
if (atom === void 0) return fallback(`malformed ${type} size`);
|
|
2266
|
+
if (atom.length < atom.headerSize) {
|
|
2267
|
+
return fallback(`malformed ${type} size`);
|
|
2268
|
+
}
|
|
2269
|
+
if (o + atom.length > bytes.length) return fallback(`oversized ${type}`);
|
|
2270
|
+
if (type === "mdat" && atom.length > atom.headerSize) {
|
|
2271
|
+
ranges.push({
|
|
2272
|
+
offset: o + atom.headerSize,
|
|
2273
|
+
length: atom.length - atom.headerSize
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
o += atom.length;
|
|
2277
|
+
}
|
|
2278
|
+
if (o !== bytes.length) return fallback("trailing bytes after the last atom");
|
|
2279
|
+
if (ranges.length === 0) return fallback("no non-empty mdat");
|
|
2280
|
+
return { kind: "ranges", ranges, detail: `${ranges.length} mdat(s)` };
|
|
2281
|
+
}
|
|
2282
|
+
function walkWav(bytes) {
|
|
2283
|
+
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
2284
|
+
if (text(bytes, 0, 4) !== "RIFF") return fallback("not RIFF");
|
|
2285
|
+
if (text(bytes, 8, 4) !== "WAVE") return fallback("not WAVE");
|
|
2286
|
+
const ranges = [];
|
|
2287
|
+
let o = 12;
|
|
2288
|
+
while (o + 8 <= bytes.length) {
|
|
2289
|
+
const id = text(bytes, o, 4);
|
|
2290
|
+
const size = view.getUint32(o + 4, true);
|
|
2291
|
+
const dataStart = o + 8;
|
|
2292
|
+
if (dataStart + size > bytes.length) return fallback(`truncated ${id}`);
|
|
2293
|
+
if (id === "data" && size > 0) {
|
|
2294
|
+
ranges.push({ offset: dataStart, length: size });
|
|
2295
|
+
}
|
|
2296
|
+
o = dataStart + size + size % 2;
|
|
2297
|
+
}
|
|
2298
|
+
if (ranges.length === 0) return fallback("no data chunk");
|
|
2299
|
+
return { kind: "ranges", ranges, detail: `${ranges.length} data chunk(s)` };
|
|
2300
|
+
}
|
|
2301
|
+
function mediaRanges(format, bytes) {
|
|
2302
|
+
const walked = (() => {
|
|
2303
|
+
switch (format.toUpperCase()) {
|
|
2304
|
+
case "MP3":
|
|
2305
|
+
case "AAC":
|
|
2306
|
+
return walkMpeg(bytes);
|
|
2307
|
+
case "FLAC":
|
|
2308
|
+
return walkFlac(bytes);
|
|
2309
|
+
case "MP4":
|
|
2310
|
+
case "M4A":
|
|
2311
|
+
return walkMp4(bytes);
|
|
2312
|
+
case "WAV":
|
|
2313
|
+
return walkWav(bytes);
|
|
2314
|
+
default:
|
|
2315
|
+
return { kind: "fallback", ranges: [], detail: "format not covered" };
|
|
2316
|
+
}
|
|
2317
|
+
})();
|
|
2318
|
+
if (walked.kind === "fallback") {
|
|
2319
|
+
return {
|
|
2320
|
+
kind: "fallback",
|
|
2321
|
+
ranges: [{ offset: 0, length: bytes.length }],
|
|
2322
|
+
detail: `${walked.detail} \u2192 whole file`
|
|
2323
|
+
};
|
|
2324
|
+
}
|
|
2325
|
+
return walked;
|
|
2326
|
+
}
|
|
2327
|
+
var decoder, text, fallback, frameLengthAt, MPEG_SCAN_LIMIT;
|
|
2328
|
+
var init_media_ranges = __esm({
|
|
2329
|
+
"src/taglib/media-ranges.ts"() {
|
|
2330
|
+
"use strict";
|
|
2331
|
+
init_metadata_extent();
|
|
2332
|
+
decoder = new TextDecoder();
|
|
2333
|
+
text = (b, o, n) => decoder.decode(b.subarray(o, o + n));
|
|
2334
|
+
fallback = (detail) => ({
|
|
2335
|
+
kind: "fallback",
|
|
2336
|
+
ranges: [],
|
|
2337
|
+
detail
|
|
2338
|
+
});
|
|
2339
|
+
frameLengthAt = (bytes, at) => mpegFrameLength(bytes, at) || adtsFrameLength(bytes, at);
|
|
2340
|
+
MPEG_SCAN_LIMIT = 65536;
|
|
2341
|
+
}
|
|
2342
|
+
});
|
|
2343
|
+
|
|
2344
|
+
// src/taglib/audio-file-checksum.ts
|
|
2345
|
+
async function fileBytes(source, io) {
|
|
2346
|
+
if (source.path) return await io.readFile(source.path);
|
|
2347
|
+
if (source.blob) return new Uint8Array(await source.blob.arrayBuffer());
|
|
2348
|
+
if (!source.partiallyLoaded && source.bytes.length > 0) return source.bytes;
|
|
2349
|
+
throw new MetadataError(
|
|
2350
|
+
"read",
|
|
2351
|
+
"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"
|
|
2352
|
+
);
|
|
2353
|
+
}
|
|
2354
|
+
function joinRanges(bytes, ranges) {
|
|
2355
|
+
const total = ranges.reduce((n, r) => n + r.length, 0);
|
|
2356
|
+
const overrun = ranges.find((r) => r.offset + r.length > bytes.length);
|
|
2357
|
+
if (overrun || total > bytes.length) {
|
|
2358
|
+
throw new MetadataError(
|
|
2359
|
+
"read",
|
|
2360
|
+
`range walk overran the buffer (${overrun ? `range ${overrun.offset}+${overrun.length}` : `${total} bytes`} past the ${bytes.length}-byte buffer)`
|
|
2361
|
+
);
|
|
2362
|
+
}
|
|
2363
|
+
if (ranges.length === 1) {
|
|
2364
|
+
const only = ranges[0];
|
|
2365
|
+
const view = bytes.subarray(only.offset, only.offset + only.length);
|
|
2366
|
+
return view.buffer instanceof ArrayBuffer ? view : new Uint8Array(view);
|
|
2367
|
+
}
|
|
2368
|
+
const out = new Uint8Array(total);
|
|
2369
|
+
let at = 0;
|
|
2370
|
+
for (const r of ranges) {
|
|
2371
|
+
out.set(bytes.subarray(r.offset, r.offset + r.length), at);
|
|
2372
|
+
at += r.length;
|
|
2373
|
+
}
|
|
2374
|
+
return out;
|
|
2375
|
+
}
|
|
2376
|
+
async function mediaChecksum(source, format, io, options = {}) {
|
|
2377
|
+
if (options.basis === "pcm") {
|
|
2378
|
+
if (format.toUpperCase() !== "FLAC") {
|
|
2379
|
+
throw new UnsupportedFormatError(format, ["FLAC"], {
|
|
2380
|
+
operation: "media checksum (basis: pcm)"
|
|
2381
|
+
});
|
|
2382
|
+
}
|
|
2383
|
+
const windowed = source.bytes.length > 0 ? source.bytes : source.path && io.readPartial ? await io.readPartial(source.path, STREAMINFO_WINDOW, 0) : void 0;
|
|
2384
|
+
let md5 = windowed === void 0 ? void 0 : flacStreamInfoMd5(windowed);
|
|
2385
|
+
if (md5 === void 0 && windowed !== source.bytes) {
|
|
2386
|
+
md5 = flacStreamInfoMd5(await fileBytes(source, io));
|
|
2387
|
+
}
|
|
2388
|
+
if (md5 === void 0) {
|
|
2389
|
+
throw new MetadataError(
|
|
2390
|
+
"read",
|
|
2391
|
+
"STREAMINFO digest unavailable"
|
|
2392
|
+
);
|
|
2393
|
+
}
|
|
2394
|
+
return {
|
|
2395
|
+
source: "flac-streaminfo-md5",
|
|
2396
|
+
algorithm: "md5",
|
|
2397
|
+
hex: md5,
|
|
2398
|
+
bytesHashed: 16
|
|
2399
|
+
};
|
|
2400
|
+
}
|
|
2401
|
+
const bytes = await fileBytes(source, io);
|
|
2402
|
+
const walk = mediaRanges(format, bytes);
|
|
2403
|
+
const payload = joinRanges(bytes, walk.ranges);
|
|
2404
|
+
const digest = new Uint8Array(
|
|
2405
|
+
await crypto.subtle.digest("SHA-256", payload)
|
|
2406
|
+
);
|
|
2407
|
+
let hex = "";
|
|
2408
|
+
for (const byte of digest) hex += byte.toString(16).padStart(2, "0");
|
|
2409
|
+
return {
|
|
2410
|
+
source: walk.kind === "ranges" ? "audio-payload" : "file",
|
|
2411
|
+
algorithm: "sha256",
|
|
2412
|
+
hex,
|
|
2413
|
+
bytesHashed: payload.length
|
|
2414
|
+
};
|
|
2415
|
+
}
|
|
2416
|
+
var STREAMINFO_WINDOW;
|
|
2417
|
+
var init_audio_file_checksum = __esm({
|
|
2418
|
+
"src/taglib/audio-file-checksum.ts"() {
|
|
2419
|
+
"use strict";
|
|
2420
|
+
init_media_ranges();
|
|
2421
|
+
init_errors2();
|
|
2422
|
+
STREAMINFO_WINDOW = 65536;
|
|
2423
|
+
}
|
|
2424
|
+
});
|
|
2425
|
+
|
|
1946
2426
|
// src/utils/file.ts
|
|
1947
2427
|
async function readFileData(file) {
|
|
1948
2428
|
if (file instanceof Uint8Array) return file;
|
|
@@ -2050,8 +2530,8 @@ function wrapEmbindHandle(raw) {
|
|
|
2050
2530
|
// Text only — language/description are not surfaced by the PropertyMap.
|
|
2051
2531
|
getLyrics() {
|
|
2052
2532
|
const props = raw.getProperties();
|
|
2053
|
-
return (props["LYRICS"] ?? []).map((
|
|
2054
|
-
text,
|
|
2533
|
+
return (props["LYRICS"] ?? []).map((text2) => ({
|
|
2534
|
+
text: text2,
|
|
2055
2535
|
description: "",
|
|
2056
2536
|
language: ""
|
|
2057
2537
|
}));
|
|
@@ -2121,7 +2601,10 @@ function wrapEmbindHandle(raw) {
|
|
|
2121
2601
|
...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: pw.isEncrypted() } : {},
|
|
2122
2602
|
...formatVersion > 0 ? { formatVersion } : {},
|
|
2123
2603
|
...isValidBitrateMode(bitrateMode) ? { bitrateMode } : {},
|
|
2124
|
-
|
|
2604
|
+
// OpusHead gain exists only in an Ogg Opus stream. Gating on codec
|
|
2605
|
+
// alone fabricated outputGainDb: 0 for MP4 tracks once the MP4 codec
|
|
2606
|
+
// enum was mapped (the WASI side never emitted it there) — taglib-kswf.
|
|
2607
|
+
...containerFormat === "OGG" && codec === "Opus" ? { outputGainDb: pw.outputGainDb() } : {}
|
|
2125
2608
|
};
|
|
2126
2609
|
}
|
|
2127
2610
|
};
|
|
@@ -2301,6 +2784,8 @@ var init_audio_file_impl = __esm({
|
|
|
2301
2784
|
init_rating();
|
|
2302
2785
|
init_detector();
|
|
2303
2786
|
init_audio_file_base();
|
|
2787
|
+
init_audio_file_checksum();
|
|
2788
|
+
init_platform_io_browser_stub();
|
|
2304
2789
|
init_save_reconstruct();
|
|
2305
2790
|
AudioFileImpl = class extends BaseAudioFileImpl {
|
|
2306
2791
|
constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
|
|
@@ -2313,6 +2798,9 @@ var init_audio_file_impl = __esm({
|
|
|
2313
2798
|
partialLoadOptions
|
|
2314
2799
|
);
|
|
2315
2800
|
__publicField(this, "pathModeBuffer", null);
|
|
2801
|
+
/** How the handle was loaded — `isPartiallyLoaded` is mutated by saveToFile(). */
|
|
2802
|
+
__publicField(this, "partiallyLoadedAtConstruction");
|
|
2803
|
+
this.partiallyLoadedAtConstruction = isPartiallyLoaded;
|
|
2316
2804
|
}
|
|
2317
2805
|
save() {
|
|
2318
2806
|
if (this.isPartiallyLoaded && this.originalSource) {
|
|
@@ -2348,6 +2836,31 @@ var init_audio_file_impl = __esm({
|
|
|
2348
2836
|
"No file data available: in-memory buffer is empty and no source path is set"
|
|
2349
2837
|
);
|
|
2350
2838
|
}
|
|
2839
|
+
mediaChecksum(options) {
|
|
2840
|
+
return mediaChecksum(
|
|
2841
|
+
{
|
|
2842
|
+
// The cheap, no-IO accessor — `getFileBuffer()` would read the whole
|
|
2843
|
+
// file from disk in path mode (and cache it), which the source rule is
|
|
2844
|
+
// about to read for itself.
|
|
2845
|
+
bytes: this.handle.getBuffer(),
|
|
2846
|
+
// empty in path mode: the data is on disk
|
|
2847
|
+
// Both optional fields are conditional: they are `undefined` far more
|
|
2848
|
+
// often than not, and `exactOptionalPropertyTypes` refuses an explicit
|
|
2849
|
+
// `undefined` on an optional property.
|
|
2850
|
+
...this.sourcePath !== void 0 ? { path: this.sourcePath } : {},
|
|
2851
|
+
// originalSource is the File when the caller passed one. A partial File
|
|
2852
|
+
// has no path, so this is the only way back to the real bytes.
|
|
2853
|
+
...this.originalSource instanceof Blob ? { blob: this.originalSource } : {},
|
|
2854
|
+
// Captured at construction, NOT this.isPartiallyLoaded: saveToFile() sets
|
|
2855
|
+
// that flag false (and clears originalSource) while the handle still holds
|
|
2856
|
+
// the spliced image, which would make the source rule hash the image.
|
|
2857
|
+
partiallyLoaded: this.partiallyLoadedAtConstruction
|
|
2858
|
+
},
|
|
2859
|
+
this.getFormat(),
|
|
2860
|
+
getPlatformIO(),
|
|
2861
|
+
options
|
|
2862
|
+
);
|
|
2863
|
+
}
|
|
2351
2864
|
async saveToFile(path) {
|
|
2352
2865
|
const targetPath = path ?? this.sourcePath;
|
|
2353
2866
|
if (!targetPath) {
|
|
@@ -2437,8 +2950,8 @@ var init_audio_file_impl = __esm({
|
|
|
2437
2950
|
}
|
|
2438
2951
|
setChapters(chapters, options) {
|
|
2439
2952
|
const fmt = this.getFormat();
|
|
2440
|
-
if (fmt !== "MP3" && fmt !== "MP4") {
|
|
2441
|
-
throw new UnsupportedFormatError(fmt, ["MP3", "MP4"], {
|
|
2953
|
+
if (fmt !== "MP3" && fmt !== "AAC" && fmt !== "MP4") {
|
|
2954
|
+
throw new UnsupportedFormatError(fmt, ["MP3", "AAC", "MP4"], {
|
|
2442
2955
|
operation: "setChapters"
|
|
2443
2956
|
});
|
|
2444
2957
|
}
|
|
@@ -2545,181 +3058,6 @@ var init_audio_file_impl = __esm({
|
|
|
2545
3058
|
}
|
|
2546
3059
|
});
|
|
2547
3060
|
|
|
2548
|
-
// src/taglib/metadata-extent.ts
|
|
2549
|
-
function be32(b, offset) {
|
|
2550
|
-
return (b[offset] << 24 | b[offset + 1] << 16 | b[offset + 2] << 8 | b[offset + 3]) >>> 0;
|
|
2551
|
-
}
|
|
2552
|
-
function startsWith(bytes, magic, at = 0) {
|
|
2553
|
-
if (bytes.length < at + magic.length) return false;
|
|
2554
|
-
for (let i = 0; i < magic.length; i++) {
|
|
2555
|
-
if (bytes[at + i] !== magic.charCodeAt(i)) return false;
|
|
2556
|
-
}
|
|
2557
|
-
return true;
|
|
2558
|
-
}
|
|
2559
|
-
function id3v2End(bytes) {
|
|
2560
|
-
if (bytes.length < 10) return void 0;
|
|
2561
|
-
const size = bytes[6] << 21 | bytes[7] << 14 | bytes[8] << 7 | bytes[9];
|
|
2562
|
-
const hasFooter = (bytes[5] & 16) !== 0;
|
|
2563
|
-
return 10 + size + (hasFooter ? 10 : 0);
|
|
2564
|
-
}
|
|
2565
|
-
function flacEnd(bytes, limit, start) {
|
|
2566
|
-
let offset = start + 4;
|
|
2567
|
-
for (; ; ) {
|
|
2568
|
-
if (offset + 4 > bytes.length) return void 0;
|
|
2569
|
-
const isLast = (bytes[offset] & 128) !== 0;
|
|
2570
|
-
const length = bytes[offset + 1] << 16 | bytes[offset + 2] << 8 | bytes[offset + 3];
|
|
2571
|
-
offset += 4 + length;
|
|
2572
|
-
if (offset > limit) return offset;
|
|
2573
|
-
if (isLast) return offset;
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
function mp4MoovEnd(bytes, limit, start) {
|
|
2577
|
-
let offset = start;
|
|
2578
|
-
const readable = Math.min(bytes.length, limit);
|
|
2579
|
-
while (offset + 8 <= readable) {
|
|
2580
|
-
const size = be32(bytes, offset);
|
|
2581
|
-
const type = String.fromCharCode(
|
|
2582
|
-
bytes[offset + 4],
|
|
2583
|
-
bytes[offset + 5],
|
|
2584
|
-
bytes[offset + 6],
|
|
2585
|
-
bytes[offset + 7]
|
|
2586
|
-
);
|
|
2587
|
-
if (size < 8) return void 0;
|
|
2588
|
-
if (type === "moov") return offset + size;
|
|
2589
|
-
offset += size;
|
|
2590
|
-
}
|
|
2591
|
-
return void 0;
|
|
2592
|
-
}
|
|
2593
|
-
function oggMetadataEnd(bytes, limit, start) {
|
|
2594
|
-
let offset = start;
|
|
2595
|
-
let packetsCompleted = 0;
|
|
2596
|
-
let first = true;
|
|
2597
|
-
for (; ; ) {
|
|
2598
|
-
if (offset + 27 > bytes.length || !startsWith(bytes, "OggS", offset)) {
|
|
2599
|
-
return void 0;
|
|
2600
|
-
}
|
|
2601
|
-
const segmentCount = bytes[offset + 26];
|
|
2602
|
-
const tableStart = offset + 27;
|
|
2603
|
-
if (tableStart + segmentCount > bytes.length) return void 0;
|
|
2604
|
-
let payload = 0;
|
|
2605
|
-
for (let i = 0; i < segmentCount; i++) {
|
|
2606
|
-
const segment = bytes[tableStart + i];
|
|
2607
|
-
payload += segment;
|
|
2608
|
-
if (segment < 255) packetsCompleted++;
|
|
2609
|
-
}
|
|
2610
|
-
if (first) {
|
|
2611
|
-
const payloadStart = tableStart + segmentCount;
|
|
2612
|
-
if (payloadStart + 5 > bytes.length) return void 0;
|
|
2613
|
-
if (bytes[payloadStart] === 127 && startsWith(bytes, "FLAC", payloadStart + 1)) {
|
|
2614
|
-
return void 0;
|
|
2615
|
-
}
|
|
2616
|
-
first = false;
|
|
2617
|
-
}
|
|
2618
|
-
offset = tableStart + segmentCount + payload;
|
|
2619
|
-
if (packetsCompleted >= 2 || offset > limit) return offset;
|
|
2620
|
-
}
|
|
2621
|
-
}
|
|
2622
|
-
function isMpegFrameSync(bytes, at = 0) {
|
|
2623
|
-
if (bytes.length < at + 4) return false;
|
|
2624
|
-
if (bytes[at] !== 255 || (bytes[at + 1] & 224) !== 224) return false;
|
|
2625
|
-
const version = bytes[at + 1] >> 3 & 3;
|
|
2626
|
-
const layer = bytes[at + 1] >> 1 & 3;
|
|
2627
|
-
if (version === 1 || layer === 0) return false;
|
|
2628
|
-
const bitrateIndex = bytes[at + 2] >> 4 & 15;
|
|
2629
|
-
const sampleRateIndex = bytes[at + 2] >> 2 & 3;
|
|
2630
|
-
if (bitrateIndex === 0 || bitrateIndex === 15 || sampleRateIndex === 3) {
|
|
2631
|
-
return false;
|
|
2632
|
-
}
|
|
2633
|
-
const mpeg1 = version === 3;
|
|
2634
|
-
const layerIndex = layer ^ 3;
|
|
2635
|
-
const bitrate = MPEG_BITRATES[mpeg1 ? 0 : 1][layerIndex][bitrateIndex];
|
|
2636
|
-
const sampleRateTable = mpeg1 ? 0 : version === 2 ? 1 : 2;
|
|
2637
|
-
const sampleRate = MPEG_SAMPLE_RATES[sampleRateTable][sampleRateIndex];
|
|
2638
|
-
const samplesPerFrame = layerIndex === 2 ? mpeg1 ? 1152 : 576 : layerIndex === 1 ? 1152 : 384;
|
|
2639
|
-
let frameLength = Math.floor(samplesPerFrame * bitrate * 125 / sampleRate);
|
|
2640
|
-
if ((bytes[at + 2] & 2) !== 0) frameLength += layerIndex === 0 ? 4 : 1;
|
|
2641
|
-
if (frameLength === 0) return false;
|
|
2642
|
-
const next = at + frameLength;
|
|
2643
|
-
if (bytes.length < next + 4) return false;
|
|
2644
|
-
const mask = 4294839296;
|
|
2645
|
-
const header = ((bytes[at] << 24 | bytes[at + 1] << 16 | bytes[at + 2] << 8 | bytes[at + 3]) >>> 0 & mask) >>> 0;
|
|
2646
|
-
const nextHeader = ((bytes[next] << 24 | bytes[next + 1] << 16 | bytes[next + 2] << 8 | bytes[next + 3]) >>> 0 & mask) >>> 0;
|
|
2647
|
-
return header === nextHeader;
|
|
2648
|
-
}
|
|
2649
|
-
function containerEnd(bytes, at, limit) {
|
|
2650
|
-
if (at >= bytes.length) return bytes.length < limit ? null : void 0;
|
|
2651
|
-
if (at + 8 > bytes.length) return void 0;
|
|
2652
|
-
if (startsWith(bytes, "fLaC", at)) return flacEnd(bytes, limit, at);
|
|
2653
|
-
if (startsWith(bytes, "ftyp", at + 4)) return mp4MoovEnd(bytes, limit, at);
|
|
2654
|
-
if (startsWith(bytes, "OggS", at)) return oggMetadataEnd(bytes, limit, at);
|
|
2655
|
-
return null;
|
|
2656
|
-
}
|
|
2657
|
-
function metadataFitsInHeader(header, limit) {
|
|
2658
|
-
let end;
|
|
2659
|
-
if (startsWith(header, "ID3")) {
|
|
2660
|
-
const tagEnd = id3v2End(header);
|
|
2661
|
-
if (tagEnd === void 0) return false;
|
|
2662
|
-
end = tagEnd;
|
|
2663
|
-
if (tagEnd <= limit) {
|
|
2664
|
-
const behind = containerEnd(header, tagEnd, limit);
|
|
2665
|
-
if (behind === void 0) return false;
|
|
2666
|
-
if (behind !== null) end = behind;
|
|
2667
|
-
}
|
|
2668
|
-
} else {
|
|
2669
|
-
const container = containerEnd(header, 0, limit);
|
|
2670
|
-
if (container === null) {
|
|
2671
|
-
end = isMpegFrameSync(header) ? 0 : void 0;
|
|
2672
|
-
} else {
|
|
2673
|
-
end = container;
|
|
2674
|
-
}
|
|
2675
|
-
}
|
|
2676
|
-
if (end === void 0 || end > header.length) return false;
|
|
2677
|
-
return end <= limit;
|
|
2678
|
-
}
|
|
2679
|
-
function trailerFitsInFooter(tail, footerSize) {
|
|
2680
|
-
if (tail.length < Math.min(footerSize, 32)) return false;
|
|
2681
|
-
for (const offsetFromEnd of [32, 32 + 128]) {
|
|
2682
|
-
const at = tail.length - offsetFromEnd;
|
|
2683
|
-
if (at < 0 || !startsWith(tail, "APETAGEX", at)) continue;
|
|
2684
|
-
const size = (tail[at + 12] | tail[at + 13] << 8 | tail[at + 14] << 16 | tail[at + 15] << 24) >>> 0;
|
|
2685
|
-
const trailing = offsetFromEnd === 32 ? 0 : 128;
|
|
2686
|
-
if (size + 32 + trailing > footerSize) return false;
|
|
2687
|
-
}
|
|
2688
|
-
return true;
|
|
2689
|
-
}
|
|
2690
|
-
var MPEG_BITRATES, MPEG_SAMPLE_RATES;
|
|
2691
|
-
var init_metadata_extent = __esm({
|
|
2692
|
-
"src/taglib/metadata-extent.ts"() {
|
|
2693
|
-
"use strict";
|
|
2694
|
-
MPEG_BITRATES = [
|
|
2695
|
-
[
|
|
2696
|
-
[0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
2697
|
-
// Layer I
|
|
2698
|
-
[0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
2699
|
-
// Layer II
|
|
2700
|
-
[0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0]
|
|
2701
|
-
// Layer III
|
|
2702
|
-
],
|
|
2703
|
-
[
|
|
2704
|
-
[0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],
|
|
2705
|
-
// Layer I
|
|
2706
|
-
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
2707
|
-
// Layer II
|
|
2708
|
-
[0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0]
|
|
2709
|
-
// Layer III
|
|
2710
|
-
]
|
|
2711
|
-
];
|
|
2712
|
-
MPEG_SAMPLE_RATES = [
|
|
2713
|
-
[44100, 48e3, 32e3],
|
|
2714
|
-
// MPEG-1
|
|
2715
|
-
[22050, 24e3, 16e3],
|
|
2716
|
-
// MPEG-2
|
|
2717
|
-
[11025, 12e3, 8e3]
|
|
2718
|
-
// MPEG-2.5
|
|
2719
|
-
];
|
|
2720
|
-
}
|
|
2721
|
-
});
|
|
2722
|
-
|
|
2723
3061
|
// src/taglib/load-audio-data.ts
|
|
2724
3062
|
async function loadAudioData(input, opts) {
|
|
2725
3063
|
if (opts.partial && typeof File !== "undefined" && input instanceof File) {
|
|
@@ -2944,6 +3282,8 @@ function normalizeTagInput(input) {
|
|
|
2944
3282
|
if (val === "explicit") props.itunesAdvisory = ["1"];
|
|
2945
3283
|
else if (val === "clean") props.itunesAdvisory = ["2"];
|
|
2946
3284
|
else if (val === "unspecified") props.itunesAdvisory = [];
|
|
3285
|
+
} else if (field === "bpm") {
|
|
3286
|
+
props[field] = [String(Math.trunc(val))];
|
|
2947
3287
|
} else if (NUMERIC_FIELDS.has(field)) {
|
|
2948
3288
|
props[field] = [String(val)];
|
|
2949
3289
|
} else if (field === "r128TrackGain" || field === "r128AlbumGain") {
|
|
@@ -3004,7 +3344,7 @@ var VERSION;
|
|
|
3004
3344
|
var init_version = __esm({
|
|
3005
3345
|
"src/version.ts"() {
|
|
3006
3346
|
"use strict";
|
|
3007
|
-
VERSION = "2.
|
|
3347
|
+
VERSION = "2.3.0";
|
|
3008
3348
|
}
|
|
3009
3349
|
});
|
|
3010
3350
|
|
|
@@ -3127,30 +3467,19 @@ var init_taglib_class = __esm({
|
|
|
3127
3467
|
const actualInput = isNamedAudioInput(input) ? input.data : input;
|
|
3128
3468
|
const sourcePath = typeof actualInput === "string" ? actualInput : void 0;
|
|
3129
3469
|
if (typeof actualInput === "string" && this.module.isWasi) {
|
|
3130
|
-
const
|
|
3131
|
-
|
|
3132
|
-
const
|
|
3133
|
-
|
|
3134
|
-
|
|
3135
|
-
|
|
3136
|
-
|
|
3137
|
-
|
|
3138
|
-
|
|
3139
|
-
|
|
3140
|
-
|
|
3141
|
-
|
|
3142
|
-
|
|
3143
|
-
fileHandle2,
|
|
3144
|
-
sourcePath,
|
|
3145
|
-
actualInput,
|
|
3146
|
-
false
|
|
3147
|
-
);
|
|
3148
|
-
}
|
|
3149
|
-
} catch (error) {
|
|
3150
|
-
if (typeof fileHandle2.destroy === "function") {
|
|
3151
|
-
fileHandle2.destroy();
|
|
3152
|
-
}
|
|
3153
|
-
throw error;
|
|
3470
|
+
const wasiPath = toWasiPath(actualInput);
|
|
3471
|
+
if (this.module.loadAudioPath) {
|
|
3472
|
+
const fileHandle2 = await this.module.loadAudioPath(
|
|
3473
|
+
wasiPath,
|
|
3474
|
+
actualInput
|
|
3475
|
+
);
|
|
3476
|
+
return new AudioFileImpl(
|
|
3477
|
+
this.module,
|
|
3478
|
+
fileHandle2,
|
|
3479
|
+
sourcePath,
|
|
3480
|
+
actualInput,
|
|
3481
|
+
false
|
|
3482
|
+
);
|
|
3154
3483
|
}
|
|
3155
3484
|
}
|
|
3156
3485
|
const opts = {
|
|
@@ -3303,6 +3632,11 @@ init_taglib2();
|
|
|
3303
3632
|
init_audio_formats();
|
|
3304
3633
|
init_errors2();
|
|
3305
3634
|
|
|
3635
|
+
// src/runtime/deno-detect.ts
|
|
3636
|
+
function isDenoCompiled() {
|
|
3637
|
+
return typeof Deno !== "undefined" && typeof Deno.mainModule === "string" && Deno.mainModule.includes("deno-compile://");
|
|
3638
|
+
}
|
|
3639
|
+
|
|
3306
3640
|
// src/simple/config.ts
|
|
3307
3641
|
var cachedTagLib = null;
|
|
3308
3642
|
var bufferModeEnabled = false;
|
|
@@ -3494,7 +3828,6 @@ async function readPictureMetadata(file) {
|
|
|
3494
3828
|
// src/simple/batch-operations.ts
|
|
3495
3829
|
init_audio_formats();
|
|
3496
3830
|
init_errors2();
|
|
3497
|
-
init_properties();
|
|
3498
3831
|
init_tag_mapping();
|
|
3499
3832
|
async function executeBatch(files, options, processor) {
|
|
3500
3833
|
if (files.length === 0) return { items: [], duration: 0 };
|
|
@@ -3613,11 +3946,10 @@ async function readMetadataBatch(files, options = {}) {
|
|
|
3613
3946
|
});
|
|
3614
3947
|
}
|
|
3615
3948
|
|
|
3616
|
-
// src/simple/
|
|
3617
|
-
|
|
3618
|
-
|
|
3619
|
-
|
|
3620
|
-
init_properties();
|
|
3949
|
+
// src/simple/checksum-operations.ts
|
|
3950
|
+
async function readMediaChecksum(file, options) {
|
|
3951
|
+
return withAudioFile(file, (audioFile) => audioFile.mediaChecksum(options));
|
|
3952
|
+
}
|
|
3621
3953
|
|
|
3622
3954
|
// src/constants/format-mappings.ts
|
|
3623
3955
|
var FormatMappings = {
|
|
@@ -3698,12 +4030,596 @@ function propertyValue(props, key) {
|
|
|
3698
4030
|
return props[key]?.[0];
|
|
3699
4031
|
}
|
|
3700
4032
|
|
|
4033
|
+
// src/folder-api/folder-disc.ts
|
|
4034
|
+
function dirname(path) {
|
|
4035
|
+
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
4036
|
+
return i === -1 ? path : path.slice(0, i);
|
|
4037
|
+
}
|
|
4038
|
+
var UNCONDITIONAL_MARKERS = [
|
|
4039
|
+
"cd",
|
|
4040
|
+
"disc",
|
|
4041
|
+
"disk",
|
|
4042
|
+
"dvd",
|
|
4043
|
+
"sacd",
|
|
4044
|
+
"blu-ray",
|
|
4045
|
+
"bd",
|
|
4046
|
+
"digital media"
|
|
4047
|
+
];
|
|
4048
|
+
var GATED_MARKERS = ["tape", "vinyl", "cassette", "lp", "record"];
|
|
4049
|
+
var MARKERS = [...UNCONDITIONAL_MARKERS, ...GATED_MARKERS];
|
|
4050
|
+
var GATED_MARKER_SET = new Set(GATED_MARKERS);
|
|
4051
|
+
var WORD_NUMBERS = {
|
|
4052
|
+
one: 1,
|
|
4053
|
+
two: 2,
|
|
4054
|
+
three: 3,
|
|
4055
|
+
four: 4,
|
|
4056
|
+
five: 5,
|
|
4057
|
+
six: 6,
|
|
4058
|
+
seven: 7,
|
|
4059
|
+
eight: 8,
|
|
4060
|
+
nine: 9,
|
|
4061
|
+
ten: 10,
|
|
4062
|
+
eleven: 11,
|
|
4063
|
+
twelve: 12
|
|
4064
|
+
};
|
|
4065
|
+
function romanToInt(s) {
|
|
4066
|
+
const values = {
|
|
4067
|
+
i: 1,
|
|
4068
|
+
v: 5,
|
|
4069
|
+
x: 10,
|
|
4070
|
+
l: 50,
|
|
4071
|
+
c: 100,
|
|
4072
|
+
d: 500,
|
|
4073
|
+
m: 1e3
|
|
4074
|
+
};
|
|
4075
|
+
let total = 0;
|
|
4076
|
+
let prev = 0;
|
|
4077
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
4078
|
+
const v = values[s[i]];
|
|
4079
|
+
if (v === void 0) return NaN;
|
|
4080
|
+
total += v < prev ? -v : v;
|
|
4081
|
+
prev = v;
|
|
4082
|
+
}
|
|
4083
|
+
return total;
|
|
4084
|
+
}
|
|
4085
|
+
function parseNumberToken(token) {
|
|
4086
|
+
if (/^\d+$/.test(token)) return parseInt(token, 10);
|
|
4087
|
+
const word = WORD_NUMBERS[token.toLowerCase()];
|
|
4088
|
+
if (word) return word;
|
|
4089
|
+
const roman = romanToInt(token.toLowerCase());
|
|
4090
|
+
return Number.isFinite(roman) ? roman : void 0;
|
|
4091
|
+
}
|
|
4092
|
+
var SEP = String.raw`[\s._#-]*`;
|
|
4093
|
+
var NUM = String.raw`(?:\d+|(?:[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|[a-z])(?![a-z]))`;
|
|
4094
|
+
function parseDiscName(name) {
|
|
4095
|
+
const trimmed = name.trim();
|
|
4096
|
+
if (/^(?:extras?|bonus)$/i.test(trimmed)) return void 0;
|
|
4097
|
+
for (const marker of MARKERS) {
|
|
4098
|
+
const left = String.raw`(?:^|[^a-z0-9])`;
|
|
4099
|
+
const esc = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
4100
|
+
const re = new RegExp(
|
|
4101
|
+
left + String.raw`(${esc})(?![a-z])${SEP}(${NUM})`,
|
|
4102
|
+
"i"
|
|
4103
|
+
);
|
|
4104
|
+
const m = trimmed.match(re);
|
|
4105
|
+
if (!m) continue;
|
|
4106
|
+
const sideLetter = /^[a-z]$/i.test(m[2]);
|
|
4107
|
+
const number = sideLetter ? void 0 : parseNumberToken(m[2]);
|
|
4108
|
+
if (number === void 0 && !sideLetter) continue;
|
|
4109
|
+
if (!sideLetter && /^[ivxlcdm]{2,}$/i.test(m[2]) && (number ?? 0) > 100) continue;
|
|
4110
|
+
const isGated = GATED_MARKER_SET.has(marker.toLowerCase()) || sideLetter;
|
|
4111
|
+
const markerStart = m.index + m[1].length - marker.length;
|
|
4112
|
+
const rawPrefix = trimmed.slice(0, markerStart).trim();
|
|
4113
|
+
const title = rawPrefix.replace(/[()[\]]/g, "").replace(/[\s._#-]+$/, "") || void 0;
|
|
4114
|
+
const isExact = title === void 0;
|
|
4115
|
+
const after = trimmed.slice(m.index + m[0].length).trim();
|
|
4116
|
+
const discTitle = after.replace(/^[()[\],:;\-–—\s]+/, "").replace(/[)\]\[,:;\-–—\s]+$/, "").trim() || void 0;
|
|
4117
|
+
const ofMatch = after.trim().match(/^of\s+(\d+)\b/i);
|
|
4118
|
+
const total = ofMatch ? parseInt(ofMatch[1], 10) : void 0;
|
|
4119
|
+
return {
|
|
4120
|
+
kind: isExact ? "exact" : "embedded",
|
|
4121
|
+
number: sideLetter ? void 0 : number,
|
|
4122
|
+
total,
|
|
4123
|
+
title,
|
|
4124
|
+
discTitle,
|
|
4125
|
+
gated: isGated,
|
|
4126
|
+
sideLetter,
|
|
4127
|
+
digit: /^\d+$/.test(m[2])
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
const volume = trimmed.match(
|
|
4131
|
+
/^(?:volume|vol\.?|part|pt\.?)\s*(\d+)(?:\s*of\s*(\d+))?$/i
|
|
4132
|
+
);
|
|
4133
|
+
if (volume) {
|
|
4134
|
+
return {
|
|
4135
|
+
kind: "volume",
|
|
4136
|
+
number: parseInt(volume[1], 10),
|
|
4137
|
+
total: volume[2] ? parseInt(volume[2], 10) : void 0,
|
|
4138
|
+
title: void 0,
|
|
4139
|
+
discTitle: void 0,
|
|
4140
|
+
gated: false,
|
|
4141
|
+
sideLetter: false,
|
|
4142
|
+
digit: true
|
|
4143
|
+
};
|
|
4144
|
+
}
|
|
4145
|
+
if (/^bonus\s+disc$/i.test(trimmed)) {
|
|
4146
|
+
return {
|
|
4147
|
+
kind: "bonus",
|
|
4148
|
+
number: void 0,
|
|
4149
|
+
total: void 0,
|
|
4150
|
+
title: void 0,
|
|
4151
|
+
discTitle: void 0,
|
|
4152
|
+
gated: true,
|
|
4153
|
+
sideLetter: false,
|
|
4154
|
+
digit: false
|
|
4155
|
+
};
|
|
4156
|
+
}
|
|
4157
|
+
if (/^(?:\d+|v\d+|p\d+|[ab])$/i.test(trimmed)) {
|
|
4158
|
+
return {
|
|
4159
|
+
kind: "bare",
|
|
4160
|
+
number: /^\d+$/i.test(trimmed) ? parseInt(trimmed, 10) : void 0,
|
|
4161
|
+
total: void 0,
|
|
4162
|
+
title: void 0,
|
|
4163
|
+
discTitle: void 0,
|
|
4164
|
+
gated: true,
|
|
4165
|
+
sideLetter: false,
|
|
4166
|
+
digit: false
|
|
4167
|
+
};
|
|
4168
|
+
}
|
|
4169
|
+
return void 0;
|
|
4170
|
+
}
|
|
4171
|
+
function baseConfidence(parse) {
|
|
4172
|
+
switch (parse.kind) {
|
|
4173
|
+
case "exact":
|
|
4174
|
+
return parse.digit ? "high" : "low";
|
|
4175
|
+
case "embedded":
|
|
4176
|
+
return "medium";
|
|
4177
|
+
case "volume":
|
|
4178
|
+
return "medium";
|
|
4179
|
+
case "bonus":
|
|
4180
|
+
case "bare":
|
|
4181
|
+
return "low";
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
function discFolderInfo(name) {
|
|
4185
|
+
const parse = parseDiscName(name);
|
|
4186
|
+
if (!parse) return void 0;
|
|
4187
|
+
return {
|
|
4188
|
+
kind: parse.kind,
|
|
4189
|
+
gated: parse.gated,
|
|
4190
|
+
number: parse.number,
|
|
4191
|
+
total: parse.total,
|
|
4192
|
+
title: parse.title,
|
|
4193
|
+
discTitle: parse.discTitle,
|
|
4194
|
+
confidence: parse.gated ? "low" : baseConfidence(parse)
|
|
4195
|
+
};
|
|
4196
|
+
}
|
|
4197
|
+
|
|
4198
|
+
// src/utils/path.ts
|
|
4199
|
+
function basename(path) {
|
|
4200
|
+
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
4201
|
+
return i === -1 ? path : path.slice(i + 1);
|
|
4202
|
+
}
|
|
4203
|
+
|
|
4204
|
+
// src/folder-api/album-corroborate.ts
|
|
4205
|
+
var CONFIDENCE_RANK = {
|
|
4206
|
+
low: 0,
|
|
4207
|
+
medium: 1,
|
|
4208
|
+
high: 2
|
|
4209
|
+
};
|
|
4210
|
+
function corroborated(parse, siblings) {
|
|
4211
|
+
if (!parse.gated && parse.kind === "exact") return true;
|
|
4212
|
+
if (siblings.length === 0) return false;
|
|
4213
|
+
const siblingParses = siblings.map((s) => parseDiscName(basename(s))).filter((p) => p !== void 0);
|
|
4214
|
+
const hasExactSibling = siblingParses.some(
|
|
4215
|
+
(p) => !p.gated && p.kind === "exact"
|
|
4216
|
+
);
|
|
4217
|
+
const hasNumberedSibling = siblingParses.some(
|
|
4218
|
+
(p) => p.number !== void 0
|
|
4219
|
+
);
|
|
4220
|
+
if (parse.number !== void 0 && hasNumberedSibling) return true;
|
|
4221
|
+
if (!parse.gated && parse.kind === "embedded" && hasExactSibling) return true;
|
|
4222
|
+
if (!parse.gated && parse.kind === "volume" && hasNumberedSibling) {
|
|
4223
|
+
return true;
|
|
4224
|
+
}
|
|
4225
|
+
if (parse.sideLetter) {
|
|
4226
|
+
return siblingParses.some((p) => p.sideLetter);
|
|
4227
|
+
}
|
|
4228
|
+
if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
4229
|
+
const numberedCount = siblingParses.filter((p) => p.number !== void 0).length;
|
|
4230
|
+
if (numberedCount >= 2) return true;
|
|
4231
|
+
if (parse.number !== void 0 && hasNumberedSibling) return true;
|
|
4232
|
+
}
|
|
4233
|
+
return false;
|
|
4234
|
+
}
|
|
4235
|
+
|
|
4236
|
+
// src/folder-api/album-identity.ts
|
|
4237
|
+
function normalizeKey(s) {
|
|
4238
|
+
return s.trim().toLowerCase().replace(/\s+/g, " ");
|
|
4239
|
+
}
|
|
4240
|
+
function isGenericAlbumArtist(albumArtist) {
|
|
4241
|
+
if (!albumArtist || albumArtist.trim() === "") return true;
|
|
4242
|
+
const n = normalizeKey(albumArtist);
|
|
4243
|
+
return n === "various artists" || n === "va";
|
|
4244
|
+
}
|
|
4245
|
+
function evidenceFor(item, confirmedDiscs, flatSubdivisions2) {
|
|
4246
|
+
const dir = dirname(item.path);
|
|
4247
|
+
const own = confirmedDiscs.get(dir);
|
|
4248
|
+
if (own) {
|
|
4249
|
+
return {
|
|
4250
|
+
folderDiscNumber: own.parse.number,
|
|
4251
|
+
total: own.parse.total,
|
|
4252
|
+
folderDiscTitle: own.parse.discTitle,
|
|
4253
|
+
confidence: own.confidence,
|
|
4254
|
+
albumDir: dirname(dir)
|
|
4255
|
+
};
|
|
4256
|
+
}
|
|
4257
|
+
let cursor = dirname(dir);
|
|
4258
|
+
while (cursor !== void 0) {
|
|
4259
|
+
const entry = confirmedDiscs.get(cursor);
|
|
4260
|
+
if (entry) {
|
|
4261
|
+
return {
|
|
4262
|
+
folderDiscNumber: entry.parse.number,
|
|
4263
|
+
total: entry.parse.total,
|
|
4264
|
+
folderDiscTitle: entry.parse.discTitle,
|
|
4265
|
+
confidence: entry.confidence,
|
|
4266
|
+
albumDir: dirname(cursor)
|
|
4267
|
+
};
|
|
4268
|
+
}
|
|
4269
|
+
const parent = dirname(cursor);
|
|
4270
|
+
if (parent === cursor) break;
|
|
4271
|
+
cursor = parent;
|
|
4272
|
+
}
|
|
4273
|
+
const flat = flatSubdivisions2.get(dir)?.get(item.path);
|
|
4274
|
+
if (flat !== void 0) {
|
|
4275
|
+
return {
|
|
4276
|
+
folderDiscNumber: flat,
|
|
4277
|
+
total: void 0,
|
|
4278
|
+
folderDiscTitle: void 0,
|
|
4279
|
+
confidence: "medium",
|
|
4280
|
+
albumDir: dir
|
|
4281
|
+
};
|
|
4282
|
+
}
|
|
4283
|
+
return void 0;
|
|
4284
|
+
}
|
|
4285
|
+
function folderIdentity(dir, confirmedDiscs, scanRoot) {
|
|
4286
|
+
const own = confirmedDiscs.get(dir);
|
|
4287
|
+
if (!own) {
|
|
4288
|
+
const title2 = basename(dir);
|
|
4289
|
+
return title2 ? { title: title2, titleSource: dir } : void 0;
|
|
4290
|
+
}
|
|
4291
|
+
if (own.parse.title !== void 0) {
|
|
4292
|
+
return { title: own.parse.title, titleSource: dirname(dir) };
|
|
4293
|
+
}
|
|
4294
|
+
const source = dirname(dir);
|
|
4295
|
+
if (source === scanRoot || source === dir) return void 0;
|
|
4296
|
+
const title = basename(source);
|
|
4297
|
+
return title ? { title, titleSource: source } : void 0;
|
|
4298
|
+
}
|
|
4299
|
+
function buildAlbumIdentity(okItems, confirmedDiscs, flatSubdivisions2, options) {
|
|
4300
|
+
const albums = /* @__PURE__ */ new Map();
|
|
4301
|
+
const unmatched = [];
|
|
4302
|
+
for (const item of okItems) {
|
|
4303
|
+
const dir = dirname(item.path);
|
|
4304
|
+
const albumTag = item.tags.album?.[0]?.trim();
|
|
4305
|
+
const albumArtistTag = item.tags.albumArtist?.[0]?.trim();
|
|
4306
|
+
if (albumTag) {
|
|
4307
|
+
const artistPart = isGenericAlbumArtist(albumArtistTag) ? "" : normalizeKey(albumArtistTag ?? "");
|
|
4308
|
+
const key2 = `${artistPart}::${normalizeKey(albumTag)}`;
|
|
4309
|
+
let acc2 = albums.get(key2);
|
|
4310
|
+
if (!acc2) {
|
|
4311
|
+
acc2 = {
|
|
4312
|
+
key: key2,
|
|
4313
|
+
album: albumTag,
|
|
4314
|
+
albumArtist: isGenericAlbumArtist(albumArtistTag) ? void 0 : albumArtistTag,
|
|
4315
|
+
source: "tags",
|
|
4316
|
+
directory: void 0,
|
|
4317
|
+
items: [],
|
|
4318
|
+
evidence: /* @__PURE__ */ new Map()
|
|
4319
|
+
};
|
|
4320
|
+
albums.set(key2, acc2);
|
|
4321
|
+
}
|
|
4322
|
+
const evidence2 = evidenceFor(item, confirmedDiscs, flatSubdivisions2);
|
|
4323
|
+
acc2.evidence.set(item.path, evidence2);
|
|
4324
|
+
acc2.items.push({
|
|
4325
|
+
...item.metadata,
|
|
4326
|
+
albumDir: evidence2?.albumDir ?? dir,
|
|
4327
|
+
discNumber: evidence2?.folderDiscNumber
|
|
4328
|
+
});
|
|
4329
|
+
continue;
|
|
4330
|
+
}
|
|
4331
|
+
if (!options.useFolderFallback) {
|
|
4332
|
+
unmatched.push(item.metadata);
|
|
4333
|
+
continue;
|
|
4334
|
+
}
|
|
4335
|
+
const identity = folderIdentity(dir, confirmedDiscs, options.scanRoot);
|
|
4336
|
+
if (!identity) {
|
|
4337
|
+
unmatched.push(item.metadata);
|
|
4338
|
+
continue;
|
|
4339
|
+
}
|
|
4340
|
+
const key = `${identity.titleSource}::${normalizeKey(identity.title)}`;
|
|
4341
|
+
let acc = albums.get(key);
|
|
4342
|
+
if (!acc) {
|
|
4343
|
+
acc = {
|
|
4344
|
+
key,
|
|
4345
|
+
album: identity.title,
|
|
4346
|
+
albumArtist: void 0,
|
|
4347
|
+
source: "folder",
|
|
4348
|
+
directory: identity.titleSource,
|
|
4349
|
+
items: [],
|
|
4350
|
+
evidence: /* @__PURE__ */ new Map()
|
|
4351
|
+
};
|
|
4352
|
+
albums.set(key, acc);
|
|
4353
|
+
}
|
|
4354
|
+
const evidence = evidenceFor(item, confirmedDiscs, flatSubdivisions2);
|
|
4355
|
+
acc.evidence.set(item.path, evidence);
|
|
4356
|
+
acc.items.push({
|
|
4357
|
+
...item.metadata,
|
|
4358
|
+
albumDir: evidence?.albumDir ?? dir,
|
|
4359
|
+
discNumber: evidence?.folderDiscNumber
|
|
4360
|
+
});
|
|
4361
|
+
}
|
|
4362
|
+
for (const [key, acc] of [...albums]) {
|
|
4363
|
+
if (acc.source === "tags") continue;
|
|
4364
|
+
for (const other of albums.values()) {
|
|
4365
|
+
if (other === acc || other.source !== "tags") continue;
|
|
4366
|
+
if (normalizeKey(other.album) !== normalizeKey(acc.album)) continue;
|
|
4367
|
+
const albumDir = acc.directory;
|
|
4368
|
+
const sharesChain = albumDir !== void 0 && other.items.some((o) => {
|
|
4369
|
+
const od = dirname(o.path);
|
|
4370
|
+
return od === albumDir || od.startsWith(albumDir + "/");
|
|
4371
|
+
});
|
|
4372
|
+
if (!sharesChain) continue;
|
|
4373
|
+
for (const item of acc.items) {
|
|
4374
|
+
other.items.push(item);
|
|
4375
|
+
other.evidence.set(item.path, acc.evidence.get(item.path));
|
|
4376
|
+
}
|
|
4377
|
+
albums.delete(key);
|
|
4378
|
+
break;
|
|
4379
|
+
}
|
|
4380
|
+
}
|
|
4381
|
+
return { albums, unmatched };
|
|
4382
|
+
}
|
|
4383
|
+
|
|
4384
|
+
// src/folder-api/album-discs.ts
|
|
4385
|
+
function assembleDiscs(albums) {
|
|
4386
|
+
const groups = [];
|
|
4387
|
+
for (const acc of albums.values()) {
|
|
4388
|
+
const dirTagCommon = /* @__PURE__ */ new Map();
|
|
4389
|
+
const resolvedByFile = /* @__PURE__ */ new Map();
|
|
4390
|
+
for (const item of acc.items) {
|
|
4391
|
+
const dir = dirname(item.path);
|
|
4392
|
+
if (!dirTagCommon.has(dir)) {
|
|
4393
|
+
const tagged = acc.items.filter((i) => dirname(i.path) === dir).map((i) => i.tags.discNumber).filter((d) => d !== void 0 && d > 0);
|
|
4394
|
+
dirTagCommon.set(
|
|
4395
|
+
dir,
|
|
4396
|
+
tagged.length > 0 && tagged.every((d) => d === tagged[0]) ? tagged[0] : void 0
|
|
4397
|
+
);
|
|
4398
|
+
}
|
|
4399
|
+
const evidence = acc.evidence.get(item.path);
|
|
4400
|
+
const tagCommon = dirTagCommon.get(dir);
|
|
4401
|
+
resolvedByFile.set(
|
|
4402
|
+
item.path,
|
|
4403
|
+
tagCommon ?? evidence?.folderDiscNumber
|
|
4404
|
+
);
|
|
4405
|
+
}
|
|
4406
|
+
const discs = /* @__PURE__ */ new Map();
|
|
4407
|
+
const order = [];
|
|
4408
|
+
const noEvidenceItems = [];
|
|
4409
|
+
for (const item of acc.items) {
|
|
4410
|
+
const evidence = acc.evidence.get(item.path);
|
|
4411
|
+
if (evidence === void 0) {
|
|
4412
|
+
noEvidenceItems.push(item);
|
|
4413
|
+
continue;
|
|
4414
|
+
}
|
|
4415
|
+
const resolved = resolvedByFile.get(item.path);
|
|
4416
|
+
const discKey = resolved === void 0 ? `dir:${dirname(item.path)}` : String(resolved);
|
|
4417
|
+
let disc = discs.get(discKey);
|
|
4418
|
+
if (!disc) {
|
|
4419
|
+
disc = {
|
|
4420
|
+
discNumber: resolved,
|
|
4421
|
+
totalDiscs: evidence.total,
|
|
4422
|
+
folderDiscNumber: evidence.folderDiscNumber,
|
|
4423
|
+
folderDiscTitle: evidence.folderDiscTitle,
|
|
4424
|
+
tagDiscNumber: void 0,
|
|
4425
|
+
confidence: evidence.confidence,
|
|
4426
|
+
items: []
|
|
4427
|
+
};
|
|
4428
|
+
discs.set(discKey, disc);
|
|
4429
|
+
order.push(discKey);
|
|
4430
|
+
}
|
|
4431
|
+
disc.items.push(item);
|
|
4432
|
+
}
|
|
4433
|
+
if (noEvidenceItems.length > 0) {
|
|
4434
|
+
const numbered = [...discs.values()].filter((d) => d.folderDiscNumber !== void 0).sort((a, b) => a.folderDiscNumber - b.folderDiscNumber);
|
|
4435
|
+
if (numbered.length > 0) {
|
|
4436
|
+
numbered[0].items.push(...noEvidenceItems);
|
|
4437
|
+
} else {
|
|
4438
|
+
const key = "dir:__none__";
|
|
4439
|
+
let disc = discs.get(key);
|
|
4440
|
+
if (!disc) {
|
|
4441
|
+
disc = {
|
|
4442
|
+
discNumber: void 0,
|
|
4443
|
+
totalDiscs: void 0,
|
|
4444
|
+
folderDiscNumber: void 0,
|
|
4445
|
+
folderDiscTitle: void 0,
|
|
4446
|
+
tagDiscNumber: void 0,
|
|
4447
|
+
confidence: "high",
|
|
4448
|
+
items: []
|
|
4449
|
+
};
|
|
4450
|
+
discs.set(key, disc);
|
|
4451
|
+
order.push(key);
|
|
4452
|
+
}
|
|
4453
|
+
disc.items.push(...noEvidenceItems);
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
for (const disc of discs.values()) {
|
|
4457
|
+
const tagged = disc.items.map((i) => i.tags.discNumber).filter((d) => d !== void 0 && d > 0);
|
|
4458
|
+
const common = tagged.length > 0 && tagged.every((d) => d === tagged[0]) ? tagged[0] : void 0;
|
|
4459
|
+
disc.tagDiscNumber = common;
|
|
4460
|
+
disc.discNumber = common ?? disc.folderDiscNumber;
|
|
4461
|
+
for (const item of disc.items) item.discNumber = disc.discNumber;
|
|
4462
|
+
}
|
|
4463
|
+
const albumTaggedTotals = acc.items.map((i) => i.tags.totalDiscs).filter((t) => t !== void 0 && t > 0);
|
|
4464
|
+
const commonTotal = albumTaggedTotals.length > 0 && albumTaggedTotals.every((t) => t === albumTaggedTotals[0]) ? albumTaggedTotals[0] : void 0;
|
|
4465
|
+
const ofN = [...discs.values()].map((d) => d.totalDiscs).find(
|
|
4466
|
+
(t) => t !== void 0
|
|
4467
|
+
);
|
|
4468
|
+
const maxSibling = [...discs.values()].map((d) => d.discNumber).filter((n) => n !== void 0).reduce((a, b) => Math.max(a, b), 0) || void 0;
|
|
4469
|
+
for (const disc of discs.values()) {
|
|
4470
|
+
disc.totalDiscs = commonTotal ?? ofN ?? maxSibling;
|
|
4471
|
+
}
|
|
4472
|
+
const sortedDiscs = [...discs.values()].sort((a, b) => {
|
|
4473
|
+
const an = a.discNumber === void 0 ? -1 : a.discNumber;
|
|
4474
|
+
const bn = b.discNumber === void 0 ? -1 : b.discNumber;
|
|
4475
|
+
if (an !== bn) return an - bn;
|
|
4476
|
+
return (a.folderDiscTitle ?? "").localeCompare(b.folderDiscTitle ?? "");
|
|
4477
|
+
});
|
|
4478
|
+
for (const disc of sortedDiscs) {
|
|
4479
|
+
disc.items.sort((a, b) => {
|
|
4480
|
+
const at = a.tags.track ?? Infinity;
|
|
4481
|
+
const bt = b.tags.track ?? Infinity;
|
|
4482
|
+
if (at !== bt) return at - bt;
|
|
4483
|
+
return a.path.localeCompare(b.path);
|
|
4484
|
+
});
|
|
4485
|
+
}
|
|
4486
|
+
const flags = acc.items.map((i) => i.tags.compilation).filter((c) => c !== void 0);
|
|
4487
|
+
const compilation = flags.length > 0 && flags.every((f) => f === flags[0]) ? flags[0] : void 0;
|
|
4488
|
+
const albumDirs = new Set(acc.items.map((i) => i.albumDir));
|
|
4489
|
+
const directory = albumDirs.size === 1 ? [...albumDirs][0] : acc.directory;
|
|
4490
|
+
groups.push({
|
|
4491
|
+
key: acc.key,
|
|
4492
|
+
albumArtist: acc.albumArtist,
|
|
4493
|
+
album: acc.album,
|
|
4494
|
+
source: acc.source,
|
|
4495
|
+
compilation,
|
|
4496
|
+
directory,
|
|
4497
|
+
discs: sortedDiscs,
|
|
4498
|
+
items: sortedDiscs.flatMap((d) => d.items)
|
|
4499
|
+
});
|
|
4500
|
+
}
|
|
4501
|
+
return groups;
|
|
4502
|
+
}
|
|
4503
|
+
|
|
4504
|
+
// src/folder-api/album-grouping.ts
|
|
4505
|
+
function flatPrefix(filename) {
|
|
4506
|
+
const separated = filename.match(/^(\d{1,2})[\s._-](\d{1,2})/);
|
|
4507
|
+
if (separated) return { disc: parseInt(separated[1], 10), form: "separated" };
|
|
4508
|
+
const compact = filename.match(/^(\d)(\d{2})(?:\D|$)/);
|
|
4509
|
+
if (compact) return { disc: parseInt(compact[1], 10), form: "compact" };
|
|
4510
|
+
return void 0;
|
|
4511
|
+
}
|
|
4512
|
+
function normalizeOk(result) {
|
|
4513
|
+
const okItems = [];
|
|
4514
|
+
const errors = [];
|
|
4515
|
+
for (const item of result.items) {
|
|
4516
|
+
if (item.status === "ok") {
|
|
4517
|
+
okItems.push({ path: item.path, tags: item.tags, metadata: item });
|
|
4518
|
+
} else {
|
|
4519
|
+
errors.push({ path: item.path, error: item.error });
|
|
4520
|
+
}
|
|
4521
|
+
}
|
|
4522
|
+
return { okItems, errors };
|
|
4523
|
+
}
|
|
4524
|
+
function buildTree(okItems) {
|
|
4525
|
+
const tree = /* @__PURE__ */ new Map();
|
|
4526
|
+
for (const item of okItems) {
|
|
4527
|
+
const dir = dirname(item.path);
|
|
4528
|
+
let node = tree.get(dir);
|
|
4529
|
+
if (!node) {
|
|
4530
|
+
node = { files: [], parent: dirname(dir) };
|
|
4531
|
+
tree.set(dir, node);
|
|
4532
|
+
}
|
|
4533
|
+
node.files.push(item);
|
|
4534
|
+
}
|
|
4535
|
+
return tree;
|
|
4536
|
+
}
|
|
4537
|
+
function confirmDiscs(tree, minRank) {
|
|
4538
|
+
const confirmedDiscs = /* @__PURE__ */ new Map();
|
|
4539
|
+
for (const [dir, node] of tree) {
|
|
4540
|
+
if (node.files.length === 0) continue;
|
|
4541
|
+
const parse = parseDiscName(basename(dir));
|
|
4542
|
+
if (!parse) continue;
|
|
4543
|
+
const siblings = [...tree.keys()].filter(
|
|
4544
|
+
(d) => d !== dir && dirname(d) === dirname(dir)
|
|
4545
|
+
);
|
|
4546
|
+
const corroboratedBySiblings = corroborated(parse, siblings);
|
|
4547
|
+
let confidence = parse.gated ? "low" : baseConfidence(parse);
|
|
4548
|
+
if (parse.gated) {
|
|
4549
|
+
if (!corroboratedBySiblings) continue;
|
|
4550
|
+
} else if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
4551
|
+
if (!corroboratedBySiblings) continue;
|
|
4552
|
+
} else if (corroboratedBySiblings) {
|
|
4553
|
+
confidence = "high";
|
|
4554
|
+
}
|
|
4555
|
+
if (CONFIDENCE_RANK[confidence] < minRank) continue;
|
|
4556
|
+
confirmedDiscs.set(dir, { parse, confidence });
|
|
4557
|
+
}
|
|
4558
|
+
return confirmedDiscs;
|
|
4559
|
+
}
|
|
4560
|
+
function flatSubdivisions(tree, confirmedDiscs, useFlatPrefixes) {
|
|
4561
|
+
const flat = /* @__PURE__ */ new Map();
|
|
4562
|
+
if (!useFlatPrefixes) return flat;
|
|
4563
|
+
for (const [dir, node] of tree) {
|
|
4564
|
+
if (node.files.length === 0 || confirmedDiscs.has(dir)) continue;
|
|
4565
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
4566
|
+
const forms = /* @__PURE__ */ new Set();
|
|
4567
|
+
for (const item of node.files) {
|
|
4568
|
+
const prefix = flatPrefix(basename(item.path));
|
|
4569
|
+
if (prefix) {
|
|
4570
|
+
byFile.set(item.path, prefix.disc);
|
|
4571
|
+
forms.add(prefix.form);
|
|
4572
|
+
}
|
|
4573
|
+
}
|
|
4574
|
+
if (byFile.size < 2) continue;
|
|
4575
|
+
const distinct = [...new Set(byFile.values())].sort((a, b) => a - b);
|
|
4576
|
+
const allCompact = forms.size === 1 && forms.has("compact");
|
|
4577
|
+
const plausible = allCompact ? distinct.every((d) => d >= 1 && d <= distinct.length) : true;
|
|
4578
|
+
if (plausible) flat.set(dir, byFile);
|
|
4579
|
+
}
|
|
4580
|
+
return flat;
|
|
4581
|
+
}
|
|
4582
|
+
function groupAlbums(result, options = {}) {
|
|
4583
|
+
const minRank = CONFIDENCE_RANK[options.minFolderConfidence ?? "low"];
|
|
4584
|
+
const useFlatPrefixes = options.flatDiscPrefixes ?? true;
|
|
4585
|
+
const useFolderFallback = options.folderFallback ?? true;
|
|
4586
|
+
const { okItems, errors } = normalizeOk(result);
|
|
4587
|
+
if (okItems.length === 0) {
|
|
4588
|
+
return { albums: [], singles: [], unmatched: [], errors };
|
|
4589
|
+
}
|
|
4590
|
+
const tree = buildTree(okItems);
|
|
4591
|
+
const confirmedDiscs = confirmDiscs(tree, minRank);
|
|
4592
|
+
const flat = flatSubdivisions(tree, confirmedDiscs, useFlatPrefixes);
|
|
4593
|
+
const { albums, unmatched } = buildAlbumIdentity(
|
|
4594
|
+
okItems,
|
|
4595
|
+
confirmedDiscs,
|
|
4596
|
+
flat,
|
|
4597
|
+
{ scanRoot: options.scanRoot, useFolderFallback }
|
|
4598
|
+
);
|
|
4599
|
+
const groups = assembleDiscs(albums);
|
|
4600
|
+
const albumsOut = [];
|
|
4601
|
+
const singles = [];
|
|
4602
|
+
for (const group of groups) {
|
|
4603
|
+
if (group.items.length === 1) {
|
|
4604
|
+
singles.push(group.items[0]);
|
|
4605
|
+
} else {
|
|
4606
|
+
albumsOut.push(group);
|
|
4607
|
+
}
|
|
4608
|
+
}
|
|
4609
|
+
albumsOut.sort(
|
|
4610
|
+
(a, b) => (a.albumArtist ?? "").localeCompare(b.albumArtist ?? "") || (a.album ?? "").localeCompare(b.album ?? "")
|
|
4611
|
+
);
|
|
4612
|
+
return { albums: albumsOut, singles, unmatched, errors };
|
|
4613
|
+
}
|
|
4614
|
+
|
|
3701
4615
|
// src/web-utils/data-url.ts
|
|
3702
4616
|
init_classes();
|
|
3703
4617
|
function pictureToDataURL(picture) {
|
|
4618
|
+
const data = picture.data;
|
|
4619
|
+
const CHUNK = 32768;
|
|
3704
4620
|
let binary = "";
|
|
3705
|
-
for (
|
|
3706
|
-
binary += String.
|
|
4621
|
+
for (let i = 0; i < data.length; i += CHUNK) {
|
|
4622
|
+
binary += String.fromCharCode(...data.subarray(i, i + CHUNK));
|
|
3707
4623
|
}
|
|
3708
4624
|
const base64 = btoa(binary);
|
|
3709
4625
|
return `data:${picture.mimeType};base64,${base64}`;
|
|
@@ -3834,6 +4750,7 @@ async function createPictureGallery(file, container, options = {}) {
|
|
|
3834
4750
|
|
|
3835
4751
|
// index.browser.ts
|
|
3836
4752
|
init_types2();
|
|
4753
|
+
init_bext();
|
|
3837
4754
|
init_rating();
|
|
3838
4755
|
init_module_loader_browser();
|
|
3839
4756
|
export {
|
|
@@ -3860,6 +4777,7 @@ export {
|
|
|
3860
4777
|
applyPictures,
|
|
3861
4778
|
applyTags,
|
|
3862
4779
|
applyTagsToFile,
|
|
4780
|
+
bext_exports as bwf,
|
|
3863
4781
|
canvasToPicture,
|
|
3864
4782
|
clearPictures,
|
|
3865
4783
|
clearTags,
|
|
@@ -3867,13 +4785,16 @@ export {
|
|
|
3867
4785
|
createPictureGallery,
|
|
3868
4786
|
createTagLib,
|
|
3869
4787
|
dataURLToPicture,
|
|
4788
|
+
discFolderInfo,
|
|
3870
4789
|
displayPicture,
|
|
3871
4790
|
findPictureByType,
|
|
3872
4791
|
getAllProperties,
|
|
3873
4792
|
getAllPropertyKeys,
|
|
3874
4793
|
getPropertiesByFormat,
|
|
3875
4794
|
getPropertyMetadata,
|
|
4795
|
+
groupAlbums,
|
|
3876
4796
|
imageFileToPicture,
|
|
4797
|
+
isDenoCompiled,
|
|
3877
4798
|
isEnvironmentError,
|
|
3878
4799
|
isFileOperationError,
|
|
3879
4800
|
isInvalidFormatError,
|
|
@@ -3890,6 +4811,7 @@ export {
|
|
|
3890
4811
|
propertyValues,
|
|
3891
4812
|
readCoverArt,
|
|
3892
4813
|
readFormat,
|
|
4814
|
+
readMediaChecksum,
|
|
3893
4815
|
readMetadata,
|
|
3894
4816
|
readMetadataBatch,
|
|
3895
4817
|
readPictureMetadata,
|