taglib-wasm
Version:
TagLib for TypeScript platforms: Deno, Node.js, Bun, Electron, browsers, and Cloudflare Workers
455 lines (454 loc) • 12.9 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 {
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
};