UNPKG

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
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 };