taglib-wasm 1.0.0 → 1.0.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/index.browser.d.ts +29 -0
- package/dist/index.browser.d.ts.map +1 -0
- package/dist/index.browser.js +2495 -0
- package/dist/simple.browser.d.ts +8 -0
- package/dist/simple.browser.d.ts.map +1 -0
- package/dist/simple.browser.js +2029 -0
- package/dist/src/runtime/module-loader-browser.d.ts +16 -0
- package/dist/src/runtime/module-loader-browser.d.ts.map +1 -0
- package/dist/src/runtime/module-loader-browser.js +37 -0
- package/dist/src/taglib/taglib-class.js +2 -2
- package/package.json +20 -5
|
@@ -0,0 +1,2029 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
3
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
4
|
+
var __esm = (fn, res) => function __init() {
|
|
5
|
+
return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
|
|
6
|
+
};
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
12
|
+
|
|
13
|
+
// src/types/audio-formats.ts
|
|
14
|
+
var init_audio_formats = __esm({
|
|
15
|
+
"src/types/audio-formats.ts"() {
|
|
16
|
+
"use strict";
|
|
17
|
+
}
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// src/types/tags.ts
|
|
21
|
+
var init_tags = __esm({
|
|
22
|
+
"src/types/tags.ts"() {
|
|
23
|
+
"use strict";
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// src/types/metadata-mappings.ts
|
|
28
|
+
var init_metadata_mappings = __esm({
|
|
29
|
+
"src/types/metadata-mappings.ts"() {
|
|
30
|
+
"use strict";
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
// src/types/pictures.ts
|
|
35
|
+
var PICTURE_TYPE_VALUES, PICTURE_TYPE_NAMES;
|
|
36
|
+
var init_pictures = __esm({
|
|
37
|
+
"src/types/pictures.ts"() {
|
|
38
|
+
"use strict";
|
|
39
|
+
PICTURE_TYPE_VALUES = {
|
|
40
|
+
Other: 0,
|
|
41
|
+
FileIcon: 1,
|
|
42
|
+
OtherFileIcon: 2,
|
|
43
|
+
FrontCover: 3,
|
|
44
|
+
BackCover: 4,
|
|
45
|
+
LeafletPage: 5,
|
|
46
|
+
Media: 6,
|
|
47
|
+
LeadArtist: 7,
|
|
48
|
+
Artist: 8,
|
|
49
|
+
Conductor: 9,
|
|
50
|
+
Band: 10,
|
|
51
|
+
Composer: 11,
|
|
52
|
+
Lyricist: 12,
|
|
53
|
+
RecordingLocation: 13,
|
|
54
|
+
DuringRecording: 14,
|
|
55
|
+
DuringPerformance: 15,
|
|
56
|
+
MovieScreenCapture: 16,
|
|
57
|
+
ColouredFish: 17,
|
|
58
|
+
Illustration: 18,
|
|
59
|
+
BandLogo: 19,
|
|
60
|
+
PublisherLogo: 20
|
|
61
|
+
};
|
|
62
|
+
PICTURE_TYPE_NAMES = {
|
|
63
|
+
0: "Other",
|
|
64
|
+
1: "FileIcon",
|
|
65
|
+
2: "OtherFileIcon",
|
|
66
|
+
3: "FrontCover",
|
|
67
|
+
4: "BackCover",
|
|
68
|
+
5: "LeafletPage",
|
|
69
|
+
6: "Media",
|
|
70
|
+
7: "LeadArtist",
|
|
71
|
+
8: "Artist",
|
|
72
|
+
9: "Conductor",
|
|
73
|
+
10: "Band",
|
|
74
|
+
11: "Composer",
|
|
75
|
+
12: "Lyricist",
|
|
76
|
+
13: "RecordingLocation",
|
|
77
|
+
14: "DuringRecording",
|
|
78
|
+
15: "DuringPerformance",
|
|
79
|
+
16: "MovieScreenCapture",
|
|
80
|
+
17: "ColouredFish",
|
|
81
|
+
18: "Illustration",
|
|
82
|
+
19: "BandLogo",
|
|
83
|
+
20: "PublisherLogo"
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
// src/types/config.ts
|
|
89
|
+
var init_config = __esm({
|
|
90
|
+
"src/types/config.ts"() {
|
|
91
|
+
"use strict";
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
// src/types/format-property-keys.ts
|
|
96
|
+
var init_format_property_keys = __esm({
|
|
97
|
+
"src/types/format-property-keys.ts"() {
|
|
98
|
+
"use strict";
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// src/types/index.ts
|
|
103
|
+
var init_types = __esm({
|
|
104
|
+
"src/types/index.ts"() {
|
|
105
|
+
"use strict";
|
|
106
|
+
init_audio_formats();
|
|
107
|
+
init_tags();
|
|
108
|
+
init_metadata_mappings();
|
|
109
|
+
init_pictures();
|
|
110
|
+
init_config();
|
|
111
|
+
init_format_property_keys();
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// src/types.ts
|
|
116
|
+
var init_types2 = __esm({
|
|
117
|
+
"src/types.ts"() {
|
|
118
|
+
"use strict";
|
|
119
|
+
init_types();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
// src/errors/base.ts
|
|
124
|
+
var SUPPORTED_FORMATS, TagLibError;
|
|
125
|
+
var init_base = __esm({
|
|
126
|
+
"src/errors/base.ts"() {
|
|
127
|
+
"use strict";
|
|
128
|
+
SUPPORTED_FORMATS = [
|
|
129
|
+
"MP3",
|
|
130
|
+
"MP4",
|
|
131
|
+
"M4A",
|
|
132
|
+
"FLAC",
|
|
133
|
+
"OGG",
|
|
134
|
+
"WAV"
|
|
135
|
+
];
|
|
136
|
+
TagLibError = class _TagLibError extends Error {
|
|
137
|
+
/**
|
|
138
|
+
* Creates a new TagLibError
|
|
139
|
+
* @param code - Error code for programmatic handling
|
|
140
|
+
* @param message - Human-readable error message
|
|
141
|
+
* @param details - Additional context about the error
|
|
142
|
+
*/
|
|
143
|
+
constructor(code, message, details) {
|
|
144
|
+
super(message);
|
|
145
|
+
this.code = code;
|
|
146
|
+
this.details = details;
|
|
147
|
+
this.name = "TagLibError";
|
|
148
|
+
Object.setPrototypeOf(this, _TagLibError.prototype);
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// src/errors/classes.ts
|
|
155
|
+
function createErrorMessage(prefix, details) {
|
|
156
|
+
return `${prefix}: ${details}`;
|
|
157
|
+
}
|
|
158
|
+
function formatFileSize(bytes) {
|
|
159
|
+
if (bytes < 1024) return `${bytes} bytes`;
|
|
160
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
161
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
162
|
+
}
|
|
163
|
+
var TagLibInitializationError, InvalidFormatError, UnsupportedFormatError, FileOperationError, MetadataError, MemoryError, EnvironmentError;
|
|
164
|
+
var init_classes = __esm({
|
|
165
|
+
"src/errors/classes.ts"() {
|
|
166
|
+
"use strict";
|
|
167
|
+
init_base();
|
|
168
|
+
TagLibInitializationError = class _TagLibInitializationError extends TagLibError {
|
|
169
|
+
/**
|
|
170
|
+
* Creates a new TagLibInitializationError
|
|
171
|
+
* @param message - Description of the initialization failure
|
|
172
|
+
* @param details - Additional context about the error
|
|
173
|
+
*/
|
|
174
|
+
constructor(message, details) {
|
|
175
|
+
super(
|
|
176
|
+
"INITIALIZATION",
|
|
177
|
+
createErrorMessage("Failed to initialize TagLib Wasm module", message),
|
|
178
|
+
details
|
|
179
|
+
);
|
|
180
|
+
this.name = "TagLibInitializationError";
|
|
181
|
+
Object.setPrototypeOf(this, _TagLibInitializationError.prototype);
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
InvalidFormatError = class _InvalidFormatError extends TagLibError {
|
|
185
|
+
/**
|
|
186
|
+
* Creates a new InvalidFormatError
|
|
187
|
+
* @param message - Description of the format error
|
|
188
|
+
* @param bufferSize - Size of the audio buffer in bytes
|
|
189
|
+
* @param details - Additional context about the error
|
|
190
|
+
*/
|
|
191
|
+
constructor(message, bufferSize, details) {
|
|
192
|
+
const errorDetails = [`Invalid audio file format: ${message}`];
|
|
193
|
+
if (bufferSize !== void 0) {
|
|
194
|
+
errorDetails.push(`Buffer size: ${formatFileSize(bufferSize)}`);
|
|
195
|
+
if (bufferSize < 1024) {
|
|
196
|
+
errorDetails.push(
|
|
197
|
+
"Audio files must be at least 1KB to contain valid headers."
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
super(
|
|
202
|
+
"INVALID_FORMAT",
|
|
203
|
+
errorDetails.join(". "),
|
|
204
|
+
{ ...details, bufferSize }
|
|
205
|
+
);
|
|
206
|
+
this.bufferSize = bufferSize;
|
|
207
|
+
this.name = "InvalidFormatError";
|
|
208
|
+
Object.setPrototypeOf(this, _InvalidFormatError.prototype);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
UnsupportedFormatError = class _UnsupportedFormatError extends TagLibError {
|
|
212
|
+
/**
|
|
213
|
+
* Creates a new UnsupportedFormatError
|
|
214
|
+
* @param format - The unsupported format that was encountered
|
|
215
|
+
* @param supportedFormats - List of formats that are supported
|
|
216
|
+
* @param details - Additional context about the error
|
|
217
|
+
*/
|
|
218
|
+
constructor(format, supportedFormats = SUPPORTED_FORMATS, details) {
|
|
219
|
+
super(
|
|
220
|
+
"UNSUPPORTED_FORMAT",
|
|
221
|
+
`Unsupported audio format: ${format}. Supported formats: ${supportedFormats.join(", ")}`,
|
|
222
|
+
{ ...details, format, supportedFormats }
|
|
223
|
+
);
|
|
224
|
+
this.format = format;
|
|
225
|
+
this.supportedFormats = supportedFormats;
|
|
226
|
+
this.name = "UnsupportedFormatError";
|
|
227
|
+
Object.setPrototypeOf(this, _UnsupportedFormatError.prototype);
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
FileOperationError = class _FileOperationError extends TagLibError {
|
|
231
|
+
/**
|
|
232
|
+
* Creates a new FileOperationError
|
|
233
|
+
* @param operation - The file operation that failed
|
|
234
|
+
* @param message - Description of the failure
|
|
235
|
+
* @param path - File path involved in the operation
|
|
236
|
+
* @param details - Additional context about the error
|
|
237
|
+
*/
|
|
238
|
+
constructor(operation, message, path, details) {
|
|
239
|
+
const errorDetails = [`Failed to ${operation} file`];
|
|
240
|
+
if (path) {
|
|
241
|
+
errorDetails.push(`Path: ${path}`);
|
|
242
|
+
}
|
|
243
|
+
errorDetails.push(message);
|
|
244
|
+
super(
|
|
245
|
+
"FILE_OPERATION",
|
|
246
|
+
errorDetails.join(". "),
|
|
247
|
+
{ ...details, operation, path }
|
|
248
|
+
);
|
|
249
|
+
this.operation = operation;
|
|
250
|
+
this.path = path;
|
|
251
|
+
this.name = "FileOperationError";
|
|
252
|
+
Object.setPrototypeOf(this, _FileOperationError.prototype);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
MetadataError = class _MetadataError extends TagLibError {
|
|
256
|
+
/**
|
|
257
|
+
* Creates a new MetadataError
|
|
258
|
+
* @param operation - The metadata operation that failed
|
|
259
|
+
* @param message - Description of the failure
|
|
260
|
+
* @param field - The metadata field involved
|
|
261
|
+
* @param details - Additional context about the error
|
|
262
|
+
*/
|
|
263
|
+
constructor(operation, message, field, details) {
|
|
264
|
+
const errorDetails = [`Failed to ${operation} metadata`];
|
|
265
|
+
if (field) {
|
|
266
|
+
errorDetails.push(`Field: ${field}`);
|
|
267
|
+
}
|
|
268
|
+
errorDetails.push(message);
|
|
269
|
+
super(
|
|
270
|
+
"METADATA",
|
|
271
|
+
errorDetails.join(". "),
|
|
272
|
+
{ ...details, operation, field }
|
|
273
|
+
);
|
|
274
|
+
this.operation = operation;
|
|
275
|
+
this.field = field;
|
|
276
|
+
this.name = "MetadataError";
|
|
277
|
+
Object.setPrototypeOf(this, _MetadataError.prototype);
|
|
278
|
+
}
|
|
279
|
+
};
|
|
280
|
+
MemoryError = class _MemoryError extends TagLibError {
|
|
281
|
+
/**
|
|
282
|
+
* Creates a new MemoryError
|
|
283
|
+
* @param message - Description of the memory failure
|
|
284
|
+
* @param details - Additional context about the error
|
|
285
|
+
*/
|
|
286
|
+
constructor(message, details) {
|
|
287
|
+
super(
|
|
288
|
+
"MEMORY",
|
|
289
|
+
createErrorMessage("Memory allocation failed", message),
|
|
290
|
+
details
|
|
291
|
+
);
|
|
292
|
+
this.name = "MemoryError";
|
|
293
|
+
Object.setPrototypeOf(this, _MemoryError.prototype);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
EnvironmentError = class _EnvironmentError extends TagLibError {
|
|
297
|
+
/**
|
|
298
|
+
* Creates a new EnvironmentError
|
|
299
|
+
* @param environment - The runtime environment name
|
|
300
|
+
* @param reason - Why the environment is incompatible
|
|
301
|
+
* @param requiredFeature - The feature that is missing
|
|
302
|
+
*/
|
|
303
|
+
constructor(environment, reason, requiredFeature) {
|
|
304
|
+
const message = requiredFeature ? `Environment '${environment}' ${reason}. Required feature: ${requiredFeature}.` : `Environment '${environment}' ${reason}.`;
|
|
305
|
+
super("ENVIRONMENT", message);
|
|
306
|
+
this.environment = environment;
|
|
307
|
+
this.reason = reason;
|
|
308
|
+
this.requiredFeature = requiredFeature;
|
|
309
|
+
this.name = "EnvironmentError";
|
|
310
|
+
Object.setPrototypeOf(this, _EnvironmentError.prototype);
|
|
311
|
+
}
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// src/errors/guards.ts
|
|
317
|
+
function isTagLibError(error) {
|
|
318
|
+
return error instanceof TagLibError;
|
|
319
|
+
}
|
|
320
|
+
function isInvalidFormatError(error) {
|
|
321
|
+
return error instanceof InvalidFormatError;
|
|
322
|
+
}
|
|
323
|
+
function isUnsupportedFormatError(error) {
|
|
324
|
+
return error instanceof UnsupportedFormatError;
|
|
325
|
+
}
|
|
326
|
+
function isFileOperationError(error) {
|
|
327
|
+
return error instanceof FileOperationError;
|
|
328
|
+
}
|
|
329
|
+
function isMetadataError(error) {
|
|
330
|
+
return error instanceof MetadataError;
|
|
331
|
+
}
|
|
332
|
+
function isMemoryError(error) {
|
|
333
|
+
return error instanceof MemoryError;
|
|
334
|
+
}
|
|
335
|
+
function isEnvironmentError(error) {
|
|
336
|
+
return error instanceof EnvironmentError;
|
|
337
|
+
}
|
|
338
|
+
var init_guards = __esm({
|
|
339
|
+
"src/errors/guards.ts"() {
|
|
340
|
+
"use strict";
|
|
341
|
+
init_base();
|
|
342
|
+
init_classes();
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
|
|
346
|
+
// src/errors/index.ts
|
|
347
|
+
var init_errors = __esm({
|
|
348
|
+
"src/errors/index.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
init_base();
|
|
351
|
+
init_classes();
|
|
352
|
+
init_guards();
|
|
353
|
+
}
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// src/errors.ts
|
|
357
|
+
var init_errors2 = __esm({
|
|
358
|
+
"src/errors.ts"() {
|
|
359
|
+
"use strict";
|
|
360
|
+
init_errors();
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
// browser-stub:platform-io-browser-stub
|
|
365
|
+
function getPlatformIO() {
|
|
366
|
+
throw new Error("Filesystem operations are not available in the browser.");
|
|
367
|
+
}
|
|
368
|
+
var init_platform_io_browser_stub = __esm({
|
|
369
|
+
"browser-stub:platform-io-browser-stub"() {
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
// src/utils/file.ts
|
|
374
|
+
async function readFileData(file) {
|
|
375
|
+
if (file instanceof Uint8Array) return file;
|
|
376
|
+
if (file instanceof ArrayBuffer) return new Uint8Array(file);
|
|
377
|
+
if (typeof File !== "undefined" && file instanceof File) {
|
|
378
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
379
|
+
}
|
|
380
|
+
if (typeof file === "string") {
|
|
381
|
+
try {
|
|
382
|
+
return await getPlatformIO().readFile(file);
|
|
383
|
+
} catch (error) {
|
|
384
|
+
if (error instanceof EnvironmentError) throw error;
|
|
385
|
+
throw new FileOperationError("read", error.message, file);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
const inputType = Object.prototype.toString.call(file);
|
|
389
|
+
throw new FileOperationError(
|
|
390
|
+
"read",
|
|
391
|
+
`Invalid file input type: ${inputType}. Expected string path, Uint8Array, ArrayBuffer, or File object.`
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
async function getFileSize(path) {
|
|
395
|
+
try {
|
|
396
|
+
const s = await getPlatformIO().stat(path);
|
|
397
|
+
return s.size;
|
|
398
|
+
} catch (error) {
|
|
399
|
+
if (error instanceof EnvironmentError) throw error;
|
|
400
|
+
throw new FileOperationError("stat", error.message, path);
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
async function readPartialFileData(path, headerSize, footerSize) {
|
|
404
|
+
const io = getPlatformIO();
|
|
405
|
+
if (!io.readPartial) {
|
|
406
|
+
throw new EnvironmentError(
|
|
407
|
+
"current runtime",
|
|
408
|
+
"does not support partial file reading",
|
|
409
|
+
"filesystem access with seek support"
|
|
410
|
+
);
|
|
411
|
+
}
|
|
412
|
+
try {
|
|
413
|
+
return await io.readPartial(path, headerSize, footerSize);
|
|
414
|
+
} catch (error) {
|
|
415
|
+
if (error instanceof EnvironmentError) throw error;
|
|
416
|
+
throw new FileOperationError("read", error.message, path);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
var init_file = __esm({
|
|
420
|
+
"src/utils/file.ts"() {
|
|
421
|
+
"use strict";
|
|
422
|
+
init_errors2();
|
|
423
|
+
init_platform_io_browser_stub();
|
|
424
|
+
}
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// src/utils/write.ts
|
|
428
|
+
async function writeFileData(path, data) {
|
|
429
|
+
try {
|
|
430
|
+
await getPlatformIO().writeFile(path, data);
|
|
431
|
+
} catch (error) {
|
|
432
|
+
if (error instanceof EnvironmentError) throw error;
|
|
433
|
+
throw new FileOperationError(
|
|
434
|
+
"write",
|
|
435
|
+
error.message,
|
|
436
|
+
path
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
var init_write = __esm({
|
|
441
|
+
"src/utils/write.ts"() {
|
|
442
|
+
"use strict";
|
|
443
|
+
init_errors2();
|
|
444
|
+
init_platform_io_browser_stub();
|
|
445
|
+
}
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
// src/constants/basic-properties.ts
|
|
449
|
+
var BASIC_PROPERTIES;
|
|
450
|
+
var init_basic_properties = __esm({
|
|
451
|
+
"src/constants/basic-properties.ts"() {
|
|
452
|
+
"use strict";
|
|
453
|
+
BASIC_PROPERTIES = {
|
|
454
|
+
title: {
|
|
455
|
+
key: "TITLE",
|
|
456
|
+
description: "The title of the track",
|
|
457
|
+
type: "string",
|
|
458
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
459
|
+
mappings: {
|
|
460
|
+
id3v2: { frame: "TIT2" },
|
|
461
|
+
vorbis: "TITLE",
|
|
462
|
+
mp4: "\xA9nam",
|
|
463
|
+
wav: "INAM"
|
|
464
|
+
}
|
|
465
|
+
},
|
|
466
|
+
artist: {
|
|
467
|
+
key: "ARTIST",
|
|
468
|
+
description: "The primary performer(s) of the track",
|
|
469
|
+
type: "string",
|
|
470
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
471
|
+
mappings: {
|
|
472
|
+
id3v2: { frame: "TPE1" },
|
|
473
|
+
vorbis: "ARTIST",
|
|
474
|
+
mp4: "\xA9ART",
|
|
475
|
+
wav: "IART"
|
|
476
|
+
}
|
|
477
|
+
},
|
|
478
|
+
album: {
|
|
479
|
+
key: "ALBUM",
|
|
480
|
+
description: "The album/collection name",
|
|
481
|
+
type: "string",
|
|
482
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
483
|
+
mappings: {
|
|
484
|
+
id3v2: { frame: "TALB" },
|
|
485
|
+
vorbis: "ALBUM",
|
|
486
|
+
mp4: "\xA9alb",
|
|
487
|
+
wav: "IPRD"
|
|
488
|
+
}
|
|
489
|
+
},
|
|
490
|
+
date: {
|
|
491
|
+
key: "DATE",
|
|
492
|
+
description: "The date of recording (typically year)",
|
|
493
|
+
type: "string",
|
|
494
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
495
|
+
mappings: {
|
|
496
|
+
id3v2: { frame: "TDRC" },
|
|
497
|
+
vorbis: "DATE",
|
|
498
|
+
mp4: "\xA9day",
|
|
499
|
+
wav: "ICRD"
|
|
500
|
+
}
|
|
501
|
+
},
|
|
502
|
+
trackNumber: {
|
|
503
|
+
key: "TRACKNUMBER",
|
|
504
|
+
description: "The track number within the album",
|
|
505
|
+
type: "string",
|
|
506
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
507
|
+
mappings: {
|
|
508
|
+
id3v2: { frame: "TRCK" },
|
|
509
|
+
vorbis: "TRACKNUMBER",
|
|
510
|
+
mp4: "trkn",
|
|
511
|
+
wav: "ITRK"
|
|
512
|
+
}
|
|
513
|
+
},
|
|
514
|
+
genre: {
|
|
515
|
+
key: "GENRE",
|
|
516
|
+
description: "The musical genre",
|
|
517
|
+
type: "string",
|
|
518
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
519
|
+
mappings: {
|
|
520
|
+
id3v2: { frame: "TCON" },
|
|
521
|
+
vorbis: "GENRE",
|
|
522
|
+
mp4: "\xA9gen",
|
|
523
|
+
wav: "IGNR"
|
|
524
|
+
}
|
|
525
|
+
},
|
|
526
|
+
comment: {
|
|
527
|
+
key: "COMMENT",
|
|
528
|
+
description: "Comments or notes about the track",
|
|
529
|
+
type: "string",
|
|
530
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis", "WAV"],
|
|
531
|
+
mappings: {
|
|
532
|
+
id3v2: { frame: "COMM" },
|
|
533
|
+
vorbis: "COMMENT",
|
|
534
|
+
mp4: "\xA9cmt",
|
|
535
|
+
wav: "ICMT"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
|
|
542
|
+
// src/constants/general-extended-properties.ts
|
|
543
|
+
var GENERAL_EXTENDED_PROPERTIES;
|
|
544
|
+
var init_general_extended_properties = __esm({
|
|
545
|
+
"src/constants/general-extended-properties.ts"() {
|
|
546
|
+
"use strict";
|
|
547
|
+
GENERAL_EXTENDED_PROPERTIES = {
|
|
548
|
+
albumArtist: {
|
|
549
|
+
key: "ALBUMARTIST",
|
|
550
|
+
description: "The album artist (band/orchestra/ensemble)",
|
|
551
|
+
type: "string",
|
|
552
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
553
|
+
mappings: {
|
|
554
|
+
id3v2: { frame: "TPE2" },
|
|
555
|
+
vorbis: "ALBUMARTIST",
|
|
556
|
+
mp4: "aART"
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
composer: {
|
|
560
|
+
key: "COMPOSER",
|
|
561
|
+
description: "The original composer(s) of the track",
|
|
562
|
+
type: "string",
|
|
563
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
564
|
+
mappings: {
|
|
565
|
+
id3v2: { frame: "TCOM" },
|
|
566
|
+
vorbis: "COMPOSER",
|
|
567
|
+
mp4: "\xA9wrt"
|
|
568
|
+
}
|
|
569
|
+
},
|
|
570
|
+
copyright: {
|
|
571
|
+
key: "COPYRIGHT",
|
|
572
|
+
description: "Copyright information",
|
|
573
|
+
type: "string",
|
|
574
|
+
supportedFormats: ["ID3v2", "Vorbis"],
|
|
575
|
+
mappings: {
|
|
576
|
+
vorbis: "COPYRIGHT"
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
encodedBy: {
|
|
580
|
+
key: "ENCODEDBY",
|
|
581
|
+
description: "The encoding software or person",
|
|
582
|
+
type: "string",
|
|
583
|
+
supportedFormats: ["ID3v2", "Vorbis"],
|
|
584
|
+
mappings: {
|
|
585
|
+
vorbis: "ENCODEDBY"
|
|
586
|
+
}
|
|
587
|
+
},
|
|
588
|
+
discNumber: {
|
|
589
|
+
key: "DISCNUMBER",
|
|
590
|
+
description: "The disc number for multi-disc sets",
|
|
591
|
+
type: "string",
|
|
592
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
593
|
+
mappings: {
|
|
594
|
+
id3v2: { frame: "TPOS" },
|
|
595
|
+
vorbis: "DISCNUMBER",
|
|
596
|
+
mp4: "disk"
|
|
597
|
+
}
|
|
598
|
+
},
|
|
599
|
+
bpm: {
|
|
600
|
+
key: "BPM",
|
|
601
|
+
description: "Beats per minute",
|
|
602
|
+
type: "string",
|
|
603
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
604
|
+
mappings: {
|
|
605
|
+
id3v2: { frame: "TBPM" },
|
|
606
|
+
vorbis: "BPM",
|
|
607
|
+
mp4: "tmpo"
|
|
608
|
+
}
|
|
609
|
+
},
|
|
610
|
+
totalTracks: {
|
|
611
|
+
key: "TRACKTOTAL",
|
|
612
|
+
description: "Total number of tracks on the album",
|
|
613
|
+
type: "string",
|
|
614
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
615
|
+
mappings: {
|
|
616
|
+
id3v2: { frame: "TRCK" },
|
|
617
|
+
vorbis: "TRACKTOTAL",
|
|
618
|
+
mp4: "trkn"
|
|
619
|
+
}
|
|
620
|
+
},
|
|
621
|
+
totalDiscs: {
|
|
622
|
+
key: "DISCTOTAL",
|
|
623
|
+
description: "Total number of discs in the set",
|
|
624
|
+
type: "string",
|
|
625
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
626
|
+
mappings: {
|
|
627
|
+
id3v2: { frame: "TPOS" },
|
|
628
|
+
vorbis: "DISCTOTAL",
|
|
629
|
+
mp4: "disk"
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
compilation: {
|
|
633
|
+
key: "COMPILATION",
|
|
634
|
+
description: "Whether the album is a compilation (various artists)",
|
|
635
|
+
type: "boolean",
|
|
636
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
637
|
+
mappings: {
|
|
638
|
+
id3v2: { frame: "TCMP" },
|
|
639
|
+
vorbis: "COMPILATION",
|
|
640
|
+
mp4: "cpil"
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
// Sorting Properties
|
|
644
|
+
titleSort: {
|
|
645
|
+
key: "TITLESORT",
|
|
646
|
+
description: "Sort name for title (for alphabetization)",
|
|
647
|
+
type: "string",
|
|
648
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
649
|
+
mappings: {
|
|
650
|
+
id3v2: { frame: "TSOT" },
|
|
651
|
+
vorbis: "TITLESORT",
|
|
652
|
+
mp4: "sonm"
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
artistSort: {
|
|
656
|
+
key: "ARTISTSORT",
|
|
657
|
+
description: "Sort name for artist (for alphabetization)",
|
|
658
|
+
type: "string",
|
|
659
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
660
|
+
mappings: {
|
|
661
|
+
id3v2: { frame: "TSOP" },
|
|
662
|
+
vorbis: "ARTISTSORT",
|
|
663
|
+
mp4: "soar"
|
|
664
|
+
}
|
|
665
|
+
},
|
|
666
|
+
albumSort: {
|
|
667
|
+
key: "ALBUMSORT",
|
|
668
|
+
description: "Sort name for album (for alphabetization)",
|
|
669
|
+
type: "string",
|
|
670
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
671
|
+
mappings: {
|
|
672
|
+
id3v2: { frame: "TSOA" },
|
|
673
|
+
vorbis: "ALBUMSORT",
|
|
674
|
+
mp4: "soal"
|
|
675
|
+
}
|
|
676
|
+
},
|
|
677
|
+
// Additional common properties
|
|
678
|
+
lyricist: {
|
|
679
|
+
key: "LYRICIST",
|
|
680
|
+
description: "The lyrics/text writer(s)",
|
|
681
|
+
type: "string",
|
|
682
|
+
supportedFormats: ["Vorbis"],
|
|
683
|
+
mappings: {
|
|
684
|
+
vorbis: "LYRICIST"
|
|
685
|
+
}
|
|
686
|
+
},
|
|
687
|
+
conductor: {
|
|
688
|
+
key: "CONDUCTOR",
|
|
689
|
+
description: "The conductor",
|
|
690
|
+
type: "string",
|
|
691
|
+
supportedFormats: ["Vorbis"],
|
|
692
|
+
mappings: {
|
|
693
|
+
vorbis: "CONDUCTOR"
|
|
694
|
+
}
|
|
695
|
+
},
|
|
696
|
+
remixedBy: {
|
|
697
|
+
key: "REMIXEDBY",
|
|
698
|
+
description: "Person who remixed the track",
|
|
699
|
+
type: "string",
|
|
700
|
+
supportedFormats: ["Vorbis"],
|
|
701
|
+
mappings: {
|
|
702
|
+
vorbis: "REMIXEDBY"
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
language: {
|
|
706
|
+
key: "LANGUAGE",
|
|
707
|
+
description: "Language of vocals/lyrics",
|
|
708
|
+
type: "string",
|
|
709
|
+
supportedFormats: ["Vorbis"],
|
|
710
|
+
mappings: {
|
|
711
|
+
vorbis: "LANGUAGE"
|
|
712
|
+
}
|
|
713
|
+
},
|
|
714
|
+
publisher: {
|
|
715
|
+
key: "PUBLISHER",
|
|
716
|
+
description: "The publisher",
|
|
717
|
+
type: "string",
|
|
718
|
+
supportedFormats: ["Vorbis"],
|
|
719
|
+
mappings: {
|
|
720
|
+
vorbis: "PUBLISHER"
|
|
721
|
+
}
|
|
722
|
+
},
|
|
723
|
+
mood: {
|
|
724
|
+
key: "MOOD",
|
|
725
|
+
description: "The mood/atmosphere of the track",
|
|
726
|
+
type: "string",
|
|
727
|
+
supportedFormats: ["Vorbis"],
|
|
728
|
+
mappings: {
|
|
729
|
+
vorbis: "MOOD"
|
|
730
|
+
}
|
|
731
|
+
},
|
|
732
|
+
media: {
|
|
733
|
+
key: "MEDIA",
|
|
734
|
+
description: "Media type (CD, vinyl, etc.)",
|
|
735
|
+
type: "string",
|
|
736
|
+
supportedFormats: ["Vorbis"],
|
|
737
|
+
mappings: {
|
|
738
|
+
vorbis: "MEDIA"
|
|
739
|
+
}
|
|
740
|
+
},
|
|
741
|
+
grouping: {
|
|
742
|
+
key: "GROUPING",
|
|
743
|
+
description: "Content group/work",
|
|
744
|
+
type: "string",
|
|
745
|
+
supportedFormats: ["Vorbis"],
|
|
746
|
+
mappings: {
|
|
747
|
+
vorbis: "GROUPING"
|
|
748
|
+
}
|
|
749
|
+
},
|
|
750
|
+
work: {
|
|
751
|
+
key: "WORK",
|
|
752
|
+
description: "Work name",
|
|
753
|
+
type: "string",
|
|
754
|
+
supportedFormats: ["Vorbis"],
|
|
755
|
+
mappings: {
|
|
756
|
+
vorbis: "WORK"
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
lyrics: {
|
|
760
|
+
key: "LYRICS",
|
|
761
|
+
description: "Lyrics content",
|
|
762
|
+
type: "string",
|
|
763
|
+
supportedFormats: ["Vorbis"],
|
|
764
|
+
mappings: {
|
|
765
|
+
vorbis: "LYRICS"
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
isrc: {
|
|
769
|
+
key: "ISRC",
|
|
770
|
+
description: "International Standard Recording Code",
|
|
771
|
+
type: "string",
|
|
772
|
+
supportedFormats: ["Vorbis"],
|
|
773
|
+
mappings: {
|
|
774
|
+
vorbis: "ISRC"
|
|
775
|
+
}
|
|
776
|
+
},
|
|
777
|
+
catalogNumber: {
|
|
778
|
+
key: "CATALOGNUMBER",
|
|
779
|
+
description: "Catalog number",
|
|
780
|
+
type: "string",
|
|
781
|
+
supportedFormats: ["Vorbis"],
|
|
782
|
+
mappings: {
|
|
783
|
+
vorbis: "CATALOGNUMBER"
|
|
784
|
+
}
|
|
785
|
+
},
|
|
786
|
+
barcode: {
|
|
787
|
+
key: "BARCODE",
|
|
788
|
+
description: "Barcode (EAN/UPC)",
|
|
789
|
+
type: "string",
|
|
790
|
+
supportedFormats: ["Vorbis"],
|
|
791
|
+
mappings: {
|
|
792
|
+
vorbis: "BARCODE"
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
};
|
|
796
|
+
}
|
|
797
|
+
});
|
|
798
|
+
|
|
799
|
+
// src/constants/specialized-properties.ts
|
|
800
|
+
var SPECIALIZED_PROPERTIES;
|
|
801
|
+
var init_specialized_properties = __esm({
|
|
802
|
+
"src/constants/specialized-properties.ts"() {
|
|
803
|
+
"use strict";
|
|
804
|
+
SPECIALIZED_PROPERTIES = {
|
|
805
|
+
// MusicBrainz Identifiers
|
|
806
|
+
musicbrainzArtistId: {
|
|
807
|
+
key: "MUSICBRAINZ_ARTISTID",
|
|
808
|
+
description: "MusicBrainz Artist ID (UUID)",
|
|
809
|
+
type: "string",
|
|
810
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
811
|
+
mappings: {
|
|
812
|
+
id3v2: { frame: "TXXX", description: "MusicBrainz Artist Id" },
|
|
813
|
+
vorbis: "MUSICBRAINZ_ARTISTID",
|
|
814
|
+
mp4: "----:com.apple.iTunes:MusicBrainz Artist Id"
|
|
815
|
+
}
|
|
816
|
+
},
|
|
817
|
+
musicbrainzReleaseId: {
|
|
818
|
+
key: "MUSICBRAINZ_ALBUMID",
|
|
819
|
+
description: "MusicBrainz Release ID (UUID)",
|
|
820
|
+
type: "string",
|
|
821
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
822
|
+
mappings: {
|
|
823
|
+
id3v2: { frame: "TXXX", description: "MusicBrainz Album Id" },
|
|
824
|
+
vorbis: "MUSICBRAINZ_ALBUMID",
|
|
825
|
+
mp4: "----:com.apple.iTunes:MusicBrainz Album Id"
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
musicbrainzTrackId: {
|
|
829
|
+
key: "MUSICBRAINZ_TRACKID",
|
|
830
|
+
description: "MusicBrainz Recording ID (UUID)",
|
|
831
|
+
type: "string",
|
|
832
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
833
|
+
mappings: {
|
|
834
|
+
id3v2: { frame: "UFID", description: "http://musicbrainz.org" },
|
|
835
|
+
vorbis: "MUSICBRAINZ_TRACKID",
|
|
836
|
+
mp4: "----:com.apple.iTunes:MusicBrainz Track Id"
|
|
837
|
+
}
|
|
838
|
+
},
|
|
839
|
+
musicbrainzReleaseGroupId: {
|
|
840
|
+
key: "MUSICBRAINZ_RELEASEGROUPID",
|
|
841
|
+
description: "MusicBrainz Release Group ID (UUID)",
|
|
842
|
+
type: "string",
|
|
843
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
844
|
+
mappings: {
|
|
845
|
+
id3v2: { frame: "TXXX", description: "MusicBrainz Release Group Id" },
|
|
846
|
+
vorbis: "MUSICBRAINZ_RELEASEGROUPID",
|
|
847
|
+
mp4: "----:com.apple.iTunes:MusicBrainz Release Group Id"
|
|
848
|
+
}
|
|
849
|
+
},
|
|
850
|
+
// ReplayGain Properties
|
|
851
|
+
replayGainTrackGain: {
|
|
852
|
+
key: "REPLAYGAIN_TRACK_GAIN",
|
|
853
|
+
description: "ReplayGain track gain in dB (e.g., '-6.54 dB')",
|
|
854
|
+
type: "string",
|
|
855
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
856
|
+
mappings: {
|
|
857
|
+
id3v2: { frame: "TXXX", description: "ReplayGain_Track_Gain" },
|
|
858
|
+
vorbis: "REPLAYGAIN_TRACK_GAIN",
|
|
859
|
+
mp4: "----:com.apple.iTunes:replaygain_track_gain"
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
replayGainTrackPeak: {
|
|
863
|
+
key: "REPLAYGAIN_TRACK_PEAK",
|
|
864
|
+
description: "ReplayGain track peak value (0.0-1.0)",
|
|
865
|
+
type: "string",
|
|
866
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
867
|
+
mappings: {
|
|
868
|
+
id3v2: { frame: "TXXX", description: "ReplayGain_Track_Peak" },
|
|
869
|
+
vorbis: "REPLAYGAIN_TRACK_PEAK",
|
|
870
|
+
mp4: "----:com.apple.iTunes:replaygain_track_peak"
|
|
871
|
+
}
|
|
872
|
+
},
|
|
873
|
+
replayGainAlbumGain: {
|
|
874
|
+
key: "REPLAYGAIN_ALBUM_GAIN",
|
|
875
|
+
description: "ReplayGain album gain in dB",
|
|
876
|
+
type: "string",
|
|
877
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
878
|
+
mappings: {
|
|
879
|
+
id3v2: { frame: "TXXX", description: "ReplayGain_Album_Gain" },
|
|
880
|
+
vorbis: "REPLAYGAIN_ALBUM_GAIN",
|
|
881
|
+
mp4: "----:com.apple.iTunes:replaygain_album_gain"
|
|
882
|
+
}
|
|
883
|
+
},
|
|
884
|
+
replayGainAlbumPeak: {
|
|
885
|
+
key: "REPLAYGAIN_ALBUM_PEAK",
|
|
886
|
+
description: "ReplayGain album peak value (0.0-1.0)",
|
|
887
|
+
type: "string",
|
|
888
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
889
|
+
mappings: {
|
|
890
|
+
id3v2: { frame: "TXXX", description: "ReplayGain_Album_Peak" },
|
|
891
|
+
vorbis: "REPLAYGAIN_ALBUM_PEAK",
|
|
892
|
+
mp4: "----:com.apple.iTunes:replaygain_album_peak"
|
|
893
|
+
}
|
|
894
|
+
},
|
|
895
|
+
// AcoustID Properties
|
|
896
|
+
acoustidFingerprint: {
|
|
897
|
+
key: "ACOUSTID_FINGERPRINT",
|
|
898
|
+
description: "AcoustID fingerprint (Chromaprint)",
|
|
899
|
+
type: "string",
|
|
900
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
901
|
+
mappings: {
|
|
902
|
+
id3v2: { frame: "TXXX", description: "Acoustid Fingerprint" },
|
|
903
|
+
vorbis: "ACOUSTID_FINGERPRINT",
|
|
904
|
+
mp4: "----:com.apple.iTunes:Acoustid Fingerprint"
|
|
905
|
+
}
|
|
906
|
+
},
|
|
907
|
+
acoustidId: {
|
|
908
|
+
key: "ACOUSTID_ID",
|
|
909
|
+
description: "AcoustID UUID",
|
|
910
|
+
type: "string",
|
|
911
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
912
|
+
mappings: {
|
|
913
|
+
id3v2: { frame: "TXXX", description: "Acoustid Id" },
|
|
914
|
+
vorbis: "ACOUSTID_ID",
|
|
915
|
+
mp4: "----:com.apple.iTunes:Acoustid Id"
|
|
916
|
+
}
|
|
917
|
+
},
|
|
918
|
+
// Apple Sound Check
|
|
919
|
+
appleSoundCheck: {
|
|
920
|
+
key: "ITUNNORM",
|
|
921
|
+
description: "Apple Sound Check normalization data",
|
|
922
|
+
type: "string",
|
|
923
|
+
supportedFormats: ["ID3v2", "MP4", "Vorbis"],
|
|
924
|
+
mappings: {
|
|
925
|
+
id3v2: { frame: "TXXX", description: "iTunNORM" },
|
|
926
|
+
vorbis: "ITUNNORM",
|
|
927
|
+
mp4: "----:com.apple.iTunes:iTunNORM"
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
// src/constants/properties.ts
|
|
935
|
+
function toTagLibKey(key) {
|
|
936
|
+
return _toTagLib[key] ?? key;
|
|
937
|
+
}
|
|
938
|
+
function fromTagLibKey(key) {
|
|
939
|
+
return _fromTagLib[key] ?? key;
|
|
940
|
+
}
|
|
941
|
+
function remapKeysFromTagLib(obj) {
|
|
942
|
+
const result = {};
|
|
943
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
944
|
+
result[fromTagLibKey(key)] = value;
|
|
945
|
+
}
|
|
946
|
+
return result;
|
|
947
|
+
}
|
|
948
|
+
var PROPERTIES, _toTagLib, _fromTagLib;
|
|
949
|
+
var init_properties = __esm({
|
|
950
|
+
"src/constants/properties.ts"() {
|
|
951
|
+
"use strict";
|
|
952
|
+
init_basic_properties();
|
|
953
|
+
init_general_extended_properties();
|
|
954
|
+
init_specialized_properties();
|
|
955
|
+
PROPERTIES = {
|
|
956
|
+
...BASIC_PROPERTIES,
|
|
957
|
+
...GENERAL_EXTENDED_PROPERTIES,
|
|
958
|
+
...SPECIALIZED_PROPERTIES
|
|
959
|
+
};
|
|
960
|
+
_toTagLib = {};
|
|
961
|
+
_fromTagLib = {};
|
|
962
|
+
for (const [camelKey, meta] of Object.entries(PROPERTIES)) {
|
|
963
|
+
const wireKey = meta.key;
|
|
964
|
+
_toTagLib[camelKey] = wireKey;
|
|
965
|
+
_fromTagLib[wireKey] = camelKey;
|
|
966
|
+
}
|
|
967
|
+
_toTagLib["year"] = "DATE";
|
|
968
|
+
_toTagLib["track"] = "TRACKNUMBER";
|
|
969
|
+
_fromTagLib["disc"] = "discNumber";
|
|
970
|
+
}
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
// src/taglib/audio-file-base.ts
|
|
974
|
+
var BaseAudioFileImpl;
|
|
975
|
+
var init_audio_file_base = __esm({
|
|
976
|
+
"src/taglib/audio-file-base.ts"() {
|
|
977
|
+
"use strict";
|
|
978
|
+
init_properties();
|
|
979
|
+
init_errors2();
|
|
980
|
+
BaseAudioFileImpl = class {
|
|
981
|
+
constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
|
|
982
|
+
this.module = module;
|
|
983
|
+
__publicField(this, "fileHandle");
|
|
984
|
+
__publicField(this, "cachedAudioProperties", null);
|
|
985
|
+
__publicField(this, "sourcePath");
|
|
986
|
+
__publicField(this, "originalSource");
|
|
987
|
+
__publicField(this, "isPartiallyLoaded", false);
|
|
988
|
+
__publicField(this, "partialLoadOptions");
|
|
989
|
+
this.fileHandle = fileHandle;
|
|
990
|
+
this.sourcePath = sourcePath;
|
|
991
|
+
this.originalSource = originalSource;
|
|
992
|
+
this.isPartiallyLoaded = isPartiallyLoaded;
|
|
993
|
+
this.partialLoadOptions = partialLoadOptions;
|
|
994
|
+
}
|
|
995
|
+
get handle() {
|
|
996
|
+
if (!this.fileHandle) {
|
|
997
|
+
throw new MetadataError("read", "File handle has been disposed");
|
|
998
|
+
}
|
|
999
|
+
return this.fileHandle;
|
|
1000
|
+
}
|
|
1001
|
+
getFormat() {
|
|
1002
|
+
return this.handle.getFormat();
|
|
1003
|
+
}
|
|
1004
|
+
isFormat(format) {
|
|
1005
|
+
return this.getFormat() === format;
|
|
1006
|
+
}
|
|
1007
|
+
tag() {
|
|
1008
|
+
const tagWrapper = this.handle.getTag();
|
|
1009
|
+
if (!tagWrapper) {
|
|
1010
|
+
throw new MetadataError(
|
|
1011
|
+
"read",
|
|
1012
|
+
"Tag may be corrupted or format not fully supported"
|
|
1013
|
+
);
|
|
1014
|
+
}
|
|
1015
|
+
const tag = {
|
|
1016
|
+
get title() {
|
|
1017
|
+
return tagWrapper.title();
|
|
1018
|
+
},
|
|
1019
|
+
get artist() {
|
|
1020
|
+
return tagWrapper.artist();
|
|
1021
|
+
},
|
|
1022
|
+
get album() {
|
|
1023
|
+
return tagWrapper.album();
|
|
1024
|
+
},
|
|
1025
|
+
get comment() {
|
|
1026
|
+
return tagWrapper.comment();
|
|
1027
|
+
},
|
|
1028
|
+
get genre() {
|
|
1029
|
+
return tagWrapper.genre();
|
|
1030
|
+
},
|
|
1031
|
+
get year() {
|
|
1032
|
+
return tagWrapper.year();
|
|
1033
|
+
},
|
|
1034
|
+
get track() {
|
|
1035
|
+
return tagWrapper.track();
|
|
1036
|
+
},
|
|
1037
|
+
setTitle: (value) => {
|
|
1038
|
+
tagWrapper.setTitle(value);
|
|
1039
|
+
return tag;
|
|
1040
|
+
},
|
|
1041
|
+
setArtist: (value) => {
|
|
1042
|
+
tagWrapper.setArtist(value);
|
|
1043
|
+
return tag;
|
|
1044
|
+
},
|
|
1045
|
+
setAlbum: (value) => {
|
|
1046
|
+
tagWrapper.setAlbum(value);
|
|
1047
|
+
return tag;
|
|
1048
|
+
},
|
|
1049
|
+
setComment: (value) => {
|
|
1050
|
+
tagWrapper.setComment(value);
|
|
1051
|
+
return tag;
|
|
1052
|
+
},
|
|
1053
|
+
setGenre: (value) => {
|
|
1054
|
+
tagWrapper.setGenre(value);
|
|
1055
|
+
return tag;
|
|
1056
|
+
},
|
|
1057
|
+
setYear: (value) => {
|
|
1058
|
+
tagWrapper.setYear(value);
|
|
1059
|
+
return tag;
|
|
1060
|
+
},
|
|
1061
|
+
setTrack: (value) => {
|
|
1062
|
+
tagWrapper.setTrack(value);
|
|
1063
|
+
return tag;
|
|
1064
|
+
}
|
|
1065
|
+
};
|
|
1066
|
+
return tag;
|
|
1067
|
+
}
|
|
1068
|
+
audioProperties() {
|
|
1069
|
+
if (!this.cachedAudioProperties) {
|
|
1070
|
+
const propsWrapper = this.handle.getAudioProperties();
|
|
1071
|
+
if (!propsWrapper) {
|
|
1072
|
+
return void 0;
|
|
1073
|
+
}
|
|
1074
|
+
this.cachedAudioProperties = {
|
|
1075
|
+
duration: propsWrapper.lengthInSeconds(),
|
|
1076
|
+
bitrate: propsWrapper.bitrate(),
|
|
1077
|
+
sampleRate: propsWrapper.sampleRate(),
|
|
1078
|
+
channels: propsWrapper.channels(),
|
|
1079
|
+
bitsPerSample: propsWrapper.bitsPerSample(),
|
|
1080
|
+
codec: propsWrapper.codec() || "unknown",
|
|
1081
|
+
containerFormat: propsWrapper.containerFormat() || "unknown",
|
|
1082
|
+
isLossless: propsWrapper.isLossless()
|
|
1083
|
+
};
|
|
1084
|
+
}
|
|
1085
|
+
return this.cachedAudioProperties;
|
|
1086
|
+
}
|
|
1087
|
+
properties() {
|
|
1088
|
+
return remapKeysFromTagLib(this.handle.getProperties());
|
|
1089
|
+
}
|
|
1090
|
+
setProperties(properties) {
|
|
1091
|
+
const translated = {};
|
|
1092
|
+
for (const [key, values] of Object.entries(properties)) {
|
|
1093
|
+
if (values !== void 0) translated[toTagLibKey(key)] = values;
|
|
1094
|
+
}
|
|
1095
|
+
this.handle.setProperties(translated);
|
|
1096
|
+
}
|
|
1097
|
+
getProperty(key) {
|
|
1098
|
+
const value = this.handle.getProperty(toTagLibKey(key));
|
|
1099
|
+
return value === "" ? void 0 : value;
|
|
1100
|
+
}
|
|
1101
|
+
setProperty(key, value) {
|
|
1102
|
+
this.handle.setProperty(toTagLibKey(key), value);
|
|
1103
|
+
}
|
|
1104
|
+
isMP4() {
|
|
1105
|
+
return this.handle.isMP4();
|
|
1106
|
+
}
|
|
1107
|
+
getMP4Item(key) {
|
|
1108
|
+
if (!this.isMP4()) {
|
|
1109
|
+
throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
|
|
1110
|
+
}
|
|
1111
|
+
const value = this.handle.getMP4Item(key);
|
|
1112
|
+
return value === "" ? void 0 : value;
|
|
1113
|
+
}
|
|
1114
|
+
setMP4Item(key, value) {
|
|
1115
|
+
if (!this.isMP4()) {
|
|
1116
|
+
throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
|
|
1117
|
+
}
|
|
1118
|
+
this.handle.setMP4Item(key, value);
|
|
1119
|
+
}
|
|
1120
|
+
removeMP4Item(key) {
|
|
1121
|
+
if (!this.isMP4()) {
|
|
1122
|
+
throw new UnsupportedFormatError(this.getFormat(), ["MP4", "M4A"]);
|
|
1123
|
+
}
|
|
1124
|
+
this.handle.removeMP4Item(key);
|
|
1125
|
+
}
|
|
1126
|
+
isValid() {
|
|
1127
|
+
return this.handle.isValid();
|
|
1128
|
+
}
|
|
1129
|
+
dispose() {
|
|
1130
|
+
if (this.fileHandle) {
|
|
1131
|
+
this.fileHandle.destroy();
|
|
1132
|
+
this.fileHandle = null;
|
|
1133
|
+
this.cachedAudioProperties = null;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
[Symbol.dispose]() {
|
|
1137
|
+
this.dispose();
|
|
1138
|
+
}
|
|
1139
|
+
};
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
// src/taglib/audio-file-impl.ts
|
|
1144
|
+
var AudioFileImpl;
|
|
1145
|
+
var init_audio_file_impl = __esm({
|
|
1146
|
+
"src/taglib/audio-file-impl.ts"() {
|
|
1147
|
+
"use strict";
|
|
1148
|
+
init_types2();
|
|
1149
|
+
init_errors2();
|
|
1150
|
+
init_file();
|
|
1151
|
+
init_write();
|
|
1152
|
+
init_audio_file_base();
|
|
1153
|
+
AudioFileImpl = class extends BaseAudioFileImpl {
|
|
1154
|
+
constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
|
|
1155
|
+
super(
|
|
1156
|
+
module,
|
|
1157
|
+
fileHandle,
|
|
1158
|
+
sourcePath,
|
|
1159
|
+
originalSource,
|
|
1160
|
+
isPartiallyLoaded,
|
|
1161
|
+
partialLoadOptions
|
|
1162
|
+
);
|
|
1163
|
+
}
|
|
1164
|
+
save() {
|
|
1165
|
+
if (this.isPartiallyLoaded && this.originalSource) {
|
|
1166
|
+
throw new FileOperationError(
|
|
1167
|
+
"save",
|
|
1168
|
+
"Cannot save partially loaded file directly. Use saveToFile() instead"
|
|
1169
|
+
);
|
|
1170
|
+
}
|
|
1171
|
+
this.cachedAudioProperties = null;
|
|
1172
|
+
return this.handle.save();
|
|
1173
|
+
}
|
|
1174
|
+
getFileBuffer() {
|
|
1175
|
+
const buffer = this.handle.getBuffer();
|
|
1176
|
+
return buffer ?? new Uint8Array(0);
|
|
1177
|
+
}
|
|
1178
|
+
async saveToFile(path) {
|
|
1179
|
+
const targetPath = path ?? this.sourcePath;
|
|
1180
|
+
if (!targetPath) {
|
|
1181
|
+
throw new FileOperationError(
|
|
1182
|
+
"save",
|
|
1183
|
+
"No file path available. Provide a path or open the file from a path"
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
if (this.isPartiallyLoaded && this.originalSource) {
|
|
1187
|
+
const fullData = await readFileData(this.originalSource);
|
|
1188
|
+
const fullFileHandle = this.module.createFileHandle();
|
|
1189
|
+
try {
|
|
1190
|
+
const success = fullFileHandle.loadFromBuffer(fullData);
|
|
1191
|
+
if (!success) {
|
|
1192
|
+
throw new InvalidFormatError(
|
|
1193
|
+
"Failed to load full audio file for saving",
|
|
1194
|
+
fullData.byteLength
|
|
1195
|
+
);
|
|
1196
|
+
}
|
|
1197
|
+
const partialTag = this.handle.getTag();
|
|
1198
|
+
const fullTag = fullFileHandle.getTag();
|
|
1199
|
+
if (partialTag && fullTag) {
|
|
1200
|
+
fullTag.setTitle(partialTag.title());
|
|
1201
|
+
fullTag.setArtist(partialTag.artist());
|
|
1202
|
+
fullTag.setAlbum(partialTag.album());
|
|
1203
|
+
fullTag.setComment(partialTag.comment());
|
|
1204
|
+
fullTag.setGenre(partialTag.genre());
|
|
1205
|
+
fullTag.setYear(partialTag.year());
|
|
1206
|
+
fullTag.setTrack(partialTag.track());
|
|
1207
|
+
}
|
|
1208
|
+
fullFileHandle.setProperties(this.handle.getProperties());
|
|
1209
|
+
fullFileHandle.setPictures(this.handle.getPictures());
|
|
1210
|
+
if (!fullFileHandle.save()) {
|
|
1211
|
+
throw new FileOperationError(
|
|
1212
|
+
"save",
|
|
1213
|
+
"Failed to save changes to full file"
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
const buffer = fullFileHandle.getBuffer();
|
|
1217
|
+
await writeFileData(targetPath, buffer);
|
|
1218
|
+
} finally {
|
|
1219
|
+
fullFileHandle.destroy();
|
|
1220
|
+
}
|
|
1221
|
+
this.isPartiallyLoaded = false;
|
|
1222
|
+
this.originalSource = void 0;
|
|
1223
|
+
} else {
|
|
1224
|
+
if (!this.save()) {
|
|
1225
|
+
throw new FileOperationError(
|
|
1226
|
+
"save",
|
|
1227
|
+
"Failed to save changes to in-memory buffer"
|
|
1228
|
+
);
|
|
1229
|
+
}
|
|
1230
|
+
await writeFileData(targetPath, this.getFileBuffer());
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
getPictures() {
|
|
1234
|
+
const picturesArray = this.handle.getPictures();
|
|
1235
|
+
return picturesArray.map((pic) => ({
|
|
1236
|
+
mimeType: pic.mimeType,
|
|
1237
|
+
data: pic.data,
|
|
1238
|
+
type: PICTURE_TYPE_NAMES[pic.type] ?? "Other",
|
|
1239
|
+
description: pic.description
|
|
1240
|
+
}));
|
|
1241
|
+
}
|
|
1242
|
+
setPictures(pictures) {
|
|
1243
|
+
this.handle.setPictures(pictures.map((pic) => ({
|
|
1244
|
+
mimeType: pic.mimeType,
|
|
1245
|
+
data: pic.data,
|
|
1246
|
+
type: PICTURE_TYPE_VALUES[pic.type] ?? 0,
|
|
1247
|
+
description: pic.description ?? ""
|
|
1248
|
+
})));
|
|
1249
|
+
}
|
|
1250
|
+
addPicture(picture) {
|
|
1251
|
+
this.handle.addPicture({
|
|
1252
|
+
mimeType: picture.mimeType,
|
|
1253
|
+
data: picture.data,
|
|
1254
|
+
type: PICTURE_TYPE_VALUES[picture.type] ?? 0,
|
|
1255
|
+
description: picture.description ?? ""
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
removePictures() {
|
|
1259
|
+
this.handle.removePictures();
|
|
1260
|
+
}
|
|
1261
|
+
getRatings() {
|
|
1262
|
+
return this.handle.getRatings().map(
|
|
1263
|
+
(r) => ({
|
|
1264
|
+
rating: r.rating,
|
|
1265
|
+
email: r.email || void 0,
|
|
1266
|
+
counter: r.counter || void 0
|
|
1267
|
+
})
|
|
1268
|
+
);
|
|
1269
|
+
}
|
|
1270
|
+
setRatings(ratings) {
|
|
1271
|
+
this.handle.setRatings(ratings.map((r) => ({
|
|
1272
|
+
rating: r.rating,
|
|
1273
|
+
email: r.email ?? "",
|
|
1274
|
+
counter: r.counter ?? 0
|
|
1275
|
+
})));
|
|
1276
|
+
}
|
|
1277
|
+
getRating() {
|
|
1278
|
+
const ratings = this.getRatings();
|
|
1279
|
+
return ratings.length > 0 ? ratings[0].rating : void 0;
|
|
1280
|
+
}
|
|
1281
|
+
setRating(rating, email) {
|
|
1282
|
+
this.setRatings([{ rating, email, counter: 0 }]);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
}
|
|
1286
|
+
});
|
|
1287
|
+
|
|
1288
|
+
// src/taglib/load-audio-data.ts
|
|
1289
|
+
async function loadAudioData(input, opts) {
|
|
1290
|
+
if (opts.partial && typeof File !== "undefined" && input instanceof File) {
|
|
1291
|
+
const headerSize = Math.min(opts.maxHeaderSize, input.size);
|
|
1292
|
+
const footerSize = Math.min(opts.maxFooterSize, input.size);
|
|
1293
|
+
if (input.size <= headerSize + footerSize) {
|
|
1294
|
+
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
1295
|
+
}
|
|
1296
|
+
const header = await input.slice(0, headerSize).arrayBuffer();
|
|
1297
|
+
const footerStart = Math.max(0, input.size - footerSize);
|
|
1298
|
+
const footer = await input.slice(footerStart).arrayBuffer();
|
|
1299
|
+
const combined = new Uint8Array(header.byteLength + footer.byteLength);
|
|
1300
|
+
combined.set(new Uint8Array(header), 0);
|
|
1301
|
+
combined.set(new Uint8Array(footer), header.byteLength);
|
|
1302
|
+
return { data: combined, isPartiallyLoaded: true };
|
|
1303
|
+
}
|
|
1304
|
+
if (opts.partial && typeof input === "string") {
|
|
1305
|
+
const fileSize = await getFileSize(input);
|
|
1306
|
+
if (fileSize > opts.maxHeaderSize + opts.maxFooterSize) {
|
|
1307
|
+
const data = await readPartialFileData(
|
|
1308
|
+
input,
|
|
1309
|
+
opts.maxHeaderSize,
|
|
1310
|
+
opts.maxFooterSize
|
|
1311
|
+
);
|
|
1312
|
+
return { data, isPartiallyLoaded: true };
|
|
1313
|
+
}
|
|
1314
|
+
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
1315
|
+
}
|
|
1316
|
+
return { data: await readFileData(input), isPartiallyLoaded: false };
|
|
1317
|
+
}
|
|
1318
|
+
var init_load_audio_data = __esm({
|
|
1319
|
+
"src/taglib/load-audio-data.ts"() {
|
|
1320
|
+
"use strict";
|
|
1321
|
+
init_file();
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
// src/utils/tag-mapping.ts
|
|
1326
|
+
function parseNumeric(value) {
|
|
1327
|
+
const parsed = Number.parseInt(value, 10);
|
|
1328
|
+
return Number.isNaN(parsed) ? void 0 : parsed;
|
|
1329
|
+
}
|
|
1330
|
+
function mapPropertiesToExtendedTag(props) {
|
|
1331
|
+
const tag = {};
|
|
1332
|
+
for (const [propKey, tagField] of Object.entries(BASIC_PROPERTY_KEYS)) {
|
|
1333
|
+
const values = props[propKey];
|
|
1334
|
+
if (!values || values.length === 0) continue;
|
|
1335
|
+
if (tagField === "year" || tagField === "track") {
|
|
1336
|
+
const num = parseNumeric(values[0]);
|
|
1337
|
+
if (num !== void 0) tag[tagField] = num;
|
|
1338
|
+
} else {
|
|
1339
|
+
tag[tagField] = values;
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
for (const [key, values] of Object.entries(props)) {
|
|
1343
|
+
if (BASIC_PROPERTY_KEYS[key]) continue;
|
|
1344
|
+
if (!values || values.length === 0) continue;
|
|
1345
|
+
const camelKey = fromTagLibKey(key);
|
|
1346
|
+
if (NUMERIC_FIELDS.has(camelKey)) {
|
|
1347
|
+
const num = parseNumeric(values[0]);
|
|
1348
|
+
if (num !== void 0) tag[camelKey] = num;
|
|
1349
|
+
} else if (camelKey === "compilation") {
|
|
1350
|
+
tag[camelKey] = values[0] === "1";
|
|
1351
|
+
} else {
|
|
1352
|
+
tag[camelKey] = values;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return tag;
|
|
1356
|
+
}
|
|
1357
|
+
function mergeTagUpdates(file, tags) {
|
|
1358
|
+
const currentProps = file.properties();
|
|
1359
|
+
const newProps = normalizeTagInput(tags);
|
|
1360
|
+
file.setProperties({ ...currentProps, ...newProps });
|
|
1361
|
+
}
|
|
1362
|
+
function normalizeTagInput(input) {
|
|
1363
|
+
const props = {};
|
|
1364
|
+
for (const field of [
|
|
1365
|
+
"title",
|
|
1366
|
+
"artist",
|
|
1367
|
+
"album",
|
|
1368
|
+
"comment",
|
|
1369
|
+
"genre"
|
|
1370
|
+
]) {
|
|
1371
|
+
const val = input[field];
|
|
1372
|
+
if (val === void 0) continue;
|
|
1373
|
+
props[field] = Array.isArray(val) ? val : [val];
|
|
1374
|
+
}
|
|
1375
|
+
if (input.year !== void 0) {
|
|
1376
|
+
props.date = [String(input.year)];
|
|
1377
|
+
}
|
|
1378
|
+
if (input.track !== void 0) {
|
|
1379
|
+
props.trackNumber = [String(input.track)];
|
|
1380
|
+
}
|
|
1381
|
+
for (const [field, val] of Object.entries(input)) {
|
|
1382
|
+
if (BASIC_FIELDS.has(field) || val === void 0) continue;
|
|
1383
|
+
if (field === "compilation") {
|
|
1384
|
+
props[field] = [val ? "1" : "0"];
|
|
1385
|
+
} else if (NUMERIC_FIELDS.has(field)) {
|
|
1386
|
+
props[field] = [String(val)];
|
|
1387
|
+
} else if (typeof val === "string") {
|
|
1388
|
+
props[field] = [val];
|
|
1389
|
+
} else if (Array.isArray(val)) {
|
|
1390
|
+
props[field] = val;
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
return props;
|
|
1394
|
+
}
|
|
1395
|
+
var BASIC_PROPERTY_KEYS, BASIC_FIELDS, NUMERIC_FIELDS;
|
|
1396
|
+
var init_tag_mapping = __esm({
|
|
1397
|
+
"src/utils/tag-mapping.ts"() {
|
|
1398
|
+
"use strict";
|
|
1399
|
+
init_properties();
|
|
1400
|
+
BASIC_PROPERTY_KEYS = {
|
|
1401
|
+
title: "title",
|
|
1402
|
+
artist: "artist",
|
|
1403
|
+
album: "album",
|
|
1404
|
+
comment: "comment",
|
|
1405
|
+
genre: "genre",
|
|
1406
|
+
date: "year",
|
|
1407
|
+
trackNumber: "track"
|
|
1408
|
+
};
|
|
1409
|
+
BASIC_FIELDS = /* @__PURE__ */ new Set([
|
|
1410
|
+
"title",
|
|
1411
|
+
"artist",
|
|
1412
|
+
"album",
|
|
1413
|
+
"comment",
|
|
1414
|
+
"genre",
|
|
1415
|
+
"year",
|
|
1416
|
+
"track"
|
|
1417
|
+
]);
|
|
1418
|
+
NUMERIC_FIELDS = /* @__PURE__ */ new Set([
|
|
1419
|
+
"year",
|
|
1420
|
+
"track",
|
|
1421
|
+
"discNumber",
|
|
1422
|
+
"totalTracks",
|
|
1423
|
+
"totalDiscs",
|
|
1424
|
+
"bpm"
|
|
1425
|
+
]);
|
|
1426
|
+
}
|
|
1427
|
+
});
|
|
1428
|
+
|
|
1429
|
+
// src/runtime/module-loader-browser.ts
|
|
1430
|
+
var module_loader_browser_exports = {};
|
|
1431
|
+
__export(module_loader_browser_exports, {
|
|
1432
|
+
loadTagLibModule: () => loadTagLibModule
|
|
1433
|
+
});
|
|
1434
|
+
async function loadTagLibModule(options) {
|
|
1435
|
+
let createTagLibModule;
|
|
1436
|
+
try {
|
|
1437
|
+
const module2 = await import("./taglib-wrapper.js");
|
|
1438
|
+
createTagLibModule = module2.default;
|
|
1439
|
+
} catch {
|
|
1440
|
+
try {
|
|
1441
|
+
const module2 = await import("./taglib-wrapper.js");
|
|
1442
|
+
createTagLibModule = module2.default;
|
|
1443
|
+
} catch {
|
|
1444
|
+
throw new TagLibInitializationError(
|
|
1445
|
+
"Could not load taglib-wrapper.js. Ensure it is co-located with the browser bundle."
|
|
1446
|
+
);
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
const moduleConfig = {};
|
|
1450
|
+
if (options?.wasmBinary) {
|
|
1451
|
+
moduleConfig.wasmBinary = options.wasmBinary;
|
|
1452
|
+
}
|
|
1453
|
+
if (options?.wasmUrl) {
|
|
1454
|
+
moduleConfig.locateFile = (path) => {
|
|
1455
|
+
if (path.endsWith(".wasm")) {
|
|
1456
|
+
return options.wasmUrl;
|
|
1457
|
+
}
|
|
1458
|
+
return path;
|
|
1459
|
+
};
|
|
1460
|
+
} else if (!options?.wasmBinary) {
|
|
1461
|
+
const wasmUrl = new URL("./taglib-web.wasm", import.meta.url);
|
|
1462
|
+
moduleConfig.locateFile = (path) => path.endsWith(".wasm") ? wasmUrl.href : path;
|
|
1463
|
+
}
|
|
1464
|
+
const module = await createTagLibModule(moduleConfig);
|
|
1465
|
+
return module;
|
|
1466
|
+
}
|
|
1467
|
+
var init_module_loader_browser = __esm({
|
|
1468
|
+
"src/runtime/module-loader-browser.ts"() {
|
|
1469
|
+
init_classes();
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
// src/taglib/taglib-class.ts
|
|
1474
|
+
async function createTagLib(module) {
|
|
1475
|
+
return new TagLib(module);
|
|
1476
|
+
}
|
|
1477
|
+
var TagLib;
|
|
1478
|
+
var init_taglib_class = __esm({
|
|
1479
|
+
"src/taglib/taglib-class.ts"() {
|
|
1480
|
+
"use strict";
|
|
1481
|
+
init_errors2();
|
|
1482
|
+
init_audio_file_impl();
|
|
1483
|
+
init_load_audio_data();
|
|
1484
|
+
init_tag_mapping();
|
|
1485
|
+
TagLib = class _TagLib {
|
|
1486
|
+
constructor(module) {
|
|
1487
|
+
__publicField(this, "module");
|
|
1488
|
+
this.module = module;
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Initialize the TagLib Wasm module and return a ready-to-use instance.
|
|
1492
|
+
* @param options - Wasm loading configuration (binary, URL, backend selection).
|
|
1493
|
+
* @returns A configured TagLib instance.
|
|
1494
|
+
* @throws {TagLibInitializationError} If the Wasm module fails to load.
|
|
1495
|
+
*/
|
|
1496
|
+
static async initialize(options) {
|
|
1497
|
+
const { loadTagLibModule: loadTagLibModule2 } = await Promise.resolve().then(() => (init_module_loader_browser(), module_loader_browser_exports));
|
|
1498
|
+
const module = await loadTagLibModule2(options);
|
|
1499
|
+
return new _TagLib(module);
|
|
1500
|
+
}
|
|
1501
|
+
/**
|
|
1502
|
+
* Open an audio file for reading and writing metadata.
|
|
1503
|
+
* @param input - File path, Uint8Array, ArrayBuffer, or File object.
|
|
1504
|
+
* @param options - Partial-loading options for large files.
|
|
1505
|
+
* @returns An AudioFile instance (use `using` for automatic cleanup).
|
|
1506
|
+
* @throws {TagLibInitializationError} If the module is not properly initialized.
|
|
1507
|
+
* @throws {InvalidFormatError} If the file is corrupted or unsupported.
|
|
1508
|
+
*/
|
|
1509
|
+
async open(input, options) {
|
|
1510
|
+
if (!this.module.createFileHandle) {
|
|
1511
|
+
throw new TagLibInitializationError(
|
|
1512
|
+
"TagLib module not properly initialized: createFileHandle not found. Make sure the module is fully loaded before calling open."
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
const sourcePath = typeof input === "string" ? input : void 0;
|
|
1516
|
+
const opts = {
|
|
1517
|
+
partial: false,
|
|
1518
|
+
maxHeaderSize: 1024 * 1024,
|
|
1519
|
+
maxFooterSize: 128 * 1024,
|
|
1520
|
+
...options
|
|
1521
|
+
};
|
|
1522
|
+
const { data: audioData, isPartiallyLoaded } = await loadAudioData(
|
|
1523
|
+
input,
|
|
1524
|
+
opts
|
|
1525
|
+
);
|
|
1526
|
+
const buffer = audioData.buffer.slice(
|
|
1527
|
+
audioData.byteOffset,
|
|
1528
|
+
audioData.byteOffset + audioData.byteLength
|
|
1529
|
+
);
|
|
1530
|
+
const uint8Array = new Uint8Array(buffer);
|
|
1531
|
+
const fileHandle = this.module.createFileHandle();
|
|
1532
|
+
try {
|
|
1533
|
+
const success = fileHandle.loadFromBuffer(uint8Array);
|
|
1534
|
+
if (!success) {
|
|
1535
|
+
throw new InvalidFormatError(
|
|
1536
|
+
"Failed to load audio file. File may be corrupted or in an unsupported format",
|
|
1537
|
+
buffer.byteLength
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
return new AudioFileImpl(
|
|
1541
|
+
this.module,
|
|
1542
|
+
fileHandle,
|
|
1543
|
+
sourcePath,
|
|
1544
|
+
input,
|
|
1545
|
+
isPartiallyLoaded,
|
|
1546
|
+
opts
|
|
1547
|
+
);
|
|
1548
|
+
} catch (error) {
|
|
1549
|
+
if (typeof fileHandle.destroy === "function") {
|
|
1550
|
+
fileHandle.destroy();
|
|
1551
|
+
}
|
|
1552
|
+
throw error;
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
async edit(input, fn) {
|
|
1556
|
+
const file = await this.open(input);
|
|
1557
|
+
try {
|
|
1558
|
+
await fn(file);
|
|
1559
|
+
if (typeof input === "string") {
|
|
1560
|
+
await file.saveToFile();
|
|
1561
|
+
} else {
|
|
1562
|
+
file.save();
|
|
1563
|
+
return file.getFileBuffer();
|
|
1564
|
+
}
|
|
1565
|
+
} finally {
|
|
1566
|
+
file.dispose();
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
/**
|
|
1570
|
+
* Update tags in a file and save to disk in one operation.
|
|
1571
|
+
* @param path - Path to the audio file.
|
|
1572
|
+
* @param tags - Tag fields to update (partial update supported).
|
|
1573
|
+
* @throws {InvalidFormatError} If the file is corrupted or unsupported.
|
|
1574
|
+
* @throws {FileOperationError} If saving to disk fails.
|
|
1575
|
+
*/
|
|
1576
|
+
async updateFile(path, tags) {
|
|
1577
|
+
const file = await this.open(path);
|
|
1578
|
+
try {
|
|
1579
|
+
mergeTagUpdates(file, tags);
|
|
1580
|
+
await file.saveToFile();
|
|
1581
|
+
} finally {
|
|
1582
|
+
file.dispose();
|
|
1583
|
+
}
|
|
1584
|
+
}
|
|
1585
|
+
/**
|
|
1586
|
+
* Copy an audio file to a new path with updated tags.
|
|
1587
|
+
* @param sourcePath - Path to the source audio file.
|
|
1588
|
+
* @param destPath - Destination path for the tagged copy.
|
|
1589
|
+
* @param tags - Tag fields to set on the copy.
|
|
1590
|
+
* @throws {InvalidFormatError} If the source file is corrupted or unsupported.
|
|
1591
|
+
* @throws {FileOperationError} If reading or writing fails.
|
|
1592
|
+
*/
|
|
1593
|
+
async copyWithTags(sourcePath, destPath, tags) {
|
|
1594
|
+
const file = await this.open(sourcePath);
|
|
1595
|
+
try {
|
|
1596
|
+
mergeTagUpdates(file, tags);
|
|
1597
|
+
await file.saveToFile(destPath);
|
|
1598
|
+
} finally {
|
|
1599
|
+
file.dispose();
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
/** Returns the taglib-wasm version with embedded TagLib version. */
|
|
1603
|
+
version() {
|
|
1604
|
+
return "1.0.2 (TagLib 2.1.1)";
|
|
1605
|
+
}
|
|
1606
|
+
};
|
|
1607
|
+
}
|
|
1608
|
+
});
|
|
1609
|
+
|
|
1610
|
+
// src/taglib/index.ts
|
|
1611
|
+
var init_taglib = __esm({
|
|
1612
|
+
"src/taglib/index.ts"() {
|
|
1613
|
+
"use strict";
|
|
1614
|
+
init_audio_file_impl();
|
|
1615
|
+
init_taglib_class();
|
|
1616
|
+
init_errors2();
|
|
1617
|
+
}
|
|
1618
|
+
});
|
|
1619
|
+
|
|
1620
|
+
// src/taglib.ts
|
|
1621
|
+
var taglib_exports = {};
|
|
1622
|
+
__export(taglib_exports, {
|
|
1623
|
+
AudioFileImpl: () => AudioFileImpl,
|
|
1624
|
+
EnvironmentError: () => EnvironmentError,
|
|
1625
|
+
FileOperationError: () => FileOperationError,
|
|
1626
|
+
InvalidFormatError: () => InvalidFormatError,
|
|
1627
|
+
MemoryError: () => MemoryError,
|
|
1628
|
+
MetadataError: () => MetadataError,
|
|
1629
|
+
SUPPORTED_FORMATS: () => SUPPORTED_FORMATS,
|
|
1630
|
+
TagLib: () => TagLib,
|
|
1631
|
+
TagLibError: () => TagLibError,
|
|
1632
|
+
TagLibInitializationError: () => TagLibInitializationError,
|
|
1633
|
+
UnsupportedFormatError: () => UnsupportedFormatError,
|
|
1634
|
+
createTagLib: () => createTagLib,
|
|
1635
|
+
isEnvironmentError: () => isEnvironmentError,
|
|
1636
|
+
isFileOperationError: () => isFileOperationError,
|
|
1637
|
+
isInvalidFormatError: () => isInvalidFormatError,
|
|
1638
|
+
isMemoryError: () => isMemoryError,
|
|
1639
|
+
isMetadataError: () => isMetadataError,
|
|
1640
|
+
isTagLibError: () => isTagLibError,
|
|
1641
|
+
isUnsupportedFormatError: () => isUnsupportedFormatError
|
|
1642
|
+
});
|
|
1643
|
+
var init_taglib2 = __esm({
|
|
1644
|
+
"src/taglib.ts"() {
|
|
1645
|
+
"use strict";
|
|
1646
|
+
init_taglib();
|
|
1647
|
+
}
|
|
1648
|
+
});
|
|
1649
|
+
|
|
1650
|
+
// src/simple/config.ts
|
|
1651
|
+
var cachedTagLib = null;
|
|
1652
|
+
var bufferModeEnabled = false;
|
|
1653
|
+
function setBufferMode(enabled) {
|
|
1654
|
+
bufferModeEnabled = enabled;
|
|
1655
|
+
cachedTagLib = null;
|
|
1656
|
+
}
|
|
1657
|
+
async function getTagLib() {
|
|
1658
|
+
if (!cachedTagLib) {
|
|
1659
|
+
const { TagLib: TagLib2 } = await Promise.resolve().then(() => (init_taglib2(), taglib_exports));
|
|
1660
|
+
const initOptions = bufferModeEnabled ? { forceWasmType: "emscripten" } : void 0;
|
|
1661
|
+
cachedTagLib = await TagLib2.initialize(initOptions);
|
|
1662
|
+
}
|
|
1663
|
+
return cachedTagLib;
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
// src/simple/tag-operations.ts
|
|
1667
|
+
init_errors2();
|
|
1668
|
+
init_write();
|
|
1669
|
+
init_tag_mapping();
|
|
1670
|
+
async function readTags(file) {
|
|
1671
|
+
const taglib = await getTagLib();
|
|
1672
|
+
const audioFile = await taglib.open(file);
|
|
1673
|
+
try {
|
|
1674
|
+
if (!audioFile.isValid()) {
|
|
1675
|
+
throw new InvalidFormatError(
|
|
1676
|
+
"File may be corrupted or in an unsupported format"
|
|
1677
|
+
);
|
|
1678
|
+
}
|
|
1679
|
+
const props = audioFile.properties();
|
|
1680
|
+
return mapPropertiesToExtendedTag(props);
|
|
1681
|
+
} finally {
|
|
1682
|
+
audioFile.dispose();
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
async function applyTags(file, tags) {
|
|
1686
|
+
const taglib = await getTagLib();
|
|
1687
|
+
const audioFile = await taglib.open(file);
|
|
1688
|
+
try {
|
|
1689
|
+
if (!audioFile.isValid()) {
|
|
1690
|
+
throw new InvalidFormatError(
|
|
1691
|
+
"File may be corrupted or in an unsupported format"
|
|
1692
|
+
);
|
|
1693
|
+
}
|
|
1694
|
+
mergeTagUpdates(audioFile, tags);
|
|
1695
|
+
if (!audioFile.save()) {
|
|
1696
|
+
throw new FileOperationError(
|
|
1697
|
+
"save",
|
|
1698
|
+
"Failed to save metadata changes. The file may be read-only or corrupted."
|
|
1699
|
+
);
|
|
1700
|
+
}
|
|
1701
|
+
return audioFile.getFileBuffer();
|
|
1702
|
+
} finally {
|
|
1703
|
+
audioFile.dispose();
|
|
1704
|
+
}
|
|
1705
|
+
}
|
|
1706
|
+
async function applyTagsToFile(file, tags) {
|
|
1707
|
+
if (typeof file !== "string") {
|
|
1708
|
+
throw new FileOperationError(
|
|
1709
|
+
"save",
|
|
1710
|
+
"applyTagsToFile requires a file path string to save changes"
|
|
1711
|
+
);
|
|
1712
|
+
}
|
|
1713
|
+
const modifiedBuffer = await applyTags(file, tags);
|
|
1714
|
+
await writeFileData(file, modifiedBuffer);
|
|
1715
|
+
}
|
|
1716
|
+
async function readProperties(file) {
|
|
1717
|
+
const taglib = await getTagLib();
|
|
1718
|
+
const audioFile = await taglib.open(file);
|
|
1719
|
+
try {
|
|
1720
|
+
if (!audioFile.isValid()) {
|
|
1721
|
+
throw new InvalidFormatError(
|
|
1722
|
+
"File may be corrupted or in an unsupported format"
|
|
1723
|
+
);
|
|
1724
|
+
}
|
|
1725
|
+
const props = audioFile.audioProperties();
|
|
1726
|
+
if (!props) {
|
|
1727
|
+
throw new MetadataError(
|
|
1728
|
+
"read",
|
|
1729
|
+
"File may not contain valid audio data",
|
|
1730
|
+
"audioProperties"
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
return props;
|
|
1734
|
+
} finally {
|
|
1735
|
+
audioFile.dispose();
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
async function isValidAudioFile(file) {
|
|
1739
|
+
try {
|
|
1740
|
+
const taglib = await getTagLib();
|
|
1741
|
+
const audioFile = await taglib.open(file);
|
|
1742
|
+
try {
|
|
1743
|
+
return audioFile.isValid();
|
|
1744
|
+
} finally {
|
|
1745
|
+
audioFile.dispose();
|
|
1746
|
+
}
|
|
1747
|
+
} catch {
|
|
1748
|
+
return false;
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
async function readFormat(file) {
|
|
1752
|
+
const taglib = await getTagLib();
|
|
1753
|
+
const audioFile = await taglib.open(file);
|
|
1754
|
+
try {
|
|
1755
|
+
if (!audioFile.isValid()) {
|
|
1756
|
+
return void 0;
|
|
1757
|
+
}
|
|
1758
|
+
return audioFile.getFormat();
|
|
1759
|
+
} finally {
|
|
1760
|
+
audioFile.dispose();
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
async function clearTags(file) {
|
|
1764
|
+
const taglib = await getTagLib();
|
|
1765
|
+
const audioFile = await taglib.open(file);
|
|
1766
|
+
try {
|
|
1767
|
+
if (!audioFile.isValid()) {
|
|
1768
|
+
throw new InvalidFormatError(
|
|
1769
|
+
"File may be corrupted or in an unsupported format"
|
|
1770
|
+
);
|
|
1771
|
+
}
|
|
1772
|
+
audioFile.setProperties({});
|
|
1773
|
+
audioFile.removePictures();
|
|
1774
|
+
if (!audioFile.save()) {
|
|
1775
|
+
throw new FileOperationError(
|
|
1776
|
+
"save",
|
|
1777
|
+
"Failed to save metadata changes. The file may be read-only or corrupted."
|
|
1778
|
+
);
|
|
1779
|
+
}
|
|
1780
|
+
return audioFile.getFileBuffer();
|
|
1781
|
+
} finally {
|
|
1782
|
+
audioFile.dispose();
|
|
1783
|
+
}
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
// src/simple/picture-operations.ts
|
|
1787
|
+
init_errors2();
|
|
1788
|
+
async function readPictures(file) {
|
|
1789
|
+
const taglib = await getTagLib();
|
|
1790
|
+
const audioFile = await taglib.open(file);
|
|
1791
|
+
try {
|
|
1792
|
+
if (!audioFile.isValid()) {
|
|
1793
|
+
throw new InvalidFormatError(
|
|
1794
|
+
"File may be corrupted or in an unsupported format"
|
|
1795
|
+
);
|
|
1796
|
+
}
|
|
1797
|
+
return audioFile.getPictures();
|
|
1798
|
+
} finally {
|
|
1799
|
+
audioFile.dispose();
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
async function applyPictures(file, pictures) {
|
|
1803
|
+
const taglib = await getTagLib();
|
|
1804
|
+
const audioFile = await taglib.open(file);
|
|
1805
|
+
try {
|
|
1806
|
+
if (!audioFile.isValid()) {
|
|
1807
|
+
throw new InvalidFormatError(
|
|
1808
|
+
"File may be corrupted or in an unsupported format"
|
|
1809
|
+
);
|
|
1810
|
+
}
|
|
1811
|
+
audioFile.setPictures(pictures);
|
|
1812
|
+
if (!audioFile.save()) {
|
|
1813
|
+
throw new FileOperationError(
|
|
1814
|
+
"save",
|
|
1815
|
+
"Failed to save picture changes. The file may be read-only or corrupted."
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
return audioFile.getFileBuffer();
|
|
1819
|
+
} finally {
|
|
1820
|
+
audioFile.dispose();
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
async function addPicture(file, picture) {
|
|
1824
|
+
const taglib = await getTagLib();
|
|
1825
|
+
const audioFile = await taglib.open(file);
|
|
1826
|
+
try {
|
|
1827
|
+
if (!audioFile.isValid()) {
|
|
1828
|
+
throw new InvalidFormatError(
|
|
1829
|
+
"File may be corrupted or in an unsupported format"
|
|
1830
|
+
);
|
|
1831
|
+
}
|
|
1832
|
+
audioFile.addPicture(picture);
|
|
1833
|
+
if (!audioFile.save()) {
|
|
1834
|
+
throw new FileOperationError(
|
|
1835
|
+
"save",
|
|
1836
|
+
"Failed to save picture changes. The file may be read-only or corrupted."
|
|
1837
|
+
);
|
|
1838
|
+
}
|
|
1839
|
+
return audioFile.getFileBuffer();
|
|
1840
|
+
} finally {
|
|
1841
|
+
audioFile.dispose();
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
async function clearPictures(file) {
|
|
1845
|
+
return applyPictures(file, []);
|
|
1846
|
+
}
|
|
1847
|
+
async function readCoverArt(file) {
|
|
1848
|
+
const pictures = await readPictures(file);
|
|
1849
|
+
if (pictures.length === 0) {
|
|
1850
|
+
return void 0;
|
|
1851
|
+
}
|
|
1852
|
+
const frontCover = pictures.find((pic) => pic.type === "FrontCover");
|
|
1853
|
+
if (frontCover) {
|
|
1854
|
+
return frontCover.data;
|
|
1855
|
+
}
|
|
1856
|
+
return pictures[0].data;
|
|
1857
|
+
}
|
|
1858
|
+
async function applyCoverArt(file, imageData, mimeType) {
|
|
1859
|
+
const picture = {
|
|
1860
|
+
mimeType,
|
|
1861
|
+
data: imageData,
|
|
1862
|
+
type: "FrontCover",
|
|
1863
|
+
description: "Front Cover"
|
|
1864
|
+
};
|
|
1865
|
+
return applyPictures(file, [picture]);
|
|
1866
|
+
}
|
|
1867
|
+
function findPictureByType(pictures, type) {
|
|
1868
|
+
return pictures.find((pic) => pic.type === type);
|
|
1869
|
+
}
|
|
1870
|
+
async function replacePictureByType(file, newPicture) {
|
|
1871
|
+
const pictures = await readPictures(file);
|
|
1872
|
+
const filteredPictures = pictures.filter(
|
|
1873
|
+
(pic) => pic.type !== newPicture.type
|
|
1874
|
+
);
|
|
1875
|
+
filteredPictures.push(newPicture);
|
|
1876
|
+
return applyPictures(file, filteredPictures);
|
|
1877
|
+
}
|
|
1878
|
+
async function readPictureMetadata(file) {
|
|
1879
|
+
const pictures = await readPictures(file);
|
|
1880
|
+
return pictures.map((pic) => ({
|
|
1881
|
+
type: pic.type,
|
|
1882
|
+
mimeType: pic.mimeType,
|
|
1883
|
+
description: pic.description,
|
|
1884
|
+
size: pic.data.length
|
|
1885
|
+
}));
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// src/simple/batch-operations.ts
|
|
1889
|
+
init_errors2();
|
|
1890
|
+
init_tag_mapping();
|
|
1891
|
+
async function executeBatch(files, options, processor) {
|
|
1892
|
+
if (files.length === 0) return { items: [], duration: 0 };
|
|
1893
|
+
const startTime = Date.now();
|
|
1894
|
+
const { concurrency = 4, continueOnError = true, onProgress, signal } = options;
|
|
1895
|
+
const items = new Array(files.length);
|
|
1896
|
+
const taglib = await getTagLib();
|
|
1897
|
+
let processed = 0;
|
|
1898
|
+
const total = files.length;
|
|
1899
|
+
for (let i = 0; i < files.length; i += concurrency) {
|
|
1900
|
+
signal?.throwIfAborted();
|
|
1901
|
+
const chunk = files.slice(i, i + concurrency);
|
|
1902
|
+
const chunkPromises = chunk.map(async (file, idx) => {
|
|
1903
|
+
const index = i + idx;
|
|
1904
|
+
const fileName = typeof file === "string" ? file : `file-${index}`;
|
|
1905
|
+
try {
|
|
1906
|
+
const audioFile = await taglib.open(file);
|
|
1907
|
+
try {
|
|
1908
|
+
if (!audioFile.isValid()) {
|
|
1909
|
+
throw new InvalidFormatError(
|
|
1910
|
+
"File may be corrupted or in an unsupported format"
|
|
1911
|
+
);
|
|
1912
|
+
}
|
|
1913
|
+
items[index] = {
|
|
1914
|
+
status: "ok",
|
|
1915
|
+
path: fileName,
|
|
1916
|
+
data: processor(audioFile)
|
|
1917
|
+
};
|
|
1918
|
+
} finally {
|
|
1919
|
+
audioFile.dispose();
|
|
1920
|
+
}
|
|
1921
|
+
} catch (error) {
|
|
1922
|
+
const err = error instanceof Error ? error : new Error(String(error));
|
|
1923
|
+
items[index] = { status: "error", path: fileName, error: err };
|
|
1924
|
+
if (!continueOnError) throw err;
|
|
1925
|
+
}
|
|
1926
|
+
processed++;
|
|
1927
|
+
onProgress?.(processed, total, fileName);
|
|
1928
|
+
});
|
|
1929
|
+
await Promise.all(chunkPromises);
|
|
1930
|
+
}
|
|
1931
|
+
return { items, duration: Date.now() - startTime };
|
|
1932
|
+
}
|
|
1933
|
+
async function readTagsBatch(files, options = {}) {
|
|
1934
|
+
return executeBatch(
|
|
1935
|
+
files,
|
|
1936
|
+
options,
|
|
1937
|
+
(audioFile) => mapPropertiesToExtendedTag(audioFile.properties())
|
|
1938
|
+
);
|
|
1939
|
+
}
|
|
1940
|
+
async function readPropertiesBatch(files, options = {}) {
|
|
1941
|
+
return executeBatch(
|
|
1942
|
+
files,
|
|
1943
|
+
options,
|
|
1944
|
+
(audioFile) => audioFile.audioProperties()
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
function extractDynamics(audioFile) {
|
|
1948
|
+
const dynamics = {};
|
|
1949
|
+
const fields = [
|
|
1950
|
+
"replayGainTrackGain",
|
|
1951
|
+
"replayGainTrackPeak",
|
|
1952
|
+
"replayGainAlbumGain",
|
|
1953
|
+
"replayGainAlbumPeak"
|
|
1954
|
+
];
|
|
1955
|
+
for (const field of fields) {
|
|
1956
|
+
const val = audioFile.getProperty(field);
|
|
1957
|
+
if (val) dynamics[field] = val;
|
|
1958
|
+
}
|
|
1959
|
+
let appleSoundCheck = audioFile.getProperty("appleSoundCheck");
|
|
1960
|
+
if (!appleSoundCheck && audioFile.isMP4()) {
|
|
1961
|
+
appleSoundCheck = audioFile.getMP4Item("----:com.apple.iTunes:iTunNORM");
|
|
1962
|
+
}
|
|
1963
|
+
if (appleSoundCheck) dynamics.appleSoundCheck = appleSoundCheck;
|
|
1964
|
+
return Object.keys(dynamics).length > 0 ? dynamics : void 0;
|
|
1965
|
+
}
|
|
1966
|
+
async function readMetadata(file) {
|
|
1967
|
+
const taglib = await getTagLib();
|
|
1968
|
+
const audioFile = await taglib.open(file);
|
|
1969
|
+
try {
|
|
1970
|
+
if (!audioFile.isValid()) {
|
|
1971
|
+
let name;
|
|
1972
|
+
if (typeof file === "string") {
|
|
1973
|
+
name = file;
|
|
1974
|
+
} else if (file instanceof File) {
|
|
1975
|
+
name = file.name;
|
|
1976
|
+
} else {
|
|
1977
|
+
name = `buffer (${file.byteLength} bytes)`;
|
|
1978
|
+
}
|
|
1979
|
+
throw new InvalidFormatError(
|
|
1980
|
+
`File may be corrupted or in an unsupported format. File: ${name}`
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
return {
|
|
1984
|
+
tags: mapPropertiesToExtendedTag(audioFile.properties()),
|
|
1985
|
+
properties: audioFile.audioProperties(),
|
|
1986
|
+
hasCoverArt: audioFile.getPictures().length > 0,
|
|
1987
|
+
dynamics: extractDynamics(audioFile)
|
|
1988
|
+
};
|
|
1989
|
+
} finally {
|
|
1990
|
+
audioFile.dispose();
|
|
1991
|
+
}
|
|
1992
|
+
}
|
|
1993
|
+
async function readMetadataBatch(files, options = {}) {
|
|
1994
|
+
return executeBatch(files, options, (audioFile) => ({
|
|
1995
|
+
tags: mapPropertiesToExtendedTag(audioFile.properties()),
|
|
1996
|
+
properties: audioFile.audioProperties(),
|
|
1997
|
+
hasCoverArt: audioFile.getPictures().length > 0,
|
|
1998
|
+
dynamics: extractDynamics(audioFile)
|
|
1999
|
+
}));
|
|
2000
|
+
}
|
|
2001
|
+
|
|
2002
|
+
// src/simple/index.ts
|
|
2003
|
+
init_types2();
|
|
2004
|
+
export {
|
|
2005
|
+
PICTURE_TYPE_NAMES,
|
|
2006
|
+
PICTURE_TYPE_VALUES,
|
|
2007
|
+
addPicture,
|
|
2008
|
+
applyCoverArt,
|
|
2009
|
+
applyPictures,
|
|
2010
|
+
applyTags,
|
|
2011
|
+
applyTagsToFile,
|
|
2012
|
+
clearPictures,
|
|
2013
|
+
clearTags,
|
|
2014
|
+
findPictureByType,
|
|
2015
|
+
getTagLib,
|
|
2016
|
+
isValidAudioFile,
|
|
2017
|
+
readCoverArt,
|
|
2018
|
+
readFormat,
|
|
2019
|
+
readMetadata,
|
|
2020
|
+
readMetadataBatch,
|
|
2021
|
+
readPictureMetadata,
|
|
2022
|
+
readPictures,
|
|
2023
|
+
readProperties,
|
|
2024
|
+
readPropertiesBatch,
|
|
2025
|
+
readTags,
|
|
2026
|
+
readTagsBatch,
|
|
2027
|
+
replacePictureByType,
|
|
2028
|
+
setBufferMode
|
|
2029
|
+
};
|