taglib-wasm 2.2.1 → 2.2.3
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 +5 -3
- package/dist/disc-folder.d.ts +3 -1
- package/dist/disc-folder.d.ts.map +1 -1
- package/dist/disc-folder.js +187 -152
- package/dist/index.browser.js +17 -10
- package/dist/index.d.ts +1 -1
- package/dist/simple.browser.js +13 -8
- 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/folder-api/album-corroborate.d.ts +13 -0
- package/dist/src/folder-api/album-corroborate.d.ts.map +1 -0
- package/dist/src/folder-api/album-corroborate.js +36 -0
- package/dist/src/folder-api/album-discs.d.ts +13 -0
- package/dist/src/folder-api/album-discs.d.ts.map +1 -0
- package/dist/src/folder-api/album-discs.js +122 -0
- package/dist/src/folder-api/album-grouping.d.ts +24 -0
- package/dist/src/folder-api/album-grouping.d.ts.map +1 -0
- package/dist/src/folder-api/album-grouping.js +118 -0
- package/dist/src/folder-api/album-identity.d.ts +27 -0
- package/dist/src/folder-api/album-identity.d.ts.map +1 -0
- package/dist/src/folder-api/album-identity.js +151 -0
- package/dist/src/folder-api/{group-albums.d.ts → album-types.d.ts} +55 -26
- package/dist/src/folder-api/album-types.d.ts.map +1 -0
- package/dist/src/folder-api/album-types.js +0 -0
- package/dist/src/folder-api/folder-disc.d.ts +24 -0
- package/dist/src/folder-api/folder-disc.d.ts.map +1 -0
- package/dist/src/folder-api/folder-disc.js +169 -0
- package/dist/src/folder-api/index.d.ts +3 -1
- package/dist/src/folder-api/index.d.ts.map +1 -1
- package/dist/src/folder-api/index.js +2 -4
- package/dist/src/folder-api/scan-for-albums.d.ts +1 -1
- package/dist/src/folder-api/scan-for-albums.d.ts.map +1 -1
- package/dist/src/folder-api/scan-for-albums.js +1 -3
- 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/audio-properties.d.ts +20 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.js +85 -0
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts +5 -19
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/file-handle.js +67 -389
- package/dist/src/runtime/wasi-adapter/handle-state.d.ts +28 -0
- package/dist/src/runtime/wasi-adapter/handle-state.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/handle-state.js +58 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.d.ts +34 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.js +80 -0
- package/dist/src/runtime/wasi-adapter/property-surface.d.ts +25 -0
- package/dist/src/runtime/wasi-adapter/property-surface.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/property-surface.js +104 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.d.ts +44 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.js +139 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.d.ts +28 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.js +46 -0
- package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-impl.js +2 -2
- 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/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/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/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/package.json +8 -13
- package/dist/src/folder-api/group-albums.d.ts.map +0 -1
- package/dist/src/folder-api/group-albums.js +0 -544
package/LICENSE
CHANGED
|
@@ -42,7 +42,7 @@ The WebAssembly binaries are subject to LGPL requirements. This means:
|
|
|
42
42
|
To rebuild the WebAssembly binaries with a modified TagLib:
|
|
43
43
|
1. Modify the TagLib source in lib/taglib/
|
|
44
44
|
2. Run: npm run build:wasm (builds both backends; requires the
|
|
45
|
-
Emscripten SDK and WASI SDK
|
|
45
|
+
Emscripten SDK and WASI SDK 34 — see CONTRIBUTING.md)
|
|
46
46
|
3. The new binaries will be in build/taglib-web.wasm and
|
|
47
47
|
build/taglib-wasi.wasm
|
|
48
48
|
|
package/README.md
CHANGED
|
@@ -386,7 +386,7 @@ import { readProperties } from "taglib-wasm/simple";
|
|
|
386
386
|
const props = await readProperties("song.m4a");
|
|
387
387
|
|
|
388
388
|
console.log(props.containerFormat); // "MP4" (container format)
|
|
389
|
-
console.log(props.codec); // "AAC"
|
|
389
|
+
console.log(props.codec); // "AAC", "ALAC", "AC-3", … (compressed media format)
|
|
390
390
|
console.log(props.isLossless); // false for AAC, true for ALAC
|
|
391
391
|
console.log(props.bitsPerSample); // 16 for most formats
|
|
392
392
|
console.log(props.bitrate); // 256 (kbps)
|
|
@@ -407,7 +407,7 @@ Container format vs Codec:
|
|
|
407
407
|
|
|
408
408
|
Supported formats:
|
|
409
409
|
|
|
410
|
-
- **MP4 container** (.mp4, .m4a) – Can contain AAC
|
|
410
|
+
- **MP4 container** (.mp4, .m4a) – Can contain AAC, ALAC, AC-3, E-AC-3, DTS, FLAC, or Opus
|
|
411
411
|
- **OGG container** (.ogg) – Can contain Vorbis, Opus, FLAC, or Speex
|
|
412
412
|
- **MP3** – Both container and codec (lossy)
|
|
413
413
|
- **FLAC** – Both container and codec (lossless)
|
|
@@ -444,9 +444,11 @@ Supported formats:
|
|
|
444
444
|
`taglib-wasm` is designed to support all formats supported by TagLib:
|
|
445
445
|
|
|
446
446
|
- **.mp3** – ID3v2 and ID3v1 tags
|
|
447
|
+
- **.aac** – ADTS / raw AAC streams (ID3v2 metadata, read through the MPEG reader)
|
|
447
448
|
- **.m4a/.mp4** – MPEG-4/AAC metadata for AAC and Apple Lossless audio
|
|
448
449
|
- **.flac** – Vorbis comments and audio properties (plus BWF `bext`/iXML)
|
|
449
|
-
- **.ogg** – Ogg Vorbis
|
|
450
|
+
- **.ogg** – Ogg container: Vorbis with full metadata support, plus FLAC-in-Ogg
|
|
451
|
+
and Speex
|
|
450
452
|
- **.wav** – INFO chunk metadata, plus BWF `bext` and iXML
|
|
451
453
|
- **Additional formats** – Opus, APE, MPC, WavPack, TrueAudio, AIFF, WMA, and
|
|
452
454
|
more
|
package/dist/disc-folder.d.ts
CHANGED
|
@@ -14,6 +14,8 @@
|
|
|
14
14
|
* discFolderInfo("CD1"); // { kind: "exact", number: 1, ... }
|
|
15
15
|
* ```
|
|
16
16
|
*/
|
|
17
|
-
export
|
|
17
|
+
export { discFolderInfo } from "./src/folder-api/folder-disc.js";
|
|
18
|
+
export { groupAlbums } from "./src/folder-api/album-grouping.js";
|
|
19
|
+
export type { AlbumDisc, AlbumGroup, AlbumGroupingResult, AlbumGroupItem, AlbumGroupKey, DiscConfidence, DiscFolderInfo, GroupAlbumsOptions, } from "./src/folder-api/album-types.js";
|
|
18
20
|
export type { FolderScanResult } from "./src/folder-api/types.js";
|
|
19
21
|
//# sourceMappingURL=disc-folder.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"disc-folder.d.ts","sourceRoot":"","sources":["../disc-folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;
|
|
1
|
+
{"version":3,"file":"disc-folder.d.ts","sourceRoot":"","sources":["../disc-folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,WAAW,EAAE,MAAM,oCAAoC,CAAC;AACjE,YAAY,EACV,SAAS,EACT,UAAU,EACV,mBAAmB,EACnB,cAAc,EACd,aAAa,EACb,cAAc,EACd,cAAc,EACd,kBAAkB,GACnB,MAAM,iCAAiC,CAAC;AACzC,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/disc-folder.js
CHANGED
|
@@ -1,23 +1,4 @@
|
|
|
1
|
-
// src/
|
|
2
|
-
function basename(path) {
|
|
3
|
-
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
4
|
-
return i === -1 ? path : path.slice(i + 1);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
// src/folder-api/group-albums.ts
|
|
8
|
-
function discFolderInfo(name) {
|
|
9
|
-
const parse = parseDiscName(name);
|
|
10
|
-
if (!parse) return void 0;
|
|
11
|
-
return {
|
|
12
|
-
kind: parse.kind,
|
|
13
|
-
gated: parse.gated,
|
|
14
|
-
number: parse.number,
|
|
15
|
-
total: parse.total,
|
|
16
|
-
title: parse.title,
|
|
17
|
-
discTitle: parse.discTitle,
|
|
18
|
-
confidence: parse.gated ? "low" : baseConfidence(parse)
|
|
19
|
-
};
|
|
20
|
-
}
|
|
1
|
+
// src/folder-api/folder-disc.ts
|
|
21
2
|
function dirname(path) {
|
|
22
3
|
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
23
4
|
return i === -1 ? path : path.slice(0, i);
|
|
@@ -168,6 +149,32 @@ function baseConfidence(parse) {
|
|
|
168
149
|
return "low";
|
|
169
150
|
}
|
|
170
151
|
}
|
|
152
|
+
function discFolderInfo(name) {
|
|
153
|
+
const parse = parseDiscName(name);
|
|
154
|
+
if (!parse) return void 0;
|
|
155
|
+
return {
|
|
156
|
+
kind: parse.kind,
|
|
157
|
+
gated: parse.gated,
|
|
158
|
+
number: parse.number,
|
|
159
|
+
total: parse.total,
|
|
160
|
+
title: parse.title,
|
|
161
|
+
discTitle: parse.discTitle,
|
|
162
|
+
confidence: parse.gated ? "low" : baseConfidence(parse)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// src/utils/path.ts
|
|
167
|
+
function basename(path) {
|
|
168
|
+
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
169
|
+
return i === -1 ? path : path.slice(i + 1);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// src/folder-api/album-corroborate.ts
|
|
173
|
+
var CONFIDENCE_RANK = {
|
|
174
|
+
low: 0,
|
|
175
|
+
medium: 1,
|
|
176
|
+
high: 2
|
|
177
|
+
};
|
|
171
178
|
function corroborated(parse, siblings) {
|
|
172
179
|
if (!parse.gated && parse.kind === "exact") return true;
|
|
173
180
|
if (siblings.length === 0) return false;
|
|
@@ -193,13 +200,8 @@ function corroborated(parse, siblings) {
|
|
|
193
200
|
}
|
|
194
201
|
return false;
|
|
195
202
|
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
if (separated) return { disc: parseInt(separated[1], 10), form: "separated" };
|
|
199
|
-
const compact = filename.match(/^(\d)(\d{2})(?:\D|$)/);
|
|
200
|
-
if (compact) return { disc: parseInt(compact[1], 10), form: "compact" };
|
|
201
|
-
return void 0;
|
|
202
|
-
}
|
|
203
|
+
|
|
204
|
+
// src/folder-api/album-identity.ts
|
|
203
205
|
function normalizeKey(s) {
|
|
204
206
|
return s.trim().toLowerCase().replace(/\s+/g, " ");
|
|
205
207
|
}
|
|
@@ -208,134 +210,63 @@ function isGenericAlbumArtist(albumArtist) {
|
|
|
208
210
|
const n = normalizeKey(albumArtist);
|
|
209
211
|
return n === "various artists" || n === "va";
|
|
210
212
|
}
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
for (const item of result.items) {
|
|
223
|
-
if (item.status === "ok") {
|
|
224
|
-
okItems.push({ path: item.path, tags: item.tags, metadata: item });
|
|
225
|
-
} else {
|
|
226
|
-
errors.push({ path: item.path, error: item.error });
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
if (okItems.length === 0) {
|
|
230
|
-
return { albums: [], singles: [], unmatched: [], errors };
|
|
213
|
+
function evidenceFor(item, confirmedDiscs, flatSubdivisions2) {
|
|
214
|
+
const dir = dirname(item.path);
|
|
215
|
+
const own = confirmedDiscs.get(dir);
|
|
216
|
+
if (own) {
|
|
217
|
+
return {
|
|
218
|
+
folderDiscNumber: own.parse.number,
|
|
219
|
+
total: own.parse.total,
|
|
220
|
+
folderDiscTitle: own.parse.discTitle,
|
|
221
|
+
confidence: own.confidence,
|
|
222
|
+
albumDir: dirname(dir)
|
|
223
|
+
};
|
|
231
224
|
}
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
225
|
+
let cursor = dirname(dir);
|
|
226
|
+
while (cursor !== void 0) {
|
|
227
|
+
const entry = confirmedDiscs.get(cursor);
|
|
228
|
+
if (entry) {
|
|
229
|
+
return {
|
|
230
|
+
folderDiscNumber: entry.parse.number,
|
|
231
|
+
total: entry.parse.total,
|
|
232
|
+
folderDiscTitle: entry.parse.discTitle,
|
|
233
|
+
confidence: entry.confidence,
|
|
234
|
+
albumDir: dirname(cursor)
|
|
235
|
+
};
|
|
240
236
|
}
|
|
241
|
-
|
|
237
|
+
const parent = dirname(cursor);
|
|
238
|
+
if (parent === cursor) break;
|
|
239
|
+
cursor = parent;
|
|
242
240
|
}
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
let confidence = parse.gated ? "low" : baseConfidence(parse);
|
|
253
|
-
if (parse.gated) {
|
|
254
|
-
if (!corroboratedBySiblings) continue;
|
|
255
|
-
} else if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
256
|
-
if (!corroboratedBySiblings) continue;
|
|
257
|
-
} else if (corroboratedBySiblings) {
|
|
258
|
-
confidence = "high";
|
|
259
|
-
}
|
|
260
|
-
if (CONFIDENCE_RANK[confidence] < minRank) continue;
|
|
261
|
-
confirmedDiscs.set(dir, { parse, confidence });
|
|
241
|
+
const flat = flatSubdivisions2.get(dir)?.get(item.path);
|
|
242
|
+
if (flat !== void 0) {
|
|
243
|
+
return {
|
|
244
|
+
folderDiscNumber: flat,
|
|
245
|
+
total: void 0,
|
|
246
|
+
folderDiscTitle: void 0,
|
|
247
|
+
confidence: "medium",
|
|
248
|
+
albumDir: dir
|
|
249
|
+
};
|
|
262
250
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
forms.add(prefix.form);
|
|
274
|
-
}
|
|
275
|
-
}
|
|
276
|
-
if (byFile.size < 2) continue;
|
|
277
|
-
const distinct = [...new Set(byFile.values())].sort((a, b) => a - b);
|
|
278
|
-
const allCompact = forms.size === 1 && forms.has("compact");
|
|
279
|
-
const plausible = allCompact ? distinct.every((d) => d >= 1 && d <= distinct.length) : true;
|
|
280
|
-
if (plausible) flatSubdivisions.set(dir, byFile);
|
|
281
|
-
}
|
|
251
|
+
return void 0;
|
|
252
|
+
}
|
|
253
|
+
function folderIdentity(dir, confirmedDiscs, scanRoot) {
|
|
254
|
+
const own = confirmedDiscs.get(dir);
|
|
255
|
+
if (!own) {
|
|
256
|
+
const title2 = basename(dir);
|
|
257
|
+
return title2 ? { title: title2, titleSource: dir } : void 0;
|
|
258
|
+
}
|
|
259
|
+
if (own.parse.title !== void 0) {
|
|
260
|
+
return { title: own.parse.title, titleSource: dirname(dir) };
|
|
282
261
|
}
|
|
262
|
+
const source = dirname(dir);
|
|
263
|
+
if (source === scanRoot || source === dir) return void 0;
|
|
264
|
+
const title = basename(source);
|
|
265
|
+
return title ? { title, titleSource: source } : void 0;
|
|
266
|
+
}
|
|
267
|
+
function buildAlbumIdentity(okItems, confirmedDiscs, flatSubdivisions2, options) {
|
|
283
268
|
const albums = /* @__PURE__ */ new Map();
|
|
284
269
|
const unmatched = [];
|
|
285
|
-
const evidenceFor = (item) => {
|
|
286
|
-
const dir = dirname(item.path);
|
|
287
|
-
const own = confirmedDiscs.get(dir);
|
|
288
|
-
if (own) {
|
|
289
|
-
return {
|
|
290
|
-
folderDiscNumber: own.parse.number,
|
|
291
|
-
total: own.parse.total,
|
|
292
|
-
folderDiscTitle: own.parse.discTitle,
|
|
293
|
-
confidence: own.confidence,
|
|
294
|
-
albumDir: dirname(dir)
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
let cursor = dirname(dir);
|
|
298
|
-
while (cursor !== void 0) {
|
|
299
|
-
const entry = confirmedDiscs.get(cursor);
|
|
300
|
-
if (entry) {
|
|
301
|
-
return {
|
|
302
|
-
folderDiscNumber: entry.parse.number,
|
|
303
|
-
total: entry.parse.total,
|
|
304
|
-
folderDiscTitle: entry.parse.discTitle,
|
|
305
|
-
confidence: entry.confidence,
|
|
306
|
-
albumDir: dirname(cursor)
|
|
307
|
-
};
|
|
308
|
-
}
|
|
309
|
-
const parent = dirname(cursor);
|
|
310
|
-
if (parent === cursor) break;
|
|
311
|
-
cursor = parent;
|
|
312
|
-
}
|
|
313
|
-
const flat = flatSubdivisions.get(dir)?.get(item.path);
|
|
314
|
-
if (flat !== void 0) {
|
|
315
|
-
return {
|
|
316
|
-
folderDiscNumber: flat,
|
|
317
|
-
total: void 0,
|
|
318
|
-
folderDiscTitle: void 0,
|
|
319
|
-
confidence: "medium",
|
|
320
|
-
albumDir: dir
|
|
321
|
-
};
|
|
322
|
-
}
|
|
323
|
-
return void 0;
|
|
324
|
-
};
|
|
325
|
-
const folderIdentity = (dir) => {
|
|
326
|
-
const own = confirmedDiscs.get(dir);
|
|
327
|
-
if (!own) {
|
|
328
|
-
const title2 = basename(dir);
|
|
329
|
-
return title2 ? { title: title2, titleSource: dir } : void 0;
|
|
330
|
-
}
|
|
331
|
-
if (own.parse.title !== void 0) {
|
|
332
|
-
return { title: own.parse.title, titleSource: dirname(dir) };
|
|
333
|
-
}
|
|
334
|
-
const source = dirname(dir);
|
|
335
|
-
if (source === scanRoot || source === dir) return void 0;
|
|
336
|
-
const title = basename(source);
|
|
337
|
-
return title ? { title, titleSource: source } : void 0;
|
|
338
|
-
};
|
|
339
270
|
for (const item of okItems) {
|
|
340
271
|
const dir = dirname(item.path);
|
|
341
272
|
const albumTag = item.tags.album?.[0]?.trim();
|
|
@@ -356,7 +287,7 @@ function groupAlbums(result, options = {}) {
|
|
|
356
287
|
};
|
|
357
288
|
albums.set(key2, acc2);
|
|
358
289
|
}
|
|
359
|
-
const evidence2 = evidenceFor(item);
|
|
290
|
+
const evidence2 = evidenceFor(item, confirmedDiscs, flatSubdivisions2);
|
|
360
291
|
acc2.evidence.set(item.path, evidence2);
|
|
361
292
|
acc2.items.push({
|
|
362
293
|
...item.metadata,
|
|
@@ -365,11 +296,11 @@ function groupAlbums(result, options = {}) {
|
|
|
365
296
|
});
|
|
366
297
|
continue;
|
|
367
298
|
}
|
|
368
|
-
if (!useFolderFallback) {
|
|
299
|
+
if (!options.useFolderFallback) {
|
|
369
300
|
unmatched.push(item.metadata);
|
|
370
301
|
continue;
|
|
371
302
|
}
|
|
372
|
-
const identity = folderIdentity(dir);
|
|
303
|
+
const identity = folderIdentity(dir, confirmedDiscs, options.scanRoot);
|
|
373
304
|
if (!identity) {
|
|
374
305
|
unmatched.push(item.metadata);
|
|
375
306
|
continue;
|
|
@@ -388,7 +319,7 @@ function groupAlbums(result, options = {}) {
|
|
|
388
319
|
};
|
|
389
320
|
albums.set(key, acc);
|
|
390
321
|
}
|
|
391
|
-
const evidence = evidenceFor(item);
|
|
322
|
+
const evidence = evidenceFor(item, confirmedDiscs, flatSubdivisions2);
|
|
392
323
|
acc.evidence.set(item.path, evidence);
|
|
393
324
|
acc.items.push({
|
|
394
325
|
...item.metadata,
|
|
@@ -415,6 +346,11 @@ function groupAlbums(result, options = {}) {
|
|
|
415
346
|
break;
|
|
416
347
|
}
|
|
417
348
|
}
|
|
349
|
+
return { albums, unmatched };
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// src/folder-api/album-discs.ts
|
|
353
|
+
function assembleDiscs(albums) {
|
|
418
354
|
const groups = [];
|
|
419
355
|
for (const acc of albums.values()) {
|
|
420
356
|
const dirTagCommon = /* @__PURE__ */ new Map();
|
|
@@ -530,6 +466,105 @@ function groupAlbums(result, options = {}) {
|
|
|
530
466
|
items: sortedDiscs.flatMap((d) => d.items)
|
|
531
467
|
});
|
|
532
468
|
}
|
|
469
|
+
return groups;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
// src/folder-api/album-grouping.ts
|
|
473
|
+
function flatPrefix(filename) {
|
|
474
|
+
const separated = filename.match(/^(\d{1,2})[\s._-](\d{1,2})/);
|
|
475
|
+
if (separated) return { disc: parseInt(separated[1], 10), form: "separated" };
|
|
476
|
+
const compact = filename.match(/^(\d)(\d{2})(?:\D|$)/);
|
|
477
|
+
if (compact) return { disc: parseInt(compact[1], 10), form: "compact" };
|
|
478
|
+
return void 0;
|
|
479
|
+
}
|
|
480
|
+
function normalizeOk(result) {
|
|
481
|
+
const okItems = [];
|
|
482
|
+
const errors = [];
|
|
483
|
+
for (const item of result.items) {
|
|
484
|
+
if (item.status === "ok") {
|
|
485
|
+
okItems.push({ path: item.path, tags: item.tags, metadata: item });
|
|
486
|
+
} else {
|
|
487
|
+
errors.push({ path: item.path, error: item.error });
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
return { okItems, errors };
|
|
491
|
+
}
|
|
492
|
+
function buildTree(okItems) {
|
|
493
|
+
const tree = /* @__PURE__ */ new Map();
|
|
494
|
+
for (const item of okItems) {
|
|
495
|
+
const dir = dirname(item.path);
|
|
496
|
+
let node = tree.get(dir);
|
|
497
|
+
if (!node) {
|
|
498
|
+
node = { files: [], parent: dirname(dir) };
|
|
499
|
+
tree.set(dir, node);
|
|
500
|
+
}
|
|
501
|
+
node.files.push(item);
|
|
502
|
+
}
|
|
503
|
+
return tree;
|
|
504
|
+
}
|
|
505
|
+
function confirmDiscs(tree, minRank) {
|
|
506
|
+
const confirmedDiscs = /* @__PURE__ */ new Map();
|
|
507
|
+
for (const [dir, node] of tree) {
|
|
508
|
+
if (node.files.length === 0) continue;
|
|
509
|
+
const parse = parseDiscName(basename(dir));
|
|
510
|
+
if (!parse) continue;
|
|
511
|
+
const siblings = [...tree.keys()].filter(
|
|
512
|
+
(d) => d !== dir && dirname(d) === dirname(dir)
|
|
513
|
+
);
|
|
514
|
+
const corroboratedBySiblings = corroborated(parse, siblings);
|
|
515
|
+
let confidence = parse.gated ? "low" : baseConfidence(parse);
|
|
516
|
+
if (parse.gated) {
|
|
517
|
+
if (!corroboratedBySiblings) continue;
|
|
518
|
+
} else if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
519
|
+
if (!corroboratedBySiblings) continue;
|
|
520
|
+
} else if (corroboratedBySiblings) {
|
|
521
|
+
confidence = "high";
|
|
522
|
+
}
|
|
523
|
+
if (CONFIDENCE_RANK[confidence] < minRank) continue;
|
|
524
|
+
confirmedDiscs.set(dir, { parse, confidence });
|
|
525
|
+
}
|
|
526
|
+
return confirmedDiscs;
|
|
527
|
+
}
|
|
528
|
+
function flatSubdivisions(tree, confirmedDiscs, useFlatPrefixes) {
|
|
529
|
+
const flat = /* @__PURE__ */ new Map();
|
|
530
|
+
if (!useFlatPrefixes) return flat;
|
|
531
|
+
for (const [dir, node] of tree) {
|
|
532
|
+
if (node.files.length === 0 || confirmedDiscs.has(dir)) continue;
|
|
533
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
534
|
+
const forms = /* @__PURE__ */ new Set();
|
|
535
|
+
for (const item of node.files) {
|
|
536
|
+
const prefix = flatPrefix(basename(item.path));
|
|
537
|
+
if (prefix) {
|
|
538
|
+
byFile.set(item.path, prefix.disc);
|
|
539
|
+
forms.add(prefix.form);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
if (byFile.size < 2) continue;
|
|
543
|
+
const distinct = [...new Set(byFile.values())].sort((a, b) => a - b);
|
|
544
|
+
const allCompact = forms.size === 1 && forms.has("compact");
|
|
545
|
+
const plausible = allCompact ? distinct.every((d) => d >= 1 && d <= distinct.length) : true;
|
|
546
|
+
if (plausible) flat.set(dir, byFile);
|
|
547
|
+
}
|
|
548
|
+
return flat;
|
|
549
|
+
}
|
|
550
|
+
function groupAlbums(result, options = {}) {
|
|
551
|
+
const minRank = CONFIDENCE_RANK[options.minFolderConfidence ?? "low"];
|
|
552
|
+
const useFlatPrefixes = options.flatDiscPrefixes ?? true;
|
|
553
|
+
const useFolderFallback = options.folderFallback ?? true;
|
|
554
|
+
const { okItems, errors } = normalizeOk(result);
|
|
555
|
+
if (okItems.length === 0) {
|
|
556
|
+
return { albums: [], singles: [], unmatched: [], errors };
|
|
557
|
+
}
|
|
558
|
+
const tree = buildTree(okItems);
|
|
559
|
+
const confirmedDiscs = confirmDiscs(tree, minRank);
|
|
560
|
+
const flat = flatSubdivisions(tree, confirmedDiscs, useFlatPrefixes);
|
|
561
|
+
const { albums, unmatched } = buildAlbumIdentity(
|
|
562
|
+
okItems,
|
|
563
|
+
confirmedDiscs,
|
|
564
|
+
flat,
|
|
565
|
+
{ scanRoot: options.scanRoot, useFolderFallback }
|
|
566
|
+
);
|
|
567
|
+
const groups = assembleDiscs(albums);
|
|
533
568
|
const albumsOut = [];
|
|
534
569
|
const singles = [];
|
|
535
570
|
for (const group of groups) {
|
package/dist/index.browser.js
CHANGED
|
@@ -574,8 +574,8 @@ var init_audio_file_bwf = __esm({
|
|
|
574
574
|
|
|
575
575
|
// src/taglib/id3v2-frames.ts
|
|
576
576
|
function assertMp3(format) {
|
|
577
|
-
if (format !== "MP3") {
|
|
578
|
-
throw new UnsupportedFormatError(format, ["MP3"], {
|
|
577
|
+
if (format !== "MP3" && format !== "AAC") {
|
|
578
|
+
throw new UnsupportedFormatError(format, ["MP3", "AAC"], {
|
|
579
579
|
operation: "id3v2Frames"
|
|
580
580
|
});
|
|
581
581
|
}
|
|
@@ -1371,7 +1371,7 @@ var init_specialized_properties = __esm({
|
|
|
1371
1371
|
},
|
|
1372
1372
|
releaseType: {
|
|
1373
1373
|
key: "RELEASETYPE",
|
|
1374
|
-
description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.
|
|
1374
|
+
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
1375
|
type: "string",
|
|
1376
1376
|
supportedFormats: [
|
|
1377
1377
|
"ID3v2",
|
|
@@ -1389,7 +1389,7 @@ var init_specialized_properties = __esm({
|
|
|
1389
1389
|
},
|
|
1390
1390
|
releaseCountry: {
|
|
1391
1391
|
key: "RELEASECOUNTRY",
|
|
1392
|
-
description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.
|
|
1392
|
+
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
1393
|
type: "string",
|
|
1394
1394
|
supportedFormats: [
|
|
1395
1395
|
"ID3v2",
|
|
@@ -2121,7 +2121,10 @@ function wrapEmbindHandle(raw) {
|
|
|
2121
2121
|
...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: pw.isEncrypted() } : {},
|
|
2122
2122
|
...formatVersion > 0 ? { formatVersion } : {},
|
|
2123
2123
|
...isValidBitrateMode(bitrateMode) ? { bitrateMode } : {},
|
|
2124
|
-
|
|
2124
|
+
// OpusHead gain exists only in an Ogg Opus stream. Gating on codec
|
|
2125
|
+
// alone fabricated outputGainDb: 0 for MP4 tracks once the MP4 codec
|
|
2126
|
+
// enum was mapped (the WASI side never emitted it there) — taglib-kswf.
|
|
2127
|
+
...containerFormat === "OGG" && codec === "Opus" ? { outputGainDb: pw.outputGainDb() } : {}
|
|
2125
2128
|
};
|
|
2126
2129
|
}
|
|
2127
2130
|
};
|
|
@@ -2437,8 +2440,8 @@ var init_audio_file_impl = __esm({
|
|
|
2437
2440
|
}
|
|
2438
2441
|
setChapters(chapters, options) {
|
|
2439
2442
|
const fmt = this.getFormat();
|
|
2440
|
-
if (fmt !== "MP3" && fmt !== "MP4") {
|
|
2441
|
-
throw new UnsupportedFormatError(fmt, ["MP3", "MP4"], {
|
|
2443
|
+
if (fmt !== "MP3" && fmt !== "AAC" && fmt !== "MP4") {
|
|
2444
|
+
throw new UnsupportedFormatError(fmt, ["MP3", "AAC", "MP4"], {
|
|
2442
2445
|
operation: "setChapters"
|
|
2443
2446
|
});
|
|
2444
2447
|
}
|
|
@@ -2944,6 +2947,8 @@ function normalizeTagInput(input) {
|
|
|
2944
2947
|
if (val === "explicit") props.itunesAdvisory = ["1"];
|
|
2945
2948
|
else if (val === "clean") props.itunesAdvisory = ["2"];
|
|
2946
2949
|
else if (val === "unspecified") props.itunesAdvisory = [];
|
|
2950
|
+
} else if (field === "bpm") {
|
|
2951
|
+
props[field] = [String(Math.trunc(val))];
|
|
2947
2952
|
} else if (NUMERIC_FIELDS.has(field)) {
|
|
2948
2953
|
props[field] = [String(val)];
|
|
2949
2954
|
} else if (field === "r128TrackGain" || field === "r128AlbumGain") {
|
|
@@ -3004,7 +3009,7 @@ var VERSION;
|
|
|
3004
3009
|
var init_version = __esm({
|
|
3005
3010
|
"src/version.ts"() {
|
|
3006
3011
|
"use strict";
|
|
3007
|
-
VERSION = "2.2.
|
|
3012
|
+
VERSION = "2.2.3";
|
|
3008
3013
|
}
|
|
3009
3014
|
});
|
|
3010
3015
|
|
|
@@ -3701,9 +3706,11 @@ function propertyValue(props, key) {
|
|
|
3701
3706
|
// src/web-utils/data-url.ts
|
|
3702
3707
|
init_classes();
|
|
3703
3708
|
function pictureToDataURL(picture) {
|
|
3709
|
+
const data = picture.data;
|
|
3710
|
+
const CHUNK = 32768;
|
|
3704
3711
|
let binary = "";
|
|
3705
|
-
for (
|
|
3706
|
-
binary += String.
|
|
3712
|
+
for (let i = 0; i < data.length; i += CHUNK) {
|
|
3713
|
+
binary += String.fromCharCode(...data.subarray(i, i + CHUNK));
|
|
3707
3714
|
}
|
|
3708
3715
|
const base64 = btoa(binary);
|
|
3709
3716
|
return `data:${picture.mimeType};base64,${base64}`;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @fileoverview Main module exports for TagLib-Wasm
|
|
3
3
|
*
|
|
4
|
-
* TagLib v2.3.
|
|
4
|
+
* TagLib v2.3.2 compiled to WebAssembly with TypeScript bindings
|
|
5
5
|
* for universal audio metadata handling across all JavaScript runtimes.
|
|
6
6
|
*
|
|
7
7
|
* @module TagLib-Wasm
|
package/dist/simple.browser.js
CHANGED
|
@@ -562,8 +562,8 @@ var init_audio_file_bwf = __esm({
|
|
|
562
562
|
|
|
563
563
|
// src/taglib/id3v2-frames.ts
|
|
564
564
|
function assertMp3(format) {
|
|
565
|
-
if (format !== "MP3") {
|
|
566
|
-
throw new UnsupportedFormatError(format, ["MP3"], {
|
|
565
|
+
if (format !== "MP3" && format !== "AAC") {
|
|
566
|
+
throw new UnsupportedFormatError(format, ["MP3", "AAC"], {
|
|
567
567
|
operation: "id3v2Frames"
|
|
568
568
|
});
|
|
569
569
|
}
|
|
@@ -1303,7 +1303,7 @@ var init_specialized_properties = __esm({
|
|
|
1303
1303
|
},
|
|
1304
1304
|
releaseType: {
|
|
1305
1305
|
key: "RELEASETYPE",
|
|
1306
|
-
description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.
|
|
1306
|
+
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
1307
|
type: "string",
|
|
1308
1308
|
supportedFormats: [
|
|
1309
1309
|
"ID3v2",
|
|
@@ -1321,7 +1321,7 @@ var init_specialized_properties = __esm({
|
|
|
1321
1321
|
},
|
|
1322
1322
|
releaseCountry: {
|
|
1323
1323
|
key: "RELEASECOUNTRY",
|
|
1324
|
-
description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.
|
|
1324
|
+
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
1325
|
type: "string",
|
|
1326
1326
|
supportedFormats: [
|
|
1327
1327
|
"ID3v2",
|
|
@@ -2053,7 +2053,10 @@ function wrapEmbindHandle(raw) {
|
|
|
2053
2053
|
...containerFormat === "MP4" || containerFormat === "ASF" ? { isEncrypted: pw.isEncrypted() } : {},
|
|
2054
2054
|
...formatVersion > 0 ? { formatVersion } : {},
|
|
2055
2055
|
...isValidBitrateMode(bitrateMode) ? { bitrateMode } : {},
|
|
2056
|
-
|
|
2056
|
+
// OpusHead gain exists only in an Ogg Opus stream. Gating on codec
|
|
2057
|
+
// alone fabricated outputGainDb: 0 for MP4 tracks once the MP4 codec
|
|
2058
|
+
// enum was mapped (the WASI side never emitted it there) — taglib-kswf.
|
|
2059
|
+
...containerFormat === "OGG" && codec === "Opus" ? { outputGainDb: pw.outputGainDb() } : {}
|
|
2057
2060
|
};
|
|
2058
2061
|
}
|
|
2059
2062
|
};
|
|
@@ -2369,8 +2372,8 @@ var init_audio_file_impl = __esm({
|
|
|
2369
2372
|
}
|
|
2370
2373
|
setChapters(chapters, options) {
|
|
2371
2374
|
const fmt = this.getFormat();
|
|
2372
|
-
if (fmt !== "MP3" && fmt !== "MP4") {
|
|
2373
|
-
throw new UnsupportedFormatError(fmt, ["MP3", "MP4"], {
|
|
2375
|
+
if (fmt !== "MP3" && fmt !== "AAC" && fmt !== "MP4") {
|
|
2376
|
+
throw new UnsupportedFormatError(fmt, ["MP3", "AAC", "MP4"], {
|
|
2374
2377
|
operation: "setChapters"
|
|
2375
2378
|
});
|
|
2376
2379
|
}
|
|
@@ -2876,6 +2879,8 @@ function normalizeTagInput(input) {
|
|
|
2876
2879
|
if (val === "explicit") props.itunesAdvisory = ["1"];
|
|
2877
2880
|
else if (val === "clean") props.itunesAdvisory = ["2"];
|
|
2878
2881
|
else if (val === "unspecified") props.itunesAdvisory = [];
|
|
2882
|
+
} else if (field === "bpm") {
|
|
2883
|
+
props[field] = [String(Math.trunc(val))];
|
|
2879
2884
|
} else if (NUMERIC_FIELDS.has(field)) {
|
|
2880
2885
|
props[field] = [String(val)];
|
|
2881
2886
|
} else if (field === "r128TrackGain" || field === "r128AlbumGain") {
|
|
@@ -2936,7 +2941,7 @@ var VERSION;
|
|
|
2936
2941
|
var init_version = __esm({
|
|
2937
2942
|
"src/version.ts"() {
|
|
2938
2943
|
"use strict";
|
|
2939
|
-
VERSION = "2.2.
|
|
2944
|
+
VERSION = "2.2.3";
|
|
2940
2945
|
}
|
|
2941
2946
|
});
|
|
2942
2947
|
|
|
@@ -299,7 +299,7 @@ export declare const PROPERTIES: {
|
|
|
299
299
|
};
|
|
300
300
|
readonly releaseType: {
|
|
301
301
|
readonly key: "RELEASETYPE";
|
|
302
|
-
readonly description: "Release type (album, single, EP, compilation, ...). Multi-value (e.g. 'album' + 'EP'). TagLib 2.3.
|
|
302
|
+
readonly 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";
|
|
303
303
|
readonly type: "string";
|
|
304
304
|
readonly supportedFormats: readonly ["ID3v2", "MP4", "Vorbis", "APE", "ASF", "Matroska"];
|
|
305
305
|
readonly mappings: {
|
|
@@ -313,7 +313,7 @@ export declare const PROPERTIES: {
|
|
|
313
313
|
};
|
|
314
314
|
readonly releaseCountry: {
|
|
315
315
|
readonly key: "RELEASECOUNTRY";
|
|
316
|
-
readonly description: "Release country (ISO 3166-1 code, e.g. 'US'). TagLib 2.3.
|
|
316
|
+
readonly 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";
|
|
317
317
|
readonly type: "string";
|
|
318
318
|
readonly supportedFormats: readonly ["ID3v2", "MP4", "Vorbis", "APE", "ASF", "Matroska", "WAV"];
|
|
319
319
|
readonly mappings: {
|