taglib-wasm
Version:
TagLib for TypeScript platforms: Deno, Node.js, Bun, Electron, browsers, and Cloudflare Workers
313 lines (312 loc) • 8.69 kB
JavaScript
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
};