taglib-wasm 2.2.1 → 2.2.2
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/dist/disc-folder.d.ts +3 -1
- package/dist/disc-folder.d.ts.map +1 -1
- package/dist/disc-folder.js +187 -152
- package/dist/index.browser.js +1 -1
- package/dist/simple.browser.js +1 -1
- package/dist/src/folder-api/album-corroborate.d.ts +13 -0
- package/dist/src/folder-api/album-corroborate.d.ts.map +1 -0
- package/dist/src/folder-api/album-corroborate.js +36 -0
- package/dist/src/folder-api/album-discs.d.ts +13 -0
- package/dist/src/folder-api/album-discs.d.ts.map +1 -0
- package/dist/src/folder-api/album-discs.js +122 -0
- package/dist/src/folder-api/album-grouping.d.ts +24 -0
- package/dist/src/folder-api/album-grouping.d.ts.map +1 -0
- package/dist/src/folder-api/album-grouping.js +118 -0
- package/dist/src/folder-api/album-identity.d.ts +27 -0
- package/dist/src/folder-api/album-identity.d.ts.map +1 -0
- package/dist/src/folder-api/album-identity.js +151 -0
- package/dist/src/folder-api/{group-albums.d.ts → album-types.d.ts} +55 -26
- package/dist/src/folder-api/album-types.d.ts.map +1 -0
- package/dist/src/folder-api/album-types.js +0 -0
- package/dist/src/folder-api/folder-disc.d.ts +24 -0
- package/dist/src/folder-api/folder-disc.d.ts.map +1 -0
- package/dist/src/folder-api/folder-disc.js +169 -0
- package/dist/src/folder-api/index.d.ts +3 -1
- package/dist/src/folder-api/index.d.ts.map +1 -1
- package/dist/src/folder-api/index.js +2 -4
- package/dist/src/folder-api/scan-for-albums.d.ts +1 -1
- package/dist/src/folder-api/scan-for-albums.d.ts.map +1 -1
- package/dist/src/folder-api/scan-for-albums.js +1 -3
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +20 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.js +84 -0
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts +5 -19
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/file-handle.js +67 -389
- package/dist/src/runtime/wasi-adapter/handle-state.d.ts +28 -0
- package/dist/src/runtime/wasi-adapter/handle-state.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/handle-state.js +58 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.d.ts +34 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.js +80 -0
- package/dist/src/runtime/wasi-adapter/property-surface.d.ts +25 -0
- package/dist/src/runtime/wasi-adapter/property-surface.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/property-surface.js +104 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.d.ts +44 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.js +139 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.d.ts +28 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.js +46 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/package.json +2 -1
- package/dist/src/folder-api/group-albums.d.ts.map +0 -1
- package/dist/src/folder-api/group-albums.js +0 -544
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { basename } from "../utils/path.js";
|
|
2
|
+
import { dirname } from "./folder-disc.js";
|
|
3
|
+
import { baseConfidence, parseDiscName } from "./folder-disc.js";
|
|
4
|
+
import { CONFIDENCE_RANK, corroborated } from "./album-corroborate.js";
|
|
5
|
+
import { buildAlbumIdentity } from "./album-identity.js";
|
|
6
|
+
import { assembleDiscs } from "./album-discs.js";
|
|
7
|
+
function flatPrefix(filename) {
|
|
8
|
+
const separated = filename.match(/^(\d{1,2})[\s._-](\d{1,2})/);
|
|
9
|
+
if (separated) return { disc: parseInt(separated[1], 10), form: "separated" };
|
|
10
|
+
const compact = filename.match(/^(\d)(\d{2})(?:\D|$)/);
|
|
11
|
+
if (compact) return { disc: parseInt(compact[1], 10), form: "compact" };
|
|
12
|
+
return void 0;
|
|
13
|
+
}
|
|
14
|
+
function normalizeOk(result) {
|
|
15
|
+
const okItems = [];
|
|
16
|
+
const errors = [];
|
|
17
|
+
for (const item of result.items) {
|
|
18
|
+
if (item.status === "ok") {
|
|
19
|
+
okItems.push({ path: item.path, tags: item.tags, metadata: item });
|
|
20
|
+
} else {
|
|
21
|
+
errors.push({ path: item.path, error: item.error });
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { okItems, errors };
|
|
25
|
+
}
|
|
26
|
+
function buildTree(okItems) {
|
|
27
|
+
const tree = /* @__PURE__ */ new Map();
|
|
28
|
+
for (const item of okItems) {
|
|
29
|
+
const dir = dirname(item.path);
|
|
30
|
+
let node = tree.get(dir);
|
|
31
|
+
if (!node) {
|
|
32
|
+
node = { files: [], parent: dirname(dir) };
|
|
33
|
+
tree.set(dir, node);
|
|
34
|
+
}
|
|
35
|
+
node.files.push(item);
|
|
36
|
+
}
|
|
37
|
+
return tree;
|
|
38
|
+
}
|
|
39
|
+
function confirmDiscs(tree, minRank) {
|
|
40
|
+
const confirmedDiscs = /* @__PURE__ */ new Map();
|
|
41
|
+
for (const [dir, node] of tree) {
|
|
42
|
+
if (node.files.length === 0) continue;
|
|
43
|
+
const parse = parseDiscName(basename(dir));
|
|
44
|
+
if (!parse) continue;
|
|
45
|
+
const siblings = [...tree.keys()].filter(
|
|
46
|
+
(d) => d !== dir && dirname(d) === dirname(dir)
|
|
47
|
+
);
|
|
48
|
+
const corroboratedBySiblings = corroborated(parse, siblings);
|
|
49
|
+
let confidence = parse.gated ? "low" : baseConfidence(parse);
|
|
50
|
+
if (parse.gated) {
|
|
51
|
+
if (!corroboratedBySiblings) continue;
|
|
52
|
+
} else if (parse.kind === "bonus" || parse.kind === "bare") {
|
|
53
|
+
if (!corroboratedBySiblings) continue;
|
|
54
|
+
} else if (corroboratedBySiblings) {
|
|
55
|
+
confidence = "high";
|
|
56
|
+
}
|
|
57
|
+
if (CONFIDENCE_RANK[confidence] < minRank) continue;
|
|
58
|
+
confirmedDiscs.set(dir, { parse, confidence });
|
|
59
|
+
}
|
|
60
|
+
return confirmedDiscs;
|
|
61
|
+
}
|
|
62
|
+
function flatSubdivisions(tree, confirmedDiscs, useFlatPrefixes) {
|
|
63
|
+
const flat = /* @__PURE__ */ new Map();
|
|
64
|
+
if (!useFlatPrefixes) return flat;
|
|
65
|
+
for (const [dir, node] of tree) {
|
|
66
|
+
if (node.files.length === 0 || confirmedDiscs.has(dir)) continue;
|
|
67
|
+
const byFile = /* @__PURE__ */ new Map();
|
|
68
|
+
const forms = /* @__PURE__ */ new Set();
|
|
69
|
+
for (const item of node.files) {
|
|
70
|
+
const prefix = flatPrefix(basename(item.path));
|
|
71
|
+
if (prefix) {
|
|
72
|
+
byFile.set(item.path, prefix.disc);
|
|
73
|
+
forms.add(prefix.form);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (byFile.size < 2) continue;
|
|
77
|
+
const distinct = [...new Set(byFile.values())].sort((a, b) => a - b);
|
|
78
|
+
const allCompact = forms.size === 1 && forms.has("compact");
|
|
79
|
+
const plausible = allCompact ? distinct.every((d) => d >= 1 && d <= distinct.length) : true;
|
|
80
|
+
if (plausible) flat.set(dir, byFile);
|
|
81
|
+
}
|
|
82
|
+
return flat;
|
|
83
|
+
}
|
|
84
|
+
function groupAlbums(result, options = {}) {
|
|
85
|
+
const minRank = CONFIDENCE_RANK[options.minFolderConfidence ?? "low"];
|
|
86
|
+
const useFlatPrefixes = options.flatDiscPrefixes ?? true;
|
|
87
|
+
const useFolderFallback = options.folderFallback ?? true;
|
|
88
|
+
const { okItems, errors } = normalizeOk(result);
|
|
89
|
+
if (okItems.length === 0) {
|
|
90
|
+
return { albums: [], singles: [], unmatched: [], errors };
|
|
91
|
+
}
|
|
92
|
+
const tree = buildTree(okItems);
|
|
93
|
+
const confirmedDiscs = confirmDiscs(tree, minRank);
|
|
94
|
+
const flat = flatSubdivisions(tree, confirmedDiscs, useFlatPrefixes);
|
|
95
|
+
const { albums, unmatched } = buildAlbumIdentity(
|
|
96
|
+
okItems,
|
|
97
|
+
confirmedDiscs,
|
|
98
|
+
flat,
|
|
99
|
+
{ scanRoot: options.scanRoot, useFolderFallback }
|
|
100
|
+
);
|
|
101
|
+
const groups = assembleDiscs(albums);
|
|
102
|
+
const albumsOut = [];
|
|
103
|
+
const singles = [];
|
|
104
|
+
for (const group of groups) {
|
|
105
|
+
if (group.items.length === 1) {
|
|
106
|
+
singles.push(group.items[0]);
|
|
107
|
+
} else {
|
|
108
|
+
albumsOut.push(group);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
albumsOut.sort(
|
|
112
|
+
(a, b) => (a.albumArtist ?? "").localeCompare(b.albumArtist ?? "") || (a.album ?? "").localeCompare(b.album ?? "")
|
|
113
|
+
);
|
|
114
|
+
return { albums: albumsOut, singles, unmatched, errors };
|
|
115
|
+
}
|
|
116
|
+
export {
|
|
117
|
+
groupAlbums
|
|
118
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Album identity resolution (spec step 5, taglib-ys7m split).
|
|
3
|
+
*
|
|
4
|
+
* Tags are authority: key on normalized (albumArtist, album), folding generic
|
|
5
|
+
* album artists into an album-only key. Untagged files fall back to a folder
|
|
6
|
+
* walk, and folder-derived groups merge into matching tag-keyed groups.
|
|
7
|
+
*/
|
|
8
|
+
import type { AudioFileMetadata } from "./types.js";
|
|
9
|
+
import type { AlbumAccumulator, AlbumGroupKey, DiscConfidence, DiscParse, OkItem } from "./album-types.js";
|
|
10
|
+
type ConfirmedDiscs = Map<string, {
|
|
11
|
+
parse: DiscParse;
|
|
12
|
+
confidence: DiscConfidence;
|
|
13
|
+
}>;
|
|
14
|
+
export interface AlbumIdentity {
|
|
15
|
+
albums: Map<AlbumGroupKey, AlbumAccumulator>;
|
|
16
|
+
unmatched: AudioFileMetadata[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Build the album map (spec step 5): tag-keyed groups first, folder fallback
|
|
20
|
+
* for untagged files, then mixed-folder merges into tag-keyed groups.
|
|
21
|
+
*/
|
|
22
|
+
export declare function buildAlbumIdentity(okItems: OkItem[], confirmedDiscs: ConfirmedDiscs, flatSubdivisions: Map<string, Map<string, number>>, options: {
|
|
23
|
+
scanRoot?: string | undefined;
|
|
24
|
+
useFolderFallback: boolean;
|
|
25
|
+
}): AlbumIdentity;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=album-identity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"album-identity.d.ts","sourceRoot":"","sources":["../../../src/folder-api/album-identity.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,YAAY,CAAC;AACpD,OAAO,KAAK,EACV,gBAAgB,EAChB,aAAa,EACb,cAAc,EACd,SAAS,EAET,MAAM,EACP,MAAM,kBAAkB,CAAC;AAY1B,KAAK,cAAc,GAAG,GAAG,CACvB,MAAM,EACN;IAAE,KAAK,EAAE,SAAS,CAAC;IAAC,UAAU,EAAE,cAAc,CAAA;CAAE,CACjD,CAAC;AAEF,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,GAAG,CAAC,aAAa,EAAE,gBAAgB,CAAC,CAAC;IAC7C,SAAS,EAAE,iBAAiB,EAAE,CAAC;CAChC;AA6ED;;;GAGG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,cAAc,EAAE,cAAc,EAC9B,gBAAgB,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,EAClD,OAAO,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAAC,iBAAiB,EAAE,OAAO,CAAA;CAAE,GACrE,aAAa,CAmGf"}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { basename } from "../utils/path.js";
|
|
2
|
+
import { dirname } from "./folder-disc.js";
|
|
3
|
+
function normalizeKey(s) {
|
|
4
|
+
return s.trim().toLowerCase().replace(/\s+/g, " ");
|
|
5
|
+
}
|
|
6
|
+
function isGenericAlbumArtist(albumArtist) {
|
|
7
|
+
if (!albumArtist || albumArtist.trim() === "") return true;
|
|
8
|
+
const n = normalizeKey(albumArtist);
|
|
9
|
+
return n === "various artists" || n === "va";
|
|
10
|
+
}
|
|
11
|
+
function evidenceFor(item, confirmedDiscs, flatSubdivisions) {
|
|
12
|
+
const dir = dirname(item.path);
|
|
13
|
+
const own = confirmedDiscs.get(dir);
|
|
14
|
+
if (own) {
|
|
15
|
+
return {
|
|
16
|
+
folderDiscNumber: own.parse.number,
|
|
17
|
+
total: own.parse.total,
|
|
18
|
+
folderDiscTitle: own.parse.discTitle,
|
|
19
|
+
confidence: own.confidence,
|
|
20
|
+
albumDir: dirname(dir)
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
let cursor = dirname(dir);
|
|
24
|
+
while (cursor !== void 0) {
|
|
25
|
+
const entry = confirmedDiscs.get(cursor);
|
|
26
|
+
if (entry) {
|
|
27
|
+
return {
|
|
28
|
+
folderDiscNumber: entry.parse.number,
|
|
29
|
+
total: entry.parse.total,
|
|
30
|
+
folderDiscTitle: entry.parse.discTitle,
|
|
31
|
+
confidence: entry.confidence,
|
|
32
|
+
albumDir: dirname(cursor)
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
const parent = dirname(cursor);
|
|
36
|
+
if (parent === cursor) break;
|
|
37
|
+
cursor = parent;
|
|
38
|
+
}
|
|
39
|
+
const flat = flatSubdivisions.get(dir)?.get(item.path);
|
|
40
|
+
if (flat !== void 0) {
|
|
41
|
+
return {
|
|
42
|
+
folderDiscNumber: flat,
|
|
43
|
+
total: void 0,
|
|
44
|
+
folderDiscTitle: void 0,
|
|
45
|
+
confidence: "medium",
|
|
46
|
+
albumDir: dir
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
return void 0;
|
|
50
|
+
}
|
|
51
|
+
function folderIdentity(dir, confirmedDiscs, scanRoot) {
|
|
52
|
+
const own = confirmedDiscs.get(dir);
|
|
53
|
+
if (!own) {
|
|
54
|
+
const title2 = basename(dir);
|
|
55
|
+
return title2 ? { title: title2, titleSource: dir } : void 0;
|
|
56
|
+
}
|
|
57
|
+
if (own.parse.title !== void 0) {
|
|
58
|
+
return { title: own.parse.title, titleSource: dirname(dir) };
|
|
59
|
+
}
|
|
60
|
+
const source = dirname(dir);
|
|
61
|
+
if (source === scanRoot || source === dir) return void 0;
|
|
62
|
+
const title = basename(source);
|
|
63
|
+
return title ? { title, titleSource: source } : void 0;
|
|
64
|
+
}
|
|
65
|
+
function buildAlbumIdentity(okItems, confirmedDiscs, flatSubdivisions, options) {
|
|
66
|
+
const albums = /* @__PURE__ */ new Map();
|
|
67
|
+
const unmatched = [];
|
|
68
|
+
for (const item of okItems) {
|
|
69
|
+
const dir = dirname(item.path);
|
|
70
|
+
const albumTag = item.tags.album?.[0]?.trim();
|
|
71
|
+
const albumArtistTag = item.tags.albumArtist?.[0]?.trim();
|
|
72
|
+
if (albumTag) {
|
|
73
|
+
const artistPart = isGenericAlbumArtist(albumArtistTag) ? "" : normalizeKey(albumArtistTag ?? "");
|
|
74
|
+
const key2 = `${artistPart}::${normalizeKey(albumTag)}`;
|
|
75
|
+
let acc2 = albums.get(key2);
|
|
76
|
+
if (!acc2) {
|
|
77
|
+
acc2 = {
|
|
78
|
+
key: key2,
|
|
79
|
+
album: albumTag,
|
|
80
|
+
albumArtist: isGenericAlbumArtist(albumArtistTag) ? void 0 : albumArtistTag,
|
|
81
|
+
source: "tags",
|
|
82
|
+
directory: void 0,
|
|
83
|
+
items: [],
|
|
84
|
+
evidence: /* @__PURE__ */ new Map()
|
|
85
|
+
};
|
|
86
|
+
albums.set(key2, acc2);
|
|
87
|
+
}
|
|
88
|
+
const evidence2 = evidenceFor(item, confirmedDiscs, flatSubdivisions);
|
|
89
|
+
acc2.evidence.set(item.path, evidence2);
|
|
90
|
+
acc2.items.push({
|
|
91
|
+
...item.metadata,
|
|
92
|
+
albumDir: evidence2?.albumDir ?? dir,
|
|
93
|
+
discNumber: evidence2?.folderDiscNumber
|
|
94
|
+
});
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (!options.useFolderFallback) {
|
|
98
|
+
unmatched.push(item.metadata);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const identity = folderIdentity(dir, confirmedDiscs, options.scanRoot);
|
|
102
|
+
if (!identity) {
|
|
103
|
+
unmatched.push(item.metadata);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
const key = `${identity.titleSource}::${normalizeKey(identity.title)}`;
|
|
107
|
+
let acc = albums.get(key);
|
|
108
|
+
if (!acc) {
|
|
109
|
+
acc = {
|
|
110
|
+
key,
|
|
111
|
+
album: identity.title,
|
|
112
|
+
albumArtist: void 0,
|
|
113
|
+
source: "folder",
|
|
114
|
+
directory: identity.titleSource,
|
|
115
|
+
items: [],
|
|
116
|
+
evidence: /* @__PURE__ */ new Map()
|
|
117
|
+
};
|
|
118
|
+
albums.set(key, acc);
|
|
119
|
+
}
|
|
120
|
+
const evidence = evidenceFor(item, confirmedDiscs, flatSubdivisions);
|
|
121
|
+
acc.evidence.set(item.path, evidence);
|
|
122
|
+
acc.items.push({
|
|
123
|
+
...item.metadata,
|
|
124
|
+
albumDir: evidence?.albumDir ?? dir,
|
|
125
|
+
discNumber: evidence?.folderDiscNumber
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
for (const [key, acc] of [...albums]) {
|
|
129
|
+
if (acc.source === "tags") continue;
|
|
130
|
+
for (const other of albums.values()) {
|
|
131
|
+
if (other === acc || other.source !== "tags") continue;
|
|
132
|
+
if (normalizeKey(other.album) !== normalizeKey(acc.album)) continue;
|
|
133
|
+
const albumDir = acc.directory;
|
|
134
|
+
const sharesChain = albumDir !== void 0 && other.items.some((o) => {
|
|
135
|
+
const od = dirname(o.path);
|
|
136
|
+
return od === albumDir || od.startsWith(albumDir + "/");
|
|
137
|
+
});
|
|
138
|
+
if (!sharesChain) continue;
|
|
139
|
+
for (const item of acc.items) {
|
|
140
|
+
other.items.push(item);
|
|
141
|
+
other.evidence.set(item.path, acc.evidence.get(item.path));
|
|
142
|
+
}
|
|
143
|
+
albums.delete(key);
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return { albums, unmatched };
|
|
148
|
+
}
|
|
149
|
+
export {
|
|
150
|
+
buildAlbumIdentity
|
|
151
|
+
};
|
|
@@ -1,22 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
* Given a folder scan, produce albums with disc subdivisions, using embedded
|
|
5
|
-
* tags as authority and folder/filename structure as evidence. Synchronous,
|
|
6
|
-
* runtime-agnostic, no wasm, no I/O: everything the algorithm needs arrives
|
|
7
|
-
* in the scan result (tags, paths, statuses).
|
|
8
|
-
*
|
|
9
|
-
* Model contract (invariants, enforced by tests):
|
|
10
|
-
* 1. Every ok item appears exactly once across albums[*].items, singles,
|
|
11
|
-
* and unmatched; errors is disjoint.
|
|
12
|
-
* 2. discs >= 1 per album; items >= 1 per disc.
|
|
13
|
-
* 3. discNumber === tagDiscNumber ?? folderDiscNumber on every disc.
|
|
14
|
-
* 4. key is stable for identical input.
|
|
15
|
-
* 5. Every item's albumDir/discNumber match the disc it was assigned to.
|
|
16
|
-
* 6. compilation is true/false only under full tag agreement, else undefined.
|
|
17
|
-
* 7. singles holds exactly the ok items whose resolved album has one file.
|
|
2
|
+
* Types for pure album grouping (taglib-ys7m split). No runtime imports:
|
|
3
|
+
* every module in the disc-folder graph must stay Wasm-free (taglib-cd7b).
|
|
18
4
|
*/
|
|
19
|
-
import type { AudioFileMetadata
|
|
5
|
+
import type { AudioFileMetadata } from "./types.js";
|
|
6
|
+
/** Opaque, stable album identity. */
|
|
20
7
|
export type AlbumGroupKey = string & {
|
|
21
8
|
__brand: "AlbumGroupKey";
|
|
22
9
|
};
|
|
@@ -111,12 +98,54 @@ export interface DiscFolderInfo {
|
|
|
111
98
|
* "high" inside groupAlbums. Gated names stay "low" even corroborated. */
|
|
112
99
|
confidence: DiscConfidence;
|
|
113
100
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*/
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
101
|
+
export type DiscKind = "exact" | "embedded" | "volume" | "bonus" | "bare";
|
|
102
|
+
export interface DiscParse {
|
|
103
|
+
kind: DiscKind;
|
|
104
|
+
/** Disc number when the name carries one. */
|
|
105
|
+
number: number | undefined;
|
|
106
|
+
/** "of N" total from "CD 1 of 2". */
|
|
107
|
+
total: number | undefined;
|
|
108
|
+
/** Title text before an embedded marker ("Album (Disc 1)" -> "Album"). */
|
|
109
|
+
title: string | undefined;
|
|
110
|
+
/** Trailing text after the number ("Bonus Tracks" from "Disc 2 (Bonus Tracks)"). */
|
|
111
|
+
discTitle: string | undefined;
|
|
112
|
+
/** True for title-word markers (tape/vinyl/cassette/lp/record) and
|
|
113
|
+
* single-letter number tokens: corroboration required, low confidence. */
|
|
114
|
+
gated: boolean;
|
|
115
|
+
/** True when the number token was a single letter ("CD D"): a side label,
|
|
116
|
+
* not a number — corroboration required (2026-08-04 feedback). */
|
|
117
|
+
sideLetter: boolean;
|
|
118
|
+
/** True when the number token was arabic digits ("CD1" vs "Disc One"). */
|
|
119
|
+
digit: boolean;
|
|
120
|
+
}
|
|
121
|
+
export interface OkItem {
|
|
122
|
+
path: string;
|
|
123
|
+
tags: AudioFileMetadata["tags"];
|
|
124
|
+
metadata: AudioFileMetadata;
|
|
125
|
+
}
|
|
126
|
+
export interface DirNode {
|
|
127
|
+
files: OkItem[];
|
|
128
|
+
parent: string | undefined;
|
|
129
|
+
}
|
|
130
|
+
/** Per-file disc evidence, resolved before tag merging. */
|
|
131
|
+
export interface FileDiscEvidence {
|
|
132
|
+
/** Folder-derived disc number (own/ancestor parse or flat prefix). */
|
|
133
|
+
folderDiscNumber: number | undefined;
|
|
134
|
+
/** "of N" total from the folder name. */
|
|
135
|
+
total: number | undefined;
|
|
136
|
+
folderDiscTitle: string | undefined;
|
|
137
|
+
confidence: DiscConfidence;
|
|
138
|
+
/** The album directory attributed to this file. */
|
|
139
|
+
albumDir: string;
|
|
140
|
+
}
|
|
141
|
+
/** Accumulator for one album during identity and disc assembly. */
|
|
142
|
+
export interface AlbumAccumulator {
|
|
143
|
+
key: AlbumGroupKey;
|
|
144
|
+
album: string;
|
|
145
|
+
albumArtist: string | undefined;
|
|
146
|
+
source: "tags" | "folder";
|
|
147
|
+
directory: string | undefined;
|
|
148
|
+
items: AlbumGroupItem[];
|
|
149
|
+
evidence: Map<string, FileDiscEvidence | undefined>;
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=album-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"album-types.d.ts","sourceRoot":"","sources":["../../../src/folder-api/album-types.ts"],"names":[],"mappings":"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"}
|
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disc-folder name recognition (pure grammar, taglib-ys7m split).
|
|
3
|
+
*
|
|
4
|
+
* Standalone recognizer: classifies a directory name as a disc folder and
|
|
5
|
+
* corroborates gated names against siblings. No Wasm, no I/O — this module
|
|
6
|
+
* is part of the disc-folder subpath's pure graph (taglib-cd7b).
|
|
7
|
+
*/
|
|
8
|
+
import type { DiscConfidence, DiscFolderInfo, DiscParse } from "./album-types.js";
|
|
9
|
+
export declare function dirname(path: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Parse a directory name as a disc folder. Returns undefined for non-disc
|
|
12
|
+
* names. Plain "Bonus" and "Extras" are NEVER discs by name (tuneup rule;
|
|
13
|
+
* the low-tier table's "Extras" row was the flagged divergence).
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseDiscName(name: string): DiscParse | undefined;
|
|
16
|
+
export declare function baseConfidence(parse: DiscParse): DiscConfidence;
|
|
17
|
+
/**
|
|
18
|
+
* Recognize a directory name as a disc folder. Standalone — it only
|
|
19
|
+
* classifies the name; it does not resolve the folder to its parent (use
|
|
20
|
+
* the album result's per-file `albumDir` for that). Returns undefined for
|
|
21
|
+
* non-disc names. Plain "Bonus" and "Extras" are never discs by name.
|
|
22
|
+
*/
|
|
23
|
+
export declare function discFolderInfo(name: string): DiscFolderInfo | undefined;
|
|
24
|
+
//# sourceMappingURL=folder-disc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"folder-disc.d.ts","sourceRoot":"","sources":["../../../src/folder-api/folder-disc.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EACV,cAAc,EACd,cAAc,EACd,SAAS,EACV,MAAM,kBAAkB,CAAC;AAE1B,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5C;AAyED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CA6GjE;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,SAAS,GAAG,cAAc,CAa/D;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAYvE"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
function dirname(path) {
|
|
2
|
+
const i = Math.max(path.lastIndexOf("/"), path.lastIndexOf("\\"));
|
|
3
|
+
return i === -1 ? path : path.slice(0, i);
|
|
4
|
+
}
|
|
5
|
+
const UNCONDITIONAL_MARKERS = [
|
|
6
|
+
"cd",
|
|
7
|
+
"disc",
|
|
8
|
+
"disk",
|
|
9
|
+
"dvd",
|
|
10
|
+
"sacd",
|
|
11
|
+
"blu-ray",
|
|
12
|
+
"bd",
|
|
13
|
+
"digital media"
|
|
14
|
+
];
|
|
15
|
+
const GATED_MARKERS = ["tape", "vinyl", "cassette", "lp", "record"];
|
|
16
|
+
const MARKERS = [...UNCONDITIONAL_MARKERS, ...GATED_MARKERS];
|
|
17
|
+
const GATED_MARKER_SET = new Set(GATED_MARKERS);
|
|
18
|
+
const WORD_NUMBERS = {
|
|
19
|
+
one: 1,
|
|
20
|
+
two: 2,
|
|
21
|
+
three: 3,
|
|
22
|
+
four: 4,
|
|
23
|
+
five: 5,
|
|
24
|
+
six: 6,
|
|
25
|
+
seven: 7,
|
|
26
|
+
eight: 8,
|
|
27
|
+
nine: 9,
|
|
28
|
+
ten: 10,
|
|
29
|
+
eleven: 11,
|
|
30
|
+
twelve: 12
|
|
31
|
+
};
|
|
32
|
+
function romanToInt(s) {
|
|
33
|
+
const values = {
|
|
34
|
+
i: 1,
|
|
35
|
+
v: 5,
|
|
36
|
+
x: 10,
|
|
37
|
+
l: 50,
|
|
38
|
+
c: 100,
|
|
39
|
+
d: 500,
|
|
40
|
+
m: 1e3
|
|
41
|
+
};
|
|
42
|
+
let total = 0;
|
|
43
|
+
let prev = 0;
|
|
44
|
+
for (let i = s.length - 1; i >= 0; i--) {
|
|
45
|
+
const v = values[s[i]];
|
|
46
|
+
if (v === void 0) return NaN;
|
|
47
|
+
total += v < prev ? -v : v;
|
|
48
|
+
prev = v;
|
|
49
|
+
}
|
|
50
|
+
return total;
|
|
51
|
+
}
|
|
52
|
+
function parseNumberToken(token) {
|
|
53
|
+
if (/^\d+$/.test(token)) return parseInt(token, 10);
|
|
54
|
+
const word = WORD_NUMBERS[token.toLowerCase()];
|
|
55
|
+
if (word) return word;
|
|
56
|
+
const roman = romanToInt(token.toLowerCase());
|
|
57
|
+
return Number.isFinite(roman) ? roman : void 0;
|
|
58
|
+
}
|
|
59
|
+
const SEP = String.raw`[\s._#-]*`;
|
|
60
|
+
const NUM = String.raw`(?:\d+|(?:[ivxlcdm]+|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|[a-z])(?![a-z]))`;
|
|
61
|
+
function parseDiscName(name) {
|
|
62
|
+
const trimmed = name.trim();
|
|
63
|
+
if (/^(?:extras?|bonus)$/i.test(trimmed)) return void 0;
|
|
64
|
+
for (const marker of MARKERS) {
|
|
65
|
+
const left = String.raw`(?:^|[^a-z0-9])`;
|
|
66
|
+
const esc = marker.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
67
|
+
const re = new RegExp(
|
|
68
|
+
left + String.raw`(${esc})(?![a-z])${SEP}(${NUM})`,
|
|
69
|
+
"i"
|
|
70
|
+
);
|
|
71
|
+
const m = trimmed.match(re);
|
|
72
|
+
if (!m) continue;
|
|
73
|
+
const sideLetter = /^[a-z]$/i.test(m[2]);
|
|
74
|
+
const number = sideLetter ? void 0 : parseNumberToken(m[2]);
|
|
75
|
+
if (number === void 0 && !sideLetter) continue;
|
|
76
|
+
if (!sideLetter && /^[ivxlcdm]{2,}$/i.test(m[2]) && (number ?? 0) > 100) continue;
|
|
77
|
+
const isGated = GATED_MARKER_SET.has(marker.toLowerCase()) || sideLetter;
|
|
78
|
+
const markerStart = m.index + m[1].length - marker.length;
|
|
79
|
+
const rawPrefix = trimmed.slice(0, markerStart).trim();
|
|
80
|
+
const title = rawPrefix.replace(/[()[\]]/g, "").replace(/[\s._#-]+$/, "") || void 0;
|
|
81
|
+
const isExact = title === void 0;
|
|
82
|
+
const after = trimmed.slice(m.index + m[0].length).trim();
|
|
83
|
+
const discTitle = after.replace(/^[()[\],:;\-–—\s]+/, "").replace(/[)\]\[,:;\-–—\s]+$/, "").trim() || void 0;
|
|
84
|
+
const ofMatch = after.trim().match(/^of\s+(\d+)\b/i);
|
|
85
|
+
const total = ofMatch ? parseInt(ofMatch[1], 10) : void 0;
|
|
86
|
+
return {
|
|
87
|
+
kind: isExact ? "exact" : "embedded",
|
|
88
|
+
number: sideLetter ? void 0 : number,
|
|
89
|
+
total,
|
|
90
|
+
title,
|
|
91
|
+
discTitle,
|
|
92
|
+
gated: isGated,
|
|
93
|
+
sideLetter,
|
|
94
|
+
digit: /^\d+$/.test(m[2])
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
const volume = trimmed.match(
|
|
98
|
+
/^(?:volume|vol\.?|part|pt\.?)\s*(\d+)(?:\s*of\s*(\d+))?$/i
|
|
99
|
+
);
|
|
100
|
+
if (volume) {
|
|
101
|
+
return {
|
|
102
|
+
kind: "volume",
|
|
103
|
+
number: parseInt(volume[1], 10),
|
|
104
|
+
total: volume[2] ? parseInt(volume[2], 10) : void 0,
|
|
105
|
+
title: void 0,
|
|
106
|
+
discTitle: void 0,
|
|
107
|
+
gated: false,
|
|
108
|
+
sideLetter: false,
|
|
109
|
+
digit: true
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (/^bonus\s+disc$/i.test(trimmed)) {
|
|
113
|
+
return {
|
|
114
|
+
kind: "bonus",
|
|
115
|
+
number: void 0,
|
|
116
|
+
total: void 0,
|
|
117
|
+
title: void 0,
|
|
118
|
+
discTitle: void 0,
|
|
119
|
+
gated: true,
|
|
120
|
+
sideLetter: false,
|
|
121
|
+
digit: false
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (/^(?:\d+|v\d+|p\d+|[ab])$/i.test(trimmed)) {
|
|
125
|
+
return {
|
|
126
|
+
kind: "bare",
|
|
127
|
+
number: /^\d+$/i.test(trimmed) ? parseInt(trimmed, 10) : void 0,
|
|
128
|
+
total: void 0,
|
|
129
|
+
title: void 0,
|
|
130
|
+
discTitle: void 0,
|
|
131
|
+
gated: true,
|
|
132
|
+
sideLetter: false,
|
|
133
|
+
digit: false
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
return void 0;
|
|
137
|
+
}
|
|
138
|
+
function baseConfidence(parse) {
|
|
139
|
+
switch (parse.kind) {
|
|
140
|
+
case "exact":
|
|
141
|
+
return parse.digit ? "high" : "low";
|
|
142
|
+
case "embedded":
|
|
143
|
+
return "medium";
|
|
144
|
+
case "volume":
|
|
145
|
+
return "medium";
|
|
146
|
+
case "bonus":
|
|
147
|
+
case "bare":
|
|
148
|
+
return "low";
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function discFolderInfo(name) {
|
|
152
|
+
const parse = parseDiscName(name);
|
|
153
|
+
if (!parse) return void 0;
|
|
154
|
+
return {
|
|
155
|
+
kind: parse.kind,
|
|
156
|
+
gated: parse.gated,
|
|
157
|
+
number: parse.number,
|
|
158
|
+
total: parse.total,
|
|
159
|
+
title: parse.title,
|
|
160
|
+
discTitle: parse.discTitle,
|
|
161
|
+
confidence: parse.gated ? "low" : baseConfidence(parse)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
export {
|
|
165
|
+
baseConfidence,
|
|
166
|
+
dirname,
|
|
167
|
+
discFolderInfo,
|
|
168
|
+
parseDiscName
|
|
169
|
+
};
|
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
export type { AudioDynamics, AudioFileMetadata, DuplicateGroup, FolderScanItem, FolderScanOptions, FolderScanResult, } from "./types.js";
|
|
6
6
|
export { scanFolder } from "./scan-operations.js";
|
|
7
7
|
export { exportFolderMetadata, findDuplicates } from "./folder-operations.js";
|
|
8
|
-
export { type AlbumDisc, type AlbumGroup, type AlbumGroupingResult, type AlbumGroupItem, type AlbumGroupKey, type DiscConfidence, type DiscFolderInfo,
|
|
8
|
+
export { type AlbumDisc, type AlbumGroup, type AlbumGroupingResult, type AlbumGroupItem, type AlbumGroupKey, type DiscConfidence, type DiscFolderInfo, type GroupAlbumsOptions, } from "./album-types.js";
|
|
9
|
+
export { discFolderInfo } from "./folder-disc.js";
|
|
10
|
+
export { groupAlbums } from "./album-grouping.js";
|
|
9
11
|
export { scanForAlbums, type ScanForAlbumsOptions } from "./scan-for-albums.js";
|
|
10
12
|
//# 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,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE9E,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,
|
|
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,GACjB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,UAAU,EAAE,MAAM,sBAAsB,CAAC;AAElD,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAE9E,OAAO,EACL,KAAK,SAAS,EACd,KAAK,UAAU,EACf,KAAK,mBAAmB,EACxB,KAAK,cAAc,EACnB,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,cAAc,EACnB,KAAK,kBAAkB,GACxB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAElD,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import { scanFolder } from "./scan-operations.js";
|
|
2
2
|
import { exportFolderMetadata, findDuplicates } from "./folder-operations.js";
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
groupAlbums
|
|
6
|
-
} from "./group-albums.js";
|
|
3
|
+
import { discFolderInfo } from "./folder-disc.js";
|
|
4
|
+
import { groupAlbums } from "./album-grouping.js";
|
|
7
5
|
import { scanForAlbums } from "./scan-for-albums.js";
|
|
8
6
|
export {
|
|
9
7
|
discFolderInfo,
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* and runtime-agnostic.
|
|
5
5
|
*/
|
|
6
6
|
import type { FolderScanOptions } from "./types.js";
|
|
7
|
-
import {
|
|
7
|
+
import type { AlbumGroupingResult, GroupAlbumsOptions } from "./album-types.js";
|
|
8
8
|
export type ScanForAlbumsOptions = FolderScanOptions & GroupAlbumsOptions;
|
|
9
9
|
/**
|
|
10
10
|
* Scan a folder and group the result into albums with disc subdivisions.
|
|
@@ -1 +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,
|
|
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,KAAK,EAAE,mBAAmB,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AAGhF,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"}
|