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

127 lines (126 loc) 4.73 kB
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 };