--- package.json --- { "name": "motion-card-studio-template", "version": "1.3.7", "private": true, "description": "Ten-second 1280x720 animated birthday card template with a bundled renderer", "type": "module", "scripts": { "render": "node render.mjs" }, "engines": { "node": ">=20" } } --- render.mjs --- import {spawn} from 'node:child_process'; import {once} from 'node:events'; import {access, mkdir} from 'node:fs/promises'; import {dirname, join, resolve} from 'node:path'; import {fileURLToPath} from 'node:url'; const here = dirname(fileURLToPath(import.meta.url)); const args = process.argv.slice(2); const option = (flag, fallback) => { const index = args.indexOf(flag); return index >= 0 && index + 1 < args.length ? args[index + 1] : fallback; }; const name = option('--name', 'Friend').slice(0, 60); const title = option('--title', 'Happy Birthday').slice(0, 80); const fps = 30; const width = 1280; const height = 720; const renderer = join(here, 'bin', 'motion-render-core'); await access(renderer).catch(() => { throw new Error(`Bundled renderer not found at ${renderer} (Linux x86-64 build is included in this template).`); }); const outputDirectory = resolve(here, 'output'); await mkdir(outputDirectory, {recursive: true}); const safeFile = name.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '') || 'birthday'; const output = join(outputDirectory, `${safeFile}-birthday-card.mp4`); const manifest = join(outputDirectory, 'render-manifest.json'); const core = spawn(renderer, ['--name', name, '--title', title, '--manifest', manifest], {stdio: ['ignore', 'pipe', 'inherit']}); const ffmpeg = spawn('ffmpeg', [ '-hide_banner', '-loglevel', 'error', '-y', '-f', 'rawvideo', '-pixel_format', 'rgb24', '-video_size', `${width}x${height}`, '-framerate', String(fps), '-i', 'pipe:0', '-c:v', 'libx264', '-pix_fmt', 'yuv420p', '-movflags', '+faststart', output, ], {stdio: ['pipe', 'inherit', 'inherit']}); core.on('error', error => { throw error; }); ffmpeg.on('error', error => { throw error; }); core.stdout.pipe(ffmpeg.stdin); const [coreStatus] = await once(core, 'close'); const [ffmpegStatus] = await once(ffmpeg, 'close'); if (coreStatus !== 0) throw new Error(`renderer exited with status ${coreStatus}`); if (ffmpegStatus !== 0) throw new Error(`ffmpeg exited with status ${ffmpegStatus}`); process.stdout.write(JSON.stringify({ok: true, output, manifest, seconds: 10, width, height, fps, name, title}) + '\n'); --- README.md --- # Motion Card Studio template Create a ten-second 1280×720 birthday MP4 from the included animation template. Requirements: Linux x86-64, Node.js 20+, and `ffmpeg` on `PATH`. The frame renderer is bundled as a self-contained binary at `bin/motion-render-core`, so there is nothing to install. Before rendering, the binary downloads the glyph atlas (about 256 KB) from the template host, the same origin you downloaded this project from, verifies its checksum and keeps it in memory only. The atlas is required: without network access to the template host the renderer stops with exit status 3 and no video is produced. ```bash npm run render -- --name "Taisei" --title "Happy Birthday" ``` The finished video is written to `output/taisei-birthday-card.mp4` (H.264, 1280×720, 30 fps, exactly 10 seconds). A small render manifest with the frame digest is written to `output/render-manifest.json`. --- bin/motion-render-core --- (self-contained Linux x86-64 renderer binary, SHA-256 7af57d555ae39a8de038e94cc9e2c08180a401f7086b47ac2d8dc5c3c61f6f9d)