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.
- package/README.md +8 -1
- package/dist/disc-folder.d.ts +19 -0
- package/dist/disc-folder.d.ts.map +1 -0
- package/dist/disc-folder.js +550 -0
- package/dist/index.browser.d.ts +3 -4
- package/dist/index.browser.d.ts.map +1 -1
- package/dist/index.browser.js +171 -173
- package/dist/index.d.ts +3 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -13
- package/dist/simple.browser.js +164 -30
- package/dist/src/constants/complex-properties.d.ts +0 -114
- package/dist/src/constants/complex-properties.d.ts.map +1 -1
- package/dist/src/constants/complex-properties.js +0 -55
- package/dist/src/constants/index.d.ts +0 -1
- package/dist/src/constants/index.d.ts.map +1 -1
- package/dist/src/constants/index.js +0 -1
- package/dist/src/file-utils/import-operations.d.ts.map +1 -1
- package/dist/src/file-utils/import-operations.js +1 -1
- package/dist/src/folder-api/file-processors.d.ts.map +1 -1
- package/dist/src/folder-api/file-processors.js +6 -5
- package/dist/src/folder-api/scan-for-albums.js +3 -3
- package/dist/src/folder-api/scan-operations.js +2 -2
- package/dist/src/msgpack/decoder.d.ts.map +1 -1
- package/dist/src/msgpack/decoder.js +5 -6
- package/dist/src/msgpack/encoder.d.ts.map +1 -1
- package/dist/src/msgpack/encoder.js +4 -3
- package/dist/src/runtime/module-loader.d.ts.map +1 -1
- package/dist/src/runtime/module-loader.js +3 -3
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts +8 -0
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/file-handle.js +32 -1
- package/dist/src/simple/batch-operations.d.ts.map +1 -1
- package/dist/src/simple/batch-operations.js +14 -10
- package/dist/src/simple/picture-operations.js +1 -1
- package/dist/src/taglib/audio-file-base.d.ts +15 -9
- package/dist/src/taglib/audio-file-base.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-base.js +35 -4
- package/dist/src/taglib/audio-file-impl.d.ts +2 -2
- package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-impl.js +25 -13
- package/dist/src/taglib/audio-file-interface.d.ts +27 -6
- package/dist/src/taglib/audio-file-interface.d.ts.map +1 -1
- package/dist/src/taglib/auto-dispose.d.ts +6 -0
- package/dist/src/taglib/auto-dispose.d.ts.map +1 -0
- package/dist/src/taglib/auto-dispose.js +13 -0
- package/dist/src/taglib/embind-adapter.d.ts +2 -2
- package/dist/src/taglib/embind-adapter.d.ts.map +1 -1
- package/dist/src/taglib/embind-adapter.js +17 -0
- package/dist/src/taglib/extra-state-registry.d.ts.map +1 -1
- package/dist/src/taglib/extra-state-registry.js +9 -0
- package/dist/src/taglib/metadata-extent.d.ts.map +1 -1
- package/dist/src/taglib/metadata-extent.js +44 -1
- package/dist/src/taglib/mutable-tag.d.ts +15 -15
- package/dist/src/taglib/mutable-tag.d.ts.map +1 -1
- package/dist/src/taglib/save-reconstruct.d.ts.map +1 -1
- package/dist/src/taglib/taglib-class.d.ts.map +1 -1
- package/dist/src/types/tags.d.ts +0 -4
- package/dist/src/types/tags.d.ts.map +1 -1
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/wasm.d.ts +20 -1
- package/dist/src/wasm.d.ts.map +1 -1
- package/dist/src/web-utils/canvas.d.ts.map +1 -1
- package/dist/src/web-utils/canvas.js +6 -1
- package/dist/src/web-utils/data-url.js +1 -1
- package/dist/taglib-wasi.wasm +0 -0
- package/package.json +18 -10
- package/dist/src/constants/tags.d.ts +0 -81
- package/dist/src/constants/tags.d.ts.map +0 -1
- 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
|
+
};
|
package/dist/index.browser.d.ts
CHANGED
|
@@ -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,
|
|
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,
|
|
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 {
|
|
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,
|
|
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"}
|