taglib-wasm 0.2.7 → 0.2.8

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.ts CHANGED
@@ -1,606 +1,261 @@
1
- /**
2
- * @fileoverview Main TagLib API wrapper
3
- */
1
+ import type { TagLibModule, WasmModule } from "./wasm-jsr.ts";
2
+ import type { AudioProperties, FileType, PropertyMap, Tag as BasicTag } from "./types.ts";
3
+
4
+ // Extended Tag interface with setters
5
+ export interface Tag extends BasicTag {
6
+ setTitle(value: string): void;
7
+ setArtist(value: string): void;
8
+ setAlbum(value: string): void;
9
+ setComment(value: string): void;
10
+ setGenre(value: string): void;
11
+ setYear(value: number): void;
12
+ setTrack(value: number): void;
13
+ }
4
14
 
5
- import type {
6
- AudioFormat,
7
- AudioProperties,
8
- BitrateControlMode,
9
- ExtendedTag,
10
- FieldMapping,
11
- Picture,
12
- PropertyMap,
13
- Tag,
14
- TagLibConfig,
15
- } from "./types.ts";
16
- import {
17
- BITRATE_CONTROL_MODE_NAMES,
18
- BITRATE_CONTROL_MODE_VALUES,
19
- METADATA_MAPPINGS
20
- } from "./types.ts";
21
- import {
22
- cStringToJS,
23
- jsToCString,
24
- loadTagLibModule,
25
- type TagLibModule,
26
- } from "./wasm.ts";
15
+ // Re-export types for JSR
16
+ export type { AudioProperties, FileType, PropertyMap } from "./types.ts";
17
+
18
+ export interface AudioFile {
19
+ getFormat(): FileType;
20
+ tag(): Tag;
21
+ audioProperties(): AudioProperties | null;
22
+ properties(): PropertyMap;
23
+ setProperties(properties: PropertyMap): void;
24
+ getProperty(key: string): string | undefined;
25
+ setProperty(key: string, value: string): void;
26
+ isMP4(): boolean;
27
+ getMP4Item(key: string): string | undefined;
28
+ setMP4Item(key: string, value: string): void;
29
+ removeMP4Item(key: string): void;
30
+ save(): boolean;
31
+ isValid(): boolean;
32
+ dispose(): void;
33
+ }
27
34
 
28
35
  /**
29
- * Represents an audio file with metadata and properties
36
+ * Audio file wrapper using Embind API
30
37
  */
