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.
@@ -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
- }
package/src/simple-jsr.ts DELETED
@@ -1,201 +0,0 @@
1
- /**
2
- * @fileoverview JSR-compatible simple API (uses taglib-jsr.ts instead of taglib.ts)
3
- */
4
-
5
- import { TagLib } from "./taglib-jsr.ts";
6
- import type { AudioProperties, Tag } from "./types.ts";
7
-
8
- // Cached TagLib instance for auto-initialization
9
- let cachedTagLib: TagLib | null = null;
10
-
11
- /**
12
- * Get or create a TagLib instance with auto-initialization
13
- */
14
- async function getTagLib(): Promise<TagLib> {
15
- if (!cachedTagLib) {
16
- cachedTagLib = await TagLib.initialize({
17
- debug: false,
18
- memory: {
19
- initial: 16 * 1024 * 1024, // 16MB default
20
- maximum: 64 * 1024 * 1024, // 64MB max
21
- },
22
- });
23
- }
24
- return cachedTagLib;
25
- }
26
-
27
- /**
28
- * Read a file's data from various sources
29
- */
30
- async function readFileData(file: string | Uint8Array | ArrayBuffer | File): Promise<Uint8Array> {
31
- // Already a Uint8Array
32
- if (file instanceof Uint8Array) {
33
- return file;
34
- }
35
-
36
- // ArrayBuffer - convert to Uint8Array
37
- if (file instanceof ArrayBuffer) {
38
- return new Uint8Array(file);
39
- }
40
-
41
- // File object (browser)
42
- if (typeof File !== 'undefined' && file instanceof File) {
43
- return new Uint8Array(await file.arrayBuffer());
44
- }
45
-
46
- // String path - read from filesystem
47
- if (typeof file === 'string') {
48
- // Deno
49
- if (typeof Deno !== 'undefined') {
50
- return await Deno.readFile(file);
51
- }
52
-
53
- throw new Error('File path reading not supported in this environment');
54
- }
55
-
56
- throw new Error('Invalid file input type');
57
- }
58
-
59
- /**
60
- * Read metadata tags from an audio file
61
- */
62
- export async function readTags(file: string | Uint8Array | ArrayBuffer | File): Promise<Tag> {
63
- const taglib = await getTagLib();
64
- const audioData = await readFileData(file);
65
-
66
- const audioFile = taglib.openFile(audioData);
67
- try {
68
- if (!audioFile.isValid()) {
69
- throw new Error('Invalid audio file');
70
- }
71
-
72
- return audioFile.tag();
73
- } finally {
74
- audioFile.dispose();
75
- }
76
- }
77
-
78
- /**
79
- * Write metadata tags to an audio file
80
- */
81
- export async function writeTags(
82
- file: string | Uint8Array | ArrayBuffer | File,
83
- tags: Partial<Tag>,
84
- options?: number
85
- ): Promise<Uint8Array> {
86
- const taglib = await getTagLib();
87
- const audioData = await readFileData(file);
88
-
89
- const audioFile = taglib.openFile(audioData);
90
- try {
91
- if (!audioFile.isValid()) {
92
- throw new Error('Invalid audio file');
93
- }
94
-
95
- // Write each tag if defined
96
- if (tags.title !== undefined) audioFile.setTitle(tags.title);
97
- if (tags.artist !== undefined) audioFile.setArtist(tags.artist);
98
- if (tags.album !== undefined) audioFile.setAlbum(tags.album);
99
- if (tags.comment !== undefined) audioFile.setComment(tags.comment);
100
- if (tags.genre !== undefined) audioFile.setGenre(tags.genre);
101
- if (tags.year !== undefined) audioFile.setYear(tags.year);
102
- if (tags.track !== undefined) audioFile.setTrack(tags.track);
103
-
104
- // Save changes to in-memory buffer
105
- if (!audioFile.save()) {
106
- throw new Error('Failed to save changes');
107
- }
108
-
109
- return audioData;
110
- } finally {
111
- audioFile.dispose();
112
- }
113
- }
114
-
115
- /**
116
- * Read audio properties from a file
117
- */
118
- export async function readProperties(file: string | Uint8Array | ArrayBuffer | File): Promise<AudioProperties> {
119
- const taglib = await getTagLib();
120
- const audioData = await readFileData(file);
121
-
122
- const audioFile = taglib.openFile(audioData);
123
- try {
124
- if (!audioFile.isValid()) {
125
- throw new Error('Invalid audio file');
126
- }
127
-
128
- return audioFile.audioProperties();
129
- } finally {
130
- audioFile.dispose();
131
- }
132
- }
133
-
134
- /**
135
- * Tag field constants for go-taglib compatibility
136
- */
137
- export const Title = "title";
138
- export const Artist = "artist";
139
- export const Album = "album";
140
- export const Comment = "comment";
141
- export const Genre = "genre";
142
- export const Year = "year";
143
- export const Track = "track";
144
- export const AlbumArtist = "albumartist";
145
- export const Composer = "composer";
146
- export const DiscNumber = "discnumber";
147
-
148
- /**
149
- * Check if a file is a valid audio file
150
- */
151
- export async function isValidAudioFile(file: string | Uint8Array | ArrayBuffer | File): Promise<boolean> {
152
- try {
153
- const taglib = await getTagLib();
154
- const audioData = await readFileData(file);
155
-
156
- const audioFile = taglib.openFile(audioData);
157
- const valid = audioFile.isValid();
158
- audioFile.dispose();
159
-
160
- return valid;
161
- } catch {
162
- return false;
163
- }
164
- }
165
-
166
- /**
167
- * Get the audio format of a file
168
- */
169
- export async function getFormat(file: string | Uint8Array | ArrayBuffer | File): Promise<string | undefined> {
170
- const taglib = await getTagLib();
171
- const audioData = await readFileData(file);
172
-
173
- const audioFile = taglib.openFile(audioData);
174
- try {
175
- if (!audioFile.isValid()) {
176
- return undefined;
177
- }
178
-
179
- return audioFile.format();
180
- } finally {
181
- audioFile.dispose();
182
- }
183
- }
184
-
185
- /**
186
- * Clear all tags from a file
187
- */
188
- export async function clearTags(file: string | Uint8Array | ArrayBuffer | File): Promise<Uint8Array> {
189
- return writeTags(file, {
190
- title: "",
191
- artist: "",
192
- album: "",
193
- comment: "",
194
- genre: "",
195
- year: 0,
196
- track: 0,
197
- });
198
- }
199
-
200
- // Type exports for convenience
201
- export type { Tag, AudioProperties } from "./types.ts";
@@ -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
- }