taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
207 lines (206 loc) • 6.87 kB
JavaScript
var __defProp = Object.defineProperty;
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);
import {
isNamedAudioInput
} from "../types/audio-formats.js";
import { InvalidFormatError, TagLibInitializationError } from "../errors.js";
import { isDeno } from "../runtime/detector.js";
import { AudioFileImpl } from "./audio-file-impl.js";
import { loadAudioData } from "./load-audio-data.js";
import { mergeTagUpdates } from "../utils/tag-mapping.js";
import { FileOperationError } from "../errors.js";
import { VERSION } from "../version.js";
import { wrapEmbindHandle } from "./embind-adapter.js";
function toWasiPath(osPath) {
if (osPath.startsWith("\\\\") || osPath.startsWith("//")) {
throw new FileOperationError(
"read",
`UNC paths are not supported. Path: ${osPath}`
);
}
let p = osPath;
if (!p.startsWith("/") && !/^[A-Za-z]:/.test(p)) {
const g = globalThis;
const cwd = isDeno() ? Deno.cwd() : g.process?.cwd?.();
if (cwd) {
p = cwd.replace(/[/\\]+$/, "") + "/" + p;
}
}
p = p.replaceAll("\\", "/");
const driveMatch = p.match(/^([A-Za-z]):\//);
if (driveMatch) {
p = `/${driveMatch[1].toUpperCase()}${p.slice(2)}`;
}
p = p.replace(/\/\.\//g, "/").replace(/\/+/g, "/");
if (!p.startsWith("/")) p = "/" + p;
return p;
}
class TagLib {
constructor(module) {
__publicField(this, "module");
this.module = module;
}
/**
* Initialize the TagLib Wasm module and return a ready-to-use instance.
* @param options - Wasm loading configuration (binary, URL, backend selection).
* @returns A configured TagLib instance.
* @throws {TagLibInitializationError} If the Wasm module fails to load.
*/
static async initialize(options) {
const { loadTagLibModule } = await import("../runtime/module-loader.js");
const module = await loadTagLibModule(options);
return new TagLib(module);
}
/**
* Open an audio file for reading and writing metadata.
* @param input - File path, Uint8Array, ArrayBuffer, File object, or NamedAudioInput.
* @param options - Partial-loading options for large files.
* @returns An AudioFile instance (use `using` for automatic cleanup).
* @throws {TagLibInitializationError} If the module is not properly initialized.
* @throws {InvalidFormatError} If the file is corrupted or unsupported.
*/
async open(input, options) {
if (!this.module.createFileHandle) {
throw new TagLibInitializationError(
"TagLib module not properly initialized: createFileHandle not found. Make sure the module is fully loaded before calling open."
);
}
const actualInput = isNamedAudioInput(input) ? input.data : input;
const sourcePath = typeof actualInput === "string" ? actualInput : void 0;
if (typeof actualInput === "string" && this.module.isWasi) {
const fileHandle2 = this.module.createFileHandle();
try {
const fh = fileHandle2;
if (fh.loadFromPath) {
const wasiPath = toWasiPath(actualInput);
const success = fh.loadFromPath(wasiPath);
if (!success) {
throw new InvalidFormatError(
`Failed to load audio file. Path: ${actualInput}`
);
}
return new AudioFileImpl(
this.module,
fileHandle2,
sourcePath,
actualInput,
false
);
}
} catch (error) {
if (typeof fileHandle2.destroy === "function") {
fileHandle2.destroy();
}
throw error;
}
}
const opts = {
partial: true,
maxHeaderSize: 1024 * 1024,
maxFooterSize: 128 * 1024,
...options
};
const { data: audioData, isPartiallyLoaded } = await loadAudioData(
actualInput,
opts
);
const uint8Array = actualInput instanceof Uint8Array && audioData.buffer === actualInput.buffer ? new Uint8Array(
audioData.buffer.slice(
audioData.byteOffset,
audioData.byteOffset + audioData.byteLength
)
) : audioData;
const rawHandle = this.module.createFileHandle();
const fileHandle = this.module.isWasi ? rawHandle : wrapEmbindHandle(rawHandle);
try {
const success = fileHandle.loadFromBuffer(uint8Array);
if (!success) {
throw new InvalidFormatError(
"Failed to load audio file. File may be corrupted or in an unsupported format",
uint8Array.byteLength
);
}
return new AudioFileImpl(
this.module,
fileHandle,
sourcePath,
actualInput,
isPartiallyLoaded,
opts
);
} catch (error) {
if (typeof fileHandle.destroy === "function") {
fileHandle.destroy();
}
throw error;
}
}
async edit(input, fn) {
const file = await this.open(input);
try {
await fn(file);
if (typeof input === "string") {
await file.saveToFile();
} else {
file.save();
return file.getFileBuffer();
}
} finally {
file.dispose();
}
}
/**
* Update tags in a file and save to disk in one operation.
* @param path - Path to the audio file.
* @param tags - Tag fields to update (partial update supported).
* @throws {InvalidFormatError} If the file is corrupted or unsupported.
* @throws {FileOperationError} If saving to disk fails.
*/
async updateFile(path, tags) {
const file = await this.open(path);
try {
mergeTagUpdates(file, tags);
await file.saveToFile();
} finally {
file.dispose();
}
}
/**
* Copy an audio file to a new path with updated tags.
* @param sourcePath - Path to the source audio file.
* @param destPath - Destination path for the tagged copy.
* @param tags - Tag fields to set on the copy.
* @throws {InvalidFormatError} If the source file is corrupted or unsupported.
* @throws {FileOperationError} If reading or writing fails.
*/
async copyWithTags(sourcePath, destPath, tags) {
const file = await this.open(sourcePath);
try {
mergeTagUpdates(file, tags);
await file.saveToFile(destPath);
} finally {
file.dispose();
}
}
/** Returns the taglib-wasm version with embedded TagLib version. */
version() {
return `${VERSION} (TagLib ${this.taglibVersion()})`;
}
taglibVersion() {
if (this.module.getVersion) {
return this.module.getVersion();
}
if (this.module.version) {
return this.module.version();
}
return "unknown";
}
}
async function createTagLib(module) {
return new TagLib(module);
}
export {
TagLib,
createTagLib
};