taglib-wasm 1.8.3 → 2.0.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.
Files changed (71) hide show
  1. package/README.md +8 -1
  2. package/dist/disc-folder.d.ts +19 -0
  3. package/dist/disc-folder.d.ts.map +1 -0
  4. package/dist/disc-folder.js +550 -0
  5. package/dist/index.browser.d.ts +3 -4
  6. package/dist/index.browser.d.ts.map +1 -1
  7. package/dist/index.browser.js +171 -173
  8. package/dist/index.d.ts +3 -4
  9. package/dist/index.d.ts.map +1 -1
  10. package/dist/index.js +1 -13
  11. package/dist/simple.browser.js +164 -30
  12. package/dist/src/constants/complex-properties.d.ts +0 -114
  13. package/dist/src/constants/complex-properties.d.ts.map +1 -1
  14. package/dist/src/constants/complex-properties.js +0 -55
  15. package/dist/src/constants/index.d.ts +0 -1
  16. package/dist/src/constants/index.d.ts.map +1 -1
  17. package/dist/src/constants/index.js +0 -1
  18. package/dist/src/file-utils/import-operations.d.ts.map +1 -1
  19. package/dist/src/file-utils/import-operations.js +1 -1
  20. package/dist/src/folder-api/file-processors.d.ts.map +1 -1
  21. package/dist/src/folder-api/file-processors.js +6 -5
  22. package/dist/src/folder-api/scan-for-albums.js +3 -3
  23. package/dist/src/folder-api/scan-operations.js +2 -2
  24. package/dist/src/msgpack/decoder.d.ts.map +1 -1
  25. package/dist/src/msgpack/decoder.js +5 -6
  26. package/dist/src/msgpack/encoder.d.ts.map +1 -1
  27. package/dist/src/msgpack/encoder.js +4 -3
  28. package/dist/src/runtime/module-loader.d.ts.map +1 -1
  29. package/dist/src/runtime/module-loader.js +3 -3
  30. package/dist/src/runtime/wasi-adapter/file-handle.d.ts +8 -0
  31. package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
  32. package/dist/src/runtime/wasi-adapter/file-handle.js +32 -1
  33. package/dist/src/simple/batch-operations.d.ts.map +1 -1
  34. package/dist/src/simple/batch-operations.js +14 -10
  35. package/dist/src/simple/picture-operations.js +1 -1
  36. package/dist/src/taglib/audio-file-base.d.ts +15 -9
  37. package/dist/src/taglib/audio-file-base.d.ts.map +1 -1
  38. package/dist/src/taglib/audio-file-base.js +35 -4
  39. package/dist/src/taglib/audio-file-impl.d.ts +2 -2
  40. package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
  41. package/dist/src/taglib/audio-file-impl.js +25 -13
  42. package/dist/src/taglib/audio-file-interface.d.ts +27 -6
  43. package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
  44. package/dist/src/taglib/auto-dispose.d.ts +6 -0
  45. package/dist/src/taglib/auto-dispose.d.ts.map +1 -0
  46. package/dist/src/taglib/auto-dispose.js +13 -0
  47. package/dist/src/taglib/embind-adapter.d.ts +2 -2
  48. package/dist/src/taglib/embind-adapter.d.ts.map +1 -1
  49. package/dist/src/taglib/embind-adapter.js +17 -0
  50. package/dist/src/taglib/extra-state-registry.d.ts.map +1 -1
  51. package/dist/src/taglib/extra-state-registry.js +9 -0
  52. package/dist/src/taglib/metadata-extent.d.ts.map +1 -1
  53. package/dist/src/taglib/metadata-extent.js +44 -1
  54. package/dist/src/taglib/mutable-tag.d.ts +15 -15
  55. package/dist/src/taglib/mutable-tag.d.ts.map +1 -1
  56. package/dist/src/taglib/save-reconstruct.d.ts.map +1 -1
  57. package/dist/src/taglib/taglib-class.d.ts.map +1 -1
  58. package/dist/src/types/tags.d.ts +0 -4
  59. package/dist/src/types/tags.d.ts.map +1 -1
  60. package/dist/src/version.d.ts +1 -1
  61. package/dist/src/version.js +1 -1
  62. package/dist/src/wasm.d.ts +20 -1
  63. package/dist/src/wasm.d.ts.map +1 -1
  64. package/dist/src/web-utils/canvas.d.ts.map +1 -1
  65. package/dist/src/web-utils/canvas.js +6 -1
  66. package/dist/src/web-utils/data-url.js +1 -1
  67. package/dist/taglib-wasi.wasm +0 -0
  68. package/package.json +18 -10
  69. package/dist/src/constants/tags.d.ts +0 -81
  70. package/dist/src/constants/tags.d.ts.map +0 -1
  71. package/dist/src/constants/tags.js +0 -86
