taglib-wasm 0.2.6 → 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.
@@ -1,296 +0,0 @@
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";
9
-
10
- /**
11
- * Enhanced error handling with error codes
12
- */
13
- export interface TagLibError extends Error {
14
- code:
15
- | "FILE_NOT_FOUND"
16
- | "INVALID_FORMAT"
17
- | "MEMORY_ERROR"
18
- | "PERMISSION_DENIED"
19
- | "WASM_ERROR";
20
- path?: string;
21
- format?: string;
22
- details?: string;
23
- }
24
-
25
- /**
26
- * Callback-style async operations (Node.js pattern)
27
- */
28
- export type TagLibCallback<T> = (err: TagLibError | null, result?: T) => void;
29
-
30
- /**
31
- * Enhanced input types
32
- */
33
- export type AudioInput =
34
- | string // File path (Node.js/Deno/Bun only)
35
- | Uint8Array // Raw bytes
36
- | ArrayBuffer // ArrayBuffer
37
- | Buffer // Node.js Buffer
38
- | File; // Browser File object
39
-
40
- /**
41
- * Enhanced TagLib class with multiple API patterns
42
- */
43
- export class EnhancedTagLib {
44
- /**
45
- * Synchronous initialization (for server environments)
46
- */
47
- static initializeSync(config?: TagLibConfig): EnhancedTagLib {
48
- // Implementation would be synchronous version
49
- throw new Error("Not implemented");
50
- }
51
-
52
- /**
53
- * Asynchronous initialization (current pattern)
54
- */
55
- static async initialize(config?: TagLibConfig): Promise<EnhancedTagLib> {
56
- // Current implementation
57
- throw new Error("Not implemented");
58
- }
59
-
60
- /**
61
- * Open file with flexible input types
62
- */
63
- openFile(input: AudioInput): EnhancedAudioFile {
64
- if (typeof input === "string") {
65
- return this.openFileFromPath(input);
66
- } else if (input instanceof File) {
67
- return this.openFileFromBrowserFile(input);
68
- } else if (input instanceof ArrayBuffer) {
69
- return this.openFileFromBuffer(new Uint8Array(input));
70
- } else if (Buffer && Buffer.isBuffer(input)) {
71
- return this.openFileFromBuffer(new Uint8Array(input));
72
- } else {
73
- return this.openFileFromBuffer(input);
74
- }
75
- }
76
-
77
- /**
78
- * Async file opening with callback pattern
79
- */
80
- openFileAsync(
81
- input: AudioInput,
82
- callback: TagLibCallback<EnhancedAudioFile>,
83
- ): void {
84
- try {
85
- const file = this.openFile(input);
86
- callback(null, file);
87
- } catch (error) {
88
- const tagLibError: TagLibError = {
89
- name: "TagLibError",
90
- message: (error as Error).message,
91
- code: "INVALID_FORMAT",
92
- details: (error as Error).stack,
93
- };
94
- callback(tagLibError);
95
- }
96
- }
97
-
98
- /**
99
- * Sync file opening (Node.js/Bun/Deno only)
100
- */
101
- openFileSync(path: string): EnhancedAudioFile {
102
- // Runtime-specific implementation
103
- if (typeof (globalThis as any).Deno !== "undefined") {
104
- const data = (globalThis as any).Deno.readFileSync(path);
105
- return this.openFileFromBuffer(data);
106
- } else if (typeof (globalThis as any).Bun !== "undefined") {
107
- const file = (globalThis as any).Bun.file(path);
108
- const data = new Uint8Array(file.arrayBufferSync());
109
- return this.openFileFromBuffer(data);
110
- } else if (typeof require !== "undefined") {
111
- const fs = require("fs");
112
- const data = fs.readFileSync(path);
113
- return this.openFileFromBuffer(new Uint8Array(data));
114
- } else {
115
- throw new Error(
116
- "Synchronous file reading not supported in browser environment",
117
- );
118
- }
119
- }
120
-
121
- private openFileFromPath(path: string): EnhancedAudioFile {
122
- throw new Error("Not implemented");
123
- }
124
-
125
- private openFileFromBrowserFile(file: File): EnhancedAudioFile {
126
- throw new Error("Not implemented - requires async operation");
127
- }
128
-
129
- private openFileFromBuffer(buffer: Uint8Array): EnhancedAudioFile {
130
- throw new Error("Not implemented");
131
- }
132
- }
133
-
134
- /**
135
- * Enhanced AudioFile with property accessors and better error handling
136
- */
137
- export class EnhancedAudioFile {
138
- // Property accessors (more intuitive than method calls)
139
- get title(): string | undefined {
140
- return this.tag().title;
141
- }
142
-
143
- set title(value: string | undefined) {
144
- if (value !== undefined) {
145
- this.setTitle(value);
146
- }
147
- }
148
-
149
- get artist(): string | undefined {
150
- return this.tag().artist;
151
- }
152
-
153
- set artist(value: string | undefined) {
154
- if (value !== undefined) {
155
- this.setArtist(value);
156
- }
157
- }
158
-
159
- get album(): string | undefined {
160
- return this.tag().album;
161
- }
162
-
163
- set album(value: string | undefined) {
164
- if (value !== undefined) {
165
- this.setAlbum(value);
166
- }
167
- }
168
-
169
- // Async operations with callbacks
170
- tagAsync(callback: TagLibCallback<any>): void {
171
- try {
172
- const tags = this.tag();
173
- callback(null, tags);
174
- } catch (error) {
175
- const tagLibError: TagLibError = {
176
- name: "TagLibError",
177
- message: (error as Error).message,
178
- code: "MEMORY_ERROR",
179
- };
180
- callback(tagLibError);
181
- }
182
- }
183
-
184
- saveAsync(callback: TagLibCallback<boolean>): void {
185
- try {
186
- const result = this.save();
187
- callback(null, result);
188
- } catch (error) {
189
- const tagLibError: TagLibError = {
190
- name: "TagLibError",
191
- message: (error as Error).message,
192
- code: "PERMISSION_DENIED",
193
- };
194
- callback(tagLibError);
195
- }
196
- }
197
-
198
- // Bulk tag setting (convenience method)
199
- setTags(tags: {
200
- title?: string;
201
- artist?: string;
202
- album?: string;
203
- year?: number;
204
- genre?: string;
205
- track?: number;
206
- comment?: string;
207
- }): void {
208
- Object.entries(tags).forEach(([key, value]) => {
209
- if (value !== undefined) {
210
- (this as any)[key] = value;
211
- }
212
- });
213
- }
214
-
215
- // Enhanced error handling for save operations
216
- saveWithValidation(): { success: boolean; errors: TagLibError[] } {
217
- const errors: TagLibError[] = [];
218
-
219
- try {
220
- // Validate before saving
221
- if (!this.isValid()) {
222
- errors.push({
223
- name: "TagLibError",
224
- message: "File is not valid for writing",
225
- code: "INVALID_FORMAT",
226
- });
227
- }
228
-
229
- const success = this.save();
230
- return { success, errors };
231
- } catch (error) {
232
- errors.push({
233
- name: "TagLibError",
234
- message: (error as Error).message,
235
- code: "PERMISSION_DENIED",
236
- });
237
- return { success: false, errors };
238
- }
239
- }
240
-
241
- // Method stubs (would delegate to existing implementation)
242
- private tag(): any {
243
- throw new Error("Not implemented");
244
- }
245
- private setTitle(title: string): void {
246
- throw new Error("Not implemented");
247
- }
248
- private setArtist(artist: string): void {
249
- throw new Error("Not implemented");
250
- }
251
- private setAlbum(album: string): void {
252
- throw new Error("Not implemented");
253
- }
254
- private save(): boolean {
255
- throw new Error("Not implemented");
256
- }
257
- private isValid(): boolean {
258
- throw new Error("Not implemented");
259
- }
260
- }
261
-
262
- /**
263
- * Usage examples demonstrating enhanced API
264
- */
265
- export function demonstrateEnhancedAPI() {
266
- // Example 1: Property-based access
267
- const file = new EnhancedAudioFile();
268
- file.title = "New Song";
269
- file.artist = "New Artist";
270
- console.log(`${file.artist} - ${file.title}`);
271
-
272
- // Example 2: Bulk tag setting
273
- file.setTags({
274
- title: "Song Title",
275
- artist: "Artist Name",
276
- album: "Album Name",
277
- year: 2024,
278
- });
279
-
280
- // Example 3: Enhanced error handling
281
- const result = file.saveWithValidation();
282
- if (!result.success) {
283
- result.errors.forEach((error) => {
284
- console.error(`Error ${error.code}: ${error.message}`);
285
- });
286
- }
287
-
288
- // Example 4: Callback-style async operations
289
- file.tagAsync((err, tags) => {
290
- if (err) {
291
- console.error(`Failed to read tags: ${err.message}`);
292
- return;
293
- }
294
- console.log("Tags:", tags);
295
- });
296
- }
@@ -1,231 +0,0 @@
1
- import type { TagLibModule, WasmModule } from "./wasm-embind.ts";
2
- import type { AudioFile as AudioFileInterface, AudioProperties, FileType, PropertyMap, Tag } from "./types.ts";
3
-
4
- /**
5
- * Audio file wrapper using Embind API
6
- */
7
- export class AudioFile implements AudioFileInterface {
8
- private fileHandle: any;
9
- private cachedTag: Tag | null = null;
10
- private cachedAudioProperties: AudioProperties | null = null;
11
-
12
- constructor(
13
- private module: TagLibModule,
14
- fileHandle: any,
15
- ) {
16
- this.fileHandle = fileHandle;
17
- }
18
-
19
- /**
20
- * Get file format
21
- */
22
- getFormat(): FileType {
23
- return this.fileHandle.getFormat() as FileType;
24
- }
25
-
26
- /**
27
- * Get tag object for reading/writing metadata
28
- */
29
- tag(): Tag {
30
- if (!this.cachedTag) {
31
- const tagWrapper = this.fileHandle.getTag();
32
- if (!tagWrapper) {
33
- throw new Error("Failed to get tag from file");
34
- }
35
-
36
- this.cachedTag = {
37
- title: () => tagWrapper.title(),
38
- artist: () => tagWrapper.artist(),
39
- album: () => tagWrapper.album(),
40
- comment: () => tagWrapper.comment(),
41
- genre: () => tagWrapper.genre(),
42
- year: () => tagWrapper.year(),
43
- track: () => tagWrapper.track(),
44
-
45
- setTitle: (value: string) => tagWrapper.setTitle(value),
46
- setArtist: (value: string) => tagWrapper.setArtist(value),
47
- setAlbum: (value: string) => tagWrapper.setAlbum(value),
48
- setComment: (value: string) => tagWrapper.setComment(value),
49
- setGenre: (value: string) => tagWrapper.setGenre(value),
50
- setYear: (value: number) => tagWrapper.setYear(value),
51
- setTrack: (value: number) => tagWrapper.setTrack(value),
52
- };
53
- }
54
-
55
- return this.cachedTag;
56
- }
57
-
58
- /**
59
- * Get audio properties
60
- */
61
- audioProperties(): AudioProperties | null {
62
- if (!this.cachedAudioProperties) {
63
- const propsWrapper = this.fileHandle.getAudioProperties();
64
- if (!propsWrapper) {
65
- return null;
66
- }
67
-
68
- this.cachedAudioProperties = {
69
- length: propsWrapper.lengthInSeconds(),
70
- lengthInMilliseconds: propsWrapper.lengthInMilliseconds(),
71
- bitrate: propsWrapper.bitrate(),
72
- sampleRate: propsWrapper.sampleRate(),
73
- channels: propsWrapper.channels(),
74
- };
75
- }
76
-
77
- return this.cachedAudioProperties;
78
- }
79
-
80
- /**
81
- * Get all properties as a PropertyMap
82
- */
83
- properties(): PropertyMap {
84
- const jsObj = this.fileHandle.getProperties();
85
- const result: PropertyMap = {};
86
-
87
- // Convert from Emscripten val to plain object
88
- const keys = Object.keys(jsObj);
89
- for (const key of keys) {
90
- result[key] = jsObj[key];
91
- }
92
-
93
- return result;
94
- }
95
-
96
- /**
97
- * Set properties from a PropertyMap
98
- */
99
- setProperties(properties: PropertyMap): void {
100
- this.fileHandle.setProperties(properties);
101
- }
102
-
103
- /**
104
- * Get a single property value
105
- */
106
- getProperty(key: string): string | undefined {
107
- const value = this.fileHandle.getProperty(key);
108
- return value === "" ? undefined : value;
109
- }
110
-
111
- /**
112
- * Set a single property value
113
- */
114
- setProperty(key: string, value: string): void {
115
- this.fileHandle.setProperty(key, value);
116
- }
117
-
118
- /**
119
- * Check if this is an MP4 file
120
- */
121
- isMP4(): boolean {
122
- return this.fileHandle.isMP4();
123
- }
124
-
125
- /**
126
- * Get MP4-specific item
127
- */
128
- getMP4Item(key: string): string | undefined {
129
- if (!this.isMP4()) {
130
- throw new Error("Not an MP4 file");
131
- }
132
- const value = this.fileHandle.getMP4Item(key);
133
- return value === "" ? undefined : value;
134
- }
135
-
136
- /**
137
- * Set MP4-specific item
138
- */
139
- setMP4Item(key: string, value: string): void {
140
- if (!this.isMP4()) {
141
- throw new Error("Not an MP4 file");
142
- }
143
- this.fileHandle.setMP4Item(key, value);
144
- }
145
-
146
- /**
147
- * Remove MP4-specific item
148
- */
149
- removeMP4Item(key: string): void {
150
- if (!this.isMP4()) {
151
- throw new Error("Not an MP4 file");
152
- }
153
- this.fileHandle.removeMP4Item(key);
154
- }
155
-
156
- /**
157
- * Save changes to the file
158
- */
159
- save(): boolean {
160
- // Clear caches since values may have changed
161
- this.cachedTag = null;
162
- this.cachedAudioProperties = null;
163
-
164
- return this.fileHandle.save();
165
- }
166
-
167
- /**
168
- * Check if the file is valid
169
- */
170
- isValid(): boolean {
171
- return this.fileHandle.isValid();
172
- }
173
-
174
- /**
175
- * Free resources
176
- */
177
- dispose(): void {
178
- if (this.fileHandle) {
179
- // Embind will handle cleanup when the object goes out of scope
180
- // But we can help by clearing our references
181
- this.fileHandle = null;
182
- this.cachedTag = null;
183
- this.cachedAudioProperties = null;
184
- }
185
- }
186
- }
187
-
188
- /**
189
- * Main TagLib interface using Embind
190
- */
191
- export class TagLib {
192
- private module: TagLibModule;
193
-
194
- constructor(module: WasmModule) {
195
- this.module = module as TagLibModule;
196
- }
197
-
198
- /**
199
- * Open a file from a buffer
200
- */
201
- async openFile(buffer: ArrayBuffer): Promise<AudioFile> {
202
- // Convert ArrayBuffer to string for Embind
203
- const uint8Array = new Uint8Array(buffer);
204
- const binaryString = Array.from(uint8Array, byte => String.fromCharCode(byte)).join('');
205
-
206
- // Create a new FileHandle
207
- const fileHandle = this.module.createFileHandle();
208
-
209
- // Load the buffer
210
- const success = fileHandle.loadFromBuffer(binaryString);
211
- if (!success) {
212
- throw new Error("Failed to load file from buffer");
213
- }
214
-
215
- return new AudioFile(this.module, fileHandle);
216
- }
217
-
218
- /**
219
- * Get version information
220
- */
221
- version(): string {
222
- return "2.1.0"; // TagLib version we're using
223
- }
224
- }
225
-
226
- /**
227
- * Create a TagLib instance
228
- */
229
- export async function createTagLib(module: WasmModule): Promise<TagLib> {
230
- return new TagLib(module);
231
- }
@@ -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
- }