taglib-wasm 2.2.1 → 2.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +1 -1
- package/README.md +5 -3
- package/dist/disc-folder.d.ts +3 -1
- package/dist/disc-folder.d.ts.map +1 -1
- package/dist/disc-folder.js +187 -152
- package/dist/index.browser.js +17 -10
- package/dist/index.d.ts +1 -1
- package/dist/simple.browser.js +13 -8
- package/dist/src/constants/properties.d.ts +2 -2
- package/dist/src/constants/specialized-properties.d.ts +2 -2
- package/dist/src/constants/specialized-properties.js +2 -2
- package/dist/src/folder-api/album-corroborate.d.ts +13 -0
- package/dist/src/folder-api/album-corroborate.d.ts.map +1 -0
- package/dist/src/folder-api/album-corroborate.js +36 -0
- package/dist/src/folder-api/album-discs.d.ts +13 -0
- package/dist/src/folder-api/album-discs.d.ts.map +1 -0
- package/dist/src/folder-api/album-discs.js +122 -0
- package/dist/src/folder-api/album-grouping.d.ts +24 -0
- package/dist/src/folder-api/album-grouping.d.ts.map +1 -0
- package/dist/src/folder-api/album-grouping.js +118 -0
- package/dist/src/folder-api/album-identity.d.ts +27 -0
- package/dist/src/folder-api/album-identity.d.ts.map +1 -0
- package/dist/src/folder-api/album-identity.js +151 -0
- package/dist/src/folder-api/{group-albums.d.ts → album-types.d.ts} +55 -26
- package/dist/src/folder-api/album-types.d.ts.map +1 -0
- package/dist/src/folder-api/album-types.js +0 -0
- package/dist/src/folder-api/folder-disc.d.ts +24 -0
- package/dist/src/folder-api/folder-disc.d.ts.map +1 -0
- package/dist/src/folder-api/folder-disc.js +169 -0
- package/dist/src/folder-api/index.d.ts +3 -1
- package/dist/src/folder-api/index.d.ts.map +1 -1
- package/dist/src/folder-api/index.js +2 -4
- package/dist/src/folder-api/scan-for-albums.d.ts +1 -1
- package/dist/src/folder-api/scan-for-albums.d.ts.map +1 -1
- package/dist/src/folder-api/scan-for-albums.js +1 -3
- package/dist/src/runtime/detector.d.ts +16 -1
- package/dist/src/runtime/detector.d.ts.map +1 -1
- package/dist/src/runtime/detector.js +58 -52
- package/dist/src/runtime/platform-io.d.ts +8 -0
- package/dist/src/runtime/platform-io.d.ts.map +1 -1
- package/dist/src/runtime/platform-io.js +11 -11
- package/dist/src/runtime/unified-loader/module-selection.d.ts +2 -2
- package/dist/src/runtime/unified-loader/module-selection.d.ts.map +1 -1
- package/dist/src/runtime/unified-loader/module-selection.js +3 -5
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts +20 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/audio-properties.js +85 -0
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts +5 -19
- package/dist/src/runtime/wasi-adapter/file-handle.d.ts.map +1 -1
- package/dist/src/runtime/wasi-adapter/file-handle.js +67 -389
- package/dist/src/runtime/wasi-adapter/handle-state.d.ts +28 -0
- package/dist/src/runtime/wasi-adapter/handle-state.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/handle-state.js +58 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.d.ts +34 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/mp4-items.js +80 -0
- package/dist/src/runtime/wasi-adapter/property-surface.d.ts +25 -0
- package/dist/src/runtime/wasi-adapter/property-surface.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/property-surface.js +104 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.d.ts +44 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/structured-fields.js +139 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.d.ts +28 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.d.ts.map +1 -0
- package/dist/src/runtime/wasi-adapter/tag-keys.js +46 -0
- package/dist/src/taglib/audio-file-impl.d.ts.map +1 -1
- package/dist/src/taglib/audio-file-impl.js +2 -2
- package/dist/src/taglib/embind-adapter.d.ts.map +1 -1
- package/dist/src/taglib/embind-adapter.js +4 -1
- package/dist/src/taglib/id3v2-frames.d.ts.map +1 -1
- package/dist/src/taglib/id3v2-frames.js +2 -2
- package/dist/src/types/audio-formats.d.ts +5 -5
- package/dist/src/types/audio-formats.d.ts.map +1 -1
- package/dist/src/types/format-property-keys.d.ts +1 -1
- package/dist/src/types/format-property-keys.d.ts.map +1 -1
- package/dist/src/types/tags.d.ts +8 -1
- package/dist/src/types/tags.d.ts.map +1 -1
- package/dist/src/utils/tag-mapping.d.ts.map +1 -1
- package/dist/src/utils/tag-mapping.js +2 -0
- package/dist/src/version.d.ts +1 -1
- package/dist/src/version.js +1 -1
- package/dist/src/web-utils/data-url.d.ts.map +1 -1
- package/dist/src/web-utils/data-url.js +4 -2
- package/dist/taglib-wasi.wasm +0 -0
- package/dist/taglib-web.wasm +0 -0
- package/package.json +8 -13
- package/dist/src/folder-api/group-albums.d.ts.map +0 -1
- package/dist/src/folder-api/group-albums.js +0 -544
|
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"}
|
|
@@ -7,6 +7,21 @@ export interface RuntimeDetectionResult {
|
|
|
7
7
|
supportsStreaming: boolean;
|
|
8
8
|
performanceTier: 1 | 2 | 3;
|
|
9
9
|
}
|
|
10
|
+
/**
|
|
11
|
+
* The pure per-environment backend configuration. `detectRuntime()` is the
|
|
12
|
+
* only sniffer in the codebase; every downstream decision (backend choice,
|
|
13
|
+
* PlatformIO creation, WASI availability) derives from this table so it can
|
|
14
|
+
* be exercised with synthetic environments instead of real runtimes
|
|
15
|
+
* (taglib-2b4).
|
|
16
|
+
*/
|
|
17
|
+
export interface EnvironmentProfile {
|
|
18
|
+
wasmType: WasmBinaryType;
|
|
19
|
+
supportsFilesystem: boolean;
|
|
20
|
+
supportsStreaming: boolean;
|
|
21
|
+
performanceTier: 1 | 2 | 3;
|
|
22
|
+
}
|
|
23
|
+
/** Pure environment -> backend configuration lookup (taglib-2b4). */
|
|
24
|
+
export declare function environmentProfile(env: RuntimeEnvironment): EnvironmentProfile;
|
|
10
25
|
/**
|
|
11
26
|
* Check if a Node.js version meets the minimum requirement (v22.6.0+).
|
|
12
27
|
* Returns an error message string if the version is too old, or undefined if OK.
|
|
@@ -22,5 +37,5 @@ export declare function detectRuntime(): RuntimeDetectionResult;
|
|
|
22
37
|
*/
|
|
23
38
|
export declare function supportsExnref(): boolean;
|
|
24
39
|
export declare function getEnvironmentDescription(env: RuntimeEnvironment): string;
|
|
25
|
-
export declare function canLoadWasmType(wasmType: WasmBinaryType): boolean;
|
|
40
|
+
export declare function canLoadWasmType(wasmType: WasmBinaryType, env?: RuntimeEnvironment): boolean;
|
|
26
41
|
//# sourceMappingURL=detector.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../../src/runtime/detector.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,WAAW,GACX,UAAU,GACV,SAAS,GACT,iBAAiB,GACjB,QAAQ,GACR,YAAY,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,CAAC;AAEnD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,EAAE,cAAc,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC5B;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAepB;AAuCD,qFAAqF;AACrF,wBAAgB,MAAM,IAAI,OAAO,CAEhC;AAED,wBAAgB,aAAa,IAAI,sBAAsB,
|
|
1
|
+
{"version":3,"file":"detector.d.ts","sourceRoot":"","sources":["../../../src/runtime/detector.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,kBAAkB,GAC1B,WAAW,GACX,WAAW,GACX,UAAU,GACV,SAAS,GACT,iBAAiB,GACjB,QAAQ,GACR,YAAY,CAAC;AAEjB,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,CAAC;AAEnD,MAAM,WAAW,sBAAsB;IACrC,WAAW,EAAE,kBAAkB,CAAC;IAChC,QAAQ,EAAE,cAAc,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC5B;AAED;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,cAAc,CAAC;IACzB,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,eAAe,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;CAC5B;AA+CD,qEAAqE;AACrE,wBAAgB,kBAAkB,CAChC,GAAG,EAAE,kBAAkB,GACtB,kBAAkB,CAEpB;AAOD;;;;GAIG;AACH,wBAAgB,gBAAgB,CAC9B,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,MAAM,GAAG,SAAS,CAepB;AAuCD,qFAAqF;AACrF,wBAAgB,MAAM,IAAI,OAAO,CAEhC;AAED,wBAAgB,aAAa,IAAI,sBAAsB,CAuCtD;AAWD;;;GAGG;AACH,wBAAgB,cAAc,IAAI,OAAO,CAMxC;AAED,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,kBAAkB,GAAG,MAAM,CAmBzE;AAED,wBAAgB,eAAe,CAC7B,QAAQ,EAAE,cAAc,EACxB,GAAG,GAAE,kBAAgD,GACpD,OAAO,CAKT"}
|
|
@@ -1,3 +1,50 @@
|
|
|
1
|
+
const ENVIRONMENT_PROFILES = {
|
|
2
|
+
"deno-wasi": {
|
|
3
|
+
wasmType: "wasi",
|
|
4
|
+
supportsFilesystem: true,
|
|
5
|
+
supportsStreaming: true,
|
|
6
|
+
performanceTier: 1
|
|
7
|
+
},
|
|
8
|
+
"node-wasi": {
|
|
9
|
+
wasmType: "wasi",
|
|
10
|
+
supportsFilesystem: true,
|
|
11
|
+
supportsStreaming: true,
|
|
12
|
+
performanceTier: 1
|
|
13
|
+
},
|
|
14
|
+
"bun-wasi": {
|
|
15
|
+
wasmType: "wasi",
|
|
16
|
+
supportsFilesystem: true,
|
|
17
|
+
supportsStreaming: true,
|
|
18
|
+
performanceTier: 1
|
|
19
|
+
},
|
|
20
|
+
browser: {
|
|
21
|
+
wasmType: "emscripten",
|
|
22
|
+
supportsFilesystem: false,
|
|
23
|
+
supportsStreaming: true,
|
|
24
|
+
performanceTier: 2
|
|
25
|
+
},
|
|
26
|
+
worker: {
|
|
27
|
+
wasmType: "emscripten",
|
|
28
|
+
supportsFilesystem: false,
|
|
29
|
+
supportsStreaming: true,
|
|
30
|
+
performanceTier: 2
|
|
31
|
+
},
|
|
32
|
+
cloudflare: {
|
|
33
|
+
wasmType: "emscripten",
|
|
34
|
+
supportsFilesystem: false,
|
|
35
|
+
supportsStreaming: false,
|
|
36
|
+
performanceTier: 3
|
|
37
|
+
},
|
|
38
|
+
"node-emscripten": {
|
|
39
|
+
wasmType: "emscripten",
|
|
40
|
+
supportsFilesystem: true,
|
|
41
|
+
supportsStreaming: true,
|
|
42
|
+
performanceTier: 3
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
function environmentProfile(env) {
|
|
46
|
+
return ENVIRONMENT_PROFILES[env];
|
|
47
|
+
}
|
|
1
48
|
const g = globalThis;
|
|
2
49
|
const MIN_NODE_MAJOR = 22;
|
|
3
50
|
const MIN_NODE_MINOR = 6;
|
|
@@ -38,73 +85,32 @@ function isDeno() {
|
|
|
38
85
|
}
|
|
39
86
|
function detectRuntime() {
|
|
40
87
|
if (isDeno() && hasWASISupport()) {
|
|
41
|
-
return {
|
|
42
|
-
environment: "deno-wasi",
|
|
43
|
-
wasmType: "wasi",
|
|
44
|
-
supportsFilesystem: true,
|
|
45
|
-
supportsStreaming: true,
|
|
46
|
-
performanceTier: 1
|
|
47
|
-
};
|
|
88
|
+
return { environment: "deno-wasi", ...environmentProfile("deno-wasi") };
|
|
48
89
|
}
|
|
49
90
|
if (isBun()) {
|
|
50
|
-
return {
|
|
51
|
-
environment: "bun-wasi",
|
|
52
|
-
wasmType: "wasi",
|
|
53
|
-
supportsFilesystem: true,
|
|
54
|
-
supportsStreaming: true,
|
|
55
|
-
performanceTier: 1
|
|
56
|
-
};
|
|
91
|
+
return { environment: "bun-wasi", ...environmentProfile("bun-wasi") };
|
|
57
92
|
}
|
|
58
93
|
if (isNode() && hasWASISupport()) {
|
|
59
|
-
return {
|
|
60
|
-
environment: "node-wasi",
|
|
61
|
-
wasmType: "wasi",
|
|
62
|
-
supportsFilesystem: true,
|
|
63
|
-
supportsStreaming: true,
|
|
64
|
-
performanceTier: 1
|
|
65
|
-
};
|
|
94
|
+
return { environment: "node-wasi", ...environmentProfile("node-wasi") };
|
|
66
95
|
}
|
|
67
96
|
if (isBrowser()) {
|
|
68
|
-
return {
|
|
69
|
-
environment: "browser",
|
|
70
|
-
wasmType: "emscripten",
|
|
71
|
-
supportsFilesystem: false,
|
|
72
|
-
supportsStreaming: true,
|
|
73
|
-
performanceTier: 2
|
|
74
|
-
};
|
|
97
|
+
return { environment: "browser", ...environmentProfile("browser") };
|
|
75
98
|
}
|
|
76
99
|
if (isWebWorker()) {
|
|
77
|
-
return {
|
|
78
|
-
environment: "worker",
|
|
79
|
-
wasmType: "emscripten",
|
|
80
|
-
supportsFilesystem: false,
|
|
81
|
-
supportsStreaming: true,
|
|
82
|
-
performanceTier: 2
|
|
83
|
-
};
|
|
100
|
+
return { environment: "worker", ...environmentProfile("worker") };
|
|
84
101
|
}
|
|
85
102
|
if (isCloudflareWorker()) {
|
|
86
|
-
return {
|
|
87
|
-
environment: "cloudflare",
|
|
88
|
-
wasmType: "emscripten",
|
|
89
|
-
supportsFilesystem: false,
|
|
90
|
-
supportsStreaming: false,
|
|
91
|
-
performanceTier: 3
|
|
92
|
-
};
|
|
103
|
+
return { environment: "cloudflare", ...environmentProfile("cloudflare") };
|
|
93
104
|
}
|
|
94
105
|
if (isNode()) {
|
|
95
106
|
return {
|
|
96
107
|
environment: "node-emscripten",
|
|
97
|
-
|
|
98
|
-
supportsFilesystem: true,
|
|
99
|
-
supportsStreaming: true,
|
|
100
|
-
performanceTier: 3
|
|
108
|
+
...environmentProfile("node-emscripten")
|
|
101
109
|
};
|
|
102
110
|
}
|
|
103
111
|
return {
|
|
104
112
|
environment: "browser",
|
|
105
|
-
|
|
106
|
-
supportsFilesystem: false,
|
|
107
|
-
supportsStreaming: true,
|
|
113
|
+
...environmentProfile("browser"),
|
|
108
114
|
performanceTier: 3
|
|
109
115
|
};
|
|
110
116
|
}
|
|
@@ -158,10 +164,9 @@ function getEnvironmentDescription(env) {
|
|
|
158
164
|
return "Unknown environment";
|
|
159
165
|
}
|
|
160
166
|
}
|
|
161
|
-
function canLoadWasmType(wasmType) {
|
|
162
|
-
const result = detectRuntime();
|
|
167
|
+
function canLoadWasmType(wasmType, env = detectRuntime().environment) {
|
|
163
168
|
if (wasmType === "wasi") {
|
|
164
|
-
return
|
|
169
|
+
return environmentProfile(env).wasmType === "wasi";
|
|
165
170
|
}
|
|
166
171
|
return true;
|
|
167
172
|
}
|
|
@@ -169,6 +174,7 @@ export {
|
|
|
169
174
|
canLoadWasmType,
|
|
170
175
|
checkNodeVersion,
|
|
171
176
|
detectRuntime,
|
|
177
|
+
environmentProfile,
|
|
172
178
|
getEnvironmentDescription,
|
|
173
179
|
isDeno,
|
|
174
180
|
supportsExnref
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type RuntimeEnvironment } from "./detector.js";
|
|
1
2
|
export type PlatformIO = {
|
|
2
3
|
readFile(path: string): Promise<Uint8Array>;
|
|
3
4
|
writeFile(path: string, data: Uint8Array): Promise<void>;
|
|
@@ -11,6 +12,13 @@ export type PlatformIO = {
|
|
|
11
12
|
}>;
|
|
12
13
|
readPartial?: (path: string, headerSize: number, footerSize: number) => Promise<Uint8Array>;
|
|
13
14
|
};
|
|
15
|
+
/**
|
|
16
|
+
* Pure environment -> PlatformIO selection (taglib-2b4): each environment's
|
|
17
|
+
* factory is chosen without sniffing, so the routing is testable with a
|
|
18
|
+
* synthetic environment. Factories are lazy — nothing touches the runtime
|
|
19
|
+
* until a method is called, so calling this under any runtime is safe.
|
|
20
|
+
*/
|
|
21
|
+
export declare function createPlatformIOFor(env: RuntimeEnvironment): PlatformIO;
|
|
14
22
|
export declare function getPlatformIO(): PlatformIO;
|
|
15
23
|
/** @internal */
|
|
16
24
|
export declare function _setPlatformIOForTesting(io: PlatformIO): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform-io.d.ts","sourceRoot":"","sources":["../../../src/runtime/platform-io.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"platform-io.d.ts","sourceRoot":"","sources":["../../../src/runtime/platform-io.ts"],"names":[],"mappings":"AACA,OAAO,EAAiB,KAAK,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAEvE,MAAM,MAAM,UAAU,GAAG;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5C,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACzD,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC9C,OAAO,CACL,IAAI,EAAE,MAAM,GACX,cAAc,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE,CAAC,CAAC;IAC3E,WAAW,CAAC,EAAE,CACZ,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,EAClB,UAAU,EAAE,MAAM,KACf,OAAO,CAAC,UAAU,CAAC,CAAC;CAC1B,CAAC;AA0LF;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,kBAAkB,GAAG,UAAU,CAgBvE;AAED,wBAAgB,aAAa,IAAI,UAAU,CAM1C;AAED,gBAAgB;AAChB,wBAAgB,wBAAwB,CAAC,EAAE,EAAE,UAAU,GAAG,IAAI,CAE7D;AAED,gBAAgB;AAChB,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC"}
|
|
@@ -152,27 +152,26 @@ function createBunIO() {
|
|
|
152
152
|
}
|
|
153
153
|
};
|
|
154
154
|
}
|
|
155
|
-
function
|
|
156
|
-
|
|
157
|
-
const runtime = detectRuntime();
|
|
158
|
-
switch (runtime.environment) {
|
|
155
|
+
function createPlatformIOFor(env) {
|
|
156
|
+
switch (env) {
|
|
159
157
|
case "deno-wasi":
|
|
160
|
-
|
|
161
|
-
break;
|
|
158
|
+
return createDenoIO();
|
|
162
159
|
case "bun-wasi":
|
|
163
|
-
|
|
164
|
-
break;
|
|
160
|
+
return createBunIO();
|
|
165
161
|
case "node-wasi":
|
|
166
162
|
case "node-emscripten":
|
|
167
|
-
|
|
168
|
-
break;
|
|
163
|
+
return createNodeIO();
|
|
169
164
|
default:
|
|
170
165
|
throw new EnvironmentError(
|
|
171
|
-
|
|
166
|
+
env,
|
|
172
167
|
"does not support filesystem operations",
|
|
173
168
|
"filesystem access"
|
|
174
169
|
);
|
|
175
170
|
}
|
|
171
|
+
}
|
|
172
|
+
function getPlatformIO() {
|
|
173
|
+
if (platformIO) return platformIO;
|
|
174
|
+
platformIO = createPlatformIOFor(detectRuntime().environment);
|
|
176
175
|
return platformIO;
|
|
177
176
|
}
|
|
178
177
|
function _setPlatformIOForTesting(io) {
|
|
@@ -184,5 +183,6 @@ function _resetPlatformIO() {
|
|
|
184
183
|
export {
|
|
185
184
|
_resetPlatformIO,
|
|
186
185
|
_setPlatformIOForTesting,
|
|
186
|
+
createPlatformIOFor,
|
|
187
187
|
getPlatformIO
|
|
188
188
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type RuntimeDetectionResult } from "../detector.js";
|
|
2
2
|
import type { UnifiedLoaderOptions } from "./types.js";
|
|
3
3
|
export declare function selectWasmType(runtime: RuntimeDetectionResult, options: UnifiedLoaderOptions): "wasi" | "emscripten";
|
|
4
|
-
export declare function isWasiAvailable(): boolean;
|
|
5
|
-
export declare function getRecommendedConfig(): UnifiedLoaderOptions;
|
|
4
|
+
export declare function isWasiAvailable(runtime?: RuntimeDetectionResult): boolean;
|
|
5
|
+
export declare function getRecommendedConfig(runtime?: RuntimeDetectionResult): UnifiedLoaderOptions;
|
|
6
6
|
//# sourceMappingURL=module-selection.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"module-selection.d.ts","sourceRoot":"","sources":["../../../../src/runtime/unified-loader/module-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,oBAAoB,GAC5B,MAAM,GAAG,YAAY,CAqBvB;AAED,wBAAgB,eAAe,
|
|
1
|
+
{"version":3,"file":"module-selection.d.ts","sourceRoot":"","sources":["../../../../src/runtime/unified-loader/module-selection.ts"],"names":[],"mappings":"AAAA,OAAO,EAAiB,KAAK,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAC5E,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,wBAAgB,cAAc,CAC5B,OAAO,EAAE,sBAAsB,EAC/B,OAAO,EAAE,oBAAoB,GAC5B,MAAM,GAAG,YAAY,CAqBvB;AAED,wBAAgB,eAAe,CAC7B,OAAO,GAAE,sBAAwC,GAChD,OAAO,CAET;AAED,wBAAgB,oBAAoB,CAClC,OAAO,GAAE,sBAAwC,GAChD,oBAAoB,CAqBtB"}
|
|
@@ -14,19 +14,17 @@ function selectWasmType(runtime, options) {
|
|
|
14
14
|
}
|
|
15
15
|
return "emscripten";
|
|
16
16
|
}
|
|
17
|
-
function isWasiAvailable() {
|
|
18
|
-
const runtime = detectRuntime();
|
|
17
|
+
function isWasiAvailable(runtime = detectRuntime()) {
|
|
19
18
|
return runtime.wasmType === "wasi" && runtime.supportsFilesystem;
|
|
20
19
|
}
|
|
21
|
-
function getRecommendedConfig() {
|
|
22
|
-
const runtime = detectRuntime();
|
|
20
|
+
function getRecommendedConfig(runtime = detectRuntime()) {
|
|
23
21
|
if (runtime.environment === "browser") {
|
|
24
22
|
return {
|
|
25
23
|
forceWasmType: "emscripten",
|
|
26
24
|
disableOptimizations: false
|
|
27
25
|
};
|
|
28
26
|
}
|
|
29
|
-
if (runtime.supportsFilesystem) {
|
|
27
|
+
if (runtime.wasmType === "wasi" && runtime.supportsFilesystem) {
|
|
30
28
|
return {
|
|
31
29
|
forceWasmType: "wasi",
|
|
32
30
|
disableOptimizations: false
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio metrics and format detection over the WASI tag-data snapshot.
|
|
3
|
+
*
|
|
4
|
+
* Pure functions over (tagData, fileData): the WasiFileHandle wrappers add
|
|
5
|
+
* destruction guards and pass their fields. Extracted from file-handle.ts in
|
|
6
|
+
* the taglib-1dfc split.
|
|
7
|
+
*/
|
|
8
|
+
import type { AudioProperties } from "../../types.js";
|
|
9
|
+
/** The audio-properties block of the C++ snapshot, null when absent. */
|
|
10
|
+
export declare function getAudioProperties(tagData: Record<string, unknown> | null): AudioProperties | null;
|
|
11
|
+
/** OGG codec sniffing: "OpusHead" in the first page payload, else OGG. */
|
|
12
|
+
export declare function detectOggCodec(fileData: Uint8Array): string;
|
|
13
|
+
/**
|
|
14
|
+
* Format detection: container-based first (works for path and buffer modes),
|
|
15
|
+
* magic-byte fallback for buffers.
|
|
16
|
+
*/
|
|
17
|
+
export declare function getFormat(tagData: Record<string, unknown> | null, fileData: Uint8Array | null): string;
|
|
18
|
+
/** MP4 detection: containerFormat (path mode) or ftyp box (buffer mode). */
|
|
19
|
+
export declare function isMP4(tagData: Record<string, unknown> | null, fileData: Uint8Array | null): boolean;
|
|
20
|
+
//# sourceMappingURL=audio-properties.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-properties.d.ts","sourceRoot":"","sources":["../../../../src/runtime/wasi-adapter/audio-properties.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAEV,eAAe,EAEhB,MAAM,gBAAgB,CAAC;AAgBxB,wEAAwE;AACxE,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,GACtC,eAAe,GAAG,IAAI,CA4BxB;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,QAAQ,EAAE,UAAU,GAAG,MAAM,CAe3D;AAED;;;GAGG;AACH,wBAAgB,SAAS,CACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACvC,QAAQ,EAAE,UAAU,GAAG,IAAI,GAC1B,MAAM,CAuDR;AAED,4EAA4E;AAC5E,wBAAgB,KAAK,CACnB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EACvC,QAAQ,EAAE,UAAU,GAAG,IAAI,GAC1B,OAAO,CAYT"}
|