taglib-wasm 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,274 @@
1
+ /**
2
+ * Enhanced API patterns inspired by node-taglib
3
+ *
4
+ * This file demonstrates potential API improvements that could be added
5
+ * to TagLib WASM to improve developer experience.
6
+ */
7
+
8
+ import type { AudioFile, TagLibConfig } from "./types.ts";
9
+
10
+ /**
11
+ * Enhanced error handling with error codes
12
+ */
13
+ export interface TagLibError extends Error {
14
+ code: 'FILE_NOT_FOUND' | 'INVALID_FORMAT' | 'MEMORY_ERROR' | 'PERMISSION_DENIED' | 'WASM_ERROR';
15
+ path?: string;
16
+ format?: string;
17
+ details?: string;
18
+ }
19
+
20
+ /**
21
+ * Callback-style async operations (Node.js pattern)
22
+ */
23
+ export type TagLibCallback<T> = (err: TagLibError | null, result?: T) => void;
24
+
25
+ /**
26
+ * Enhanced input types
27
+ */
28
+ export type AudioInput =
29
+ | string // File path (Node.js/Deno/Bun only)
30
+ | Uint8Array // Raw bytes
31
+ | ArrayBuffer // ArrayBuffer
32
+ | Buffer // Node.js Buffer
33
+ | File; // Browser File object
34
+
35
+ /**
36
+ * Enhanced TagLib class with multiple API patterns
37
+ */
38
+ export class EnhancedTagLib {
39
+ /**
40
+ * Synchronous initialization (for server environments)
41
+ */
42
+ static initializeSync(config?: TagLibConfig): EnhancedTagLib {
43
+ // Implementation would be synchronous version
44
+ throw new Error("Not implemented");
45
+ }
46
+
47
+ /**
48
+ * Asynchronous initialization (current pattern)
49
+ */
50
+ static async initialize(config?: TagLibConfig): Promise<EnhancedTagLib> {
51
+ // Current implementation
52
+ throw new Error("Not implemented");
53
+ }
54
+
55
+ /**
56
+ * Open file with flexible input types
57
+ */
58
+ openFile(input: AudioInput): EnhancedAudioFile {
59
+ if (typeof input === 'string') {
60
+ return this.openFileFromPath(input);
61
+ } else if (input instanceof File) {
62
+ return this.openFileFromBrowserFile(input);
63
+ } else if (input instanceof ArrayBuffer) {
64
+ return this.openFileFromBuffer(new Uint8Array(input));
65
+ } else if (Buffer && Buffer.isBuffer(input)) {
66
+ return this.openFileFromBuffer(new Uint8Array(input));
67
+ } else {
68
+ return this.openFileFromBuffer(input);
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Async file opening with callback pattern
74
+ */
75
+ openFileAsync(input: AudioInput, callback: TagLibCallback<EnhancedAudioFile>): void {
76
+ try {
77
+ const file = this.openFile(input);
78
+ callback(null, file);
79
+ } catch (error) {
80
+ const tagLibError: TagLibError = {
81
+ name: 'TagLibError',
82
+ message: error.message,
83
+ code: 'INVALID_FORMAT',
84
+ details: error.stack,
85
+ };
86
+ callback(tagLibError);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Sync file opening (Node.js/Bun/Deno only)
92
+ */
93
+ openFileSync(path: string): EnhancedAudioFile {
94
+ // Runtime-specific implementation
95
+ if (typeof Deno !== 'undefined') {
96
+ const data = Deno.readFileSync(path);
97
+ return this.openFileFromBuffer(data);
98
+ } else if (typeof Bun !== 'undefined') {
99
+ const file = Bun.file(path);
100
+ const data = new Uint8Array(file.arrayBufferSync());
101
+ return this.openFileFromBuffer(data);
102
+ } else if (typeof require !== 'undefined') {
103
+ const fs = require('fs');
104
+ const data = fs.readFileSync(path);
105
+ return this.openFileFromBuffer(new Uint8Array(data));
106
+ } else {
107
+ throw new Error('Synchronous file reading not supported in browser environment');
108
+ }
109
+ }
110
+
111
+ private openFileFromPath(path: string): EnhancedAudioFile {
112
+ throw new Error("Not implemented");
113
+ }
114
+
115
+ private openFileFromBrowserFile(file: File): EnhancedAudioFile {
116
+ throw new Error("Not implemented - requires async operation");
117
+ }
118
+
119
+ private openFileFromBuffer(buffer: Uint8Array): EnhancedAudioFile {
120
+ throw new Error("Not implemented");
121
+ }
122
+ }
123
+
124
+ /**
125
+ * Enhanced AudioFile with property accessors and better error handling
126
+ */
127
+ export class EnhancedAudioFile {
128
+ // Property accessors (more intuitive than method calls)
129
+ get title(): string | undefined {
130
+ return this.tag().title;
131
+ }
132
+
133
+ set title(value: string | undefined) {
134
+ if (value !== undefined) {
135
+ this.setTitle(value);
136
+ }
137
+ }
138
+
139
+ get artist(): string | undefined {
140
+ return this.tag().artist;
141
+ }
142
+
143
+ set artist(value: string | undefined) {
144
+ if (value !== undefined) {
145
+ this.setArtist(value);
146
+ }
147
+ }
148
+
149
+ get album(): string | undefined {
150
+ return this.tag().album;
151
+ }
152
+
153
+ set album(value: string | undefined) {
154
+ if (value !== undefined) {
155
+ this.setAlbum(value);
156
+ }
157
+ }
158
+
159
+ // Async operations with callbacks
160
+ tagAsync(callback: TagLibCallback<any>): void {
161
+ try {
162
+ const tags = this.tag();
163
+ callback(null, tags);
164
+ } catch (error) {
165
+ const tagLibError: TagLibError = {
166
+ name: 'TagLibError',
167
+ message: error.message,
168
+ code: 'MEMORY_ERROR',
169
+ };
170
+ callback(tagLibError);
171
+ }
172
+ }
173
+
174
+ saveAsync(callback: TagLibCallback<boolean>): void {
175
+ try {
176
+ const result = this.save();
177
+ callback(null, result);
178
+ } catch (error) {
179
+ const tagLibError: TagLibError = {
180
+ name: 'TagLibError',
181
+ message: error.message,
182
+ code: 'PERMISSION_DENIED',
183
+ };
184
+ callback(tagLibError);
185
+ }
186
+ }
187
+
188
+ // Bulk tag setting (convenience method)
189
+ setTags(tags: {
190
+ title?: string;
191
+ artist?: string;
192
+ album?: string;
193
+ year?: number;
194
+ genre?: string;
195
+ track?: number;
196
+ comment?: string;
197
+ }): void {
198
+ Object.entries(tags).forEach(([key, value]) => {
199
+ if (value !== undefined) {
200
+ (this as any)[key] = value;
201
+ }
202
+ });
203
+ }
204
+
205
+ // Enhanced error handling for save operations
206
+ saveWithValidation(): { success: boolean; errors: TagLibError[] } {
207
+ const errors: TagLibError[] = [];
208
+
209
+ try {
210
+ // Validate before saving
211
+ if (!this.isValid()) {
212
+ errors.push({
213
+ name: 'TagLibError',
214
+ message: 'File is not valid for writing',
215
+ code: 'INVALID_FORMAT',
216
+ });
217
+ }
218
+
219
+ const success = this.save();
220
+ return { success, errors };
221
+ } catch (error) {
222
+ errors.push({
223
+ name: 'TagLibError',
224
+ message: error.message,
225
+ code: 'PERMISSION_DENIED',
226
+ });
227
+ return { success: false, errors };
228
+ }
229
+ }
230
+
231
+ // Method stubs (would delegate to existing implementation)
232
+ private tag(): any { throw new Error("Not implemented"); }
233
+ private setTitle(title: string): void { throw new Error("Not implemented"); }
234
+ private setArtist(artist: string): void { throw new Error("Not implemented"); }
235
+ private setAlbum(album: string): void { throw new Error("Not implemented"); }
236
+ private save(): boolean { throw new Error("Not implemented"); }
237
+ private isValid(): boolean { throw new Error("Not implemented"); }
238
+ }
239
+
240
+ /**
241
+ * Usage examples demonstrating enhanced API
242
+ */
243
+ export function demonstrateEnhancedAPI() {
244
+ // Example 1: Property-based access
245
+ const file = new EnhancedAudioFile();
246
+ file.title = "New Song";
247
+ file.artist = "New Artist";
248
+ console.log(`${file.artist} - ${file.title}`);
249
+
250
+ // Example 2: Bulk tag setting
251
+ file.setTags({
252
+ title: "Song Title",
253
+ artist: "Artist Name",
254
+ album: "Album Name",
255
+ year: 2024,
256
+ });
257
+
258
+ // Example 3: Enhanced error handling
259
+ const result = file.saveWithValidation();
260
+ if (!result.success) {
261
+ result.errors.forEach(error => {
262
+ console.error(`Error ${error.code}: ${error.message}`);
263
+ });
264
+ }
265
+
266
+ // Example 4: Callback-style async operations
267
+ file.tagAsync((err, tags) => {
268
+ if (err) {
269
+ console.error(`Failed to read tags: ${err.message}`);
270
+ return;
271
+ }
272
+ console.log('Tags:', tags);
273
+ });
274
+ }
package/src/mod.ts ADDED
@@ -0,0 +1,8 @@
1
+ /**
2
+ * @fileoverview TagLib WebAssembly TypeScript bindings
3
+ * Universal audio metadata handling for Deno, Node.js, and browsers
4
+ */
5
+
6
+ export * from "./taglib.ts";
7
+ export * from "./types.ts";
8
+ export { type TagLibModule } from "./wasm.ts";
package/src/taglib.ts ADDED
@@ -0,0 +1,441 @@
1
+ /**
2
+ * @fileoverview Main TagLib API wrapper
3
+ */
4
+
5
+ import type {
6
+ AudioFormat,
7
+ AudioProperties,
8
+ ExtendedTag,
9
+ FieldMapping,
10
+ Picture,
11
+ PropertyMap,
12
+ Tag,
13
+ TagLibConfig,
14
+ } from "./types.ts";
15
+ import { METADATA_MAPPINGS } from "./types.ts";
16
+ import {
17
+ cStringToJS,
18
+ jsToCString,
19
+ loadTagLibModule,
20
+ type TagLibModule,
21
+ } from "./wasm.ts";
22
+
23
+ /**
24
+ * Represents an audio file with metadata and properties
25
+ */
26
+ export class AudioFile {
27
+ private module: TagLibModule;
28
+ private fileId: number;
29
+ private tagPtr: number;
30
+ private propsPtr: number;
31
+
32
+ constructor(module: TagLibModule, fileId: number) {
33
+ this.module = module;
34
+ this.fileId = fileId;
35
+ this.tagPtr = module._taglib_file_tag(fileId);
36
+ this.propsPtr = module._taglib_file_audioproperties(fileId);
37
+ }
38
+
39
+ /**
40
+ * Check if the file is valid and was loaded successfully
41
+ */
42
+ isValid(): boolean {
43
+ return this.module._taglib_file_is_valid(this.fileId) !== 0;
44
+ }
45
+
46
+ /**
47
+ * Get the file format
48
+ */
49
+ format(): AudioFormat {
50
+ const formatPtr = this.module._taglib_file_format(this.fileId);
51
+ if (formatPtr === 0) return "MP3"; // fallback
52
+ const formatStr = cStringToJS(this.module, formatPtr);
53
+ return formatStr as AudioFormat;
54
+ }
55
+
56
+ /**
57
+ * Get basic tag information
58
+ */
59
+ tag(): Tag {
60
+ if (this.tagPtr === 0) return {};
61
+
62
+ const title = this.module._taglib_tag_title(this.tagPtr);
63
+ const artist = this.module._taglib_tag_artist(this.tagPtr);
64
+ const album = this.module._taglib_tag_album(this.tagPtr);
65
+ const comment = this.module._taglib_tag_comment(this.tagPtr);
66
+ const genre = this.module._taglib_tag_genre(this.tagPtr);
67
+ const year = this.module._taglib_tag_year(this.tagPtr);
68
+ const track = this.module._taglib_tag_track(this.tagPtr);
69
+
70
+ return {
71
+ title: title ? cStringToJS(this.module, title) : undefined,
72
+ artist: artist ? cStringToJS(this.module, artist) : undefined,
73
+ album: album ? cStringToJS(this.module, album) : undefined,
74
+ comment: comment ? cStringToJS(this.module, comment) : undefined,
75
+ genre: genre ? cStringToJS(this.module, genre) : undefined,
76
+ year: year || undefined,
77
+ track: track || undefined,
78
+ };
79
+ }
80
+
81
+ /**
82
+ * Get audio properties (duration, bitrate, etc.)
83
+ */
84
+ audioProperties(): AudioProperties | null {
85
+ if (this.propsPtr === 0) return null;
86
+
87
+ const length = this.module._taglib_audioproperties_length(this.propsPtr);
88
+ const bitrate = this.module._taglib_audioproperties_bitrate(this.propsPtr);
89
+ const sampleRate = this.module._taglib_audioproperties_samplerate(
90
+ this.propsPtr,
91
+ );
92
+ const channels = this.module._taglib_audioproperties_channels(
93
+ this.propsPtr,
94
+ );
95
+
96
+ return {
97
+ length,
98
+ bitrate,
99
+ sampleRate,
100
+ channels,
101
+ format: this.format(),
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Set the title tag
107
+ */
108
+ setTitle(title: string): void {
109
+ if (this.tagPtr === 0) return;
110
+ const titlePtr = jsToCString(this.module, title);
111
+ this.module._taglib_tag_set_title(this.tagPtr, titlePtr);
112
+ this.module._free(titlePtr);
113
+ }
114
+
115
+ /**
116
+ * Set the artist tag
117
+ */
118
+ setArtist(artist: string): void {
119
+ if (this.tagPtr === 0) return;
120
+ const artistPtr = jsToCString(this.module, artist);
121
+ this.module._taglib_tag_set_artist(this.tagPtr, artistPtr);
122
+ this.module._free(artistPtr);
123
+ }
124
+
125
+ /**
126
+ * Set the album tag
127
+ */
128
+ setAlbum(album: string): void {
129
+ if (this.tagPtr === 0) return;
130
+ const albumPtr = jsToCString(this.module, album);
131
+ this.module._taglib_tag_set_album(this.tagPtr, albumPtr);
132
+ this.module._free(albumPtr);
133
+ }
134
+
135
+ /**
136
+ * Set the comment tag
137
+ */
138
+ setComment(comment: string): void {
139
+ if (this.tagPtr === 0) return;
140
+ const commentPtr = jsToCString(this.module, comment);
141
+ this.module._taglib_tag_set_comment(this.tagPtr, commentPtr);
142
+ this.module._free(commentPtr);
143
+ }
144
+
145
+ /**
146
+ * Set the genre tag
147
+ */
148
+ setGenre(genre: string): void {
149
+ if (this.tagPtr === 0) return;
150
+ const genrePtr = jsToCString(this.module, genre);
151
+ this.module._taglib_tag_set_genre(this.tagPtr, genrePtr);
152
+ this.module._free(genrePtr);
153
+ }
154
+
155
+ /**
156
+ * Set the year tag
157
+ */
158
+ setYear(year: number): void {
159
+ if (this.tagPtr === 0) return;
160
+ this.module._taglib_tag_set_year(this.tagPtr, year);
161
+ }
162
+
163
+ /**
164
+ * Set the track number tag
165
+ */
166
+ setTrack(track: number): void {
167
+ if (this.tagPtr === 0) return;
168
+ this.module._taglib_tag_set_track(this.tagPtr, track);
169
+ }
170
+
171
+ /**
172
+ * Save changes to the file
173
+ */
174
+ save(): boolean {
175
+ return this.module._taglib_file_save(this.fileId) !== 0;
176
+ }
177
+
178
+ /**
179
+ * Get extended metadata with format-agnostic field names
180
+ */
181
+ extendedTag(): ExtendedTag {
182
+ // Get basic tags first
183
+ const basicTag = this.tag();
184
+
185
+ // TODO: Implement advanced metadata reading via PropertyMap
186
+ // For now, return basic tags with placeholder for advanced fields
187
+ return {
188
+ ...basicTag,
189
+ // Advanced fields would be populated by PropertyMap reading
190
+ acoustidFingerprint: undefined,
191
+ acoustidId: undefined,
192
+ musicbrainzTrackId: undefined,
193
+ musicbrainzReleaseId: undefined,
194
+ musicbrainzArtistId: undefined,
195
+ musicbrainzReleaseGroupId: undefined,
196
+ albumArtist: undefined,
197
+ composer: undefined,
198
+ discNumber: undefined,
199
+ totalTracks: undefined,
200
+ totalDiscs: undefined,
201
+ bpm: undefined,
202
+ compilation: undefined,
203
+ titleSort: undefined,
204
+ artistSort: undefined,
205
+ albumSort: undefined,
206
+ // ReplayGain fields
207
+ replayGainTrackGain: undefined,
208
+ replayGainTrackPeak: undefined,
209
+ replayGainAlbumGain: undefined,
210
+ replayGainAlbumPeak: undefined,
211
+ // Apple Sound Check
212
+ appleSoundCheck: undefined,
213
+ };
214
+ }
215
+
216
+ /**
217
+ * Set extended metadata using format-agnostic field names
218
+ * The library automatically maps fields to the correct format-specific storage
219
+ */
220
+ setExtendedTag(tag: Partial<ExtendedTag>): void {
221
+ // Set basic tags using existing methods
222
+ if (tag.title !== undefined) this.setTitle(tag.title);
223
+ if (tag.artist !== undefined) this.setArtist(tag.artist);
224
+ if (tag.album !== undefined) this.setAlbum(tag.album);
225
+ if (tag.comment !== undefined) this.setComment(tag.comment);
226
+ if (tag.genre !== undefined) this.setGenre(tag.genre);
227
+ if (tag.year !== undefined) this.setYear(tag.year);
228
+ if (tag.track !== undefined) this.setTrack(tag.track);
229
+
230
+ // TODO: Implement advanced metadata writing via PropertyMap
231
+ // For fields like acoustidFingerprint, acoustidId, etc.
232
+ }
233
+
234
+ /**
235
+ * Set AcoustID fingerprint (format-agnostic)
236
+ * Automatically stores in the correct location for each format:
237
+ * - MP3: TXXX frame with "Acoustid Fingerprint" description
238
+ * - FLAC/OGG: ACOUSTID_FINGERPRINT Vorbis comment
239
+ * - MP4: ----:com.apple.iTunes:Acoustid Fingerprint atom
240
+ */
241
+ setAcoustidFingerprint(fingerprint: string): void {
242
+ // TODO: Implement format-specific storage
243
+ console.warn("setAcoustidFingerprint: Advanced metadata not yet implemented");
244
+ }
245
+
246
+ /**
247
+ * Get AcoustID fingerprint (format-agnostic)
248
+ */
249
+ getAcoustidFingerprint(): string | undefined {
250
+ // TODO: Implement format-specific reading
251
+ return undefined;
252
+ }
253
+
254
+ /**
255
+ * Set AcoustID UUID (format-agnostic)
256
+ * Automatically stores in the correct location for each format:
257
+ * - MP3: TXXX frame with "Acoustid Id" description
258
+ * - FLAC/OGG: ACOUSTID_ID Vorbis comment
259
+ * - MP4: ----:com.apple.iTunes:Acoustid Id atom
260
+ */
261
+ setAcoustidId(id: string): void {
262
+ // TODO: Implement format-specific storage
263
+ console.warn("setAcoustidId: Advanced metadata not yet implemented");
264
+ }
265
+
266
+ /**
267
+ * Get AcoustID UUID (format-agnostic)
268
+ */
269
+ getAcoustidId(): string | undefined {
270
+ // TODO: Implement format-specific reading
271
+ return undefined;
272
+ }
273
+
274
+ /**
275
+ * Set MusicBrainz Track ID (format-agnostic)
276
+ */
277
+ setMusicBrainzTrackId(id: string): void {
278
+ // TODO: Implement format-specific storage
279
+ console.warn("setMusicBrainzTrackId: Advanced metadata not yet implemented");
280
+ }
281
+
282
+ /**
283
+ * Get MusicBrainz Track ID (format-agnostic)
284
+ */
285
+ getMusicBrainzTrackId(): string | undefined {
286
+ // TODO: Implement format-specific reading
287
+ return undefined;
288
+ }
289
+
290
+ /**
291
+ * Set ReplayGain track gain (format-agnostic)
292
+ * Automatically stores in the correct location for each format:
293
+ * - MP3: TXXX frame with "ReplayGain_Track_Gain" description
294
+ * - FLAC/OGG: REPLAYGAIN_TRACK_GAIN Vorbis comment
295
+ * - MP4: ----:com.apple.iTunes:replaygain_track_gain atom
296
+ */
297
+ setReplayGainTrackGain(gain: string): void {
298
+ // TODO: Implement format-specific storage
299
+ console.warn("setReplayGainTrackGain: Advanced metadata not yet implemented");
300
+ }
301
+
302
+ /**
303
+ * Get ReplayGain track gain (format-agnostic)
304
+ */
305
+ getReplayGainTrackGain(): string | undefined {
306
+ // TODO: Implement format-specific reading
307
+ return undefined;
308
+ }
309
+
310
+ /**
311
+ * Set ReplayGain track peak (format-agnostic)
312
+ */
313
+ setReplayGainTrackPeak(peak: string): void {
314
+ // TODO: Implement format-specific storage
315
+ console.warn("setReplayGainTrackPeak: Advanced metadata not yet implemented");
316
+ }
317
+
318
+ /**
319
+ * Get ReplayGain track peak (format-agnostic)
320
+ */
321
+ getReplayGainTrackPeak(): string | undefined {
322
+ // TODO: Implement format-specific reading
323
+ return undefined;
324
+ }
325
+
326
+ /**
327
+ * Set ReplayGain album gain (format-agnostic)
328
+ */
329
+ setReplayGainAlbumGain(gain: string): void {
330
+ // TODO: Implement format-specific storage
331
+ console.warn("setReplayGainAlbumGain: Advanced metadata not yet implemented");
332
+ }
333
+
334
+ /**
335
+ * Get ReplayGain album gain (format-agnostic)
336
+ */
337
+ getReplayGainAlbumGain(): string | undefined {
338
+ // TODO: Implement format-specific reading
339
+ return undefined;
340
+ }
341
+
342
+ /**
343
+ * Set ReplayGain album peak (format-agnostic)
344
+ */
345
+ setReplayGainAlbumPeak(peak: string): void {
346
+ // TODO: Implement format-specific storage
347
+ console.warn("setReplayGainAlbumPeak: Advanced metadata not yet implemented");
348
+ }
349
+
350
+ /**
351
+ * Get ReplayGain album peak (format-agnostic)
352
+ */
353
+ getReplayGainAlbumPeak(): string | undefined {
354
+ // TODO: Implement format-specific reading
355
+ return undefined;
356
+ }
357
+
358
+ /**
359
+ * Set Apple Sound Check normalization data (format-agnostic)
360
+ * Automatically stores in the correct location for each format:
361
+ * - MP3: TXXX frame with "iTunNORM" description
362
+ * - FLAC/OGG: ITUNNORM Vorbis comment
363
+ * - MP4: ----:com.apple.iTunes:iTunNORM atom
364
+ */
365
+ setAppleSoundCheck(iTunNORM: string): void {
366
+ // TODO: Implement format-specific storage
367
+ console.warn("setAppleSoundCheck: Advanced metadata not yet implemented");
368
+ }
369
+
370
+ /**
371
+ * Get Apple Sound Check normalization data (format-agnostic)
372
+ */
373
+ getAppleSoundCheck(): string | undefined {
374
+ // TODO: Implement format-specific reading
375
+ return undefined;
376
+ }
377
+
378
+ /**
379
+ * Clean up resources
380
+ */
381
+ dispose(): void {
382
+ if (this.fileId !== 0) {
383
+ this.module._taglib_file_delete(this.fileId);
384
+ this.fileId = 0;
385
+ }
386
+ }
387
+ }
388
+
389
+ /**
390
+ * Main TagLib class for creating and managing audio files
391
+ */
392
+ export class TagLib {
393
+ private module: TagLibModule;
394
+
395
+ private constructor(module: TagLibModule) {
396
+ this.module = module;
397
+ }
398
+
399
+ /**
400
+ * Initialize TagLib with optional configuration
401
+ */
402
+ static async initialize(config?: TagLibConfig): Promise<TagLib> {
403
+ const module = await loadTagLibModule(config);
404
+ return new TagLib(module);
405
+ }
406
+
407
+ /**
408
+ * Open an audio file from a buffer
409
+ */
410
+ openFile(buffer: Uint8Array): AudioFile {
411
+ if (!this.module.HEAPU8) {
412
+ throw new Error("WASM module not properly initialized - missing HEAPU8");
413
+ }
414
+
415
+ // Use Emscripten's allocate function for proper memory management
416
+ const dataPtr = this.module.allocate(buffer, this.module.ALLOC_NORMAL);
417
+
418
+ const fileId = this.module._taglib_file_new_from_buffer(
419
+ dataPtr,
420
+ buffer.length,
421
+ );
422
+
423
+ if (fileId === 0) {
424
+ // Don't free memory immediately to debug reuse issue
425
+ console.log(`DEBUG: File creation failed, not freeing memory at ${dataPtr}`);
426
+ throw new Error("Failed to open audio file - invalid format or corrupted data");
427
+ }
428
+
429
+ // Free the temporary buffer copy (TagLib has made its own copy in ByteVector)
430
+ this.module._free(dataPtr);
431
+
432
+ return new AudioFile(this.module, fileId);
433
+ }
434
+
435
+ /**
436
+ * Get the underlying WASM module (for advanced usage)
437
+ */
438
+ getModule(): TagLibModule {
439
+ return this.module;
440
+ }
441
+ }