const fs = require('fs'); const SIGNATURES = [ { kind: 'mp4-iso', test: (b) => b.length >= 12 && b.slice(4, 8).toString('ascii') === 'ftyp' }, { kind: 'matroska', test: (b) => b.length >= 4 && b[0] === 0x1A && b[1] === 0x45 && b[2] === 0xDF && b[3] === 0xA3 }, { kind: 'avi', test: (b) => b.length >= 12 && b.slice(0, 4).toString('ascii') === 'RIFF' && b.slice(8, 12).toString('ascii') === 'AVI ' }, { kind: 'wav', test: (b) => b.length >= 12 && b.slice(0, 4).toString('ascii') === 'RIFF' && b.slice(8, 12).toString('ascii') === 'WAVE' }, { kind: 'flv', test: (b) => b.length >= 3 && b.slice(0, 3).toString('ascii') === 'FLV' }, { kind: 'asf-wmv', test: (b) => b.length >= 4 && b[0] === 0x30 && b[1] === 0x26 && b[2] === 0xB2 && b[3] === 0x75 }, { kind: 'mpeg-ps', test: (b) => b.length >= 4 && b[0] === 0x00 && b[1] === 0x00 && b[2] === 0x01 && (b[3] === 0xBA || b[3] === 0xB3) }, { kind: 'mpeg-ts', test: (b) => b.length >= 1 && b[0] === 0x47 }, { kind: 'mp3', test: (b) => b.length >= 3 && (b.slice(0, 3).toString('ascii') === 'ID3' || (b[0] === 0xFF && (b[1] & 0xE0) === 0xE0)) }, { kind: 'ogg', test: (b) => b.length >= 4 && b.slice(0, 4).toString('ascii') === 'OggS' }, { kind: 'jpeg', test: (b) => b.length >= 3 && b[0] === 0xFF && b[1] === 0xD8 && b[2] === 0xFF }, { kind: 'png', test: (b) => b.length >= 8 && b[0] === 0x89 && b.slice(1, 4).toString('ascii') === 'PNG' }, { kind: 'pdf', test: (b) => b.length >= 5 && b.slice(0, 5).toString('ascii') === '%PDF-' }, { kind: 'zip', test: (b) => b.length >= 4 && b[0] === 0x50 && b[1] === 0x4B && (b[2] === 0x03 || b[2] === 0x05 || b[2] === 0x07) }, { kind: 'html', test: (b) => { const s = b.toString('ascii', 0, Math.min(b.length, 64)).trimStart().toLowerCase(); return s.startsWith(' 0 ? bytes : 64; return new Promise((resolve) => { fs.open(filePath, 'r', (err, fd) => { if (err) return resolve({ ok: false, error: err.message, kind: 'unreadable' }); const buf = Buffer.alloc(want); fs.read(fd, buf, 0, want, 0, (rerr, bytesRead) => { fs.close(fd, () => {}); if (rerr) return resolve({ ok: false, error: rerr.message, kind: 'unreadable' }); const slice = buf.slice(0, bytesRead); resolve({ ok: true, bytesRead, kind: detectKind(slice), isVideoLike: isVideoLikeKind(detectKind(slice)), headHex: slice.toString('hex') }); }); }); }); } function summarizeFileStat(filePath) { try { const st = fs.statSync(filePath); return { size: st.size, mtime: st.mtime.toISOString(), isFile: st.isFile() }; } catch (err) { return { error: err.message }; } } module.exports = { detectKind, isVideoLikeKind, probeFileHead, summarizeFileStat, VIDEO_KINDS, SIGNATURES };