278 lines · 8.9 KB
#!/usr/bin/env bun
/**
 * Encodes a source video into an immutable HLS (fMP4) package plus scrub sprite sheets:
 *   bun scripts/video/prepare.ts --source in.mov --id intro --version v1 [--out dir]
 * Needs ffmpeg/ffprobe (libx264, aac) and cwebp. Upload the result with scripts/video/upload.ts.
 */
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { parseArgs } from "node:util";
import { SCRUB_MANIFEST, type ScrubManifest } from "../../src/lib/video";

const LADDER = [
  { height: 240, bitrate: 400_000, audio: "64k" },
  { height: 360, bitrate: 1_000_000, audio: "96k" },
  { height: 720, bitrate: 3_000_000, audio: "128k" },
  { height: 1080, bitrate: 6_000_000, audio: "128k" },
  { height: 1440, bitrate: 10_000_000, audio: "160k" },
  { height: 2160, bitrate: 20_000_000, audio: "160k" },
];
const SEGMENT_SECONDS = 4;
const SCRUB = { width: 256, height: 144, columns: 10, rows: 10, maxFrames: 1000 };
const MAX_FRAME_RATE = 60;
const QUARTER_TURN = 90;
const HALF_TURN = 180;
const ID = /^[a-z0-9][a-z0-9-]{0,63}$/;

async function run(argv: string[]): Promise<string> {
  const proc = Bun.spawn(argv, { stdout: "pipe", stderr: "pipe" });
  const [out, err, code] = await Promise.all([
    new Response(proc.stdout).text(),
    new Response(proc.stderr).text(),
    proc.exited,
  ]);
  if (code !== 0) throw new Error(`${argv[0]} exited ${code}: ${err.trim()}`);
  return out;
}

interface Probe {
  width: number;
  height: number;