taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
247 lines (246 loc) • 6.62 kB
JavaScript
import { encode } from "@msgpack/msgpack";
import { errorMessage, MetadataError } from "../errors/classes.js";
import { toTagLibKey } from "../constants/properties.js";
import { isShadowedNumericMirror } from "../utils/mirror-fields.js";
const PASSTHROUGH_KEYS = /* @__PURE__ */ new Set([
"pictures",
"ratings",
"id3v2Frames",
"lyrics",
"chapters",
"_mp4ChapterStyle",
"bextData",
"ixml",
"id3Tags",
"_stripId3",
// Exact MP4 atom names — must reach C++ verbatim, not via toTagLibKey.
"_mp4ItemNames"
]);
const MSGPACK_ENCODE_OPTIONS = {
sortKeys: false,
forceFloat32: false,
ignoreUndefined: true,
initialBufferSize: 2048,
maxDepth: 32,
extensionCodec: void 0
};
function lyricsTexts(value) {
const entries = Array.isArray(value) ? value : [value];
return entries.map(
(entry) => typeof entry === "string" ? entry : String(entry?.text ?? "")
).filter((text) => text !== "");
}
function encodeTagData(tagData) {
try {
const data = tagData;
const remapped = {};
for (const [key, value] of Object.entries(tagData)) {
if (isShadowedNumericMirror(data, key)) continue;
if (key === "lyrics") {
const texts = lyricsTexts(value);
if (texts.length > 0) remapped["LYRICS"] = texts;
continue;
}
if (PASSTHROUGH_KEYS.has(key)) {
remapped[key] = value;
} else {
remapped[toTagLibKey(key)] = value;
}
}
return encode(cleanObject(remapped), MSGPACK_ENCODE_OPTIONS);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode tag data: ${errorMessage(error)}`
);
}
}
function encodeAudioProperties(audioProps) {
try {
return encode(cleanObject(audioProps), MSGPACK_ENCODE_OPTIONS);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode audio properties: ${errorMessage(error)}`
);
}
}
function encodePropertyMap(propertyMap) {
try {
return encode(propertyMap, MSGPACK_ENCODE_OPTIONS);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode property map: ${errorMessage(error)}`
);
}
}
function encodePicture(picture) {
try {
const cleanedPicture = {
...picture,
data: picture.data instanceof Uint8Array ? picture.data : new Uint8Array(picture.data)
};
return encode(cleanedPicture, MSGPACK_ENCODE_OPTIONS);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode picture: ${errorMessage(error)}`
);
}
}
function encodePictureArray(pictures) {
try {
const cleanedPictures = pictures.map((picture) => ({
...picture,
data: picture.data instanceof Uint8Array ? picture.data : new Uint8Array(picture.data)
}));
return encode(cleanedPictures, MSGPACK_ENCODE_OPTIONS);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode picture array: ${errorMessage(error)}`
);
}
}
function encodeMessagePack(data, options = {}) {
try {
const mergedOptions = { ...MSGPACK_ENCODE_OPTIONS, ...options };
return encode(cleanObject(data), mergedOptions);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode data: ${errorMessage(error)}`
);
}
}
function encodeMessagePackCompact(data) {
try {
const compactOptions = {
...MSGPACK_ENCODE_OPTIONS,
sortKeys: true,
initialBufferSize: 512,
forceFloat32: true
};
return encode(cleanObject(data), compactOptions);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode compact data: ${errorMessage(error)}`
);
}
}
function cleanObject(obj) {
if (obj === null || obj === void 0) return null;
if (typeof obj !== "object") return obj;
if (obj instanceof Uint8Array || Array.isArray(obj)) return obj;
if (obj instanceof Date) return obj;
const cleaned = {};
for (const [key, value] of Object.entries(obj)) {
if (value === void 0) continue;
if (value === null) {
cleaned[key] = null;
continue;
}
if (typeof value === "string" && value === "") continue;
cleaned[key] = typeof value === "object" ? cleanObject(value) : value;
}
return cleaned;
}
function encodeBatchTagData(tagDataArray) {
try {
const cleanedArray = tagDataArray.map((tagData) => cleanObject(tagData));
return encode(cleanedArray, {
...MSGPACK_ENCODE_OPTIONS,
initialBufferSize: 8192,
maxDepth: 16
});
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode batch tag data: ${errorMessage(error)}`
);
}
}
function* encodeMessagePackStream(dataIterator) {
try {
for (const item of dataIterator) {
yield encode(cleanObject(item), {
...MSGPACK_ENCODE_OPTIONS,
initialBufferSize: 1024
});
}
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode streaming data: ${errorMessage(error)}`
);
}
}
function estimateMessagePackSize(data) {
try {
return encode(cleanObject(data), {
...MSGPACK_ENCODE_OPTIONS,
initialBufferSize: 512
}).length;
} catch {
return Math.floor(JSON.stringify(data).length * 0.75);
}
}
function encodeFastTagData(tagData) {
try {
const fastOptions = {
sortKeys: false,
ignoreUndefined: true,
initialBufferSize: 256,
maxDepth: 8
};
return encode(cleanObject(tagData), fastOptions);
} catch (error) {
throw new MetadataError(
"write",
`Failed to encode fast tag data: ${errorMessage(error)}`
);
}
}
function canEncodeToMessagePack(data) {
try {
encode(cleanObject(data), {
...MSGPACK_ENCODE_OPTIONS,
maxDepth: 16,
initialBufferSize: 256
});
return true;
} catch {
return false;
}
}
function compareEncodingEfficiency(data) {
const jsonString = JSON.stringify(data);
const jsonSize = new TextEncoder().encode(jsonString).length;
const messagePackData = encode(cleanObject(data), MSGPACK_ENCODE_OPTIONS);
const messagePackSize = messagePackData.length;
const sizeReduction = (jsonSize - messagePackSize) / jsonSize * 100;
return {
messagePackSize,
jsonSize,
sizeReduction: Math.max(0, sizeReduction),
speedImprovement: 10
};
}
export {
PASSTHROUGH_KEYS,
canEncodeToMessagePack,
compareEncodingEfficiency,
encodeAudioProperties,
encodeBatchTagData,
encodeFastTagData,
encodeMessagePack,
encodeMessagePackCompact,
encodeMessagePackStream,
encodePicture,
encodePictureArray,
encodePropertyMap,
encodeTagData,
estimateMessagePackSize
};