taglib-wasm
Version:
TagLib-Wasm is the universal tagging library for TypeScript/JavaScript platforms: Browsers, Node.js, Deno, Bun, Cloudflare Workers, and Electron apps
127 lines (126 loc) • 4.73 kB
JavaScript
const FIXED_PREFIX_LEN = 602;
const VERSION_OFFSET = 346;
const UMID_OFFSET = 348;
const UMID_LEN = 64;
const LOUDNESS_OFFSET = 412;
const LOUDNESS_UNSET = 32767;
function readFixedString(bytes, offset, len) {
let end = offset;
const max = Math.min(offset + len, bytes.length);
while (end < max && bytes[end] !== 0) end++;
let s = "";
for (let i = offset; i < end; i++) s += String.fromCharCode(bytes[i]);
return s;
}
function writeFixedString(bytes, offset, len, value) {
for (let i = 0; i < len; i++) {
bytes[offset + i] = i < value.length ? value.charCodeAt(i) & 255 : 0;
}
}
function clampInt16(n) {
return Math.max(-32768, Math.min(32767, Math.round(n)));
}
function loudnessRaw(v) {
return v === void 0 ? LOUDNESS_UNSET : clampInt16(v * 100);
}
function loudnessFromRaw(raw) {
return raw === LOUDNESS_UNSET ? void 0 : raw / 100;
}
function bytesToHex(bytes) {
let s = "";
for (const b of bytes) s += b.toString(16).padStart(2, "0");
return s;
}
function hexToBytes(hex, outLen) {
const clean = hex.replace(/[^0-9a-fA-F]/g, "");
const out = new Uint8Array(outLen);
for (let i = 0; i + 1 < clean.length + 1 && i / 2 < outLen; i += 2) {
const pair = clean.slice(i, i + 2);
if (pair.length === 2) out[i / 2] = parseInt(pair, 16);
}
return out;
}
function decodeBext(bytes) {
if (bytes.length < VERSION_OFFSET + 2) return void 0;
const dv = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
const timeRefLow = dv.getUint32(338, true);
const timeRefHigh = dv.getUint32(342, true);
const version = dv.getUint16(VERSION_OFFSET, true);
const result = {
description: readFixedString(bytes, 0, 256),
originator: readFixedString(bytes, 256, 32),
originatorReference: readFixedString(bytes, 288, 32),
originationDate: readFixedString(bytes, 320, 10),
originationTime: readFixedString(bytes, 330, 8),
timeReferenceSamples: BigInt(timeRefHigh) << 32n | BigInt(timeRefLow),
version,
codingHistory: bytes.length > FIXED_PREFIX_LEN ? readFixedString(
bytes,
FIXED_PREFIX_LEN,
bytes.length - FIXED_PREFIX_LEN
) : ""
};
if (version >= 1 && bytes.length >= UMID_OFFSET + UMID_LEN) {
result.umid = bytesToHex(
bytes.subarray(UMID_OFFSET, UMID_OFFSET + UMID_LEN)
);
}
if (version >= 2 && bytes.length >= LOUDNESS_OFFSET + 10) {
const lv = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET, true));
const lr = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 2, true));
const mtp = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 4, true));
const mml = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 6, true));
const msl = loudnessFromRaw(dv.getInt16(LOUDNESS_OFFSET + 8, true));
if (lv !== void 0) result.loudnessValueDb = lv;
if (lr !== void 0) result.loudnessRangeDb = lr;
if (mtp !== void 0) result.maxTruePeakLevelDbtp = mtp;
if (mml !== void 0) result.maxMomentaryLoudnessDb = mml;
if (msl !== void 0) result.maxShortTermLoudnessDb = msl;
}
return result;
}
function encodeBext(b) {
const codingHistory = b.codingHistory ?? "";
const bytes = new Uint8Array(FIXED_PREFIX_LEN + codingHistory.length);
const dv = new DataView(bytes.buffer);
const hasLoudness = b.loudnessValueDb !== void 0 || b.loudnessRangeDb !== void 0 || b.maxTruePeakLevelDbtp !== void 0 || b.maxMomentaryLoudnessDb !== void 0 || b.maxShortTermLoudnessDb !== void 0;
const version = Number.isInteger(b.version) ? b.version : hasLoudness ? 2 : b.umid ? 1 : 0;
writeFixedString(bytes, 0, 256, b.description ?? "");
writeFixedString(bytes, 256, 32, b.originator ?? "");
writeFixedString(bytes, 288, 32, b.originatorReference ?? "");
writeFixedString(bytes, 320, 10, b.originationDate ?? "");
writeFixedString(bytes, 330, 8, b.originationTime ?? "");
const tr = b.timeReferenceSamples ?? 0n;
dv.setUint32(338, Number(tr & 0xffffffffn), true);
dv.setUint32(342, Number(tr >> 32n & 0xffffffffn), true);
dv.setUint16(VERSION_OFFSET, version, true);
if (version >= 1 && b.umid) {
bytes.set(hexToBytes(b.umid, UMID_LEN), UMID_OFFSET);
}
if (version >= 2) {
dv.setInt16(LOUDNESS_OFFSET, loudnessRaw(b.loudnessValueDb), true);
dv.setInt16(LOUDNESS_OFFSET + 2, loudnessRaw(b.loudnessRangeDb), true);
dv.setInt16(LOUDNESS_OFFSET + 4, loudnessRaw(b.maxTruePeakLevelDbtp), true);
dv.setInt16(
LOUDNESS_OFFSET + 6,
loudnessRaw(b.maxMomentaryLoudnessDb),
true
);
dv.setInt16(
LOUDNESS_OFFSET + 8,
loudnessRaw(b.maxShortTermLoudnessDb),
true
);
}
writeFixedString(
bytes,
FIXED_PREFIX_LEN,
codingHistory.length,
codingHistory
);
return bytes;
}
export {
decodeBext,
encodeBext
};