UNPKG

taglib-wasm

Version:

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

455 lines (454 loc) 12.9 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 { WasmerExecutionError } from "./wasmer-sdk-loader.js"; import { WasmArena, WasmMemoryError } from "./wasi-memory.js"; import { decodeTagData } from "../msgpack/decoder.js"; import { encodeTagData } from "../msgpack/encoder.js"; class WasiToTagLibAdapter { constructor(wasiModule) { __publicField(this, "wasi"); __publicField(this, "heap"); // Embind class constructors (stubs for WASI compatibility) __publicField(this, "FileHandle", class { constructor() { throw new WasmerExecutionError( "Use createFileHandle() instead of new FileHandle()" ); } }); __publicField(this, "TagWrapper", class { constructor() { throw new WasmerExecutionError("TagWrapper not directly constructable"); } }); __publicField(this, "AudioPropertiesWrapper", class { constructor() { throw new WasmerExecutionError( "AudioPropertiesWrapper not directly constructable" ); } }); this.wasi = wasiModule; this.heap = new Uint8Array(wasiModule.memory.buffer); } // Emscripten compatibility properties get ready() { return Promise.resolve(this); } get HEAP8() { return new Int8Array(this.wasi.memory.buffer); } get HEAP16() { return new Int16Array(this.wasi.memory.buffer); } get HEAPU8() { return new Uint8Array(this.wasi.memory.buffer); } get HEAP32() { return new Int32Array(this.wasi.memory.buffer); } get HEAPU16() { return new Uint16Array(this.wasi.memory.buffer); } get HEAPU32() { return new Uint32Array(this.wasi.memory.buffer); } get HEAPF32() { return new Float32Array(this.wasi.memory.buffer); } get HEAPF64() { return new Float64Array(this.wasi.memory.buffer); } // Memory management _malloc(size) { return this.wasi.malloc(size); } _free(ptr) { this.wasi.free(ptr); } _realloc(ptr, newSize) { const newPtr = this.wasi.malloc(newSize); if (newPtr && ptr) { const oldData = this.heap.slice(ptr, ptr + newSize); this.heap.set(oldData, newPtr); this.wasi.free(ptr); } return newPtr; } // String operations UTF8ToString(ptr) { if (!ptr) return ""; let end = ptr; while (this.heap[end] !== 0) end++; return new TextDecoder().decode(this.heap.slice(ptr, end)); } stringToUTF8(str, ptr, maxBytes) { const bytes = new TextEncoder().encode(str); const len = Math.min(bytes.length, maxBytes - 1); this.heap.set(bytes.slice(0, len), ptr); this.heap[ptr + len] = 0; return len; } lengthBytesUTF8(str) { return new TextEncoder().encode(str).length; } // File handle creation createFileHandle() { return new WasiFileHandle(this.wasi, this.heap); } // Version info version() { return this.wasi.tl_version(); } // Stubs for Emscripten-specific features not in WASI addFunction(func) { throw new WasmerExecutionError( "addFunction not supported in WASI mode" ); } removeFunction(ptr) { throw new WasmerExecutionError( "removeFunction not supported in WASI mode" ); } cwrap(name, returnType, argTypes) { throw new WasmerExecutionError( "cwrap not supported in WASI mode - use direct exports" ); } ccall(name, returnType, argTypes, args) { throw new WasmerExecutionError( "ccall not supported in WASI mode - use direct exports" ); } } class WasiFileHandle { constructor(wasiModule, heap) { __publicField(this, "wasi"); __publicField(this, "heap"); __publicField(this, "fileData", null); __publicField(this, "tagData", null); __publicField(this, "destroyed", false); this.wasi = wasiModule; this.heap = heap; } checkNotDestroyed() { if (this.destroyed) { throw new WasmerExecutionError( "FileHandle has been destroyed" ); } } loadFromBuffer(buffer) { this.checkNotDestroyed(); try { this.fileData = buffer; const msgpackData = this.readTagsSync(buffer); this.tagData = decodeTagData(msgpackData); return true; } catch (error) { console.error("Failed to load from buffer:", error); return false; } } readTagsSync(buffer) { var _stack = []; try { const arena = __using(_stack, new WasmArena(this.wasi)); const inputBuf = arena.allocBuffer(buffer); const outSizePtr = arena.allocUint32(); const sizeResult = this.wasi.tl_read_tags( 0, inputBuf.ptr, inputBuf.size, outSizePtr.ptr ); if (sizeResult !== 0) { const errorCode = this.wasi.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 = this.wasi.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); } } loadFromPath(path) { this.checkNotDestroyed(); throw new WasmerExecutionError( "loadFromPath not implemented for WASI - use loadFromBuffer" ); } isValid() { this.checkNotDestroyed(); return this.fileData !== null && this.fileData.length > 0; } save() { this.checkNotDestroyed(); if (!this.fileData || !this.tagData) { return false; } try { return this.performSave(); } catch (error) { console.error("Failed to save:", error); return false; } } performSave() { var _stack = []; try { const arena = __using(_stack, new WasmArena(this.wasi)); const tagBytes = encodeTagData(this.tagData); const inputBuf = arena.allocBuffer(this.fileData); const tagBuf = arena.allocBuffer(tagBytes); const outSizePtr = arena.allocUint32(); const sizeResult = this.wasi.tl_write_tags( 0, inputBuf.ptr, inputBuf.size, tagBuf.ptr, tagBuf.size, 0, outSizePtr.ptr ); if (sizeResult !== 0) { return false; } const outputSize = outSizePtr.readUint32(); const outputBuf = arena.alloc(outputSize); const writeResult = this.wasi.tl_write_tags( 0, inputBuf.ptr, inputBuf.size, tagBuf.ptr, tagBuf.size, outputBuf.ptr, outSizePtr.ptr ); if (writeResult === 0) { this.fileData = new Uint8Array(outputBuf.read().slice()); return true; } return false; } catch (_) { var _error = _, _hasError = true; } finally { __callDispose(_stack, _error, _hasError); } } getTag() { this.checkNotDestroyed(); if (!this.tagData) { return this.createEmptyTag(); } return this.createTagWrapper(this.tagData); } createEmptyTag() { return this.createTagWrapper({}); } createTagWrapper(data) { return { title: () => data.title || "", artist: () => data.artist || "", album: () => data.album || "", comment: () => data.comment || "", genre: () => data.genre || "", year: () => data.year || 0, track: () => data.track || 0, setTitle: (value) => { this.tagData = { ...this.tagData, title: value }; }, setArtist: (value) => { this.tagData = { ...this.tagData, artist: value }; }, setAlbum: (value) => { this.tagData = { ...this.tagData, album: value }; }, setComment: (value) => { this.tagData = { ...this.tagData, comment: value }; }, setGenre: (value) => { this.tagData = { ...this.tagData, genre: value }; }, setYear: (value) => { this.tagData = { ...this.tagData, year: value }; }, setTrack: (value) => { this.tagData = { ...this.tagData, track: value }; } }; } getAudioProperties() { this.checkNotDestroyed(); return { length: () => 0, lengthInSeconds: () => 0, lengthInMilliseconds: () => 0, bitrate: () => 0, sampleRate: () => 0, channels: () => 0 }; } getFormat() { this.checkNotDestroyed(); if (!this.fileData) return "Unknown"; const magic = this.fileData.slice(0, 4); if (magic[0] === 255 && (magic[1] & 224) === 224) return "MP3"; if (magic[0] === 102 && magic[1] === 76 && magic[2] === 97 && magic[3] === 67) return "FLAC"; if (magic[0] === 79 && magic[1] === 103 && magic[2] === 103 && magic[3] === 83) return "OGG"; return "Unknown"; } getBuffer() { this.checkNotDestroyed(); return this.fileData ?? new Uint8Array(0); } getProperties() { this.checkNotDestroyed(); return this.tagData ?? {}; } setProperties(props) { this.checkNotDestroyed(); this.tagData = { ...this.tagData, ...props }; } getProperty(key) { this.checkNotDestroyed(); const props = this.tagData; return props?.[key]?.toString() ?? ""; } setProperty(key, value) { this.checkNotDestroyed(); this.tagData = { ...this.tagData, [key]: value }; } isMP4() { this.checkNotDestroyed(); if (!this.fileData || this.fileData.length < 8) return false; const magic = this.fileData.slice(4, 8); return magic[0] === 102 && magic[1] === 116 && magic[2] === 121 && magic[3] === 112; } getMP4Item(key) { this.checkNotDestroyed(); return this.getProperty(key); } setMP4Item(key, value) { this.checkNotDestroyed(); this.setProperty(key, value); } removeMP4Item(key) { this.checkNotDestroyed(); if (this.tagData) { const props = this.tagData; delete props[key]; } } getPictures() { this.checkNotDestroyed(); return this.tagData?.pictures ?? []; } setPictures(pictures) { this.checkNotDestroyed(); this.tagData = { ...this.tagData }; this.tagData.pictures = pictures; } addPicture(picture) { this.checkNotDestroyed(); const pictures = this.getPictures(); pictures.push(picture); this.setPictures(pictures); } removePictures() { this.checkNotDestroyed(); this.tagData = { ...this.tagData }; this.tagData.pictures = []; } getRatings() { this.checkNotDestroyed(); return this.tagData?.ratings ?? []; } setRatings(ratings) { this.checkNotDestroyed(); const normalizedRatings = ratings.map((r) => ({ rating: r.rating, email: r.email ?? "", counter: r.counter ?? 0 })); this.tagData = { ...this.tagData }; this.tagData.ratings = normalizedRatings; } destroy() { this.fileData = null; this.tagData = null; this.destroyed = true; } } export { WasiToTagLibAdapter };