package/README.md CHANGED
@@ -205,7 +205,6 @@ const result = await scanFolder("/path/to/music", {
205
205
  console.log(`Processing ${processed}/${total}: ${file}`);
206
206
  },
207
207
  });
208
-
209
208
  console.log(`Found ${result.items.length} audio files`);
210
209
  console.log(
211
210
  `Successfully processed ${
@@ -241,6 +240,14 @@ for (const album of albums) {
241
240
  console.log(`${singles.length} singles, ${unmatched.length} unmatched`);
242
241
  ```
243
242
 
243
+ The disc-folder grammar (`discFolderInfo`) and the pure `groupAlbums` core are
244
+ Wasm-free — import them from the dedicated subpath in browser/UI contexts
245
+ (no TagLib runtime is loaded):
246
+
247
+ ```typescript
248
+ import { discFolderInfo, groupAlbums } from "taglib-wasm/disc-folder";
249
+ ```
250
+
244
251
  ### Working with Cover Art
245
252
 
246
253
  ```typescript
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Pure disc-folder grammar and album grouping — no Wasm, no runtime deps.
3
+ *
4
+ * @module disc-folder
5
+ *
6
+ * Import from this subpath when all you need is `discFolderInfo` (or the pure
7
+ * `groupAlbums` core): unlike the main entry or `./folder`, this module's
8
+ * graph contains no Wasm loader, so it works in browsers and UI contexts
9
+ * without ever loading TagLib. CI guards the property (taglib-cd7b) — if a
10
+ * future change pulls a non-pure import into this graph, the build fails.
11
+ *
12
+ * ```typescript
13
+ * import { discFolderInfo } from "taglib-wasm/disc-folder";
14
+ * discFolderInfo("CD1"); // { kind: "exact", number: 1, ... }
15
+ * ```
16
+ */
17
+ export * from "./src/folder-api/group-albums.js";
18
+ export type { FolderScanResult } from "./src/folder-api/types.js";
19
+ //# sourceMappingURL=disc-folder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"disc-folder.d.ts","sourceRoot":"","sources":["../disc-folder.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,cAAc,kCAAkC,CAAC;AACjD,YAAY,EAAE,gBAAgB,EAAE,MAAM,2BAA2B,CAAC"}
@@ -0,0 +1,550 @@
1
+ // src/utils/path.ts
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
+ }
21
+ function dirname(path) {
22
+ const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
23
+ return i === -1 ? path : path.slice(0, i);
24
+ }
25
+ var UNCONDITIONAL_MARKERS = [
26
+ "cd",
27
+ "disc",
28
+ "disk",
29
+ "dvd",
30
+ "sacd",
31
+ "blu-ray",
32
+ "bd",
33
+ "digital media"
34
+ ];
35
+ var GATED_MARKERS = ["tape", "vinyl", "cassette", "lp", "record"];
36
+ var MARKERS = [...UNCONDITIONAL_MARKERS, ...GATED_MARKERS];
37
+ var GATED_MARKER_SET = new Set(GATED_MARKERS);
38
+ var WORD_NUMBERS = {
39
+ one: 1,
40
+ two: 2,
41
+ three: 3,
42
+ four: 4,
43
+ five: 5,
44
+ six: 6,
45
+ seven: 7,
46
+ eight: 8,
47
+ nine: 9,
48
+ ten: 10,
49
+ eleven: 11,
50
+ twelve: 12
51
+ };
52
+ function romanToInt(s) {
53
+ const values = {
54
+ i: 1,
55
+ v: 5,
56
+ x: 10,
57
+ l: 50,
58
+ c: 100,
59
+ d: 500,
60
+ m: 1e3
61
+ };
62
+ let total = 0;
63
+ let prev = 0;
64
+ for (let i = s.length - 1; i >= 0; i--) {
65
+ const v = values[s[i]];
66
+ if (v === void 0) return NaN;
67
+ total += v < prev ? -v : v;
68
+ prev = v;
69
+ }
70
+ return total;
71
+ }
72
+ function parseNumberToken(token) {
73
+ if (/^\d+$/.test(token)) return parseInt(token, 10);
74
+ const word = WORD_NUMBERS[token.toLowerCase()];
75
+ if (word) return word;
76
+ const roman = romanToInt(token.toLowerCase());
77
+ return Number.isFinite(roman) ? roman : void 0;
78
+ }
79
+ var SEP = String.raw`[\s._#-]*`;
80
+ var NUM = String.raw`(?:\d+|(?:[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|[a-z])(?![a-z]))`;
81
+ function parseDiscName(name) {
82
+ const trimmed = name.trim();
83
+ if (/^(?:extras?|bonus)$/i.test(trimmed)) return void 0;
84
+ for (const marker of MARKERS) {
85
+ const left = String.raw`(?:^|[^a-z0-9])`;
86
+ const esc = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
87
+ const re = new RegExp(
88
+ left + String.raw`(${esc})(?![a-z])${SEP}(${NUM})`,
89
+ "i"
90
+ );
91
+ const m = trimmed.match(re);
92
+ if (!m) continue;
93
+ const sideLetter = /^[a-z]$/i.test(m[2]);
94
+ const number = sideLetter ? void 0 : parseNumberToken(m[2]);
95
+ if (number === void 0 && !sideLetter) continue;
96
+ if (!sideLetter && /^[ivxlcdm]{2,}$/i.test(m[2]) && (number ?? 0) > 100) continue;
97
+ const isGated = GATED_MARKER_SET.has(marker.toLowerCase()) || sideLetter;
98
+ const markerStart = m.index + m[1].length - marker.length;
99
+ const rawPrefix = trimmed.slice(0, markerStart).trim();
100
+ const title = rawPrefix.replace(/[()[\]]/g, "").replace(/[\s._#-]+$/, "") || void 0;
101
+ const isExact = title === void 0;
102
+ const after = trimmed.slice(m.index + m[0].length).trim();
103
+ const discTitle = after.replace(/^[()[\],:;\-–—\s]+/, "").replace(/[)\]\[,:;\-–—\s]+$/, "").trim() || void 0;
104
+ const ofMatch = after.trim().match(/^of\s+(\d+)\b/i);
105
+ const total = ofMatch ? parseInt(ofMatch[1], 10) : void 0;
106
+ return {
107
+ kind: isExact ? "exact" : "embedded",
108
+ number: sideLetter ? void 0 : number,
109
+ total,
110
+ title,
111
+ discTitle,
112
+ gated: isGated,
113
+ sideLetter,
114
+ digit: /^\d+$/.test(m[2])
115
+ };
116
+ }
117
+ const volume = trimmed.match(
118
+ /^(?:volume|vol\.?|part|pt\.?)\s*(\d+)(?:\s*of\s*(\d+))?$/i
119
+ );
120
+ if (volume) {
121
+ return {
122
+ kind: "volume",
123
+ number: parseInt(volume[1], 10),
124
+ total: volume[2] ? parseInt(volume[2], 10) : void 0,
125
+ title: void 0,
126
+ discTitle: void 0,
127
+ gated: false,
128
+ sideLetter: false,
129
+ digit: true
130
+ };
131
+ }
132
+ if (/^bonus\s+disc$/i.test(trimmed)) {
133
+ return {
134
+ kind: "bonus",
135
+ number: void 0,
136
+ total: void 0,
137
+ title: void 0,
138
+ discTitle: void 0,
139
+ gated: true,
140
+ sideLetter: false,
141
+ digit: false
142
+ };
143
+ }
144
+ if (/^(?:\d+|v\d+|p\d+|[ab])$/i.test(trimmed)) {
145
+ return {
146
+ kind: "bare",
147
+ number: /^\d+$/i.test(trimmed) ? parseInt(trimmed, 10) : void 0,
148
+ total: void 0,
149
+ title: void 0,
150
+ discTitle: void 0,
151
+ gated: true,
152
+ sideLetter: false,
153
+ digit: false
154
+ };
155
+ }
156
+ return void 0;
157
+ }
158
+ function baseConfidence(parse) {
159
+ switch (parse.kind) {
160
+ case "exact":
161
+ return parse.digit ? "high" : "low";
162
+ case "embedded":
163
+ return "medium";
164
+ case "volume":
165
+ return "medium";
166
+ case "bonus":
167
+ case "bare":
168
+ return "low";
169
+ }
170
+ }
171
+ function corroborated(parse, siblings) {
172
+ if (!parse.gated && parse.kind === "exact") return true;
173
+ if (siblings.length === 0) return false;
174
+ const siblingParses = siblings.map((s) => parseDiscName(basename(s))).filter((p) => p !== void 0);
175
+ const hasExactSibling = siblingParses.some(
176
+ (p) => !p.gated && p.kind === "exact"
177
+ );
178
+ const hasNumberedSibling = siblingParses.some(
179
+ (p) => p.number !== void 0
180
+ );
181
+ if (parse.number !== void 0 && hasNumberedSibling) return true;
182
+ if (!parse.gated && parse.kind === "embedded" && hasExactSibling) return true;
183
+ if (!parse.gated && parse.kind === "volume" && hasNumberedSibling) {
184
+ return true;
185
+ }
186
+ if (parse.sideLetter) {
187
+ return siblingParses.some((p) => p.sideLetter);
188
+ }
189
+ if (parse.kind === "bonus" || parse.kind === "bare") {
190
+ const numberedCount = siblingParses.filter((p) => p.number !== void 0).length;
191
+ if (numberedCount >= 2) return true;
192
+ if (parse.number !== void 0 && hasNumberedSibling) return true;
193
+ }
194
+ return false;
195
+ }
196
+ function flatPrefix(filename) {
197
+ const separated = filename.match(/^(\d{1,2})[\s._-](\d{1,2})/);
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
+ function normalizeKey(s) {
204
+ return s.trim().toLowerCase().replace(/\s+/g, " ");
205
+ }
206
+ function isGenericAlbumArtist(albumArtist) {
207
+ if (!albumArtist || albumArtist.trim() === "") return true;
208
+ const n = normalizeKey(albumArtist);
209
+ return n === "various artists" || n === "va";
210
+ }
211
+ var CONFIDENCE_RANK = {
212
+ low: 0,
213
+ medium: 1,
214
+ high: 2
215
+ };
216
+ function groupAlbums(result, options = {}) {
217
+ const minRank = CONFIDENCE_RANK[options.minFolderConfidence ?? "low"];
218
+ const useFlatPrefixes = options.flatDiscPrefixes ?? true;
219
+ const useFolderFallback = options.folderFallback ?? true;
220
+ const okItems = [];
221
+ const errors = [];
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 };
231
+ }
232
+ const scanRoot = options.scanRoot;
233
+ const tree = /* @__PURE__ */ new Map();
234
+ for (const item of okItems) {
235
+ const dir = dirname(item.path);
236
+ let node = tree.get(dir);
237
+ if (!node) {
238
+ node = { files: [], parent: dirname(dir) };
239
+ tree.set(dir, node);
240
+ }
241
+ node.files.push(item);
242
+ }
243
+ const confirmedDiscs = /* @__PURE__ */ new Map();
244
+ for (const [dir, node] of tree) {
245
+ if (node.files.length === 0) continue;
246
+ const parse = parseDiscName(basename(dir));
247
+ if (!parse) continue;
248
+ const siblings = [...tree.keys()].filter(
249
+ (d) => d !== dir && dirname(d) === dirname(dir)
250
+ );
251
+ const corroboratedBySiblings = corroborated(parse, siblings);
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 });
262
+ }
263
+ const flatSubdivisions = /* @__PURE__ */ new Map();
264
+ if (useFlatPrefixes) {
265
+ for (const [dir, node] of tree) {
266
+ if (node.files.length === 0 || confirmedDiscs.has(dir)) continue;
267
+ const byFile = /* @__PURE__ */ new Map();
268
+ const forms = /* @__PURE__ */ new Set();
269
+ for (const item of node.files) {
270
+ const prefix = flatPrefix(basename(item.path));
271
+ if (prefix) {
272
+ byFile.set(item.path, prefix.disc);
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
+ }
282
+ }
283
+ const albums = /* @__PURE__ */ new Map();
284
+ 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
+ for (const item of okItems) {
340
+ const dir = dirname(item.path);
341
+ const albumTag = item.tags.album?.[0]?.trim();
342
+ const albumArtistTag = item.tags.albumArtist?.[0]?.trim();
343
+ if (albumTag) {
344
+ const artistPart = isGenericAlbumArtist(albumArtistTag) ? "" : normalizeKey(albumArtistTag ?? "");
345
+ const key2 = `${artistPart}::${normalizeKey(albumTag)}`;
346
+ let acc2 = albums.get(key2);
347
+ if (!acc2) {
348
+ acc2 = {
349
+ key: key2,
350
+ album: albumTag,
351
+ albumArtist: isGenericAlbumArtist(albumArtistTag) ? void 0 : albumArtistTag,
352
+ source: "tags",
353
+ directory: void 0,
354
+ items: [],
355
+ evidence: /* @__PURE__ */ new Map()
356
+ };
357
+ albums.set(key2, acc2);
358
+ }
359
+ const evidence2 = evidenceFor(item);
360
+ acc2.evidence.set(item.path, evidence2);
361
+ acc2.items.push({
362
+ ...item.metadata,
363
+ albumDir: evidence2?.albumDir ?? dir,
364
+ discNumber: evidence2?.folderDiscNumber
365
+ });
366
+ continue;
367
+ }
368
+ if (!useFolderFallback) {
369
+ unmatched.push(item.metadata);
370
+ continue;
371
+ }
372
+ const identity = folderIdentity(dir);
373
+ if (!identity) {
374
+ unmatched.push(item.metadata);
375
+ continue;
376
+ }
377
+ const key = `${identity.titleSource}::${normalizeKey(identity.title)}`;
378
+ let acc = albums.get(key);
379
+ if (!acc) {
380
+ acc = {
381
+ key,
382
+ album: identity.title,
383
+ albumArtist: void 0,
384
+ source: "folder",
385
+ directory: identity.titleSource,
386
+ items: [],
387
+ evidence: /* @__PURE__ */ new Map()
388
+ };
389
+ albums.set(key, acc);
390
+ }
391
+ const evidence = evidenceFor(item);
392
+ acc.evidence.set(item.path, evidence);
393
+ acc.items.push({
394
+ ...item.metadata,
395
+ albumDir: evidence?.albumDir ?? dir,
396
+ discNumber: evidence?.folderDiscNumber
397
+ });
398
+ }
399
+ for (const [key, acc] of [...albums]) {
400
+ if (acc.source === "tags") continue;
401
+ for (const other of albums.values()) {
402
+ if (other === acc || other.source !== "tags") continue;
403
+ if (normalizeKey(other.album) !== normalizeKey(acc.album)) continue;
404
+ const albumDir = acc.directory;
405
+ const sharesChain = albumDir !== void 0 && other.items.some((o) => {
406
+ const od = dirname(o.path);
407
+ return od === albumDir || od.startsWith(albumDir + "/");
408
+ });
409
+ if (!sharesChain) continue;
410
+ for (const item of acc.items) {
411
+ other.items.push(item);
412
+ other.evidence.set(item.path, acc.evidence.get(item.path));
413
+ }
414
+ albums.delete(key);
415
+ break;
416
+ }
417
+ }
418
+ const groups = [];
419
+ for (const acc of albums.values()) {
420
+ const dirTagCommon = /* @__PURE__ */ new Map();
421
+ const resolvedByFile = /* @__PURE__ */ new Map();
422
+ for (const item of acc.items) {
423
+ const dir = dirname(item.path);
424
+ if (!dirTagCommon.has(dir)) {
425
+ const tagged = acc.items.filter((i) => dirname(i.path) === dir).map((i) => i.tags.discNumber).filter((d) => d !== void 0 && d > 0);
426
+ dirTagCommon.set(
427
+ dir,
428
+ tagged.length > 0 && tagged.every((d) => d === tagged[0]) ? tagged[0] : void 0
429
+ );
430
+ }
431
+ const evidence = acc.evidence.get(item.path);
432
+ const tagCommon = dirTagCommon.get(dir);
433
+ resolvedByFile.set(
434
+ item.path,
435
+ tagCommon ?? evidence?.folderDiscNumber
436
+ );
437
+ }
438
+ const discs = /* @__PURE__ */ new Map();
439
+ const order = [];
440
+ const noEvidenceItems = [];
441
+ for (const item of acc.items) {
442
+ const evidence = acc.evidence.get(item.path);
443
+ if (evidence === void 0) {
444
+ noEvidenceItems.push(item);
445
+ continue;
446
+ }
447
+ const resolved = resolvedByFile.get(item.path);
448
+ const discKey = resolved === void 0 ? `dir:${dirname(item.path)}` : String(resolved);
449
+ let disc = discs.get(discKey);
450
+ if (!disc) {
451
+ disc = {
452
+ discNumber: resolved,
453
+ totalDiscs: evidence.total,
454
+ folderDiscNumber: evidence.folderDiscNumber,
455
+ folderDiscTitle: evidence.folderDiscTitle,
456
+ tagDiscNumber: void 0,
457
+ confidence: evidence.confidence,
458
+ items: []
459
+ };
460
+ discs.set(discKey, disc);
461
+ order.push(discKey);
462
+ }
463
+ disc.items.push(item);
464
+ }
465
+ if (noEvidenceItems.length > 0) {
466
+ const numbered = [...discs.values()].filter((d) => d.folderDiscNumber !== void 0).sort((a, b) => a.folderDiscNumber - b.folderDiscNumber);
467
+ if (numbered.length > 0) {
468
+ numbered[0].items.push(...noEvidenceItems);
469
+ } else {
470
+ const key = "dir:__none__";
471
+ let disc = discs.get(key);
472
+ if (!disc) {
473
+ disc = {
474
+ discNumber: void 0,
475
+ totalDiscs: void 0,
476
+ folderDiscNumber: void 0,
477
+ folderDiscTitle: void 0,
478
+ tagDiscNumber: void 0,
479
+ confidence: "high",
480
+ items: []
481
+ };
482
+ discs.set(key, disc);
483
+ order.push(key);
484
+ }
485
+ disc.items.push(...noEvidenceItems);
486
+ }
487
+ }
488
+ for (const disc of discs.values()) {
489
+ const tagged = disc.items.map((i) => i.tags.discNumber).filter((d) => d !== void 0 && d > 0);
490
+ const common = tagged.length > 0 && tagged.every((d) => d === tagged[0]) ? tagged[0] : void 0;
491
+ disc.tagDiscNumber = common;
492
+ disc.discNumber = common ?? disc.folderDiscNumber;
493
+ for (const item of disc.items) item.discNumber = disc.discNumber;
494
+ }
495
+ const albumTaggedTotals = acc.items.map((i) => i.tags.totalDiscs).filter((t) => t !== void 0 && t > 0);
496
+ const commonTotal = albumTaggedTotals.length > 0 && albumTaggedTotals.every((t) => t === albumTaggedTotals[0]) ? albumTaggedTotals[0] : void 0;
497
+ const ofN = [...discs.values()].map((d) => d.totalDiscs).find(
498
+ (t) => t !== void 0
499
+ );
500
+ const maxSibling = [...discs.values()].map((d) => d.discNumber).filter((n) => n !== void 0).reduce((a, b) => Math.max(a, b), 0) || void 0;
501
+ for (const disc of discs.values()) {
502
+ disc.totalDiscs = commonTotal ?? ofN ?? maxSibling;
503
+ }
504
+ const sortedDiscs = [...discs.values()].sort((a, b) => {
505
+ const an = a.discNumber === void 0 ? -1 : a.discNumber;
506
+ const bn = b.discNumber === void 0 ? -1 : b.discNumber;
507
+ if (an !== bn) return an - bn;
508
+ return (a.folderDiscTitle ?? "").localeCompare(b.folderDiscTitle ?? "");
509
+ });
510
+ for (const disc of sortedDiscs) {
511
+ disc.items.sort((a, b) => {
512
+ const at = a.tags.track ?? Infinity;
513
+ const bt = b.tags.track ?? Infinity;
514
+ if (at !== bt) return at - bt;
515
+ return a.path.localeCompare(b.path);
516
+ });
517
+ }
518
+ const flags = acc.items.map((i) => i.tags.compilation).filter((c) => c !== void 0);
519
+ const compilation = flags.length > 0 && flags.every((f) => f === flags[0]) ? flags[0] : void 0;
520
+ const albumDirs = new Set(acc.items.map((i) => i.albumDir));
521
+ const directory = albumDirs.size === 1 ? [...albumDirs][0] : acc.directory;
522
+ groups.push({
523
+ key: acc.key,
524
+ albumArtist: acc.albumArtist,
525
+ album: acc.album,
526
+ source: acc.source,
527
+ compilation,
528
+ directory,
529
+ discs: sortedDiscs,
530
+ items: sortedDiscs.flatMap((d) => d.items)
531
+ });
532
+ }
533
+ const albumsOut = [];
534
+ const singles = [];
535
+ for (const group of groups) {
536
+ if (group.items.length === 1) {
537
+ singles.push(group.items[0]);
538
+ } else {
539
+ albumsOut.push(group);
540
+ }
541
+ }
542
+ albumsOut.sort(
543
+ (a, b) => (a.albumArtist ?? "").localeCompare(b.albumArtist ?? "") || (a.album ?? "").localeCompare(b.album ?? "")
544
+ );
545
+ return { albums: albumsOut, singles, unmatched, errors };
546
+ }
547
+ export {
548
+ discFolderInfo,
549
+ groupAlbums
550
+ };
@@ -13,15 +13,14 @@ export { isNamedAudioInput } from "./src/types/audio-formats.js";
13
13
  export { EnvironmentError, FileOperationError, InvalidFormatError, isEnvironmentError, isFileOperationError, isInvalidFormatError, isMemoryError, isMetadataError, isTagLibError, isUnsupportedFormatError, MemoryError, MetadataError, SUPPORTED_FORMATS, TagLibError, TagLibInitializationError, UnsupportedFormatError, } from "./src/errors.js";
14
14
  export type { TagLibErrorCode } from "./src/errors.js";
15
15
  export { addPicture, applyCoverArt, applyPictures, applyTags, applyTagsToFile, type BatchItem, type BatchOptions, type BatchResult, clearPictures, clearTags, type FileMetadata, findPictureByType, isValidAudioFile, readCoverArt, readFormat, readMetadata, readMetadataBatch, readPictureMetadata, readPictures, readProperties, readPropertiesBatch, readTags, readTagsBatch, replacePictureByType, setBufferMode, } from "./src/simple/index.js";
16
- export { FormatMappings, getAllProperties, getAllPropertyKeys, getAllTagNames, getPropertiesByFormat, getPropertyMetadata, isValidProperty, isValidTagName, PROPERTIES, Tags, } from "./src/constants.js";
16
+ export { FormatMappings, getAllProperties, getAllPropertyKeys, getPropertiesByFormat, getPropertyMetadata, isValidProperty, PROPERTIES, } from "./src/constants.js";
17
17
  export type { PropertyMetadata } from "./src/constants/property-types.js";
18
18
  export { canvasToPicture, createPictureDownloadURL, createPictureGallery, dataURLToPicture, displayPicture, imageFileToPicture, pictureToDataURL, setCoverArtFromCanvas, } from "./src/web-utils/index.js";
19
- export type { AudioCodec, AudioFileInput, AudioProperties, BitrateControlMode, ContainerFormat, ExtendedTag, FieldMapping, FileType, NamedAudioInput, OpenOptions, Picture, PictureType, PropertyMap, Tag, TagInput, TagName, } from "./src/types.js";
19
+ export type { AudioCodec, AudioFileInput, AudioProperties, BitrateControlMode, ContainerFormat, ExtendedTag, FieldMapping, FileType, NamedAudioInput, OpenOptions, Picture, PictureType, PropertyMap, Tag, TagInput, } from "./src/types.js";
20
20
  export { BITRATE_CONTROL_MODE_NAMES, BITRATE_CONTROL_MODE_VALUES, PICTURE_TYPE_NAMES, PICTURE_TYPE_VALUES, } from "./src/types.js";
21
21
  export type { PropertyKey, PropertyValue } from "./src/constants.js";
22
22
  export type { FormatPropertyKey, TagFormat, } from "./src/types/format-property-keys.js";
23
- export { COMPLEX_PROPERTIES, COMPLEX_PROPERTY_KEY, } from "./src/constants/complex-properties.js";
24
- export type { ComplexPropertyKey, ComplexPropertyKeyMap, ComplexPropertyValueMap, Rating, UnsyncedLyrics, VariantMap, } from "./src/constants/complex-properties.js";
23
+ export type { Rating, UnsyncedLyrics, VariantMap, } from "./src/constants/complex-properties.js";
25
24
  export { RatingUtils } from "./src/utils/rating.js";
26
25
  export type { NormalizedRating, PopmRating } from "./src/utils/rating.js";
27
26
  export type { TagLibModule, WasmModule } from "./src/wasm.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../index.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,SAAS,EACT,cAAc,GACf,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACtE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,aAAa,EACb,wBAAwB,EACxB,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACb,SAAS,EACT,eAAe,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,aAAa,EACb,SAAS,EACT,KAAK,YAAY,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,QAAQ,EACR,aAAa,EACb,oBAAoB,EACpB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACd,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,cAAc,EACd,UAAU,EACV,IAAI,GACL,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,WAAW,EACX,OAAO,EACP,WAAW,EACX,WAAW,EACX,GAAG,EACH,QAAQ,EACR,OAAO,GACR,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAC3B,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACrE,YAAY,EACV,iBAAiB,EACjB,SAAS,GACV,MAAM,qCAAqC,CAAC;AAG7C,OAAO,EACL,kBAAkB,EAClB,oBAAoB,GACrB,MAAM,uCAAuC,CAAC;AAC/C,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,uBAAuB,EACvB,MAAM,EACN,cAAc,EACd,UAAU,GACX,MAAM,uCAAuC,CAAC;AAG/C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG1E,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC"}
1
+ {"version":3,"file":"index.browser.d.ts","sourceRoot":"","sources":["../index.browser.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,YAAY,EACV,SAAS,EACT,cAAc,GACf,MAAM,sCAAsC,CAAC;AAC9C,OAAO,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACtE,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,8BAA8B,CAAC;AAGjE,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,kBAAkB,EAClB,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,aAAa,EACb,wBAAwB,EACxB,WAAW,EACX,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,yBAAyB,EACzB,sBAAsB,GACvB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAGvD,OAAO,EACL,UAAU,EACV,aAAa,EACb,aAAa,EACb,SAAS,EACT,eAAe,EACf,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,aAAa,EACb,SAAS,EACT,KAAK,YAAY,EACjB,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,iBAAiB,EACjB,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,mBAAmB,EACnB,QAAQ,EACR,aAAa,EACb,oBAAoB,EACpB,aAAa,GACd,MAAM,uBAAuB,CAAC;AAG/B,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,qBAAqB,EACrB,mBAAmB,EACnB,eAAe,EACf,UAAU,GACX,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAG1E,OAAO,EACL,eAAe,EACf,wBAAwB,EACxB,oBAAoB,EACpB,gBAAgB,EAChB,cAAc,EACd,kBAAkB,EAClB,gBAAgB,EAChB,qBAAqB,GACtB,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,UAAU,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,eAAe,EACf,WAAW,EACX,OAAO,EACP,WAAW,EACX,WAAW,EACX,GAAG,EACH,QAAQ,GACT,MAAM,gBAAgB,CAAC;AACxB,OAAO,EACL,0BAA0B,EAC1B,2BAA2B,EAC3B,kBAAkB,EAClB,mBAAmB,GACpB,MAAM,gBAAgB,CAAC;AAExB,YAAY,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACrE,YAAY,EACV,iBAAiB,EACjB,SAAS,GACV,MAAM,qCAAqC,CAAC;AAG7C,YAAY,EACV,MAAM,EACN,cAAc,EACd,UAAU,GACX,MAAM,uCAAuC,CAAC;AAG/C,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACpD,YAAY,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAG1E,YAAY,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC9D,YAAY,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,wCAAwC,CAAC"}