UNPKG

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
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 };