taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
274 lines (273 loc) • 8.02 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 { PICTURE_TYPE_NAMES, PICTURE_TYPE_VALUES } from "../types.js";
import * as bwf from "./audio-file-bwf.js";
import {
assertFrameBodies,
assertFrameId,
assertMp3,
toPublicFrame
} from "./id3v2-frames.js";
import {
errorMessage,
FileOperationError,
UnsupportedFormatError
} from "../errors.js";
import { writeFileData } from "../utils/write.js";
import { getNodeFsSync } from "../utils/node-fs.js";
import { normalized } from "../utils/rating.js";
import { isDeno } from "../runtime/detector.js";
import { BaseAudioFileImpl } from "./audio-file-base.js";
import { saveViaFreshHandle } from "./save-reconstruct.js";
function sortChapters(list) {
return [...list].sort((a, b) => a.startTimeMs - b.startTimeMs);
}
function inferEndTimeMs(sorted, index, trackEndMs) {
const own = sorted[index].endTimeMs;
if (own !== void 0) return own;
const next = sorted[index + 1];
return next ? next.startTimeMs : trackEndMs;
}
function readFileSync(path) {
if (isDeno()) return Deno.readFileSync(path);
const fs = getNodeFsSync();
if (fs) return new Uint8Array(fs.readFileSync(path));
throw new FileOperationError(
"read",
"node:fs is unavailable in this runtime: cannot read file data from disk",
path
);
}
class AudioFileImpl extends BaseAudioFileImpl {
constructor(module, fileHandle, sourcePath, originalSource, isPartiallyLoaded = false, partialLoadOptions) {
super(
module,
fileHandle,
sourcePath,
originalSource,
isPartiallyLoaded,
partialLoadOptions
);
__publicField(this, "pathModeBuffer", null);
}
save() {
if (this.isPartiallyLoaded && this.originalSource) {
throw new FileOperationError(
"save",
"Cannot save partially loaded file directly. Use saveToFile() instead"
);
}
this.cachedAudioProperties = null;
this.pathModeBuffer = null;
return this.handle.save();
}
getFileBuffer() {
const buffer = this.handle.getBuffer();
if (buffer.length > 0) return buffer;
if (this.pathModeBuffer) return this.pathModeBuffer;
if (this.sourcePath) {
try {
this.pathModeBuffer = readFileSync(this.sourcePath);
return this.pathModeBuffer;
} catch (error) {
if (error instanceof FileOperationError) throw error;
throw new FileOperationError(
"read",
`Cannot return file data: ${errorMessage(error)}`,
this.sourcePath,
{ cause: error }
);
}
}
throw new FileOperationError(
"read",
"No file data available: in-memory buffer is empty and no source path is set"
);
}
async saveToFile(path) {
const targetPath = path ?? this.sourcePath;
if (!targetPath) {
throw new FileOperationError(
"save",
"No file path available. Provide a path or open the file from a path"
);
}
if (this.isPartiallyLoaded && this.originalSource) {
await saveViaFreshHandle(
this.module,
this.handle,
this.originalSource,
targetPath,
false,
this.partialDeletedPropertyKeys()
);
this.isPartiallyLoaded = false;
this.originalSource = void 0;
} else if (this.module.isWasi && this.sourcePath && targetPath !== this.sourcePath) {
await saveViaFreshHandle(
this.module,
this.handle,
this.sourcePath,
targetPath,
true
);
} else {
if (!this.save()) {
throw new FileOperationError(
"save",
"Failed to save changes to in-memory buffer"
);
}
const buffer = this.handle.getBuffer();
if (buffer.length > 0) {
await writeFileData(targetPath, buffer);
}
}
this.pathModeBuffer = null;
}
getPictures() {
const picturesArray = this.handle.getPictures();
return picturesArray.map((pic) => ({
mimeType: pic.mimeType,
data: pic.data,
type: PICTURE_TYPE_NAMES[pic.type] ?? "Other",
description: pic.description
}));
}
setPictures(pictures) {
this.handle.setPictures(pictures.map((pic) => ({
mimeType: pic.mimeType,
data: pic.data,
type: PICTURE_TYPE_VALUES[pic.type] ?? 0,
description: pic.description ?? ""
})));
}
addPicture(picture) {
this.handle.addPicture({
mimeType: picture.mimeType,
data: picture.data,
type: PICTURE_TYPE_VALUES[picture.type] ?? 0,
description: picture.description ?? ""
});
}
removePictures() {
this.handle.removePictures();
}
getChapters() {
const sorted = sortChapters(this.handle.getChapters());
const trackEndMs = this.audioProperties()?.durationMs;
return sorted.map((c, i) => ({
startTimeMs: c.startTimeMs,
endTimeMs: inferEndTimeMs(sorted, i, trackEndMs),
title: c.title || void 0,
id: c.id || void 0,
source: c.source
}));
}
setChapters(chapters, options) {
const fmt = this.getFormat();
if (fmt !== "MP3" && fmt !== "MP4") {
throw new UnsupportedFormatError(fmt, ["MP3", "MP4"], {
operation: "setChapters"
});
}
const sorted = sortChapters(chapters);
const trackEndMs = this.audioProperties()?.durationMs;
const style = options?.mp4ChapterStyle ?? "quicktime";
const source = fmt === "MP4" ? style : "id3";
const raw = sorted.map((c, i) => ({
id: c.id,
startTimeMs: c.startTimeMs,
endTimeMs: inferEndTimeMs(sorted, i, trackEndMs) ?? c.startTimeMs,
title: c.title,
source
}));
this.handle.setChapters(raw, style);
}
getBext() {
return bwf.getBext(this.handle);
}
setBext(bext) {
bwf.setBext(this.handle, this.getFormat(), bext);
}
getBextData() {
return bwf.getBextData(this.handle);
}
setBextData(data) {
bwf.setBextData(this.handle, this.getFormat(), data);
}
getIxml() {
return bwf.getIxml(this.handle);
}
setIxml(data) {
bwf.setIxml(this.handle, this.getFormat(), data);
}
getRatings() {
return this.handle.getRatings().map(
(r) => ({
rating: r.rating,
email: r.email || void 0,
counter: r.counter || void 0
})
);
}
setRatings(ratings) {
this.handle.setRatings(ratings.map((r) => ({
rating: r.rating,
email: r.email ?? "",
counter: r.counter ?? 0
})));
}
getLyrics() {
return this.handle.getLyrics().map((l) => {
const out = { text: l.text };
if (l.description) out.description = l.description;
if (l.language) out.language = l.language;
return out;
});
}
setLyrics(lyrics) {
this.handle.setLyrics(lyrics.map((l) => ({
text: l.text,
description: l.description ?? "",
language: l.language ?? ""
})));
}
getRating() {
const ratings = this.getRatings();
return ratings.length > 0 ? normalized(ratings[0].rating) : void 0;
}
setRating(rating, email) {
this.setRatings([{ rating, email, counter: 0 }]);
}
getId3v2Frames(id) {
assertMp3(this.getFormat());
if (id !== void 0) assertFrameId(id, "read");
return this.handle.getId3v2Frames(id ?? "").map(toPublicFrame);
}
setId3v2Frames(id, data) {
assertMp3(this.getFormat());
assertFrameId(id, "write");
assertFrameBodies(id, data);
this.handle.setId3v2Frames(id, data);
}
removeId3v2Frames(id) {
assertMp3(this.getFormat());
assertFrameId(id, "write");
this.handle.removeId3v2Frames(id);
}
hasId3Tags() {
return this.handle.hasId3Tags();
}
stripId3Tags(opts) {
this.handle.stripId3Tags({
v1: opts?.v1 ?? true,
v2: opts?.v2 ?? true
});
}
}
export {
AudioFileImpl,
readFileSync
};