/** * Recodifica um vídeo para a vitrine animada, em que o tempo do vídeo muda a cada quadro da rolagem. * Para a busca ser instantânea o vídeo precisa de um quadro-chave a cada 6 quadros e nenhum B-frame. O vídeo roda * a 12 quadros por segundo (--fps): o tempo dele anda devagar em relação à rolagem (cerca de 1 s de vídeo por 50 px), * então 12 quadros por segundo de vídeo são dezenas por segundo de tela, e o arquivo cai a menos da metade. O tamanho * de cada arquivo fica em até 1,2 MB (--max-mb): sobe o CRF (34, 37, 40, 43) e, se preciso, reduz a imagem até caber. * * ffmpeg-static não é dependência do site: instale-o numa pasta temporária e rode o script a partir dela * (as dependências são resolvidas a partir da pasta onde o comando foi executado; vale também um `ffmpeg` no PATH * ou --ffmpeg ): * * mkdir rec && cd rec && npm init -y && npm i ffmpeg-static * node /scripts/encode-scrub-video.mjs --in desktop-original.mp4 --out /public/videos//scrub-desktop.mp4 --size 960x600 * * Só existe o scrub do computador: o celular da moldura mostra o pôster, sem vídeo. * * Use como entrada o vídeo de melhor qualidade que existir (a entrada de record-project-videos.mjs antes do corte * para MP4/WebM), não os MP4 já comprimidos de public/videos. * * A capa de compartilhamento (og.jpg, 1200x630) sai do poster desktop: * ffmpeg -i public/videos//desktop.webp -vf "scale=1200:750:flags=lanczos,crop=1200:630:0:0" -q:v 4 public/videos//og.jpg */ import { spawnSync } from "node:child_process"; import { statSync } from "node:fs"; import { createRequire } from "node:module"; import { join } from "node:path"; const require = createRequire(join(process.cwd(), "noop.js")); const args = Object.fromEntries( process.argv.slice(2).reduce((acc, cur, i, all) => { if (cur.startsWith("--")) acc.push([cur.slice(2), all[i + 1]]); return acc; }, []), ); function resolveFfmpeg() { if (args.ffmpeg) return args.ffmpeg; try { return require("ffmpeg-static"); } catch { return "ffmpeg"; } } const ffmpegPath = resolveFfmpeg(); const { in: input, out: output } = args; const [width, height] = (args.size ?? "960x600").split("x").map(Number); const FPS = Number(args.fps ?? 12); const MAX_BYTES = Number(args["max-mb"] ?? 1.2) * 1024 * 1024; const CRFS = (args.crfs ?? "34,37,40,43").split(",").map(Number); if (!input || !output || !width || !height) { console.error( "uso: --in