@bendyline/squisq-cli 1.1.6 → 1.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/util/nativeEncoder.ts"],"sourcesContent":["/**\n * Native FFmpeg Encoder\n *\n * Encodes PNG frames to MP4 using a locally installed ffmpeg binary.\n * Writes frames to a temporary directory, invokes ffmpeg as a child process,\n * and reads the resulting MP4.\n *\n * This is the fast path — used when native ffmpeg is detected on the system.\n * Falls back to the WASM encoder (in @bendyline/squisq-video) when unavailable.\n */\n\nimport { execFile } from 'node:child_process';\nimport { writeFile, readFile, mkdir, rm } from 'node:fs/promises';\nimport { join } from 'node:path';\nimport { tmpdir } from 'node:os';\nimport { randomBytes } from 'node:crypto';\n\nimport type { VideoExportOptions } from '@bendyline/squisq-video';\nimport { QUALITY_PRESETS, resolveDimensions } from '@bendyline/squisq-video';\n\n/**\n * Encode frame PNGs to MP4 using native ffmpeg.\n *\n * @param ffmpegPath - Absolute path to the ffmpeg binary\n * @param frames - Array of PNG image bytes (one per frame)\n * @param audio - Optional audio file bytes to mux\n * @param outputPath - Where to write the final MP4\n * @param options - Encoding options (fps, quality, dimensions, progress)\n */\nexport async function framesToMp4Native(\n ffmpegPath: string,\n frames: Uint8Array[],\n audio: Uint8Array | null,\n outputPath: string,\n options: VideoExportOptions = {},\n): Promise<void> {\n const fps = options.fps ?? 30;\n const quality = options.quality ?? 'normal';\n const { width, height } = resolveDimensions(options);\n const preset = QUALITY_PRESETS[quality];\n const onProgress = options.onProgress;\n\n if (frames.length === 0) {\n throw new Error('No frames provided for encoding');\n }\n\n // Create a temp working directory\n const tmpId = randomBytes(8).toString('hex');\n const workDir = join(tmpdir(), `squisq-video-${tmpId}`);\n await mkdir(workDir, { recursive: true });\n\n try {\n onProgress?.(0, 'writing frames');\n\n // Write frame PNGs to temp directory\n const padLen = String(frames.length).length;\n for (let i = 0; i < frames.length; i++) {\n const name = `frame-${String(i + 1).padStart(padLen, '0')}.png`;\n await writeFile(join(workDir, name), frames[i]);\n\n if (onProgress && i % 10 === 0) {\n onProgress(Math.round((i / frames.length) * 30), 'writing frames');\n }\n }\n\n // Write audio if provided\n let audioPath: string | null = null;\n if (audio) {\n audioPath = join(workDir, 'audio-input');\n await writeFile(audioPath, audio);\n }\n\n onProgress?.(30, 'encoding');\n\n // Build ffmpeg arguments\n const padPattern = join(workDir, `frame-%0${padLen}d.png`);\n const args = ['-y', '-framerate', String(fps), '-i', padPattern];\n\n if (audioPath) {\n args.push('-i', audioPath);\n }\n\n args.push(\n '-c:v',\n 'libx264',\n '-preset',\n preset.preset,\n '-crf',\n String(preset.crf),\n '-pix_fmt',\n 'yuv420p',\n '-vf',\n `scale=${width}:${height}:force_original_aspect_ratio=decrease,pad=${width}:${height}:(ow-iw)/2:(oh-ih)/2`,\n );\n\n if (audioPath) {\n args.push('-c:a', 'aac', '-b:a', '128k', '-shortest');\n }\n\n args.push(outputPath);\n\n // Run ffmpeg\n await new Promise<void>((resolve, reject) => {\n const proc = execFile(ffmpegPath, args, { timeout: 600_000 }, (err) => {\n if (err) {\n reject(new Error(`ffmpeg failed: ${err.message}`));\n } else {\n resolve();\n }\n });\n\n // ffmpeg writes progress to stderr\n proc.stderr?.on('data', () => {\n // Could parse ffmpeg progress output here in the future\n });\n });\n\n onProgress?.(100, 'done');\n } finally {\n // Clean up temp directory\n await rm(workDir, { recursive: true, force: true });\n }\n}\n\n/**\n * Encode frames using native ffmpeg and return the MP4 bytes (instead of writing to disk).\n * Useful when the caller needs the bytes in memory.\n */\nexport async function framesToMp4NativeBytes(\n ffmpegPath: string,\n frames: Uint8Array[],\n audio: Uint8Array | null,\n options: VideoExportOptions = {},\n): Promise<Uint8Array> {\n const tmpId = randomBytes(8).toString('hex');\n const tmpOutput = join(tmpdir(), `squisq-video-out-${tmpId}.mp4`);\n\n try {\n await framesToMp4Native(ffmpegPath, frames, audio, tmpOutput, options);\n const data = await readFile(tmpOutput);\n return new Uint8Array(data.buffer, data.byteOffset, data.byteLength);\n } finally {\n await rm(tmpOutput, { force: true });\n }\n}\n"],"mappings":"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","names":[]}