UNPKG

cerevox

Version:

TypeScript SDK for browser automation and secure command execution in highly available and scalable micro computer environments

231 lines 7.19 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.getMp4Duration = getMp4Duration; exports.checkMp4HasAudio = checkMp4HasAudio; // mp4-duration.ts const fs_1 = require("fs"); function readAtom(buf, offset, end) { if (offset + 8 > end) return null; let size = buf.readUInt32BE(offset); const type = buf.toString('ascii', offset + 4, offset + 8); let header = 8; if (size === 1) { if (offset + 16 > end) return null; const high = buf.readUInt32BE(offset + 8); const low = buf.readUInt32BE(offset + 12); size = Number((BigInt(high) << BigInt(32)) | BigInt(low)); header = 16; } else if (size === 0) { size = end - offset; // box 一直到父级末尾 } // 基础合法性 if (size < header || offset + size > end) return null; return { type, offset, header, size, contentStart: offset + header, contentEnd: offset + size, }; } function* iterateChildren(buf, parent) { let p = parent.contentStart; while (p + 8 <= parent.contentEnd) { const child = readAtom(buf, p, parent.contentEnd); if (!child) break; yield child; if (child.size <= 0) break; // 防御 p += child.size; } } function findChild(buf, parent, type) { const children = Array.from(iterateChildren(buf, parent)); for (const c of children) { if (c.type === type) return c; } return null; } function findChildren(buf, parent, type) { const out = []; const children = Array.from(iterateChildren(buf, parent)); for (const c of children) { if (c.type === type) out.push(c); } return out; } function readUInt64BE(buf, offset) { const hi = buf.readUInt32BE(offset); const lo = buf.readUInt32BE(offset + 4); return (BigInt(hi) << BigInt(32)) | BigInt(lo); } /** 读取 mvhd 的 timescale/duration(秒) */ function parseMvhdSeconds(buf, mvhd) { const base = mvhd.contentStart; // FullBox: version(1) + flags(3) 后跟字段 const version = buf.readUInt8(base); if (version === 0) { const timescale = buf.readUInt32BE(base + 12); const duration = buf.readUInt32BE(base + 16); if (!timescale) return null; return duration / timescale; } if (version === 1) { const timescale = buf.readUInt32BE(base + 20); const duration = Number(readUInt64BE(buf, base + 24)); if (!timescale) return null; return duration / timescale; } return null; } /** 读取 mdhd 的 timescale/duration(秒) */ function parseMdhdSeconds(buf, mdhd) { const base = mdhd.contentStart; // FullBox const version = buf.readUInt8(base); if (version === 0) { const timescale = buf.readUInt32BE(base + 12); const duration = buf.readUInt32BE(base + 16); if (!timescale) return null; return duration / timescale; } if (version === 1) { const timescale = buf.readUInt32BE(base + 20); const duration = Number(readUInt64BE(buf, base + 24)); if (!timescale) return null; return duration / timescale; } return null; } /** 主函数:获取 MP4 时长(秒) */ async function getMp4Duration(filePath) { const buf = await fs_1.promises.readFile(filePath); // 根层遍历,找到 moov(可能在头也可能在尾;faststart 会把 moov 前移) let moov = null; { let p = 0; const end = buf.length; while (p + 8 <= end) { const a = readAtom(buf, p, end); if (!a) break; if (a.type === 'moov') { moov = a; break; } p += a.size; } } if (!moov) throw new Error('moov box not found'); // 先尝试 mvhd const mvhd = findChild(buf, moov, 'mvhd'); const best = mvhd ? parseMvhdSeconds(buf, mvhd) : null; // 轨道兜底:取所有 trak→mdia→mdhd 的最大值 const traks = findChildren(buf, moov, 'trak'); let trackMax = null; for (const trak of traks) { const mdia = findChild(buf, trak, 'mdia'); if (!mdia) continue; const mdhd = findChild(buf, mdia, 'mdhd'); if (!mdhd) continue; const sec = parseMdhdSeconds(buf, mdhd); if (sec != null) { trackMax = trackMax == null ? sec : Math.max(trackMax, sec); } } const final = best ?? trackMax; if (final == null || !isFinite(final)) { return null; } return final; } /** * 检查MP4文件是否包含音频轨道 * @param filePath MP4文件路径 * @returns Promise<boolean> 是否包含音频轨道 */ async function checkMp4HasAudio(filePath) { try { const buf = await fs_1.promises.readFile(filePath); // 找到 moov box let moov = null; { let p = 0; const end = buf.length; while (p + 8 <= end) { const a = readAtom(buf, p, end); if (!a) break; if (a.type === 'moov') { moov = a; break; } p += a.size; } } if (!moov) return false; // 遍历所有 trak (轨道) const traks = findChildren(buf, moov, 'trak'); for (const trak of traks) { // 查找 trak -> mdia -> hdlr const mdia = findChild(buf, trak, 'mdia'); if (!mdia) continue; const hdlr = findChild(buf, mdia, 'hdlr'); if (!hdlr) continue; // 解析 hdlr box 来确定轨道类型 const handlerType = parseHandlerType(buf, hdlr); if (handlerType === 'soun') { return true; // 找到音频轨道 } } return false; // 没有找到音频轨道 } catch (error) { console.warn(`Failed to check MP4 audio for ${filePath}:`, error); return false; } } /** * 解析 hdlr box 获取处理器类型 * @param buf Buffer * @param hdlr hdlr atom * @returns 处理器类型字符串 */ function parseHandlerType(buf, hdlr) { try { const base = hdlr.contentStart; // hdlr box 结构: version(1) + flags(3) + pre_defined(4) + handler_type(4) + reserved(12) + name(variable) if (base + 16 > hdlr.contentEnd) return null; // handler_type 在偏移 8 处 (version + flags + pre_defined 后) const handlerType = buf.toString('ascii', base + 8, base + 12); return handlerType; } catch (error) { return null; } } // 用法示例 // (async () => { // const s = await getMp4Duration("example.mp4"); // console.log("视频时长(秒):", s); // const hasAudio = await checkMp4HasAudio("example.mp4"); // console.log("包含音频:", hasAudio); // })(); //# sourceMappingURL=mp4-duration.js.map