remotion-render-pipeline
Turns the composition into a delivered MP4.
Triggers
remotion-composition-builder reports ready: true.
- User says "render it" / "ship it" with a known job.
Inputs
jobId
composition — id matching src/Root.tsx (e.g. Explainer)
outputPath — relative to remotion-studio/output/
props — full input props (brand + storyboard + ...) as JSON
linear — {teamId, projectId} for ticket routing
Method
- Pre-flight typecheck —
cd remotion-studio && npx tsc --noEmit. On red, return to remotion-composition-builder with the error.
- Voiceover synthesis —
render/voiceover.ts writes per-scene MP3s into public/audio/{jobId}/scene-N.mp3 (under public/ so staticFile() resolves them). Uses cache key sha1(voiceId|style|text). Skips if ELEVENLABS_API_KEY is missing — composition must still render with on-screen text only.
- Render —
npx tsx render/render.ts --comp={composition} --out={outputPath} --props='{...}' --jobId={jobId}. Wraps in 600s timeout; one retry on hang/crash.
- Validate — file exists, duration ±0.5s of expected, codec
h264, resolution matches composition, size <100MB.
- Deliver:
- Upload to Supabase bucket
remotion-renders — signed URL, 90-day retention.
- Telegram via
app.server.telegram_video.send_telegram_video() (the shared util). If file >45MB, send the signed URL link instead of inline upload (Telegram cap is 50MB).
- Open Linear ticket in
{teamId}/{projectId} with: render URL, source brief, BrandConfig version (git sha of src/brands/{slug}.ts), composition file path.
- Status — write summary to
.research/renders/{jobId}.json.
Output
{
"jobId": "ra-nir-explainer-2026-04-28T15-30-00",
"mp4Path": "remotion-studio/output/ra-nir-explainer-2026-04-28T15-30-00.mp4",
"supabaseUrl": "https://<project>.supabase.co/storage/v1/object/sign/remotion-renders/...",
"telegramSent": true,
"linearIssue": "RA-2049",
"validation": { "durationSec": 60.0, "codec": "h264", "resolution": "1920x1080", "sizeMB": 12.4 }
}
Boundaries
- Never run the render with
tsc --noEmit failing — wastes 2-5 minutes for a guaranteed bad output.
- Never auto-merge the source PR (composition + brand changes) before render verification passes.
- Never upload renders with
[DRAFT] baked in to the brand bucket — drafts go to remotion-drafts.
Failure escalation
- Timeout / hang → kill, retry once → if second failure, open Linear with
priority: 2, attach stderr.
- Validation fail (wrong duration / codec) → Linear
priority: 3 with diff vs expected.
- Telegram fail → fall back to
send_telegram_document() with .mp4 as attachment.
Reused
Per-project API keys
This skill is invoked from inside the calling project (Synthex, Pi-SEO, RestoreAssist, anywhere). Each project supplies its own keys via its own .env / .env.local. The skill never reads keys from Pi-Dev-Ops.
Required-or-warned env at render time:
ELEVENLABS_API_KEY — voiceover synthesis. Missing = silent render with on-screen text only (warning logged, render continues).
TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID — Telegram delivery. Missing = skip Telegram step (the MP4 still lands on disk + Supabase if configured).
SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY — signed-URL upload. Missing = skip Supabase step.
LINEAR_API_KEY — ticket creation. Missing = skip Linear step.
REMOTION_LICENSE_KEY — required for any commercial render under Remotion's fair-source licence.
Invocation pattern from a project shell:
cd ~/Pi-CEO/Synthex
source .env.local # project's own keys
cd ~/Pi-Dev-Ops/remotion-studio
npx tsx render/render.ts \
--comp=Explainer \
--out=$OLDPWD/.remotion-renders/synthex-launch.mp4 \
--jobId=synthex-launch-$(date +%s) \
--props='{"brand":"synthex","hookSec":8,"ctaSec":8,"storyboard":[...]}'
Or invoke through the Pi-Dev-Ops orchestrator API which inherits the calling project's env at session-spawn time.
Output convention: write MP4 into <calling-project>/.remotion-renders/ (gitignored) by default — keeps each project's artifacts in its own tree. Override with --out=....
1---2name: remotion-render-pipeline3description: Final step. Synthesises ElevenLabs voiceover for each scene, runs `npx tsx render/render.ts` to produce an MP4, validates duration / codec / resolution / size, uploads to Supabase storage, ships the result via Telegram, and opens a Linear ticket. Triggered after remotion-composition-builder reports ready. Failure modes (timeout, hang, type error) escalate to Linear.4---56# remotion-render-pipeline78Turns the composition into a delivered MP4.910## Triggers1112- `remotion-composition-builder` reports `ready: true`.13- User says "render it" / "ship it" with a known job.1415## Inputs1617- `jobId`18- `composition` — id matching `src/Root.tsx` (e.g. `Explainer`)19- `outputPath` — relative to `remotion-studio/output/`20- `props` — full input props (brand + storyboard + ...) as JSON21- `linear` — `{teamId, projectId}` for ticket routing2223## Method24251. **Pre-flight typecheck** — `cd remotion-studio && npx tsc --noEmit`. On red, return to `remotion-composition-builder` with the error.262. **Voiceover synthesis** — `render/voiceover.ts` writes per-scene MP3s into `public/audio/{jobId}/scene-N.mp3` (under `public/` so `staticFile()` resolves them). Uses cache key `sha1(voiceId|style|text)`. Skips if `ELEVENLABS_API_KEY` is missing — composition must still render with on-screen text only.273. **Render** — `npx tsx render/render.ts --comp={composition} --out={outputPath} --props='{...}' --jobId={jobId}`. Wraps in 600s timeout; one retry on hang/crash.284. **Validate** — file exists, duration ±0.5s of expected, codec `h264`, resolution matches composition, size <100MB.295. **Deliver**:30 - Upload to Supabase bucket `remotion-renders` — signed URL, 90-day retention.31 - Telegram via `app.server.telegram_video.send_telegram_video()` (the shared util). If file >45MB, send the signed URL link instead of inline upload (Telegram cap is 50MB).32 - Open Linear ticket in `{teamId}/{projectId}` with: render URL, source brief, BrandConfig version (git sha of `src/brands/{slug}.ts`), composition file path.336. **Status** — write summary to `.research/renders/{jobId}.json`.3435## Output3637```jsonc38{39 "jobId": "ra-nir-explainer-2026-04-28T15-30-00",40 "mp4Path": "remotion-studio/output/ra-nir-explainer-2026-04-28T15-30-00.mp4",41 "supabaseUrl": "https://<project>.supabase.co/storage/v1/object/sign/remotion-renders/...",42 "telegramSent": true,43 "linearIssue": "RA-2049",44 "validation": { "durationSec": 60.0, "codec": "h264", "resolution": "1920x1080", "sizeMB": 12.4 }45}46```4748## Boundaries4950- Never run the render with `tsc --noEmit` failing — wastes 2-5 minutes for a guaranteed bad output.51- Never auto-merge the source PR (composition + brand changes) before render verification passes.52- Never upload renders with `[DRAFT]` baked in to the brand bucket — drafts go to `remotion-drafts`.5354## Failure escalation5556- Timeout / hang → kill, retry once → if second failure, open Linear with `priority: 2`, attach stderr.57- Validation fail (wrong duration / codec) → Linear `priority: 3` with diff vs expected.58- Telegram fail → fall back to `send_telegram_document()` with `.mp4` as attachment.5960## Reused6162- [`render/render.ts`](../../remotion-studio/render/render.ts)63- [`render/voiceover.ts`](../../remotion-studio/render/voiceover.ts)64- [`app/server/telegram_video.py`](../../app/server/telegram_video.py) (shared util)65- `config/harness/projects.json` for Linear team/project resolution6667## Per-project API keys6869This skill is invoked **from inside the calling project** (Synthex, Pi-SEO, RestoreAssist, anywhere). Each project supplies its own keys via its own `.env` / `.env.local`. The skill never reads keys from Pi-Dev-Ops.7071Required-or-warned env at render time:72- `ELEVENLABS_API_KEY` — voiceover synthesis. Missing = silent render with on-screen text only (warning logged, render continues).73- `TELEGRAM_BOT_TOKEN`, `TELEGRAM_CHAT_ID` — Telegram delivery. Missing = skip Telegram step (the MP4 still lands on disk + Supabase if configured).74- `SUPABASE_URL`, `SUPABASE_SERVICE_ROLE_KEY` — signed-URL upload. Missing = skip Supabase step.75- `LINEAR_API_KEY` — ticket creation. Missing = skip Linear step.76- `REMOTION_LICENSE_KEY` — required for any commercial render under Remotion's fair-source licence.7778Invocation pattern from a project shell:79```bash80cd ~/Pi-CEO/Synthex81source .env.local # project's own keys82cd ~/Pi-Dev-Ops/remotion-studio83npx tsx render/render.ts \84 --comp=Explainer \85 --out=$OLDPWD/.remotion-renders/synthex-launch.mp4 \86 --jobId=synthex-launch-$(date +%s) \87 --props='{"brand":"synthex","hookSec":8,"ctaSec":8,"storyboard":[...]}'88```8990Or invoke through the Pi-Dev-Ops orchestrator API which inherits the calling project's env at session-spawn time.9192Output convention: write MP4 into `<calling-project>/.remotion-renders/` (gitignored) by default — keeps each project's artifacts in its own tree. Override with `--out=...`.