UNPKG

taglib-wasm

Version:

TagLib for TypeScript platforms: Deno, Node.js, Bun, Electron, browsers, and Cloudflare Workers

313 lines (312 loc) 8.69 kB
var __defProp = Object.defineProperty; var __knownSymbol = (name, symbol) => (symbol = Symbol[name]) ? symbol : /* @__PURE__ */ Symbol.for("Symbol." + name); var __typeError = (msg) => { throw TypeError(msg); }; var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); var __using = (stack, value, async) => { if (value != null) { if (typeof value !== "object" && typeof value !== "function") __typeError("Object expected"); var dispose, inner; if (async) dispose = value[__knownSymbol("asyncDispose")]; if (dispose === void 0) { dispose = value[__knownSymbol("dispose")]; if (async) inner = dispose; } if (typeof dispose !== "function") __typeError("Object not disposable"); if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } }; stack.push([async, dispose, value]); } else if (async) { stack.push([async]); } return value; }; var __callDispose = (stack, error, hasError) => { var E = typeof SuppressedError === "function" ? SuppressedError : function(e, s, m, _) { return _ = Error(m), _.name = "SuppressedError", _.error = e, _.suppressed = s, _; }; var fail = (e) => error = hasError ? new E(e, error, "An error was suppressed during disposal") : (hasError = true, e); var next = (it) => { while (it = stack.pop()) { try { var result = it[1] && it[1].call(it[2]); if (it[0]) return Promise.resolve(result).then(next, (e) => (fail(e), next())); } catch (e) { fail(e); } } if (hasError) throw error; }; return next(); }; import { Directory, init } from "@wasmer/sdk"; import { heapViews, WasmArena, WasmMemoryError } from "./wasi-memory.js"; class WasmerInitError extends Error { constructor(message, cause) { super(message, { cause }); __publicField(this, "code", "WASMER_INIT_ERROR"); this.name = "WasmerInitError"; } } class WasmerLoadError extends Error { constructor(message, cause) { super(message, { cause }); __publicField(this, "code", "WASMER_LOAD_ERROR"); this.name = "WasmerLoadError"; } } class WasmerExecutionError extends Error { constructor(message, cause) { super(message, { cause }); __publicField(this, "code", "WASMER_EXECUTION_ERROR"); this.name = "WasmerExecutionError"; } } let isInitialized = false; async function initializeWasmer(useInline = false) { if (isInitialized) return; try { if (useInline) { try { const wasmInline = await import("@wasmer/sdk/wasm-inline"); await init({ module: wasmInline.default || wasmInline }); } catch { await init(); } } else { await init(); } isInitialized = true; } catch (error) { throw new WasmerInitError( `Failed to initialize Wasmer SDK: ${error}`, error ); } } async function loadWasmerWasi(config = {}) { const { wasmPath = "./dist/taglib-wasi.wasm", useInlineWasm = false, mounts = {}, env = {}, args = [], debug = false } = config; await initializeWasmer(useInlineWasm); if (debug) { console.log("[WasmerSDK] Loading WASI module from:", wasmPath); } try { const wasmBytes = await loadWasmBinary(wasmPath); const wasmModule = await WebAssembly.compile(wasmBytes); const mountConfig = { "/": new Directory(), // Root directory ...mounts }; const instance = await instantiateWasi(wasmModule, { env, args, mount: mountConfig }); return createWasiModule(instance, debug); } catch (error) { throw new WasmerLoadError( `Failed to load WASI module from ${wasmPath}: ${error}`, error ); } } async function loadWasmBinary(path) { if (path.startsWith("http://") || path.startsWith("https://")) { const response = await fetch(path); if (!response.ok) { throw new Error(`Failed to fetch WASM: ${response.statusText}`); } return new Uint8Array(await response.arrayBuffer()); } else { return await Deno.readFile(path); } } async function instantiateWasi(wasmModule, config) { const importObject = { wasi_snapshot_preview1: { // Minimal WASI stubs for the test binary fd_write: () => 0, fd_read: () => 0, fd_close: () => 0, fd_seek: () => 0, environ_get: () => 0, environ_sizes_get: () => 0, args_get: () => 0, args_sizes_get: () => 0, proc_exit: () => { }, clock_time_get: () => 0, random_get: () => 0 }, env: { // Environment imports if needed } }; const instance = await WebAssembly.instantiate(wasmModule, importObject); if (instance.exports._initialize) { instance.exports._initialize(); } return instance; } function validateWasiExports(exports) { const requiredExports = [ "memory", "tl_malloc", "tl_free", "tl_version", "tl_read_tags", "tl_write_tags", "tl_get_last_error" ]; for (const name of requiredExports) { if (!(name in exports)) { throw new WasmerLoadError( `WASI module missing required export: ${name}` ); } } } function createMemoryUtils(memory) { const views = heapViews(memory); return { readCString: (ptr) => { if (!ptr) return ""; let end = ptr; while (views.u8[end] !== 0) end++; return new TextDecoder().decode(views.u8.slice(ptr, end)); }, writeCString: (str, ptr) => { const bytes = new TextEncoder().encode(str); views.u8.set(bytes, ptr); views.u8[ptr + bytes.length] = 0; } }; } function createWasiModule(instance, debug) { const exports = instance.exports; validateWasiExports(exports); const memory = exports.memory; const memUtils = createMemoryUtils(memory); if (debug) { console.log( "[WasmerSDK] WASI module loaded with exports:", Object.keys(exports) ); } return createWasiInterface(exports, memory, memUtils); } function createWasiInterface(exports, memory, memUtils) { return { // Core metadata tl_version: () => { const ptr = exports.tl_version(); return memUtils.readCString(ptr); }, tl_api_version: () => { return exports.tl_api_version ? exports.tl_api_version() : 100; }, // Memory management malloc: (size) => exports.tl_malloc(size), free: (ptr) => exports.tl_free(ptr), // MessagePack API tl_read_tags: (pathPtr, bufPtr, len, outSizePtr) => exports.tl_read_tags( pathPtr, bufPtr, len, outSizePtr ), tl_write_tags: (pathPtr, bufPtr, len, tagsPtr, tagsSize, outBufPtr, outSizePtr) => exports.tl_write_tags( pathPtr, bufPtr, len, tagsPtr, tagsSize, outBufPtr, outSizePtr ), // Error handling 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 access memory }; } async function readTagsWithWasi(audioBuffer, wasiModule) { var _stack = []; try { const arena = __using(_stack, new WasmArena(wasiModule)); const inputBuf = arena.allocBuffer(audioBuffer); const outSizePtr = arena.allocUint32(); const sizeResult = wasiModule.tl_read_tags( 0, inputBuf.ptr, inputBuf.size, outSizePtr.ptr ); if (sizeResult !== 0) { const errorCode = wasiModule.tl_get_last_error_code(); throw new WasmMemoryError( `error code ${errorCode}`, "read tags size", errorCode ); } const outputSize = outSizePtr.readUint32(); const outputBuf = arena.alloc(outputSize); const readResult = wasiModule.tl_read_tags( 0, inputBuf.ptr, inputBuf.size, outputBuf.ptr ); if (readResult !== 0) { throw new WasmMemoryError( "failed to read data into buffer", "read tags data", readResult ); } return new Uint8Array(outputBuf.read().slice()); } catch (_) { var _error = _, _hasError = true; } finally { __callDispose(_stack, _error, _hasError); } } async function isWasmerAvailable() { try { await initializeWasmer(); return true; } catch { return false; } } export { WasmerExecutionError, WasmerInitError, WasmerLoadError, initializeWasmer, isWasmerAvailable, loadWasmerWasi, readTagsWithWasi };