import { resolve } from "node:path"; import { spawn } from "bun"; import { getHighlighterTokens, keyframes, Scene } from "./Scene"; import { render } from "takumi-js"; const fps = 30; const durationSeconds = 4; const totalFrames = fps * durationSeconds; const devicePixelRatio = 1.2; const width = 1200 * devicePixelRatio; const height = 630 * devicePixelRatio; const outputPath = resolve(import.meta.dir, "../output/animation.mp4"); const tokens = await getHighlighterTokens(); const ffmpeg = spawn( [ "ffmpeg", "-y", "-f", "rawvideo", "-pixel_format", "rgba", "-video_size", `${width}x${height}`, "-framerate", `${fps}`, "-i", "pipe:0", "-vf", "format=yuv420p10le", "-c:v", "libx265", "-crf", "16", "-preset", "medium", "-tag:v", "hvc1", outputPath, ], { stdin: "pipe", stdout: "ignore", stderr: "ignore" }, ); const images = [ { src: "logo.svg", path: "takumi.svg" }, { src: "background.jpg", path: "martin-martz-W0NRebXbsjM-unsplash.jpg" }, ].map(({ src, path }) => ({ src, data: () => Bun.file(resolve(import.meta.dir, "../../../assets/images", path)).arrayBuffer(), })); const thumbnailPath = resolve(import.meta.dir, "../output/thumbnail.webp"); console.log(`Rendering thumbnail to ${thumbnailPath}...`); // Generate thumbnail at 2.5s where things are stable const thumbnailFrame = await render(, { width, height, devicePixelRatio, format: "webp", keyframes, images, timeMs: 2500, }); if (!thumbnailFrame) throw new Error("Thumbnail frame is undefined"); await Bun.write(thumbnailPath, thumbnailFrame); console.log(`Success! Thumbnail saved to ${thumbnailPath}`); console.log(`Rendering ${totalFrames} frames to ${outputPath}...`); const scene = ; const framePromises = Array.from({ length: totalFrames }, (_, i) => { const timeMs = (i / fps) * 1000; return render(scene, { width, height, devicePixelRatio, format: "raw", keyframes, images, timeMs, }); }); for (let i = 0; i < totalFrames; i++) { const frame = await framePromises[i]; if (!frame) throw new Error("Frame is undefined"); ffmpeg.stdin.write(frame); if (i % fps === 0) console.log( ` Progress: ${Math.round((i / totalFrames) * 100) .toString() .padStart(3)}%`, ); } ffmpeg.stdin.end(); const exitCode = await ffmpeg.exited; if (exitCode === 0) { console.log(`\nSuccess! Video saved to ${outputPath}`); } else { console.error(`ffmpeg failed with exit code ${exitCode}`); process.exit(1); }