31
- export class AudioFile {
32
- private module: TagLibModule;
33
- private fileId: number;
34
- private tagPtr: number;
35
- private propsPtr: number;
38
+ export class AudioFileImpl implements AudioFile {
39
+ private fileHandle: any;
40
+ private cachedTag: Tag | null = null;
41
+ private cachedAudioProperties: AudioProperties | null = null;
36
42
 
37
- constructor(module: TagLibModule, fileId: number) {
38
- this.module = module;
39
- this.fileId = fileId;
40
- this.tagPtr = module._taglib_file_tag(fileId);
41
- this.propsPtr = module._taglib_file_audioproperties(fileId);
43
+ constructor(
44
+ private module: TagLibModule,
45
+ fileHandle: any,
46
+ ) {
47
+ this.fileHandle = fileHandle;
42
48
  }
43
49
 
44
50
  /**
45
- * Check if the file is valid and was loaded successfully
51
+ * Get file format
46
52
  */
47
- isValid(): boolean {
48
- return this.module._taglib_file_is_valid(this.fileId) !== 0;
53
+ getFormat(): FileType {
54
+ return this.fileHandle.getFormat() as FileType;
49
55
  }
50
56
 
51
57
  /**
52
- * Get the file format
53
- */
54
- format(): AudioFormat {
55
- const formatPtr = this.module._taglib_file_format(this.fileId);
56
- if (formatPtr === 0) return "MP3"; // fallback
57
- const formatStr = cStringToJS(this.module, formatPtr);
58
- return formatStr as AudioFormat;
59
- }
60
-
61
- /**
62
- * Get basic tag information
58
+ * Get tag object for reading/writing metadata
63
59
  */
64
60
  tag(): Tag {
65
- if (this.tagPtr === 0) return {};
66
-
67
- const title = this.module._taglib_tag_title(this.tagPtr);
68
- const artist = this.module._taglib_tag_artist(this.tagPtr);
69
- const album = this.module._taglib_tag_album(this.tagPtr);
70
- const comment = this.module._taglib_tag_comment(this.tagPtr);
71
- const genre = this.module._taglib_tag_genre(this.tagPtr);
72
- const year = this.module._taglib_tag_year(this.tagPtr);
73
- const track = this.module._taglib_tag_track(this.tagPtr);
74
-
61
+ const tagWrapper = this.fileHandle.getTag();
62
+ if (!tagWrapper) {
63
+ throw new Error("Failed to get tag from file");
64
+ }
65
+
75
66
  return {
76
- title: title ? cStringToJS(this.module, title) : undefined,
77
- artist: artist ? cStringToJS(this.module, artist) : undefined,
78
- album: album ? cStringToJS(this.module, album) : undefined,
79
- comment: comment ? cStringToJS(this.module, comment) : undefined,
80
- genre: genre ? cStringToJS(this.module, genre) : undefined,
81
- year: year || undefined,
82
- track: track || undefined,
67
+ title: tagWrapper.title(),
68
+ artist: tagWrapper.artist(),
69
+ album: tagWrapper.album(),
70
+ comment: tagWrapper.comment(),
71
+ genre: tagWrapper.genre(),
72
+ year: tagWrapper.year(),
73
+ track: tagWrapper.track(),
74
+
75
+ setTitle: (value: string) => tagWrapper.setTitle(value),
76
+ setArtist: (value: string) => tagWrapper.setArtist(value),
77
+ setAlbum: (value: string) => tagWrapper.setAlbum(value),
78
+ setComment: (value: string) => tagWrapper.setComment(value),
79
+ setGenre: (value: string) => tagWrapper.setGenre(value),
80
+ setYear: (value: number) => tagWrapper.setYear(value),
81
+ setTrack: (value: number) => tagWrapper.setTrack(value),
83
82
  };
84
83
  }
85
84
 
86
85
  /**
87
- * Get audio properties (duration, bitrate, etc.)
86
+ * Get audio properties
88
87
  */
89
88
  audioProperties(): AudioProperties | null {
90
- if (this.propsPtr === 0) return null;
91
-
92
- const length = this.module._taglib_audioproperties_length(this.propsPtr);
93
- const bitrate = this.module._taglib_audioproperties_bitrate(this.propsPtr);
94
- const sampleRate = this.module._taglib_audioproperties_samplerate(
95
- this.propsPtr,
96
- );
97
- const channels = this.module._taglib_audioproperties_channels(
98
- this.propsPtr,
99
- );
100
-
101
- return {
102
- length,
103
- bitrate,
104
- sampleRate,
105
- channels,
106
- format: this.format(),
107
- };
108
- }
109
-
110
- /**
111
- * Set the title tag
112
- */
113
- setTitle(title: string): void {
114
- if (this.tagPtr === 0) return;
115
- const titlePtr = jsToCString(this.module, title);
116
- this.module._taglib_tag_set_title(this.tagPtr, titlePtr);
117
- this.module._free(titlePtr);
118
- }
119
-
120
- /**
121
- * Set the artist tag
122
- */
123
- setArtist(artist: string): void {
124
- if (this.tagPtr === 0) return;
125
- const artistPtr = jsToCString(this.module, artist);
126
- this.module._taglib_tag_set_artist(this.tagPtr, artistPtr);
127
- this.module._free(artistPtr);
128
- }
129
-
130
- /**
131
- * Set the album tag
132
- */
133
- setAlbum(album: string): void {
134
- if (this.tagPtr === 0) return;
135
- const albumPtr = jsToCString(this.module, album);
136
- this.module._taglib_tag_set_album(this.tagPtr, albumPtr);
137
- this.module._free(albumPtr);
138
- }
139
-
140
- /**
141
- * Set the comment tag
142
- */
143
- setComment(comment: string): void {
144
- if (this.tagPtr === 0) return;
145
- const commentPtr = jsToCString(this.module, comment);
146
- this.module._taglib_tag_set_comment(this.tagPtr, commentPtr);
147
- this.module._free(commentPtr);
148
- }
149
-
150
- /**
151
- * Set the genre tag
152
- */
153
- setGenre(genre: string): void {
154
- if (this.tagPtr === 0) return;
155
- const genrePtr = jsToCString(this.module, genre);
156
- this.module._taglib_tag_set_genre(this.tagPtr, genrePtr);
157
- this.module._free(genrePtr);
158
- }
159
-
160
- /**
161
- * Set the year tag
162
- */
163
- setYear(year: number): void {
164
- if (this.tagPtr === 0) return;
165
- this.module._taglib_tag_set_year(this.tagPtr, year);
166
- }
167
-
168
- /**
169
- * Set the track number tag
170
- */
171
- setTrack(track: number): void {
172
- if (this.tagPtr === 0) return;
173
- this.module._taglib_tag_set_track(this.tagPtr, track);
89
+ if (!this.cachedAudioProperties) {
90
+ const propsWrapper = this.fileHandle.getAudioProperties();
91
+ if (!propsWrapper) {
92
+ return null;
93
+ }
94
+
95
+ this.cachedAudioProperties = {
96
+ length: propsWrapper.lengthInSeconds(),
97
+ bitrate: propsWrapper.bitrate(),
98
+ sampleRate: propsWrapper.sampleRate(),
99
+ channels: propsWrapper.channels(),
100
+ };
101
+ }
102
+
103
+ return this.cachedAudioProperties;
174
104
  }
175
105
 
176
106
  /**
177
107
  * Get all properties as a PropertyMap
178
108
  */
179
109
  properties(): PropertyMap {
180
- const jsonPtr = this.module._taglib_file_properties_json(this.fileId);
181
- if (jsonPtr === 0) return {};
110
+ const jsObj = this.fileHandle.getProperties();
111
+ const result: PropertyMap = {};
182
112
 
183
- const jsonStr = cStringToJS(this.module, jsonPtr);
184
- try {
185
- return JSON.parse(jsonStr);
186
- } catch {
187
- return {};
113
+ // Convert from Emscripten val to plain object
114
+ const keys = Object.keys(jsObj);
115
+ for (const key of keys) {
116
+ result[key] = jsObj[key];
188
117
  }
118
+
119
+ return result;
189
120
  }
190
121
 
191
122
  /**
192
123
  * Set properties from a PropertyMap
193
124
  */
194
- setProperties(properties: PropertyMap): boolean {
195
- const jsonStr = JSON.stringify(properties);
196
- const jsonPtr = jsToCString(this.module, jsonStr);
197
- try {
198
- return this.module._taglib_file_set_properties_json(this.fileId, jsonPtr) !== 0;
199
- } finally {
200
- this.module._free(jsonPtr);
201
- }
125
+ setProperties(properties: PropertyMap): void {
126
+ this.fileHandle.setProperties(properties);
202
127
  }
203
128
 
204
129
  /**
205
- * Get a specific property value
130
+ * Get a single property value
206
131
  */
207
- getProperty(key: string): string[] | undefined {
208
- const keyPtr = jsToCString(this.module, key);
209
- try {
210
- const valuePtr = this.module._taglib_file_get_property(this.fileId, keyPtr);
211
- if (valuePtr === 0) return undefined;
212
-
213
- const value = cStringToJS(this.module, valuePtr);
214
- return value ? [value] : undefined;
215
- } finally {
216
- this.module._free(keyPtr);
217
- }
132
+ getProperty(key: string): string | undefined {
133
+ const value = this.fileHandle.getProperty(key);
134
+ return value === "" ? undefined : value;
218
135
  }
219
136
 
220
137
  /**
221
- * Set a specific property value
138
+ * Set a single property value
222
139
  */
223
- setProperty(key: string, values: string | string[]): boolean {
224
- const value = Array.isArray(values) ? values[0] : values;
225
- if (!value) return false;
226
-
227
- const keyPtr = jsToCString(this.module, key);
228
- const valuePtr = jsToCString(this.module, value);
229
- try {
230
- return this.module._taglib_file_set_property(this.fileId, keyPtr, valuePtr) !== 0;
231
- } finally {
232
- this.module._free(keyPtr);
233
- this.module._free(valuePtr);
234
- }
140
+ setProperty(key: string, value: string): void {
141
+ this.fileHandle.setProperty(key, value);
235
142
  }
236
143
 
237
144
  /**
238
145
  * Check if this is an MP4 file
239
146
  */
240
147
  isMP4(): boolean {
241
- return this.module._taglib_file_is_mp4(this.fileId) !== 0;
148
+ return this.fileHandle.isMP4();
242
149
  }
243
150
 
244
151
  /**
245
- * Get MP4-specific item (for custom atoms)
152
+ * Get MP4-specific item
246
153
  */
247
154
  getMP4Item(key: string): string | undefined {
248
- if (!this.isMP4()) return undefined;
249
-
250
- const keyPtr = jsToCString(this.module, key);
251
- try {
252
- const valuePtr = this.module._taglib_mp4_get_item(this.fileId, keyPtr);
253
- if (valuePtr === 0) return undefined;
254
-
255
- return cStringToJS(this.module, valuePtr);
256
- } finally {
257
- this.module._free(keyPtr);
155
+ if (!this.isMP4()) {
156
+ throw new Error("Not an MP4 file");
258
157
  }
158
+ const value = this.fileHandle.getMP4Item(key);
159
+ return value === "" ? undefined : value;
259
160
  }
260
161
 
261
162
  /**
262
- * Set MP4-specific item (for custom atoms)
163
+ * Set MP4-specific item
263
164
  */
264
- setMP4Item(key: string, value: string): boolean {
265
- if (!this.isMP4()) return false;
266
-
267
- const keyPtr = jsToCString(this.module, key);
268
- const valuePtr = jsToCString(this.module, value);
269
- try {
270
- return this.module._taglib_mp4_set_item(this.fileId, keyPtr, valuePtr) !== 0;
271
- } finally {
272
- this.module._free(keyPtr);
273
- this.module._free(valuePtr);
165
+ setMP4Item(key: string, value: string): void {
166
+ if (!this.isMP4()) {
167
+ throw new Error("Not an MP4 file");
274
168
  }
169
+ this.fileHandle.setMP4Item(key, value);
275
170
  }
276
171
 
277
172
  /**
278
173
  * Remove MP4-specific item
279
174
  */
280
- removeMP4Item(key: string): boolean {
281
- if (!this.isMP4()) return false;
282
-
283
- const keyPtr = jsToCString(this.module, key);
284
- try {
285
- return this.module._taglib_mp4_remove_item(this.fileId, keyPtr) !== 0;
286
- } finally {
287
- this.module._free(keyPtr);
175
+ removeMP4Item(key: string): void {
176
+ if (!this.isMP4()) {
177
+ throw new Error("Not an MP4 file");
288
178
  }
289
- }
290
-
291
- /**
292
- * Get bitrate control mode (MP4/M4A specific)
293
- * Reads from the 'acbf' atom
294
- */
295
- getBitrateControlMode(): BitrateControlMode | undefined {
296
- if (!this.isMP4()) return undefined;
297
-
298
- const value = this.getMP4Item("acbf");
299
- if (!value) return undefined;
300
-
301
- const numValue = parseInt(value, 10);
302
- if (isNaN(numValue) || numValue < 0 || numValue > 3) return undefined;
303
-
304
- return BITRATE_CONTROL_MODE_NAMES[numValue];
305
- }
306
-
307
- /**
308
- * Set bitrate control mode (MP4/M4A specific)
309
- * Writes to the 'acbf' atom
310
- */
311
- setBitrateControlMode(mode: BitrateControlMode): boolean {
312
- if (!this.isMP4()) return false;
313
-
314
- const numValue = BITRATE_CONTROL_MODE_VALUES[mode];
315
- if (numValue === undefined) return false;
316
-
317
- return this.setMP4Item("acbf", numValue.toString());
179
+ this.fileHandle.removeMP4Item(key);
318
180
  }
319
181
 
320
182
  /**
321
183
  * Save changes to the file
322
184
  */
323
185
  save(): boolean {
324
- return this.module._taglib_file_save(this.fileId) !== 0;
325
- }
326
-
327
- /**
328
- * Get extended metadata with format-agnostic field names
329
- */
330
- extendedTag(): ExtendedTag {
331
- // Get basic tags first
332
- const basicTag = this.tag();
333
-
334
- // TODO: Implement automatic tag mapping reading via PropertyMap
335
- // For now, return basic tags with placeholder for advanced fields
336
- return {
337
- ...basicTag,
338
- // Advanced fields would be populated by PropertyMap reading
339
- acoustidFingerprint: undefined,
340
- acoustidId: undefined,
341
- musicbrainzTrackId: undefined,
342
- musicbrainzReleaseId: undefined,
343
- musicbrainzArtistId: undefined,
344
- musicbrainzReleaseGroupId: undefined,
345
- albumArtist: undefined,
346
- composer: undefined,
347
- discNumber: undefined,
348
- totalTracks: undefined,
349
- totalDiscs: undefined,
350
- bpm: undefined,
351
- compilation: undefined,
352
- titleSort: undefined,
353
- artistSort: undefined,
354
- albumSort: undefined,
355
- // ReplayGain fields
356
- replayGainTrackGain: undefined,
357
- replayGainTrackPeak: undefined,
358
- replayGainAlbumGain: undefined,
359
- replayGainAlbumPeak: undefined,
360
- // Apple Sound Check
361
- appleSoundCheck: undefined,
362
- };
363
- }
364
-
365
- /**
366
- * Set extended metadata using format-agnostic field names
367
- * The library automatically maps fields to the correct format-specific storage
368
- */
369
- setExtendedTag(tag: Partial<ExtendedTag>): void {
370
- // Set basic tags using existing methods
371
- if (tag.title !== undefined) this.setTitle(tag.title);
372
- if (tag.artist !== undefined) this.setArtist(tag.artist);
373
- if (tag.album !== undefined) this.setAlbum(tag.album);
374
- if (tag.comment !== undefined) this.setComment(tag.comment);
375
- if (tag.genre !== undefined) this.setGenre(tag.genre);
376
- if (tag.year !== undefined) this.setYear(tag.year);
377
- if (tag.track !== undefined) this.setTrack(tag.track);
378
-
379
- // TODO: Implement automatic tag mapping writing via PropertyMap
380
- // For fields like acoustidFingerprint, acoustidId, etc.
381
- }
382
-
383
- /**
384
- * Set AcoustID fingerprint (format-agnostic)
385
- * Automatically stores in the correct location for each format:
386
- * - MP3: TXXX frame with "Acoustid Fingerprint" description
387
- * - FLAC/OGG: ACOUSTID_FINGERPRINT Vorbis comment
388
- * - MP4: ----:com.apple.iTunes:Acoustid Fingerprint atom
389
- */
390
- setAcoustidFingerprint(fingerprint: string): void {
391
- // TODO: Implement format-specific storage
392
- console.warn(
393
- "setAcoustidFingerprint: Advanced metadata not yet implemented",
394
- );
395
- }
396
-
397
- /**
398
- * Get AcoustID fingerprint (format-agnostic)
399
- */
400
- getAcoustidFingerprint(): string | undefined {
401
- // TODO: Implement format-specific reading
402
- return undefined;
403
- }
404
-
405
- /**
406
- * Set AcoustID UUID (format-agnostic)
407
- * Automatically stores in the correct location for each format:
408
- * - MP3: TXXX frame with "Acoustid Id" description
409
- * - FLAC/OGG: ACOUSTID_ID Vorbis comment
410
- * - MP4: ----:com.apple.iTunes:Acoustid Id atom
411
- */
412
- setAcoustidId(id: string): void {
413
- // TODO: Implement format-specific storage
414
- console.warn("setAcoustidId: Advanced metadata not yet implemented");
415
- }
416
-
417
- /**
418
- * Get AcoustID UUID (format-agnostic)
419
- */
420
- getAcoustidId(): string | undefined {
421
- // TODO: Implement format-specific reading
422
- return undefined;
423
- }
424
-
425
- /**
426
- * Set MusicBrainz Track ID (format-agnostic)
427
- */
428
- setMusicBrainzTrackId(id: string): void {
429
- // TODO: Implement format-specific storage
430
- console.warn(
431
- "setMusicBrainzTrackId: Advanced metadata not yet implemented",
432
- );
433
- }
434
-
435
- /**
436
- * Get MusicBrainz Track ID (format-agnostic)
437
- */
438
- getMusicBrainzTrackId(): string | undefined {
439
- // TODO: Implement format-specific reading
440
- return undefined;
441
- }
442
-
443
- /**
444
- * Set ReplayGain track gain (format-agnostic)
445
- * Automatically stores in the correct location for each format:
446
- * - MP3: TXXX frame with "ReplayGain_Track_Gain" description
447
- * - FLAC/OGG: REPLAYGAIN_TRACK_GAIN Vorbis comment
448
- * - MP4: ----:com.apple.iTunes:replaygain_track_gain atom
449
- */
450
- setReplayGainTrackGain(gain: string): void {
451
- // TODO: Implement format-specific storage
452
- console.warn(
453
- "setReplayGainTrackGain: Advanced metadata not yet implemented",
454
- );
455
- }
456
-
457
- /**
458
- * Get ReplayGain track gain (format-agnostic)
459
- */
460
- getReplayGainTrackGain(): string | undefined {
461
- // TODO: Implement format-specific reading
462
- return undefined;
463
- }
464
-
465
- /**
466
- * Set ReplayGain track peak (format-agnostic)
467
- */
468
- setReplayGainTrackPeak(peak: string): void {
469
- // TODO: Implement format-specific storage
470
- console.warn(
471
- "setReplayGainTrackPeak: Advanced metadata not yet implemented",
472
- );
473
- }
474
-
475
- /**
476
- * Get ReplayGain track peak (format-agnostic)
477
- */
478
- getReplayGainTrackPeak(): string | undefined {
479
- // TODO: Implement format-specific reading
480
- return undefined;
481
- }
482
-
483
- /**
484
- * Set ReplayGain album gain (format-agnostic)
485
- */
486
- setReplayGainAlbumGain(gain: string): void {
487
- // TODO: Implement format-specific storage
488
- console.warn(
489
- "setReplayGainAlbumGain: Advanced metadata not yet implemented",
490
- );
491
- }
492
-
493
- /**
494
- * Get ReplayGain album gain (format-agnostic)
495
- */
496
- getReplayGainAlbumGain(): string | undefined {
497
- // TODO: Implement format-specific reading
498
- return undefined;
499
- }
500
-
501
- /**
502
- * Set ReplayGain album peak (format-agnostic)
503
- */
504
- setReplayGainAlbumPeak(peak: string): void {
505
- // TODO: Implement format-specific storage
506
- console.warn(
507
- "setReplayGainAlbumPeak: Advanced metadata not yet implemented",
508
- );
509
- }
510
-
511
- /**
512
- * Get ReplayGain album peak (format-agnostic)
513
- */
514
- getReplayGainAlbumPeak(): string | undefined {
515
- // TODO: Implement format-specific reading
516
- return undefined;
517
- }
518
-
519
- /**
520
- * Set Apple Sound Check normalization data (format-agnostic)
521
- * Automatically stores in the correct location for each format:
522
- * - MP3: TXXX frame with "iTunNORM" description
523
- * - FLAC/OGG: ITUNNORM Vorbis comment
524
- * - MP4: ----:com.apple.iTunes:iTunNORM atom
525
- */
526
- setAppleSoundCheck(iTunNORM: string): void {
527
- // TODO: Implement format-specific storage
528
- console.warn("setAppleSoundCheck: Advanced metadata not yet implemented");
186
+ // Clear caches since values may have changed
187
+ this.cachedTag = null;
188
+ this.cachedAudioProperties = null;
189
+
190
+ return this.fileHandle.save();
529
191
  }
530
192
 
531
193
  /**
532
- * Get Apple Sound Check normalization data (format-agnostic)
194
+ * Check if the file is valid
533
195
  */
534
- getAppleSoundCheck(): string | undefined {
535
- // TODO: Implement format-specific reading
536
- return undefined;
196
+ isValid(): boolean {
197
+ return this.fileHandle.isValid();
537
198
  }
538
199
 
539
200
  /**
540
- * Clean up resources
201
+ * Free resources
541
202
  */
542
203
  dispose(): void {
543
- if (this.fileId !== 0) {
544
- this.module._taglib_file_delete(this.fileId);
545
- this.fileId = 0;
204
+ if (this.fileHandle) {
205
+ // Embind will handle cleanup when the object goes out of scope
206
+ // But we can help by clearing our references
207
+ this.fileHandle = null;
208
+ this.cachedTag = null;
209
+ this.cachedAudioProperties = null;
546
210
  }
547
211
  }
548
212
  }
549
213
 
550
214
  /**
551
- * Main TagLib class for creating and managing audio files
215
+ * Main TagLib interface using Embind
552
216
  */
553
217
  export class TagLib {
554
218
  private module: TagLibModule;
555
219
 
556
- private constructor(module: TagLibModule) {
557
- this.module = module;
558
- }
559
-
560
- /**
561
- * Initialize TagLib with optional configuration
562
- */
563
- static async initialize(config?: TagLibConfig): Promise<TagLib> {
564
- const module = await loadTagLibModule(config);
565
- return new TagLib(module);
220
+ constructor(module: WasmModule) {
221
+ this.module = module as TagLibModule;
566
222
  }
567
223
 
568
224
  /**
569
- * Open an audio file from a buffer
225
+ * Open a file from a buffer
570
226
  */
571
- openFile(buffer: Uint8Array): AudioFile {
572
- if (!this.module.HEAPU8) {
573
- throw new Error("WASM module not properly initialized - missing HEAPU8");
227
+ async openFile(buffer: ArrayBuffer): Promise<AudioFile> {
228
+ // Check if Embind is available
229
+ if (!this.module.createFileHandle) {
230
+ throw new Error("TagLib module not properly initialized - createFileHandle not found");
574
231
  }
575
-
576
- // Use Emscripten's allocate function for proper memory management
577
- const dataPtr = this.module.allocate(buffer, this.module.ALLOC_NORMAL);
578
-
579
- const fileId = this.module._taglib_file_new_from_buffer(
580
- dataPtr,
581
- buffer.length,
582
- );
583
-
584
- if (fileId === 0) {
585
- // Don't free memory immediately to debug reuse issue
586
- console.log(
587
- `DEBUG: File creation failed, not freeing memory at ${dataPtr}`,
588
- );
589
- throw new Error(
590
- "Failed to open audio file - invalid format or corrupted data",
591
- );
232
+
233
+ // Convert ArrayBuffer to Uint8Array for Embind
234
+ const uint8Array = new Uint8Array(buffer);
235
+
236
+ // Create a new FileHandle
237
+ const fileHandle = this.module.createFileHandle();
238
+
239
+ // Load the buffer - Embind should handle Uint8Array conversion
240
+ const success = fileHandle.loadFromBuffer(uint8Array);
241
+ if (!success) {
242
+ throw new Error("Failed to load file from buffer");
592
243
  }
593
-
594
- // Free the temporary buffer copy (TagLib has made its own copy in ByteVector)
595
- this.module._free(dataPtr);
596
-
597
- return new AudioFile(this.module, fileId);
244
+
245
+ return new AudioFileImpl(this.module, fileHandle);
598
246
  }
599
247
 
600
248
  /**
601
- * Get the underlying WASM module (for advanced usage)
249
+ * Get version information
602
250
  */
603
- getModule(): TagLibModule {
604
- return this.module;
251
+ version(): string {
252
+ return "2.1.0"; // TagLib version we're using
605
253
  }
606
254
  }
255
+
256
+ /**
257
+ * Create a TagLib instance
258
+ */
259
+ export async function createTagLib(module: WasmModule): Promise<TagLib> {
260
+ return new TagLib(module);
261
+ }