taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
110 lines (109 loc) • 3.9 kB
JavaScript
import {
createWasiImports
} from "./wasi-host.js";
import { TagLibError } from "../errors/base.js";
import { fileUrlToPath } from "../utils/path.js";
import { isDeno } from "./detector.js";
class WasiHostLoadError extends TagLibError {
constructor(message, cause) {
super("WASI_HOST", message, cause ? { cause } : void 0);
this.name = "WasiHostLoadError";
if (cause) this.cause = cause;
Object.setPrototypeOf(this, WasiHostLoadError.prototype);
}
}
async function resolveFs(provided) {
if (provided) return provided;
if (isDeno()) {
const { createDenoFsProvider } = await import("./wasi-fs-deno.js");
return createDenoFsProvider();
}
const { createNodeFsProvider } = await import("./wasi-fs-node.js");
return createNodeFsProvider();
}
async function loadWasiHost(config) {
const defaultPath = (() => {
const url = new URL("../../taglib-wasi.wasm", import.meta.url);
return url.protocol === "file:" ? fileUrlToPath(url) : url.href;
})();
const wasmPath = config.wasmPath ?? defaultPath;
const preopens = config.preopens ?? {};
const fs = await resolveFs(config.fs);
const wasmBytes = await loadWasmBinary(wasmPath, fs);
const wasmModule = await WebAssembly.compile(wasmBytes);
const memoryProxy = { buffer: new ArrayBuffer(0) };
const hostConfig = {
preopens,
fs,
stderr: (data) => {
const text = new TextDecoder().decode(data);
if (text.trim()) console.error(`[wasi-host] ${text}`);
}
};
const wasiImports = createWasiImports(memoryProxy, hostConfig);
const importObject = {
wasi_snapshot_preview1: wasiImports,
"./wasi_snapshot_preview1": wasiImports,
env: {},
"./env": {}
};
const instance = await WebAssembly.instantiate(wasmModule, importObject);
const memory = instance.exports.memory;
Object.defineProperty(memoryProxy, "buffer", {
get: () => memory.buffer
});
if (instance.exports._initialize) {
instance.exports._initialize();
}
return createWasiModuleFromInstance(instance, memory, wasiImports);
}
async function loadWasmBinary(path, fs) {
if (path.startsWith("http://") || path.startsWith("https://")) {
const response = await fetch(path);
if (!response.ok) {
throw new WasiHostLoadError(
`Failed to fetch Wasm binary: ${response.statusText}`
);
}
return new Uint8Array(await response.arrayBuffer());
}
try {
return await fs.readFile(path);
} catch (cause) {
throw new WasiHostLoadError(
`Failed to read Wasm binary: ${path}`,
cause
);
}
}
function createWasiModuleFromInstance(instance, memory, wasiImports) {
const exports = instance.exports;
function readCString(ptr) {
if (!ptr) return "";
const u8 = new Uint8Array(memory.buffer);
let end = ptr;
while (end < u8.length && u8[end] !== 0) end++;
return new TextDecoder().decode(u8.slice(ptr, end));
}
return {
tl_version: () => {
const ptr = exports.tl_version();
return readCString(ptr);
},
tl_api_version: () => exports.tl_api_version ? exports.tl_api_version() : 100,
malloc: (size) => exports.tl_malloc(size),
free: (ptr) => exports.tl_free(ptr),
tl_read_tags: (pathPtr, bufPtr, len, outSizePtr) => exports.tl_read_tags(pathPtr, bufPtr, len, outSizePtr),
tl_read_id3v2_frames: (pathPtr, bufPtr, len, idPtr, outSizePtr) => exports.tl_read_id3v2_frames(pathPtr, bufPtr, len, idPtr, outSizePtr),
tl_write_tags: (pathPtr, bufPtr, len, tagsPtr, tagsSz, outPtr, outSzPtr) => exports.tl_write_tags(pathPtr, bufPtr, len, tagsPtr, tagsSz, outPtr, outSzPtr),
tl_get_last_error: () => exports.tl_get_last_error(),
tl_get_last_error_code: () => exports.tl_get_last_error_code(),
tl_clear_error: () => exports.tl_clear_error(),
memory,
[Symbol.dispose]: () => wasiImports[Symbol.dispose]()
};
}
export {
WasiHostLoadError,
loadWasiHost
};