taglib-wasm 0.3.3 → 0.3.9
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/CONTRIBUTING.md +293 -0
- package/NOTICE +34 -0
- package/README.md +122 -511
- package/dist/index.d.ts +132 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +137 -0
- package/dist/index.ts +220 -0
- package/dist/src/constants.d.ts +201 -0
- package/dist/src/constants.d.ts.map +1 -0
- package/dist/src/constants.ts +227 -0
- package/dist/src/errors.d.ts +89 -0
- package/dist/src/errors.d.ts.map +1 -0
- package/dist/src/errors.ts +237 -0
- package/dist/src/file-utils.d.ts +205 -0
- package/dist/src/file-utils.d.ts.map +1 -0
- package/dist/src/file-utils.ts +467 -0
- package/dist/src/file.js +47 -0
- package/dist/src/global.d.ts +10 -0
- package/dist/src/mod.d.ts +9 -0
- package/dist/src/mod.d.ts.map +1 -0
- package/dist/src/mod.ts +19 -0
- package/dist/src/simple.d.ts +347 -0
- package/dist/src/simple.d.ts.map +1 -0
- package/dist/src/simple.ts +659 -0
- package/dist/src/taglib.d.ts +502 -0
- package/dist/src/taglib.d.ts.map +1 -0
- package/dist/src/taglib.ts +959 -0
- package/dist/src/types.d.ts +323 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.ts +538 -0
- package/dist/src/utils/file.d.ts +15 -0
- package/dist/src/utils/file.d.ts.map +1 -0
- package/dist/src/utils/file.ts +82 -0
- package/dist/src/utils/write.d.ts +15 -0
- package/dist/src/utils/write.d.ts.map +1 -0
- package/dist/src/utils/write.ts +61 -0
- package/dist/src/wasm-workers.d.ts +33 -0
- package/dist/src/wasm-workers.d.ts.map +1 -0
- package/dist/src/wasm-workers.ts +176 -0
- package/dist/src/wasm.d.ts +97 -0
- package/dist/src/wasm.d.ts.map +1 -0
- package/dist/src/wasm.ts +133 -0
- package/dist/src/web-utils.d.ts +180 -0
- package/dist/src/web-utils.d.ts.map +1 -0
- package/dist/src/web-utils.ts +347 -0
- package/dist/src/workers.d.ts +219 -0
- package/dist/src/workers.d.ts.map +1 -0
- package/dist/src/workers.ts +465 -0
- package/dist/src/write.js +33 -0
- package/dist/taglib-wrapper.d.ts +5 -0
- package/dist/taglib-wrapper.js +14 -0
- package/dist/taglib.wasm +0 -0
- package/index.ts +100 -7
- package/package.json +40 -16
- package/src/errors.ts +237 -0
- package/src/file-utils.ts +467 -0
- package/src/global.d.ts +10 -0
- package/src/simple.ts +399 -84
- package/src/taglib.ts +522 -28
- package/src/types.ts +1 -1
- package/src/utils/file.ts +82 -0
- package/src/utils/write.ts +61 -0
- package/src/wasm-workers.ts +13 -4
- package/src/wasm.ts +1 -1
- package/src/web-utils.ts +347 -0
- package/src/workers.ts +32 -13
- package/build/taglib.js +0 -2407
- package/build/taglib.wasm +0 -0
package/src/types.ts
CHANGED
|
@@ -18,7 +18,7 @@ export type { TagLibModule } from "./wasm.ts";
|
|
|
18
18
|
*
|
|
19
19
|
* @example
|
|
20
20
|
* ```typescript
|
|
21
|
-
* const file = await taglib.
|
|
21
|
+
* const file = await taglib.open(buffer);
|
|
22
22
|
* const format = file.getFormat();
|
|
23
23
|
* if (format === "MP3") {
|
|
24
24
|
* // Handle MP3-specific features
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File reading utilities for taglib-wasm
|
|
3
|
+
* Provides cross-runtime support for reading files from various sources
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EnvironmentError, FileOperationError } from "../errors.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Read a file's data from various sources.
|
|
10
|
+
* Supports file paths (Node.js/Deno/Bun), buffers, and File objects (browser).
|
|
11
|
+
*
|
|
12
|
+
* @param file - File path, Uint8Array, ArrayBuffer, or File object
|
|
13
|
+
* @returns Promise resolving to Uint8Array of file data
|
|
14
|
+
* @throws {FileOperationError} If file read fails
|
|
15
|
+
* @throws {EnvironmentError} If environment doesn't support file paths
|
|
16
|
+
*/
|
|
17
|
+
export async function readFileData(
|
|
18
|
+
file: string | Uint8Array | ArrayBuffer | File,
|
|
19
|
+
): Promise<Uint8Array> {
|
|
20
|
+
// Already a Uint8Array
|
|
21
|
+
if (file instanceof Uint8Array) {
|
|
22
|
+
return file;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ArrayBuffer - convert to Uint8Array
|
|
26
|
+
if (file instanceof ArrayBuffer) {
|
|
27
|
+
return new Uint8Array(file);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// File object (browser)
|
|
31
|
+
if (typeof File !== "undefined" && file instanceof File) {
|
|
32
|
+
return new Uint8Array(await file.arrayBuffer());
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// String path - read from filesystem
|
|
36
|
+
if (typeof file === "string") {
|
|
37
|
+
try {
|
|
38
|
+
// Deno
|
|
39
|
+
if (typeof Deno !== "undefined") {
|
|
40
|
+
return await Deno.readFile(file);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Node.js
|
|
44
|
+
if (
|
|
45
|
+
typeof process !== "undefined" && process.versions &&
|
|
46
|
+
process.versions.node
|
|
47
|
+
) {
|
|
48
|
+
const { readFile } = await import("fs/promises");
|
|
49
|
+
return new Uint8Array(await readFile(file));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Bun
|
|
53
|
+
if (typeof (globalThis as any).Bun !== "undefined") {
|
|
54
|
+
const bunFile = (globalThis as any).Bun.file(file);
|
|
55
|
+
return new Uint8Array(await bunFile.arrayBuffer());
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
// Convert system file errors to FileOperationError
|
|
59
|
+
throw new FileOperationError(
|
|
60
|
+
"read",
|
|
61
|
+
(error as Error).message,
|
|
62
|
+
file
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const env = typeof Deno !== "undefined" ? "Deno" :
|
|
67
|
+
typeof process !== "undefined" ? "Node.js" :
|
|
68
|
+
typeof (globalThis as any).Bun !== "undefined" ? "Bun" :
|
|
69
|
+
"Browser";
|
|
70
|
+
throw new EnvironmentError(
|
|
71
|
+
env,
|
|
72
|
+
"does not support file path reading",
|
|
73
|
+
"filesystem access"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const inputType = Object.prototype.toString.call(file);
|
|
78
|
+
throw new FileOperationError(
|
|
79
|
+
"read",
|
|
80
|
+
`Invalid file input type: ${inputType}. Expected string path, Uint8Array, ArrayBuffer, or File object.`
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File writing utilities for taglib-wasm
|
|
3
|
+
* Provides cross-runtime support for writing files
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EnvironmentError, FileOperationError } from "../errors.ts";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Write data to a file across different runtimes.
|
|
10
|
+
* Supports Node.js, Deno, and Bun environments.
|
|
11
|
+
*
|
|
12
|
+
* @param path - File path to write to
|
|
13
|
+
* @param data - Data to write (Uint8Array)
|
|
14
|
+
* @throws {FileOperationError} If file write fails
|
|
15
|
+
* @throws {EnvironmentError} If environment doesn't support file writing
|
|
16
|
+
*/
|
|
17
|
+
export async function writeFileData(
|
|
18
|
+
path: string,
|
|
19
|
+
data: Uint8Array,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
try {
|
|
22
|
+
// Deno
|
|
23
|
+
if (typeof Deno !== "undefined") {
|
|
24
|
+
await Deno.writeFile(path, data);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Node.js
|
|
29
|
+
if (
|
|
30
|
+
typeof process !== "undefined" && process.versions &&
|
|
31
|
+
process.versions.node
|
|
32
|
+
) {
|
|
33
|
+
const { writeFile } = await import("fs/promises");
|
|
34
|
+
await writeFile(path, data);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Bun
|
|
39
|
+
if (typeof (globalThis as any).Bun !== "undefined") {
|
|
40
|
+
await (globalThis as any).Bun.write(path, data);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// Convert system file errors to FileOperationError
|
|
45
|
+
throw new FileOperationError(
|
|
46
|
+
"write",
|
|
47
|
+
(error as Error).message,
|
|
48
|
+
path
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const env = typeof Deno !== "undefined" ? "Deno" :
|
|
53
|
+
typeof process !== "undefined" ? "Node.js" :
|
|
54
|
+
typeof (globalThis as any).Bun !== "undefined" ? "Bun" :
|
|
55
|
+
"Browser";
|
|
56
|
+
throw new EnvironmentError(
|
|
57
|
+
env,
|
|
58
|
+
"does not support file path writing",
|
|
59
|
+
"filesystem access"
|
|
60
|
+
);
|
|
61
|
+
}
|
package/src/wasm-workers.ts
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { TagLibConfig, TagLibModule } from "./types.ts";
|
|
6
|
+
import { TagLibInitializationError } from "./errors.ts";
|
|
6
7
|
|
|
7
8
|
// Re-export TagLibModule for convenience
|
|
8
9
|
export type { TagLibModule };
|
|
@@ -69,7 +70,10 @@ export async function loadTagLibModuleForWorkers(
|
|
|
69
70
|
const TagLibWasm = await createWorkersCompatibleModule();
|
|
70
71
|
|
|
71
72
|
if (typeof TagLibWasm !== "function") {
|
|
72
|
-
throw new
|
|
73
|
+
throw new TagLibInitializationError(
|
|
74
|
+
"Failed to load taglib-wasm module for Workers. " +
|
|
75
|
+
"The module may not be properly bundled for the Workers environment.",
|
|
76
|
+
);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
const wasmInstance = await TagLibWasm(moduleConfig);
|
|
@@ -91,8 +95,12 @@ export async function loadTagLibModuleForWorkers(
|
|
|
91
95
|
|
|
92
96
|
return wasmInstance as TagLibModule;
|
|
93
97
|
} catch (error) {
|
|
94
|
-
|
|
98
|
+
if (error instanceof TagLibInitializationError) {
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
throw new TagLibInitializationError(
|
|
95
102
|
`Failed to load taglib-wasm for Workers: ${(error as Error).message}`,
|
|
103
|
+
{ error: (error as Error).message },
|
|
96
104
|
);
|
|
97
105
|
}
|
|
98
106
|
}
|
|
@@ -110,14 +118,15 @@ async function createWorkersCompatibleModule(): Promise<any> {
|
|
|
110
118
|
// For now, we'll attempt to load the existing module with Workers compatibility
|
|
111
119
|
try {
|
|
112
120
|
// Try to import the existing module
|
|
113
|
-
const wasmModule = await import("../build/taglib.js");
|
|
121
|
+
const wasmModule = await import("../build/taglib-wrapper.js");
|
|
114
122
|
return wasmModule.default || wasmModule;
|
|
115
123
|
} catch (error) {
|
|
116
124
|
// If that fails, provide a fallback implementation
|
|
117
|
-
throw new
|
|
125
|
+
throw new TagLibInitializationError(
|
|
118
126
|
"Workers-compatible Wasm module not available. " +
|
|
119
127
|
"Please build with Workers target or use a bundler that supports Wasm modules. " +
|
|
120
128
|
`Original error: ${(error as Error).message}`,
|
|
129
|
+
{ error: (error as Error).message },
|
|
121
130
|
);
|
|
122
131
|
}
|
|
123
132
|
}
|
package/src/wasm.ts
CHANGED
|
@@ -84,7 +84,7 @@ export interface AudioPropertiesWrapper {
|
|
|
84
84
|
channels(): number;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
export interface TagLibModule extends EmscriptenModule {
|
|
87
|
+
export interface TagLibModule extends Omit<EmscriptenModule, 'then'> {
|
|
88
88
|
// Embind classes
|
|
89
89
|
FileHandle: new () => FileHandle;
|
|
90
90
|
TagWrapper: new () => TagWrapper;
|
package/src/web-utils.ts
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Web browser utilities for working with cover art in taglib-wasm
|
|
3
|
+
*
|
|
4
|
+
* This module provides browser-specific helpers for integrating taglib-wasm
|
|
5
|
+
* with web applications, including canvas integration and data URL support.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { pictureToDataURL, setCoverArtFromCanvas } from "taglib-wasm/web-utils";
|
|
10
|
+
*
|
|
11
|
+
* // Display cover art in an <img> element
|
|
12
|
+
* const pictures = await readPictures("song.mp3");
|
|
13
|
+
* if (pictures.length > 0) {
|
|
14
|
+
* const dataURL = pictureToDataURL(pictures[0]);
|
|
15
|
+
* document.getElementById('cover').src = dataURL;
|
|
16
|
+
* }
|
|
17
|
+
*
|
|
18
|
+
* // Set cover art from a canvas
|
|
19
|
+
* const canvas = document.getElementById('myCanvas') as HTMLCanvasElement;
|
|
20
|
+
* const modifiedBuffer = await setCoverArtFromCanvas("song.mp3", canvas);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type { Picture } from "./types.ts";
|
|
25
|
+
import { PictureType } from "./types.ts";
|
|
26
|
+
import { applyPictures, readPictures } from "./simple.ts";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Convert a Picture object to a data URL for display in web browsers
|
|
30
|
+
*
|
|
31
|
+
* @param picture - Picture object from taglib-wasm
|
|
32
|
+
* @returns Data URL string that can be used as src for <img> elements
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const pictures = await readPictures("song.mp3");
|
|
37
|
+
* const imgElement = document.getElementById('coverArt') as HTMLImageElement;
|
|
38
|
+
* imgElement.src = pictureToDataURL(pictures[0]);
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function pictureToDataURL(picture: Picture): string {
|
|
42
|
+
// Convert Uint8Array to base64
|
|
43
|
+
const base64 = btoa(String.fromCharCode(...picture.data));
|
|
44
|
+
return `data:${picture.mimeType};base64,${base64}`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Convert a data URL to a Picture object
|
|
49
|
+
*
|
|
50
|
+
* @param dataURL - Data URL string (e.g., "data:image/jpeg;base64,...")
|
|
51
|
+
* @param type - Picture type (defaults to FrontCover)
|
|
52
|
+
* @param description - Optional description
|
|
53
|
+
* @returns Picture object
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* ```typescript
|
|
57
|
+
* const dataURL = canvas.toDataURL('image/jpeg');
|
|
58
|
+
* const picture = dataURLToPicture(dataURL, PictureType.FrontCover);
|
|
59
|
+
* const modifiedBuffer = await applyPictures("song.mp3", [picture]);
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export function dataURLToPicture(
|
|
63
|
+
dataURL: string,
|
|
64
|
+
type: PictureType = PictureType.FrontCover,
|
|
65
|
+
description?: string,
|
|
66
|
+
): Picture {
|
|
67
|
+
// Parse data URL
|
|
68
|
+
const matches = dataURL.match(/^data:([^;]+);base64,(.+)$/);
|
|
69
|
+
if (!matches) {
|
|
70
|
+
throw new Error("Invalid data URL format");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const [, mimeType, base64] = matches;
|
|
74
|
+
|
|
75
|
+
// Convert base64 to Uint8Array
|
|
76
|
+
const binaryString = atob(base64);
|
|
77
|
+
const data = new Uint8Array(binaryString.length);
|
|
78
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
79
|
+
data[i] = binaryString.charCodeAt(i);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
mimeType,
|
|
84
|
+
data,
|
|
85
|
+
type,
|
|
86
|
+
description,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Set cover art from an HTML canvas element
|
|
92
|
+
*
|
|
93
|
+
* @param file - File path, Uint8Array buffer, ArrayBuffer, or File object
|
|
94
|
+
* @param canvas - HTMLCanvasElement containing the image
|
|
95
|
+
* @param options - Options for image format and quality
|
|
96
|
+
* @returns Modified file buffer with cover art from canvas
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* ```typescript
|
|
100
|
+
* const canvas = document.getElementById('albumArt') as HTMLCanvasElement;
|
|
101
|
+
* const modifiedBuffer = await setCoverArtFromCanvas("song.mp3", canvas, {
|
|
102
|
+
* format: 'image/jpeg',
|
|
103
|
+
* quality: 0.9
|
|
104
|
+
* });
|
|
105
|
+
* ```
|
|
106
|
+
*/
|
|
107
|
+
export async function setCoverArtFromCanvas(
|
|
108
|
+
file: string | Uint8Array | ArrayBuffer | File,
|
|
109
|
+
canvas: HTMLCanvasElement,
|
|
110
|
+
options: {
|
|
111
|
+
format?: 'image/jpeg' | 'image/png' | 'image/webp';
|
|
112
|
+
quality?: number;
|
|
113
|
+
type?: PictureType;
|
|
114
|
+
description?: string;
|
|
115
|
+
} = {},
|
|
116
|
+
): Promise<Uint8Array> {
|
|
117
|
+
const {
|
|
118
|
+
format = 'image/jpeg',
|
|
119
|
+
quality = 0.92,
|
|
120
|
+
type = PictureType.FrontCover,
|
|
121
|
+
description = "Front Cover",
|
|
122
|
+
} = options;
|
|
123
|
+
|
|
124
|
+
// Convert canvas to data URL
|
|
125
|
+
const dataURL = canvas.toDataURL(format, quality);
|
|
126
|
+
|
|
127
|
+
// Convert to Picture object
|
|
128
|
+
const picture = dataURLToPicture(dataURL, type, description);
|
|
129
|
+
|
|
130
|
+
// Apply to file
|
|
131
|
+
return applyPictures(file, [picture]);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Convert canvas to Picture object using blob for better performance
|
|
136
|
+
*
|
|
137
|
+
* This is more efficient than toDataURL for large images as it avoids
|
|
138
|
+
* the base64 encoding/decoding step.
|
|
139
|
+
*
|
|
140
|
+
* @param canvas - HTMLCanvasElement containing the image
|
|
141
|
+
* @param options - Options for image format and quality
|
|
142
|
+
* @returns Promise resolving to Picture object
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* const canvas = document.getElementById('albumArt') as HTMLCanvasElement;
|
|
147
|
+
* const picture = await canvasToPicture(canvas, {
|
|
148
|
+
* format: 'image/png',
|
|
149
|
+
* type: PictureType.BackCover
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
export async function canvasToPicture(
|
|
154
|
+
canvas: HTMLCanvasElement,
|
|
155
|
+
options: {
|
|
156
|
+
format?: 'image/jpeg' | 'image/png' | 'image/webp';
|
|
157
|
+
quality?: number;
|
|
158
|
+
type?: PictureType;
|
|
159
|
+
description?: string;
|
|
160
|
+
} = {},
|
|
161
|
+
): Promise<Picture> {
|
|
162
|
+
const {
|
|
163
|
+
format = 'image/jpeg',
|
|
164
|
+
quality = 0.92,
|
|
165
|
+
type = PictureType.FrontCover,
|
|
166
|
+
description,
|
|
167
|
+
} = options;
|
|
168
|
+
|
|
169
|
+
return new Promise((resolve, reject) => {
|
|
170
|
+
canvas.toBlob(
|
|
171
|
+
async (blob) => {
|
|
172
|
+
if (!blob) {
|
|
173
|
+
reject(new Error("Failed to convert canvas to blob"));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Convert blob to Uint8Array
|
|
178
|
+
const arrayBuffer = await blob.arrayBuffer();
|
|
179
|
+
const data = new Uint8Array(arrayBuffer);
|
|
180
|
+
|
|
181
|
+
resolve({
|
|
182
|
+
mimeType: format,
|
|
183
|
+
data,
|
|
184
|
+
type,
|
|
185
|
+
description,
|
|
186
|
+
});
|
|
187
|
+
},
|
|
188
|
+
format,
|
|
189
|
+
quality,
|
|
190
|
+
);
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Load an image file into a Picture object
|
|
196
|
+
*
|
|
197
|
+
* @param file - File object from <input type="file"> or drag-and-drop
|
|
198
|
+
* @param type - Picture type (defaults to FrontCover)
|
|
199
|
+
* @param description - Optional description
|
|
200
|
+
* @returns Promise resolving to Picture object
|
|
201
|
+
*
|
|
202
|
+
* @example
|
|
203
|
+
* ```typescript
|
|
204
|
+
* // From file input
|
|
205
|
+
* const input = document.getElementById('fileInput') as HTMLInputElement;
|
|
206
|
+
* input.addEventListener('change', async (e) => {
|
|
207
|
+
* const file = e.target.files[0];
|
|
208
|
+
* const picture = await imageFileToPicture(file);
|
|
209
|
+
* const modifiedBuffer = await applyPictures("song.mp3", [picture]);
|
|
210
|
+
* });
|
|
211
|
+
* ```
|
|
212
|
+
*/
|
|
213
|
+
export async function imageFileToPicture(
|
|
214
|
+
file: File,
|
|
215
|
+
type: PictureType = PictureType.FrontCover,
|
|
216
|
+
description?: string,
|
|
217
|
+
): Promise<Picture> {
|
|
218
|
+
const arrayBuffer = await file.arrayBuffer();
|
|
219
|
+
const data = new Uint8Array(arrayBuffer);
|
|
220
|
+
|
|
221
|
+
return {
|
|
222
|
+
mimeType: file.type,
|
|
223
|
+
data,
|
|
224
|
+
type,
|
|
225
|
+
description: description || file.name,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Display a Picture in an HTML img element
|
|
231
|
+
*
|
|
232
|
+
* @param picture - Picture object from taglib-wasm
|
|
233
|
+
* @param imgElement - HTMLImageElement to display the picture in
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* ```typescript
|
|
237
|
+
* const pictures = await readPictures("song.mp3");
|
|
238
|
+
* const img = document.getElementById('coverArt') as HTMLImageElement;
|
|
239
|
+
* displayPicture(pictures[0], img);
|
|
240
|
+
* ```
|
|
241
|
+
*/
|
|
242
|
+
export function displayPicture(
|
|
243
|
+
picture: Picture,
|
|
244
|
+
imgElement: HTMLImageElement,
|
|
245
|
+
): void {
|
|
246
|
+
// Clean up previous object URL if any
|
|
247
|
+
if (imgElement.src.startsWith('blob:')) {
|
|
248
|
+
URL.revokeObjectURL(imgElement.src);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Create blob and object URL
|
|
252
|
+
const blob = new Blob([picture.data], { type: picture.mimeType });
|
|
253
|
+
const objectURL = URL.createObjectURL(blob);
|
|
254
|
+
|
|
255
|
+
// Set the src
|
|
256
|
+
imgElement.src = objectURL;
|
|
257
|
+
|
|
258
|
+
// Clean up object URL when image is no longer needed
|
|
259
|
+
imgElement.addEventListener('load', () => {
|
|
260
|
+
// Keep the URL alive for a bit to ensure image is rendered
|
|
261
|
+
setTimeout(() => URL.revokeObjectURL(objectURL), 100);
|
|
262
|
+
}, { once: true });
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Create a download link for a Picture
|
|
267
|
+
*
|
|
268
|
+
* @param picture - Picture object to download
|
|
269
|
+
* @param filename - Suggested filename for download
|
|
270
|
+
* @returns Temporary download URL (remember to revoke it after use)
|
|
271
|
+
*
|
|
272
|
+
* @example
|
|
273
|
+
* ```typescript
|
|
274
|
+
* const pictures = await readPictures("song.mp3");
|
|
275
|
+
* const downloadUrl = createPictureDownloadURL(pictures[0], "cover.jpg");
|
|
276
|
+
*
|
|
277
|
+
* const link = document.createElement('a');
|
|
278
|
+
* link.href = downloadUrl;
|
|
279
|
+
* link.download = "cover.jpg";
|
|
280
|
+
* link.click();
|
|
281
|
+
*
|
|
282
|
+
* // Clean up
|
|
283
|
+
* URL.revokeObjectURL(downloadUrl);
|
|
284
|
+
* ```
|
|
285
|
+
*/
|
|
286
|
+
export function createPictureDownloadURL(
|
|
287
|
+
picture: Picture,
|
|
288
|
+
filename: string = "cover",
|
|
289
|
+
): string {
|
|
290
|
+
const blob = new Blob([picture.data], { type: picture.mimeType });
|
|
291
|
+
return URL.createObjectURL(blob);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Extract all pictures and create a gallery
|
|
296
|
+
*
|
|
297
|
+
* @param file - Audio file to extract pictures from
|
|
298
|
+
* @param container - HTML element to append gallery items to
|
|
299
|
+
* @param options - Gallery display options
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```typescript
|
|
303
|
+
* const galleryDiv = document.getElementById('pictureGallery');
|
|
304
|
+
* await createPictureGallery("song.mp3", galleryDiv, {
|
|
305
|
+
* className: 'album-art',
|
|
306
|
+
* includeDescription: true
|
|
307
|
+
* });
|
|
308
|
+
* ```
|
|
309
|
+
*/
|
|
310
|
+
export async function createPictureGallery(
|
|
311
|
+
file: string | Uint8Array | ArrayBuffer | File,
|
|
312
|
+
container: HTMLElement,
|
|
313
|
+
options: {
|
|
314
|
+
className?: string;
|
|
315
|
+
includeDescription?: boolean;
|
|
316
|
+
onClick?: (picture: Picture, index: number) => void;
|
|
317
|
+
} = {},
|
|
318
|
+
): Promise<void> {
|
|
319
|
+
const pictures = await readPictures(file);
|
|
320
|
+
|
|
321
|
+
// Clear container
|
|
322
|
+
container.innerHTML = '';
|
|
323
|
+
|
|
324
|
+
pictures.forEach((picture, index) => {
|
|
325
|
+
const wrapper = document.createElement('div');
|
|
326
|
+
wrapper.className = options.className || 'picture-item';
|
|
327
|
+
|
|
328
|
+
const img = document.createElement('img');
|
|
329
|
+
displayPicture(picture, img);
|
|
330
|
+
img.alt = picture.description || `Picture ${index + 1}`;
|
|
331
|
+
|
|
332
|
+
if (options.onClick) {
|
|
333
|
+
img.style.cursor = 'pointer';
|
|
334
|
+
img.addEventListener('click', () => options.onClick!(picture, index));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
wrapper.appendChild(img);
|
|
338
|
+
|
|
339
|
+
if (options.includeDescription && picture.description) {
|
|
340
|
+
const caption = document.createElement('p');
|
|
341
|
+
caption.textContent = picture.description;
|
|
342
|
+
wrapper.appendChild(caption);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
container.appendChild(wrapper);
|
|
346
|
+
});
|
|
347
|
+
}
|
package/src/workers.ts
CHANGED
|
@@ -21,6 +21,11 @@ import {
|
|
|
21
21
|
loadTagLibModuleForWorkers,
|
|
22
22
|
type TagLibModule,
|
|
23
23
|
} from "./wasm-workers.ts";
|
|
24
|
+
import {
|
|
25
|
+
EnvironmentError,
|
|
26
|
+
InvalidFormatError,
|
|
27
|
+
MemoryError,
|
|
28
|
+
} from "./errors.ts";
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
31
|
* Represents an audio file with metadata and properties (Workers-compatible).
|
|
@@ -123,7 +128,6 @@ export class AudioFileWorkers {
|
|
|
123
128
|
bitrate,
|
|
124
129
|
sampleRate,
|
|
125
130
|
channels,
|
|
126
|
-
format: this.format(),
|
|
127
131
|
};
|
|
128
132
|
}
|
|
129
133
|
|
|
@@ -324,7 +328,7 @@ export class TagLibWorkers {
|
|
|
324
328
|
* import wasmBinary from "../build/taglib.wasm";
|
|
325
329
|
*
|
|
326
330
|
* const taglib = await TagLibWorkers.initialize(wasmBinary);
|
|
327
|
-
* const file = taglib.
|
|
331
|
+
* const file = taglib.open(audioBuffer);
|
|
328
332
|
* const metadata = file.tag();
|
|
329
333
|
* ```
|
|
330
334
|
*/
|
|
@@ -348,12 +352,15 @@ export class TagLibWorkers {
|
|
|
348
352
|
* @example
|
|
349
353
|
* ```typescript
|
|
350
354
|
* const audioData = new Uint8Array(await request.arrayBuffer());
|
|
351
|
-
* const file = taglib.
|
|
355
|
+
* const file = taglib.open(audioData);
|
|
352
356
|
* ```
|
|
353
357
|
*/
|
|
354
|
-
|
|
358
|
+
open(buffer: Uint8Array): AudioFileWorkers {
|
|
355
359
|
if (!this.module.HEAPU8) {
|
|
356
|
-
throw new
|
|
360
|
+
throw new MemoryError(
|
|
361
|
+
"Wasm module not properly initialized: missing HEAPU8. " +
|
|
362
|
+
"The module may not have loaded correctly in the Workers environment."
|
|
363
|
+
);
|
|
357
364
|
}
|
|
358
365
|
|
|
359
366
|
// Use Emscripten's allocate function for proper memory management
|
|
@@ -366,8 +373,10 @@ export class TagLibWorkers {
|
|
|
366
373
|
}
|
|
367
374
|
|
|
368
375
|
if (!this.module._taglib_file_new_from_buffer) {
|
|
369
|
-
throw new
|
|
370
|
-
"Workers
|
|
376
|
+
throw new EnvironmentError(
|
|
377
|
+
"Workers",
|
|
378
|
+
"requires C-style functions which are not available. Use the Core API instead for this environment",
|
|
379
|
+
"C-style function exports"
|
|
371
380
|
);
|
|
372
381
|
}
|
|
373
382
|
|
|
@@ -377,11 +386,11 @@ export class TagLibWorkers {
|
|
|
377
386
|
);
|
|
378
387
|
|
|
379
388
|
if (fileId === 0) {
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
389
|
+
// Free the allocated memory since file creation failed
|
|
390
|
+
this.module._free(dataPtr);
|
|
391
|
+
throw new InvalidFormatError(
|
|
392
|
+
"Failed to open audio file. File format may be invalid or not supported",
|
|
393
|
+
buffer.length
|
|
385
394
|
);
|
|
386
395
|
}
|
|
387
396
|
|
|
@@ -391,6 +400,16 @@ export class TagLibWorkers {
|
|
|
391
400
|
return new AudioFileWorkers(this.module, fileId);
|
|
392
401
|
}
|
|
393
402
|
|
|
403
|
+
/**
|
|
404
|
+
* @deprecated Use `open()` instead. This method will be removed in the next major version.
|
|
405
|
+
* Open an audio file from a buffer (backward compatibility).
|
|
406
|
+
* @param buffer Audio file data as Uint8Array
|
|
407
|
+
* @returns Audio file instance
|
|
408
|
+
*/
|
|
409
|
+
openFile(buffer: Uint8Array): AudioFileWorkers {
|
|
410
|
+
return this.open(buffer);
|
|
411
|
+
}
|
|
412
|
+
|
|
394
413
|
/**
|
|
395
414
|
* Get the underlying Wasm module for advanced usage.
|
|
396
415
|
* @returns The initialized TagLib Wasm module
|
|
@@ -425,7 +444,7 @@ export async function processAudioMetadata(
|
|
|
425
444
|
{ tag: Tag; properties: AudioProperties | null; format: AudioFormat }
|
|
426
445
|
> {
|
|
427
446
|
const taglib = await TagLibWorkers.initialize(wasmBinary, config);
|
|
428
|
-
const file = taglib.
|
|
447
|
+
const file = taglib.open(audioData);
|
|
429
448
|
|
|
430
449
|
try {
|
|
431
450
|
const tag = file.tag();
|