taglib-wasm 1.6.0 → 1.7.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/README.md +13 -1
- package/dist/index.browser.js +148 -6
- package/dist/simple.browser.js +148 -6
- package/dist/src/folder-api/group-albums.d.ts +96 -0
- package/dist/src/folder-api/group-albums.d.ts.map +1 -0
- package/dist/src/folder-api/group-albums.js +505 -0
- package/dist/src/folder-api/index.d.ts +2 -0
- package/dist/src/folder-api/index.d.ts.map +1 -1
- package/dist/src/folder-api/index.js +6 -0
- package/dist/src/folder-api/scan-for-albums.d.ts +20 -0
- package/dist/src/folder-api/scan-for-albums.d.ts.map +1 -0
- package/dist/src/folder-api/scan-for-albums.js +22 -0
- package/dist/src/runtime/loader-types.d.ts +11 -0
- package/dist/src/runtime/loader-types.d.ts.map +1 -1
- package/dist/src/runtime/loader-types.js +10 -0
- package/dist/src/runtime/module-loader.d.ts.map +1 -1
- package/dist/src/runtime/module-loader.js +9 -0
- package/dist/src/runtime/unified-loader/loader.d.ts.map +1 -1
- package/dist/src/runtime/unified-loader/loader.js +5 -0
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/file-handle.js +28 -3
- package/dist/src/taglib/load-audio-data.d.ts.map +1 -1
- package/dist/src/taglib/load-audio-data.js +19 -5
- package/dist/src/taglib/metadata-extent.d.ts +41 -0
- package/dist/src/taglib/metadata-extent.d.ts.map +1 -0
- package/dist/src/taglib/metadata-extent.js +128 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/taglib-wasi.wasm +0 -0
- package/dist/taglib-web.wasm +0 -0
- package/dist/taglib-wrapper.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,505 @@
|
|
|
1
|
+
import { basename } from "../utils/path.js";
|
|
2
|
+
function dirname(path) {
|
|
3
|
+
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
4
|
+
return i === -1 ? path : path.slice(0, i);
|
|
5
|
+
}
|
|
6
|
+
const MARKERS = [
|
|
7
|
+
"cd",
|
|
8
|
+
"disc",
|
|
9
|
+
"disk",
|
|
10
|
+
"dvd",
|
|
11
|
+
"sacd",
|
|
12
|
+
"blu-ray",
|
|
13
|
+
"bd",
|
|
14
|
+
"vinyl",
|
|
15
|
+
"lp",
|
|
16
|
+
"record",
|
|
17
|
+
"cassette",
|
|
18
|
+
"tape",
|
|
19
|
+
"digital media"
|
|
20
|
+
];
|
|
21
|
+
const WORD_NUMBERS = {
|
|
22
|
+
one: 1,
|
|
23
|
+
two: 2,
|
|
24
|
+
three: 3,
|
|
25
|
+
four: 4,
|
|
26
|
+
five: 5,
|
|
27
|
+
six: 6,
|
|
28
|
+
seven: 7,
|
|
29
|
+
eight: 8,
|
|
30
|
+
nine: 9,
|
|
31
|
+
ten: 10,
|
|
32
|
+
eleven: 11,
|
|
33
|
+
twelve: 12
|
|
34
|
+
};
|
|
35
|
+
function romanToInt(s) {
|
|
36
|
+
const values = {
|
|
37
|
+
i: 1,
|
|
38
|
+
v: 5,
|
|
39
|
+
x: 10,
|
|
40
|
+
l: 50,
|
|
41
|
+
c: 100,
|
|
42
|
+
d: 500,
|
|
43
|
+
m: 1e3
|
|
44
|
+
};
|
|
45
|
+
let total = 0;
|
|
46
|
+
let prev = 0;
|
|
47
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
48
|
+
const v = values[s[i]];
|
|
49
|
+
if (v === void 0) return NaN;
|
|
50
|
+
total += v < prev ? -v : v;
|
|
51
|
+
prev = v;
|
|
52
|
+
}
|
|
53
|
+
return total;
|
|
54
|
+
}
|
|
55
|
+
function parseNumberToken(token) {
|
|
56
|
+
if (/^\d+$/.test(token)) return parseInt(token, 10);
|
|
57
|
+
const word = WORD_NUMBERS[token.toLowerCase()];
|
|
58
|
+
if (word) return word;
|
|
59
|
+
const roman = romanToInt(token.toLowerCase());
|
|
60
|
+
return Number.isFinite(roman) ? roman : void 0;
|
|
61
|
+
}
|
|
62
|
+
const SEP = String.raw`[\s._#-]*`;
|
|
63
|
+
const NUM = String.raw`(?:\d+|[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)`;
|
|
64
|
+
function parseDiscName(name) {
|
|
65
|
+
const trimmed = name.trim();
|
|
66
|
+
if (/^(?:extras?|bonus)$/i.test(trimmed)) return void 0;
|
|
67
|
+
for (const marker of MARKERS) {
|
|
68
|
+
const left = String.raw`(?:^|[^a-z0-9])`;
|
|
69
|
+
const esc = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
70
|
+
const re = new RegExp(left + String.raw`(${esc})${SEP}(${NUM})`, "i");
|
|
71
|
+
const m = trimmed.match(re);
|
|
72
|
+
if (!m) continue;
|
|
73
|
+
const number = parseNumberToken(m[2]);
|
|
74
|
+
if (number === void 0) continue;
|
|
75
|
+
const markerStart = m.index + m[1].length - marker.length;
|
|
76
|
+
const rawPrefix = trimmed.slice(0, markerStart).trim();
|
|
77
|
+
const title = rawPrefix.replace(/[()[\]]/g, "").replace(/[\s._#-]+$/, "") || void 0;
|
|
78
|
+
const isExact = title === void 0;
|
|
79
|
+
const after = trimmed.slice(m.index + m[0].length).trim();
|
|
80
|
+
const discTitle = after.replace(/^[()[\],:;\-–—\s]+/, "").replace(/[)\]\[,:;\-–—\s]+$/, "").trim() || void 0;
|
|
81
|
+
const ofMatch = after.trim().match(/^of\s+(\d+)\b/i);
|
|
82
|
+
const total = ofMatch ? parseInt(ofMatch[1], 10) : void 0;
|
|
83
|
+
return {
|
|
84
|
+
kind: isExact ? "exact" : "embedded",
|
|
85
|
+
number,
|
|
86
|
+
total,
|
|
87
|
+
title,
|
|
88
|
+
discTitle
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const volume = trimmed.match(
|
|
92
|
+
/^(?:volume|vol\.?|part|pt\.?)\s*(\d+)(?:\s*of\s*(\d+))?$/i
|
|
93
|
+
);
|
|
94
|
+
if (volume) {
|
|
95
|
+
return {
|
|
96
|
+
kind: "volume",
|
|
97
|
+
number: parseInt(volume[1], 10),
|
|
98
|
+
total: volume[2] ? parseInt(volume[2], 10) : void 0,
|
|
99
|
+
title: void 0,
|
|
100
|
+
discTitle: void 0
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (/^bonus\s+disc$/i.test(trimmed)) {
|
|
104
|
+
return {
|
|
105
|
+
kind: "bonus",
|
|
106
|
+
number: void 0,
|
|
107
|
+
total: void 0,
|
|
108
|
+
title: void 0,
|
|
109
|
+
discTitle: void 0
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (/^(?:\d+|v\d+|p\d+|[ab])$/i.test(trimmed)) {
|
|
113
|
+
return {
|
|
114
|
+
kind: "bare",
|
|
115
|
+
number: /^\d+$/i.test(trimmed) ? parseInt(trimmed, 10) : void 0,
|
|
116
|
+
total: void 0,
|
|
117
|
+
title: void 0,
|
|
118
|
+
discTitle: void 0
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return void 0;
|
|
122
|
+
}
|
|
123
|
+
function baseConfidence(parse) {
|
|
124
|
+
switch (parse.kind) {
|
|
125
|
+
case "exact":
|
|
126
|
+
return typeof parse.number === "number" && /^\d+$/.test(String(parse.number)) ? "high" : "low";
|
|
127
|
+
case "embedded":
|
|
128
|
+
return "medium";
|
|
129
|
+
case "volume":
|
|
130
|
+
return "medium";
|
|
131
|
+
case "bonus":
|
|
132
|
+
case "bare":
|
|
133
|
+
return "low";
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
function corroborated(parse, siblings) {
|
|
137
|
+
if (parse.kind === "exact") return true;
|
|
138
|
+
if (siblings.length === 0) return false;
|
|
139
|
+
const siblingParses = siblings.map((s) => parseDiscName(basename(s))).filter((p) => p !== void 0);
|
|
140
|
+
const hasExactSibling = siblingParses.some((p) => p.kind === "exact");
|
|
141
|
+
const hasNumberedSibling = siblingParses.some(
|
|
142
|
+
(p) => p.number !== void 0
|
|
143
|
+
);
|
|
144
|
+
if (parse.number !== void 0 && hasNumberedSibling) return true;
|
|
145
|
+
if (parse.kind === "embedded" && hasExactSibling) return true;
|
|
146
|
+
if (parse.kind === "volume" && hasNumberedSibling) return true;
|
|
147
|
+
if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
148
|
+
const numberedCount = siblingParses.filter((p) => p.number !== void 0).length;
|
|
149
|
+
if (numberedCount >= 2) return true;
|
|
150
|
+
if (parse.number !== void 0 && hasNumberedSibling) return true;
|
|
151
|
+
}
|
|
152
|
+
return false;
|
|
153
|
+
}
|
|
154
|
+
function flatPrefix(filename) {
|
|
155
|
+
const separated = filename.match(/^(\d{1,2})[\s._-](\d{1,2})/);
|
|
156
|
+
if (separated) return { disc: parseInt(separated[1], 10), form: "separated" };
|
|
157
|
+
const compact = filename.match(/^(\d)(\d{2})(?:\D|$)/);
|
|
158
|
+
if (compact) return { disc: parseInt(compact[1], 10), form: "compact" };
|
|
159
|
+
return void 0;
|
|
160
|
+
}
|
|
161
|
+
function normalizeKey(s) {
|
|
162
|
+
return s.trim().toLowerCase().replace(/\s+/g, " ");
|
|
163
|
+
}
|
|
164
|
+
function isGenericAlbumArtist(albumArtist) {
|
|
165
|
+
if (!albumArtist || albumArtist.trim() === "") return true;
|
|
166
|
+
const n = normalizeKey(albumArtist);
|
|
167
|
+
return n === "various artists" || n === "va";
|
|
168
|
+
}
|
|
169
|
+
const CONFIDENCE_RANK = {
|
|
170
|
+
low: 0,
|
|
171
|
+
medium: 1,
|
|
172
|
+
high: 2
|
|
173
|
+
};
|
|
174
|
+
function groupAlbums(result, options = {}) {
|
|
175
|
+
const minRank = CONFIDENCE_RANK[options.minFolderConfidence ?? "low"];
|
|
176
|
+
const useFlatPrefixes = options.flatDiscPrefixes ?? true;
|
|
177
|
+
const useFolderFallback = options.folderFallback ?? true;
|
|
178
|
+
const okItems = [];
|
|
179
|
+
const errors = [];
|
|
180
|
+
for (const item of result.items) {
|
|
181
|
+
if (item.status === "ok") {
|
|
182
|
+
okItems.push({ path: item.path, tags: item.tags, metadata: item });
|
|
183
|
+
} else {
|
|
184
|
+
errors.push({ path: item.path, error: item.error });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
if (okItems.length === 0) {
|
|
188
|
+
return { albums: [], singles: [], unmatched: [], errors };
|
|
189
|
+
}
|
|
190
|
+
const scanRoot = options.scanRoot;
|
|
191
|
+
const tree = /* @__PURE__ */ new Map();
|
|
192
|
+
for (const item of okItems) {
|
|
193
|
+
const dir = dirname(item.path);
|
|
194
|
+
let node = tree.get(dir);
|
|
195
|
+
if (!node) {
|
|
196
|
+
node = { files: [], parent: dirname(dir) };
|
|
197
|
+
tree.set(dir, node);
|
|
198
|
+
}
|
|
199
|
+
node.files.push(item);
|
|
200
|
+
}
|
|
201
|
+
const confirmedDiscs = /* @__PURE__ */ new Map();
|
|
202
|
+
for (const [dir, node] of tree) {
|
|
203
|
+
if (node.files.length === 0) continue;
|
|
204
|
+
const parse = parseDiscName(basename(dir));
|
|
205
|
+
if (!parse) continue;
|
|
206
|
+
const siblings = [...tree.keys()].filter(
|
|
207
|
+
(d) => d !== dir && dirname(d) === dirname(dir)
|
|
208
|
+
);
|
|
209
|
+
const corroboratedBySiblings = corroborated(parse, siblings);
|
|
210
|
+
let confidence = baseConfidence(parse);
|
|
211
|
+
if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
212
|
+
if (!corroboratedBySiblings) continue;
|
|
213
|
+
} else if (corroboratedBySiblings) {
|
|
214
|
+
confidence = "high";
|
|
215
|
+
}
|
|
216
|
+
if (CONFIDENCE_RANK[confidence] < minRank) continue;
|
|
217
|
+
confirmedDiscs.set(dir, { parse, confidence });
|
|
218
|
+
}
|
|
219
|
+
const flatSubdivisions = /* @__PURE__ */ new Map();
|
|
220
|
+
if (useFlatPrefixes) {
|
|
221
|
+
for (const [dir, node] of tree) {
|
|
222
|
+
if (node.files.length === 0 || confirmedDiscs.has(dir)) continue;
|
|
223
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
224
|
+
const forms = /* @__PURE__ */ new Set();
|
|
225
|
+
for (const item of node.files) {
|
|
226
|
+
const prefix = flatPrefix(basename(item.path));
|
|
227
|
+
if (prefix) {
|
|
228
|
+
byFile.set(item.path, prefix.disc);
|
|
229
|
+
forms.add(prefix.form);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (byFile.size < 2) continue;
|
|
233
|
+
const distinct = [...new Set(byFile.values())].sort((a, b) => a - b);
|
|
234
|
+
const allCompact = forms.size === 1 && forms.has("compact");
|
|
235
|
+
const plausible = allCompact ? distinct.every((d) => d >= 1 && d <= distinct.length) : true;
|
|
236
|
+
if (plausible) flatSubdivisions.set(dir, byFile);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
const albums = /* @__PURE__ */ new Map();
|
|
240
|
+
const unmatched = [];
|
|
241
|
+
const evidenceFor = (item) => {
|
|
242
|
+
const dir = dirname(item.path);
|
|
243
|
+
const own = confirmedDiscs.get(dir);
|
|
244
|
+
if (own) {
|
|
245
|
+
return {
|
|
246
|
+
folderDiscNumber: own.parse.number,
|
|
247
|
+
total: own.parse.total,
|
|
248
|
+
folderDiscTitle: own.parse.discTitle,
|
|
249
|
+
confidence: own.confidence,
|
|
250
|
+
albumDir: dirname(dir)
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
let cursor = dirname(dir);
|
|
254
|
+
while (cursor !== void 0) {
|
|
255
|
+
const entry = confirmedDiscs.get(cursor);
|
|
256
|
+
if (entry) {
|
|
257
|
+
return {
|
|
258
|
+
folderDiscNumber: entry.parse.number,
|
|
259
|
+
total: entry.parse.total,
|
|
260
|
+
folderDiscTitle: entry.parse.discTitle,
|
|
261
|
+
confidence: entry.confidence,
|
|
262
|
+
albumDir: dirname(cursor)
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
const parent = dirname(cursor);
|
|
266
|
+
if (parent === cursor) break;
|
|
267
|
+
cursor = parent;
|
|
268
|
+
}
|
|
269
|
+
const flat = flatSubdivisions.get(dir)?.get(item.path);
|
|
270
|
+
if (flat !== void 0) {
|
|
271
|
+
return {
|
|
272
|
+
folderDiscNumber: flat,
|
|
273
|
+
total: void 0,
|
|
274
|
+
folderDiscTitle: void 0,
|
|
275
|
+
confidence: "medium",
|
|
276
|
+
albumDir: dir
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
return void 0;
|
|
280
|
+
};
|
|
281
|
+
const folderIdentity = (dir) => {
|
|
282
|
+
const own = confirmedDiscs.get(dir);
|
|
283
|
+
if (!own) {
|
|
284
|
+
const title2 = basename(dir);
|
|
285
|
+
return title2 ? { title: title2, titleSource: dir } : void 0;
|
|
286
|
+
}
|
|
287
|
+
if (own.parse.title !== void 0) {
|
|
288
|
+
return { title: own.parse.title, titleSource: dirname(dir) };
|
|
289
|
+
}
|
|
290
|
+
const source = dirname(dir);
|
|
291
|
+
if (source === scanRoot || source === dir) return void 0;
|
|
292
|
+
const title = basename(source);
|
|
293
|
+
return title ? { title, titleSource: source } : void 0;
|
|
294
|
+
};
|
|
295
|
+
for (const item of okItems) {
|
|
296
|
+
const dir = dirname(item.path);
|
|
297
|
+
const albumTag = item.tags.album?.[0]?.trim();
|
|
298
|
+
const albumArtistTag = item.tags.albumArtist?.[0]?.trim();
|
|
299
|
+
if (albumTag) {
|
|
300
|
+
const artistPart = isGenericAlbumArtist(albumArtistTag) ? "" : normalizeKey(albumArtistTag ?? "");
|
|
301
|
+
const key2 = `${artistPart}::${normalizeKey(albumTag)}`;
|
|
302
|
+
let acc2 = albums.get(key2);
|
|
303
|
+
if (!acc2) {
|
|
304
|
+
acc2 = {
|
|
305
|
+
key: key2,
|
|
306
|
+
album: albumTag,
|
|
307
|
+
albumArtist: isGenericAlbumArtist(albumArtistTag) ? void 0 : albumArtistTag,
|
|
308
|
+
source: "tags",
|
|
309
|
+
directory: void 0,
|
|
310
|
+
items: [],
|
|
311
|
+
evidence: /* @__PURE__ */ new Map()
|
|
312
|
+
};
|
|
313
|
+
albums.set(key2, acc2);
|
|
314
|
+
}
|
|
315
|
+
const evidence2 = evidenceFor(item);
|
|
316
|
+
acc2.evidence.set(item.path, evidence2);
|
|
317
|
+
acc2.items.push({
|
|
318
|
+
...item.metadata,
|
|
319
|
+
albumDir: evidence2?.albumDir ?? dir,
|
|
320
|
+
discNumber: evidence2?.folderDiscNumber
|
|
321
|
+
});
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
if (!useFolderFallback) {
|
|
325
|
+
unmatched.push(item.metadata);
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
const identity = folderIdentity(dir);
|
|
329
|
+
if (!identity) {
|
|
330
|
+
unmatched.push(item.metadata);
|
|
331
|
+
continue;
|
|
332
|
+
}
|
|
333
|
+
const key = `${identity.titleSource}::${normalizeKey(identity.title)}`;
|
|
334
|
+
let acc = albums.get(key);
|
|
335
|
+
if (!acc) {
|
|
336
|
+
acc = {
|
|
337
|
+
key,
|
|
338
|
+
album: identity.title,
|
|
339
|
+
albumArtist: void 0,
|
|
340
|
+
source: "folder",
|
|
341
|
+
directory: identity.titleSource,
|
|
342
|
+
items: [],
|
|
343
|
+
evidence: /* @__PURE__ */ new Map()
|
|
344
|
+
};
|
|
345
|
+
albums.set(key, acc);
|
|
346
|
+
}
|
|
347
|
+
const evidence = evidenceFor(item);
|
|
348
|
+
acc.evidence.set(item.path, evidence);
|
|
349
|
+
acc.items.push({
|
|
350
|
+
...item.metadata,
|
|
351
|
+
albumDir: evidence?.albumDir ?? dir,
|
|
352
|
+
discNumber: evidence?.folderDiscNumber
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
for (const [key, acc] of [...albums]) {
|
|
356
|
+
if (acc.source === "tags") continue;
|
|
357
|
+
for (const other of albums.values()) {
|
|
358
|
+
if (other === acc || other.source !== "tags") continue;
|
|
359
|
+
if (normalizeKey(other.album) !== normalizeKey(acc.album)) continue;
|
|
360
|
+
const albumDir = acc.directory;
|
|
361
|
+
const sharesChain = albumDir !== void 0 && other.items.some((o) => {
|
|
362
|
+
const od = dirname(o.path);
|
|
363
|
+
return od === albumDir || od.startsWith(albumDir + "/");
|
|
364
|
+
});
|
|
365
|
+
if (!sharesChain) continue;
|
|
366
|
+
for (const item of acc.items) {
|
|
367
|
+
other.items.push(item);
|
|
368
|
+
other.evidence.set(item.path, acc.evidence.get(item.path));
|
|
369
|
+
}
|
|
370
|
+
albums.delete(key);
|
|
371
|
+
break;
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
const groups = [];
|
|
375
|
+
for (const acc of albums.values()) {
|
|
376
|
+
const dirTagCommon = /* @__PURE__ */ new Map();
|
|
377
|
+
const resolvedByFile = /* @__PURE__ */ new Map();
|
|
378
|
+
for (const item of acc.items) {
|
|
379
|
+
const dir = dirname(item.path);
|
|
380
|
+
if (!dirTagCommon.has(dir)) {
|
|
381
|
+
const tagged = acc.items.filter((i) => dirname(i.path) === dir).map((i) => i.tags.discNumber).filter((d) => d !== void 0 && d > 0);
|
|
382
|
+
dirTagCommon.set(
|
|
383
|
+
dir,
|
|
384
|
+
tagged.length > 0 && tagged.every((d) => d === tagged[0]) ? tagged[0] : void 0
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
const evidence = acc.evidence.get(item.path);
|
|
388
|
+
const tagCommon = dirTagCommon.get(dir);
|
|
389
|
+
resolvedByFile.set(
|
|
390
|
+
item.path,
|
|
391
|
+
tagCommon ?? evidence?.folderDiscNumber
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const discs = /* @__PURE__ */ new Map();
|
|
395
|
+
const order = [];
|
|
396
|
+
const noEvidenceItems = [];
|
|
397
|
+
for (const item of acc.items) {
|
|
398
|
+
const evidence = acc.evidence.get(item.path);
|
|
399
|
+
if (evidence === void 0) {
|
|
400
|
+
noEvidenceItems.push(item);
|
|
401
|
+
continue;
|
|
402
|
+
}
|
|
403
|
+
const resolved = resolvedByFile.get(item.path);
|
|
404
|
+
const discKey = resolved === void 0 ? `dir:${dirname(item.path)}` : String(resolved);
|
|
405
|
+
let disc = discs.get(discKey);
|
|
406
|
+
if (!disc) {
|
|
407
|
+
disc = {
|
|
408
|
+
discNumber: resolved,
|
|
409
|
+
totalDiscs: evidence.total,
|
|
410
|
+
folderDiscNumber: evidence.folderDiscNumber,
|
|
411
|
+
folderDiscTitle: evidence.folderDiscTitle,
|
|
412
|
+
tagDiscNumber: void 0,
|
|
413
|
+
confidence: evidence.confidence,
|
|
414
|
+
items: []
|
|
415
|
+
};
|
|
416
|
+
discs.set(discKey, disc);
|
|
417
|
+
order.push(discKey);
|
|
418
|
+
}
|
|
419
|
+
disc.items.push(item);
|
|
420
|
+
}
|
|
421
|
+
if (noEvidenceItems.length > 0) {
|
|
422
|
+
const numbered = [...discs.values()].filter((d) => d.folderDiscNumber !== void 0).sort((a, b) => a.folderDiscNumber - b.folderDiscNumber);
|
|
423
|
+
if (numbered.length > 0) {
|
|
424
|
+
numbered[0].items.push(...noEvidenceItems);
|
|
425
|
+
} else {
|
|
426
|
+
const key = "dir:__none__";
|
|
427
|
+
let disc = discs.get(key);
|
|
428
|
+
if (!disc) {
|
|
429
|
+
disc = {
|
|
430
|
+
discNumber: void 0,
|
|
431
|
+
totalDiscs: void 0,
|
|
432
|
+
folderDiscNumber: void 0,
|
|
433
|
+
folderDiscTitle: void 0,
|
|
434
|
+
tagDiscNumber: void 0,
|
|
435
|
+
confidence: "high",
|
|
436
|
+
items: []
|
|
437
|
+
};
|
|
438
|
+
discs.set(key, disc);
|
|
439
|
+
order.push(key);
|
|
440
|
+
}
|
|
441
|
+
disc.items.push(...noEvidenceItems);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
for (const disc of discs.values()) {
|
|
445
|
+
const tagged = disc.items.map((i) => i.tags.discNumber).filter((d) => d !== void 0 && d > 0);
|
|
446
|
+
const common = tagged.length > 0 && tagged.every((d) => d === tagged[0]) ? tagged[0] : void 0;
|
|
447
|
+
disc.tagDiscNumber = common;
|
|
448
|
+
disc.discNumber = common ?? disc.folderDiscNumber;
|
|
449
|
+
for (const item of disc.items) item.discNumber = disc.discNumber;
|
|
450
|
+
}
|
|
451
|
+
const albumTaggedTotals = acc.items.map((i) => i.tags.totalDiscs).filter((t) => t !== void 0 && t > 0);
|
|
452
|
+
const commonTotal = albumTaggedTotals.length > 0 && albumTaggedTotals.every((t) => t === albumTaggedTotals[0]) ? albumTaggedTotals[0] : void 0;
|
|
453
|
+
const ofN = [...discs.values()].map((d) => d.totalDiscs).find(
|
|
454
|
+
(t) => t !== void 0
|
|
455
|
+
);
|
|
456
|
+
const maxSibling = [...discs.values()].map((d) => d.discNumber).filter((n) => n !== void 0).reduce((a, b) => Math.max(a, b), 0) || void 0;
|
|
457
|
+
for (const disc of discs.values()) {
|
|
458
|
+
disc.totalDiscs = commonTotal ?? ofN ?? maxSibling;
|
|
459
|
+
}
|
|
460
|
+
const sortedDiscs = [...discs.values()].sort((a, b) => {
|
|
461
|
+
const an = a.discNumber === void 0 ? -1 : a.discNumber;
|
|
462
|
+
const bn = b.discNumber === void 0 ? -1 : b.discNumber;
|
|
463
|
+
if (an !== bn) return an - bn;
|
|
464
|
+
return (a.folderDiscTitle ?? "").localeCompare(b.folderDiscTitle ?? "");
|
|
465
|
+
});
|
|
466
|
+
for (const disc of sortedDiscs) {
|
|
467
|
+
disc.items.sort((a, b) => {
|
|
468
|
+
const at = a.tags.track ?? Infinity;
|
|
469
|
+
const bt = b.tags.track ?? Infinity;
|
|
470
|
+
if (at !== bt) return at - bt;
|
|
471
|
+
return a.path.localeCompare(b.path);
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
const flags = acc.items.map((i) => i.tags.compilation).filter((c) => c !== void 0);
|
|
475
|
+
const compilation = flags.length > 0 && flags.every((f) => f === flags[0]) ? flags[0] : void 0;
|
|
476
|
+
const albumDirs = new Set(acc.items.map((i) => i.albumDir));
|
|
477
|
+
const directory = albumDirs.size === 1 ? [...albumDirs][0] : acc.directory;
|
|
478
|
+
groups.push({
|
|
479
|
+
key: acc.key,
|
|
480
|
+
albumArtist: acc.albumArtist,
|
|
481
|
+
album: acc.album,
|
|
482
|
+
source: acc.source,
|
|
483
|
+
compilation,
|
|
484
|
+
directory,
|
|
485
|
+
discs: sortedDiscs,
|
|
486
|
+
items: sortedDiscs.flatMap((d) => d.items)
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
const albumsOut = [];
|
|
490
|
+
const singles = [];
|
|
491
|
+
for (const group of groups) {
|
|
492
|
+
if (group.items.length === 1) {
|
|
493
|
+
singles.push(group.items[0]);
|
|
494
|
+
} else {
|
|
495
|
+
albumsOut.push(group);
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
albumsOut.sort(
|
|
499
|
+
(a, b) => (a.albumArtist ?? "").localeCompare(b.albumArtist ?? "") || (a.album ?? "").localeCompare(b.album ?? "")
|
|
500
|
+
);
|
|
501
|
+
return { albums: albumsOut, singles, unmatched, errors };
|
|
502
|
+
}
|
|
503
|
+
export {
|
|
504
|
+
groupAlbums
|
|
505
|
+
};
|
|
@@ -5,4 +5,6 @@
|
|
|
5
5
|
export type { AudioDynamics, AudioFileMetadata, DuplicateGroup, FolderScanItem, FolderScanOptions, FolderScanResult, FolderUpdateItem, FolderUpdateResult, } from "./types.js";
|
|
6
6
|
export { scanFolder } from "./scan-operations.js";
|
|
7
7
|
export { exportFolderMetadata, findDuplicates, updateFolderTags, } from "./folder-operations.js";
|
|
8
|
+
export { type AlbumDisc, type AlbumGroup, type AlbumGroupingResult, type AlbumGroupItem, type AlbumGroupKey, type DiscConfidence, groupAlbums, type GroupAlbumsOptions, } from "./group-albums.js";
|
|
9
|
+
export { scanForAlbums, type ScanForAlbumsOptions } from "./scan-for-albums.js";
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/folder-api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,gBAAgB,GACjB,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/folder-api/index.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,YAAY,EACV,aAAa,EACb,iBAAiB,EACjB,cAAc,EACd,cAAc,EACd,iBAAiB,EACjB,gBAAgB,EAChB,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EACL,oBAAoB,EACpB,cAAc,EACd,gBAAgB,GACjB,MAAM,wBAAwB,CAAC;AAEhC,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,WAAW,EACX,KAAK,kBAAkB,GACxB,MAAM,mBAAmB,CAAC;AAE3B,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -4,9 +4,15 @@ import {
|
|
|
4
4
|
findDuplicates,
|
|
5
5
|
updateFolderTags
|
|
6
6
|
} from "./folder-operations.js";
|
|
7
|
+
import {
|
|
8
|
+
groupAlbums
|
|
9
|
+
} from "./group-albums.js";
|
|
10
|
+
import { scanForAlbums } from "./scan-for-albums.js";
|
|
7
11
|
export {
|
|
8
12
|
exportFolderMetadata,
|
|
9
13
|
findDuplicates,
|
|
14
|
+
groupAlbums,
|
|
10
15
|
scanFolder,
|
|
16
|
+
scanForAlbums,
|
|
11
17
|
updateFolderTags
|
|
12
18
|
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Thin async wrapper: scan a folder, then group the scan into
|
|
3
|
+
* albums. Deno/Node/Bun only, like scanFolder — groupAlbums itself is pure
|
|
4
|
+
* and runtime-agnostic.
|
|
5
|
+
*/
|
|
6
|
+
import type { FolderScanOptions } from "./types.js";
|
|
7
|
+
import { type AlbumGroupingResult, type GroupAlbumsOptions } from "./group-albums.js";
|
|
8
|
+
export type ScanForAlbumsOptions = FolderScanOptions & GroupAlbumsOptions;
|
|
9
|
+
/**
|
|
10
|
+
* Scan a folder and group the result into albums with disc subdivisions.
|
|
11
|
+
*
|
|
12
|
+
* Forwards every {@link FolderScanOptions} (recursive, extensions, maxFiles,
|
|
13
|
+
* onProgress, includeProperties, continueOnError, criteria, signal) to the
|
|
14
|
+
* scan; grouping options (minFolderConfidence, flatDiscPrefixes,
|
|
15
|
+
* folderFallback) apply to {@link groupAlbums}.
|
|
16
|
+
*
|
|
17
|
+
* @throws the same errors scanFolder throws (permission, missing path, abort).
|
|
18
|
+
*/
|
|
19
|
+
export declare function scanForAlbums(folderPath: string, options?: ScanForAlbumsOptions): Promise<AlbumGroupingResult>;
|
|
20
|
+
//# sourceMappingURL=scan-for-albums.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scan-for-albums.d.ts","sourceRoot":"","sources":["../../../src/folder-api/scan-for-albums.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAEpD,OAAO,EACL,KAAK,mBAAmB,EAExB,KAAK,kBAAkB,EACxB,MAAM,mBAAmB,CAAC;AAE3B,MAAM,MAAM,oBAAoB,GAAG,iBAAiB,GAAG,kBAAkB,CAAC;AAE1E;;;;;;;;;GASG;AACH,wBAAsB,aAAa,CACjC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAc9B"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { scanFolder } from "./scan-operations.js";
|
|
2
|
+
import {
|
|
3
|
+
groupAlbums
|
|
4
|
+
} from "./group-albums.js";
|
|
5
|
+
async function scanForAlbums(folderPath, options = {}) {
|
|
6
|
+
const {
|
|
7
|
+
minFolderConfidence,
|
|
8
|
+
flatDiscPrefixes,
|
|
9
|
+
folderFallback,
|
|
10
|
+
...scanOptions
|
|
11
|
+
} = options;
|
|
12
|
+
const scan = await scanFolder(folderPath, scanOptions);
|
|
13
|
+
return groupAlbums(scan, {
|
|
14
|
+
minFolderConfidence,
|
|
15
|
+
flatDiscPrefixes,
|
|
16
|
+
folderFallback,
|
|
17
|
+
scanRoot: folderPath
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
export {
|
|
21
|
+
scanForAlbums
|
|
22
|
+
};
|
|
@@ -32,4 +32,15 @@ export interface LoadTagLibOptions {
|
|
|
32
32
|
*/
|
|
33
33
|
disableOptimizations?: boolean;
|
|
34
34
|
}
|
|
35
|
+
/**
|
|
36
|
+
* Describe why `wasmBinary` is not a WebAssembly module, or `null` if it is.
|
|
37
|
+
*
|
|
38
|
+
* Both loader entry points validate this before handing the buffer to
|
|
39
|
+
* Emscripten. Left to the glue, a bad buffer surfaces as an asynchronous
|
|
40
|
+
* `Aborted(CompileError: ...)` written straight to the console AFTER the
|
|
41
|
+
* returned promise settles — so a caller can neither attach it to their own
|
|
42
|
+
* failure nor suppress it. The two entry points raise different error types,
|
|
43
|
+
* hence a shared predicate rather than a shared throw.
|
|
44
|
+
*/
|
|
45
|
+
export declare function describeNonWasmBinary(binary: ArrayBuffer | Uint8Array): string | null;
|
|
35
46
|
//# sourceMappingURL=loader-types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader-types.d.ts","sourceRoot":"","sources":["../../../src/runtime/loader-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAEtC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC"}
|
|
1
|
+
{"version":3,"file":"loader-types.d.ts","sourceRoot":"","sources":["../../../src/runtime/loader-types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,WAAW,GAAG,UAAU,CAAC;IAEtC;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IAEjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;IAEtC;;;;OAIG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;;;GASG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,WAAW,GAAG,UAAU,GAC/B,MAAM,GAAG,IAAI,CASf"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
function describeNonWasmBinary(binary) {
|
|
2
|
+
const bytes = binary instanceof Uint8Array ? binary : new Uint8Array(binary);
|
|
3
|
+
const MAGIC = [0, 97, 115, 109];
|
|
4
|
+
if (bytes.length >= 4 && MAGIC.every((b, i) => bytes[i] === b)) return null;
|
|
5
|
+
const found = Array.from(bytes.slice(0, 4)).map((b) => b.toString(16).padStart(2, "0")).join(" ");
|
|
6
|
+
return `wasmBinary is not a WebAssembly module: expected magic bytes 00 61 73 6d, found ${found || "(empty)"}. Size: ${bytes.length} bytes`;
|
|
7
|
+
}
|
|
8
|
+
export {
|
|
9
|
+
describeNonWasmBinary
|
|
10
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-loader.d.ts","sourceRoot":"","sources":["../../../src/runtime/module-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"module-loader.d.ts","sourceRoot":"","sources":["../../../src/runtime/module-loader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAE3D,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAS/C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAsB,gBAAgB,CACpC,OAAO,CAAC,EAAE,iBAAiB,GAC1B,OAAO,CAAC,YAAY,CAAC,CA8EvB"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { describeNonWasmBinary } from "./loader-types.js";
|
|
1
2
|
import {
|
|
2
3
|
EnvironmentError,
|
|
3
4
|
errorMessage,
|
|
@@ -18,6 +19,14 @@ async function loadTagLibModule(options) {
|
|
|
18
19
|
{ forceWasmType: "wasi" }
|
|
19
20
|
);
|
|
20
21
|
}
|
|
22
|
+
if (options?.wasmBinary) {
|
|
23
|
+
const problem = describeNonWasmBinary(options.wasmBinary);
|
|
24
|
+
if (problem) {
|
|
25
|
+
throw new TagLibInitializationError(problem, {
|
|
26
|
+
byteLength: options.wasmBinary.byteLength
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
21
30
|
if (!options?.wasmBinary && !options?.wasmUrl && !options?.forceWasmType && isDenoCompiled()) {
|
|
22
31
|
const wasmBinary = await tryLoadEmbeddedWasm();
|
|
23
32
|
if (!wasmBinary) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../src/runtime/unified-loader/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"loader.d.ts","sourceRoot":"","sources":["../../../../src/runtime/unified-loader/loader.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,oBAAoB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAM5E,wBAAsB,uBAAuB,CAC3C,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,mBAAmB,CAAC,CAiD9B"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { detectRuntime } from "../detector.js";
|
|
2
2
|
import { ModuleLoadError } from "./types.js";
|
|
3
|
+
import { describeNonWasmBinary } from "../loader-types.js";
|
|
3
4
|
import { selectWasmType } from "./module-selection.js";
|
|
4
5
|
import { loadModule } from "./module-loading.js";
|
|
5
6
|
async function loadUnifiedTagLibModule(options = {}) {
|
|
@@ -9,6 +10,10 @@ async function loadUnifiedTagLibModule(options = {}) {
|
|
|
9
10
|
"wasi"
|
|
10
11
|
);
|
|
11
12
|
}
|
|
13
|
+
if (options.wasmBinary) {
|
|
14
|
+
const problem = describeNonWasmBinary(options.wasmBinary);
|
|
15
|
+
if (problem) throw new ModuleLoadError(problem, "emscripten");
|
|
16
|
+
}
|
|
12
17
|
const startTime = performance.now();
|
|
13
18
|
const runtime = detectRuntime();
|
|
14
19
|
if (options.debug) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-handle.d.ts","sourceRoot":"","sources":["../../../../src/runtime/wasi-adapter/file-handle.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,UAAU,EACV,aAAa,EACb,SAAS,EACT,UAAU,EACX,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AACxB,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;
|
|
1
|
+
{"version":3,"file":"file-handle.d.ts","sourceRoot":"","sources":["../../../../src/runtime/wasi-adapter/file-handle.ts"],"names":[],"mappings":"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"}
|