taglib-wasm 0.2.7 → 0.3.1

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/src/taglib-jsr.ts DELETED
@@ -1,544 +0,0 @@
1
- /**
2
- * @fileoverview JSR-compatible TagLib implementation
3
- *
4
- * This version uses direct WASM loading without Emscripten's JS file
5
- * to be compatible with JSR publishing requirements.
6
- */
7
-
8
- import type {
9
- AudioFormat,
10
- AudioProperties,
11
- BitrateControlMode,
12
- ExtendedTag,
13
- Picture,
14
- PropertyMap,
15
- Tag,
16
- TagLibConfig,
17
- } from "./types.ts";
18
- import {
19
- cStringToJSJSR,
20
- jsToCStringJSR,
21
- loadTagLibModuleJSR,
22
- type TagLibModule,
23
- } from "./wasm-jsr.ts";
24
- import {
25
- BITRATE_CONTROL_MODE_NAMES,
26
- BITRATE_CONTROL_MODE_VALUES,
27
- METADATA_MAPPINGS
28
- } from "./types.ts";
29
-
30
- /**
31
- * JSR-compatible TagLib singleton for WASM module management
32
- */
33
- export class TagLibJSR {
34
- private static instance: TagLibJSR | null = null;
35
- private module: TagLibModule | null = null;
36
- private initialized = false;
37
-
38
- private constructor() {}
39
-
40
- static getInstance(): TagLibJSR {
41
- if (!TagLibJSR.instance) {
42
- TagLibJSR.instance = new TagLibJSR();
43
- }
44
- return TagLibJSR.instance;
45
- }
46
-
47
- async initialize(config?: TagLibConfig): Promise<void> {
48
- if (this.initialized) return;
49
-
50
- this.module = await loadTagLibModuleJSR(config);
51
- this.initialized = true;
52
- }
53
-
54
- getModule(): TagLibModule {
55
- if (!this.module) {
56
- throw new Error(
57
- "TagLib not initialized. Call TagLib.initialize() first.",
58
- );
59
- }
60
- return this.module;
61
- }
62
-
63
- isInitialized(): boolean {
64
- return this.initialized;
65
- }
66
-
67
- /**
68
- * Initialize taglib-wasm module
69
- */
70
- static async initialize(config?: TagLibConfig): Promise<void> {
71
- const instance = TagLibJSR.getInstance();
72
- await instance.initialize(config);
73
- }
74
-
75
- /**
76
- * Get the WASM module instance
77
- */
78
- static getModule(): TagLibModule {
79
- return TagLibJSR.getInstance().getModule();
80
- }
81
-
82
- /**
83
- * Check if TagLib is initialized
84
- */
85
- static isInitialized(): boolean {
86
- return TagLibJSR.getInstance().isInitialized();
87
- }
88
- }
89
-
90
- /**
91
- * JSR-compatible audio file handling class
92
- */
93
- export class AudioFileJSR {
94
- private fileId: number = 0;
95
- private module: TagLibModule;
96
- private dataPtr: number = 0;
97
-
98
- constructor(data: Uint8Array) {
99
- this.module = TagLibJSR.getModule();
100
-
101
- // Allocate memory for file data
102
- this.dataPtr = this.module.allocate(data, this.module.ALLOC_NORMAL);
103
-
104
- // Create TagLib file from buffer
105
- this.fileId = this.module._taglib_file_new_from_buffer(
106
- this.dataPtr,
107
- data.length,
108
- );
109
-
110
- if (!this.isValid()) {
111
- this.cleanup();
112
- throw new Error(
113
- "Failed to load audio file - invalid format or corrupted data",
114
- );
115
- }
116
- }
117
-
118
- /**
119
- * Check if the file is valid
120
- */
121
- isValid(): boolean {
122
- return this.module._taglib_file_is_valid(this.fileId) === 1;
123
- }
124
-
125
- /**
126
- * Get the audio format
127
- */
128
- getFormat(): AudioFormat {
129
- const formatId = this.module._taglib_file_format(this.fileId);
130
- const formats: Record<number, AudioFormat> = {
131
- 1: "MP3",
132
- 2: "FLAC",
133
- 3: "OGG",
134
- 4: "MP4",
135
- 5: "WMA",
136
- 6: "APE",
137
- 7: "MPC",
138
- 8: "WV",
139
- 9: "OPUS",
140
- 10: "TTA",
141
- 11: "WAV",
142
- 12: "AIFF",
143
- 13: "MOD",
144
- 14: "S3M",
145
- 15: "XM",
146
- 16: "IT",
147
- };
148
- return formats[formatId] || "MP3";
149
- }
150
-
151
- /**
152
- * Get basic tag information
153
- */
154
- getTag(): Tag {
155
- const tagPtr = this.module._taglib_file_tag(this.fileId);
156
- if (tagPtr === 0) {
157
- return {
158
- title: "",
159
- artist: "",
160
- album: "",
161
- comment: "",
162
- genre: "",
163
- year: 0,
164
- track: 0,
165
- };
166
- }
167
-
168
- return {
169
- title: cStringToJSJSR(this.module, this.module._taglib_tag_title(tagPtr)),
170
- artist: cStringToJSJSR(
171
- this.module,
172
- this.module._taglib_tag_artist(tagPtr),
173
- ),
174
- album: cStringToJSJSR(this.module, this.module._taglib_tag_album(tagPtr)),
175
- comment: cStringToJSJSR(
176
- this.module,
177
- this.module._taglib_tag_comment(tagPtr),
178
- ),
179
- genre: cStringToJSJSR(this.module, this.module._taglib_tag_genre(tagPtr)),
180
- year: this.module._taglib_tag_year(tagPtr),
181
- track: this.module._taglib_tag_track(tagPtr),
182
- };
183
- }
184
-
185
- /**
186
- * Set basic tag information
187
- */
188
- setTag(tag: Partial<Tag>): void {
189
- const tagPtr = this.module._taglib_file_tag(this.fileId);
190
- if (tagPtr === 0) return;
191
-
192
- if (tag.title !== undefined) {
193
- const titlePtr = jsToCStringJSR(this.module, tag.title);
194
- this.module._taglib_tag_set_title(tagPtr, titlePtr);
195
- this.module._free(titlePtr);
196
- }
197
-
198
- if (tag.artist !== undefined) {
199
- const artistPtr = jsToCStringJSR(this.module, tag.artist);
200
- this.module._taglib_tag_set_artist(tagPtr, artistPtr);
201
- this.module._free(artistPtr);
202
- }
203
-
204
- if (tag.album !== undefined) {
205
- const albumPtr = jsToCStringJSR(this.module, tag.album);
206
- this.module._taglib_tag_set_album(tagPtr, albumPtr);
207
- this.module._free(albumPtr);
208
- }
209
-
210
- if (tag.comment !== undefined) {
211
- const commentPtr = jsToCStringJSR(this.module, tag.comment);
212
- this.module._taglib_tag_set_comment(tagPtr, commentPtr);
213
- this.module._free(commentPtr);
214
- }
215
-
216
- if (tag.genre !== undefined) {
217
- const genrePtr = jsToCStringJSR(this.module, tag.genre);
218
- this.module._taglib_tag_set_genre(tagPtr, genrePtr);
219
- this.module._free(genrePtr);
220
- }
221
-
222
- if (tag.year !== undefined) {
223
- this.module._taglib_tag_set_year(tagPtr, tag.year);
224
- }
225
-
226
- if (tag.track !== undefined) {
227
- this.module._taglib_tag_set_track(tagPtr, tag.track);
228
- }
229
- }
230
-
231
- /**
232
- * Get audio properties
233
- */
234
- getAudioProperties(): AudioProperties {
235
- const propsPtr = this.module._taglib_file_audioproperties(this.fileId);
236
- if (propsPtr === 0) {
237
- return {
238
- length: 0,
239
- bitrate: 0,
240
- sampleRate: 0,
241
- channels: 0,
242
- format: this.getFormat(),
243
- };
244
- }
245
-
246
- return {
247
- length: this.module._taglib_audioproperties_length(propsPtr),
248
- bitrate: this.module._taglib_audioproperties_bitrate(propsPtr),
249
- sampleRate: this.module._taglib_audioproperties_samplerate(propsPtr),
250
- channels: this.module._taglib_audioproperties_channels(propsPtr),
251
- format: this.getFormat(),
252
- };
253
- }
254
-
255
- /**
256
- * Get extended tag information (placeholder for JSR version)
257
- */
258
- getExtendedTag(): ExtendedTag {
259
- // For JSR compatibility, return basic implementation
260
- const basicTag = this.getTag();
261
- return {
262
- ...basicTag,
263
-
264
- // Fingerprinting & identification - placeholder
265
- acoustidFingerprint: "",
266
- acoustidId: "",
267
- musicbrainzTrackId: "",
268
- musicbrainzReleaseId: "",
269
- musicbrainzArtistId: "",
270
- musicbrainzReleaseGroupId: "",
271
- albumArtist: "",
272
- composer: "",
273
- discNumber: 0,
274
- totalTracks: 0,
275
- totalDiscs: 0,
276
- bpm: 0,
277
- compilation: false,
278
- titleSort: "",
279
- artistSort: "",
280
- albumSort: "",
281
-
282
- // Volume normalization - placeholder
283
- replayGainTrackGain: "",
284
- replayGainTrackPeak: "",
285
- replayGainAlbumGain: "",
286
- replayGainAlbumPeak: "",
287
- appleSoundCheck: "",
288
- };
289
- }
290
-
291
- /**
292
- * Set extended tag fields (placeholder for JSR version)
293
- */
294
- setExtendedTag(extendedTag: Partial<ExtendedTag>): void {
295
- // Set basic fields
296
- this.setTag(extendedTag);
297
-
298
- // Extended fields are placeholder for JSR version
299
- // Full implementation would require PropertyMap integration
300
- }
301
-
302
- /**
303
- * Get pictures from the file (placeholder for JSR version)
304
- */
305
- getPictures(): Picture[] {
306
- // Placeholder - full implementation would require format-specific picture extraction
307
- return [];
308
- }
309
-
310
- /**
311
- * Set pictures in the file (placeholder for JSR version)
312
- */
313
- setPictures(pictures: Picture[]): void {
314
- // Placeholder - full implementation would require format-specific picture handling
315
- }
316
-
317
- // Placeholder methods for extended metadata (same as main implementation)
318
- getAcoustidFingerprint(): string {
319
- return "";
320
- }
321
- setAcoustidFingerprint(fingerprint: string): void {}
322
- getMusicbrainzTrackId(): string {
323
- return "";
324
- }
325
- setMusicbrainzTrackId(id: string): void {}
326
- getMusicbrainzRecordingId(): string {
327
- return "";
328
- }
329
- setMusicbrainzRecordingId(id: string): void {}
330
- getMusicbrainzArtistId(): string {
331
- return "";
332
- }
333
- setMusicbrainzArtistId(id: string): void {}
334
- getMusicbrainzAlbumId(): string {
335
- return "";
336
- }
337
- setMusicbrainzAlbumId(id: string): void {}
338
- getMusicbrainzAlbumArtistId(): string {
339
- return "";
340
- }
341
- setMusicbrainzAlbumArtistId(id: string): void {}
342
- getMusicbrainzReleaseGroupId(): string {
343
- return "";
344
- }
345
- setMusicbrainzReleaseGroupId(id: string): void {}
346
- getReplayGainTrackGain(): string {
347
- return "";
348
- }
349
- setReplayGainTrackGain(gain: string): void {}
350
- getReplayGainTrackPeak(): string {
351
- return "";
352
- }
353
- setReplayGainTrackPeak(peak: string): void {}
354
- getReplayGainAlbumGain(): string {
355
- return "";
356
- }
357
- setReplayGainAlbumGain(gain: string): void {}
358
- getReplayGainAlbumPeak(): string {
359
- return "";
360
- }
361
- setReplayGainAlbumPeak(peak: string): void {}
362
- getAppleSoundCheck(): string {
363
- return "";
364
- }
365
- setAppleSoundCheck(soundCheck: string): void {}
366
-
367
- /**
368
- * Get all properties as a PropertyMap
369
- */
370
- properties(): PropertyMap {
371
- const jsonPtr = this.module._taglib_file_properties_json(this.fileId);
372
- if (jsonPtr === 0) return {};
373
-
374
- const jsonStr = cStringToJSJSR(this.module, jsonPtr);
375
- try {
376
- return JSON.parse(jsonStr);
377
- } catch {
378
- return {};
379
- }
380
- }
381
-
382
- /**
383
- * Set properties from a PropertyMap
384
- */
385
- setProperties(properties: PropertyMap): boolean {
386
- const jsonStr = JSON.stringify(properties);
387
- const jsonPtr = jsToCStringJSR(this.module, jsonStr);
388
- try {
389
- return this.module._taglib_file_set_properties_json(this.fileId, jsonPtr) !== 0;
390
- } finally {
391
- this.module._free(jsonPtr);
392
- }
393
- }
394
-
395
- /**
396
- * Get a specific property value
397
- */
398
- getProperty(key: string): string[] | undefined {
399
- const keyPtr = jsToCStringJSR(this.module, key);
400
- try {
401
- const valuePtr = this.module._taglib_file_get_property(this.fileId, keyPtr);
402
- if (valuePtr === 0) return undefined;
403
-
404
- const value = cStringToJSJSR(this.module, valuePtr);
405
- return value ? [value] : undefined;
406
- } finally {
407
- this.module._free(keyPtr);
408
- }
409
- }
410
-
411
- /**
412
- * Set a specific property value
413
- */
414
- setProperty(key: string, values: string | string[]): boolean {
415
- const value = Array.isArray(values) ? values[0] : values;
416
- if (!value) return false;
417
-
418
- const keyPtr = jsToCStringJSR(this.module, key);
419
- const valuePtr = jsToCStringJSR(this.module, value);
420
- try {
421
- return this.module._taglib_file_set_property(this.fileId, keyPtr, valuePtr) !== 0;
422
- } finally {
423
- this.module._free(keyPtr);
424
- this.module._free(valuePtr);
425
- }
426
- }
427
-
428
- /**
429
- * Check if this is an MP4 file
430
- */
431
- isMP4(): boolean {
432
- return this.module._taglib_file_is_mp4(this.fileId) !== 0;
433
- }
434
-
435
- /**
436
- * Get MP4-specific item (for custom atoms)
437
- */
438
- getMP4Item(key: string): string | undefined {
439
- if (!this.isMP4()) return undefined;
440
-
441
- const keyPtr = jsToCStringJSR(this.module, key);
442
- try {
443
- const valuePtr = this.module._taglib_mp4_get_item(this.fileId, keyPtr);
444
- if (valuePtr === 0) return undefined;
445
-
446
- return cStringToJSJSR(this.module, valuePtr);
447
- } finally {
448
- this.module._free(keyPtr);
449
- }
450
- }
451
-
452
- /**
453
- * Set MP4-specific item (for custom atoms)
454
- */
455
- setMP4Item(key: string, value: string): boolean {
456
- if (!this.isMP4()) return false;
457
-
458
- const keyPtr = jsToCStringJSR(this.module, key);
459
- const valuePtr = jsToCStringJSR(this.module, value);
460
- try {
461
- return this.module._taglib_mp4_set_item(this.fileId, keyPtr, valuePtr) !== 0;
462
- } finally {
463
- this.module._free(keyPtr);
464
- this.module._free(valuePtr);
465
- }
466
- }
467
-
468
- /**
469
- * Remove MP4-specific item
470
- */
471
- removeMP4Item(key: string): boolean {
472
- if (!this.isMP4()) return false;
473
-
474
- const keyPtr = jsToCStringJSR(this.module, key);
475
- try {
476
- return this.module._taglib_mp4_remove_item(this.fileId, keyPtr) !== 0;
477
- } finally {
478
- this.module._free(keyPtr);
479
- }
480
- }
481
-
482
- /**
483
- * Get bitrate control mode (MP4/M4A specific)
484
- * Reads from the 'acbf' atom
485
- */
486
- getBitrateControlMode(): BitrateControlMode | undefined {
487
- if (!this.isMP4()) return undefined;
488
-
489
- const value = this.getMP4Item("acbf");
490
- if (!value) return undefined;
491
-
492
- const numValue = parseInt(value, 10);
493
- if (isNaN(numValue) || numValue < 0 || numValue > 3) return undefined;
494
-
495
- return BITRATE_CONTROL_MODE_NAMES[numValue];
496
- }
497
-
498
- /**
499
- * Set bitrate control mode (MP4/M4A specific)
500
- * Writes to the 'acbf' atom
501
- */
502
- setBitrateControlMode(mode: BitrateControlMode): boolean {
503
- if (!this.isMP4()) return false;
504
-
505
- const numValue = BITRATE_CONTROL_MODE_VALUES[mode];
506
- if (numValue === undefined) return false;
507
-
508
- return this.setMP4Item("acbf", numValue.toString());
509
- }
510
-
511
- /**
512
- * Save changes to the file
513
- */
514
- save(): Uint8Array {
515
- if (!this.module._taglib_file_save(this.fileId)) {
516
- throw new Error("Failed to save audio file");
517
- }
518
-
519
- // In a real implementation, we'd need to extract the modified file data
520
- // For now, return empty array as placeholder
521
- return new Uint8Array(0);
522
- }
523
-
524
- /**
525
- * Clean up resources
526
- */
527
- cleanup(): void {
528
- if (this.fileId) {
529
- this.module._taglib_file_delete(this.fileId);
530
- this.fileId = 0;
531
- }
532
- if (this.dataPtr) {
533
- this.module._free(this.dataPtr);
534
- this.dataPtr = 0;
535
- }
536
- }
537
-
538
- /**
539
- * Destructor to ensure cleanup
540
- */
541
- destroy(): void {
542
- this.cleanup();
543
- }
544
- }
@@ -1,55 +0,0 @@
1
- /**
2
- * WebAssembly module interface for Embind version
3
- */
4
- export interface WasmModule {
5
- // Memory access
6
- HEAP8: Int8Array;
7
- HEAP16: Int16Array;
8
- HEAP32: Int32Array;
9
- HEAPU8: Uint8Array;
10
- HEAPU16: Uint16Array;
11
- HEAPU32: Uint32Array;
12
- HEAPF32: Float32Array;
13
- HEAPF64: Float64Array;
14
-
15
- // Runtime methods
16
- allocate: (size: number, type: number) => number;
17
- _malloc: (size: number) => number;
18
- _free: (ptr: number) => void;
19
- getValue: (ptr: number, type: string) => number;
20
- setValue: (ptr: number, value: number, type: string) => void;
21
- UTF8ToString: (ptr: number) => string;
22
- stringToUTF8: (str: string, outPtr: number, maxBytesToWrite: number) => void;
23
- lengthBytesUTF8: (str: string) => number;
24
-
25
- // Allocation types
26
- ALLOC_NORMAL: number;
27
- ALLOC_STACK: number;
28
-
29
- // Embind classes (these will be available after module loads)
30
- FileHandle: any;
31
- TagWrapper: any;
32
- AudioPropertiesWrapper: any;
33
- createFileHandle: () => any;
34
- }
35
-
36
- /**
37
- * Extended module interface with our Embind classes
38
- */
39
- export interface TagLibModule extends WasmModule {
40
- // These are the actual class constructors from Embind
41
- FileHandle: {
42
- new(): any;
43
- };
44
-
45
- TagWrapper: {
46
- new(tagPtr: number): any;
47
- };
48
-
49
- AudioPropertiesWrapper: {
50
- new(propsPtr: number): any;
51
- };
52
-
53
- // Factory function
54
- createFileHandle: () => any;
55
- }