remotion-render-pipeline
Turns the composition into a delivered MP4.
Triggers
remotion-composition-builderreportsready: true.- User says "render it" / "ship it" with a known job.
Inputs
jobIdcomposition— id matchingsrc/Root.tsx(e.g.Explainer)outputPath— relative toremotion-studio/output/props— full input props (brand + storyboard + ...) as JSONlinear—{teamId, projectId}for ticket routing
Method
- Pre-flight typecheck —
cd remotion-studio && npx tsc --noEmit. On red, return toremotion-composition-builderwith the error. - Voiceover synthesis —
render/voiceover.tswrites per-scene MP3s intopublic/audio/{jobId}/scene-N.mp3(underpublic/sostaticFile()resolves them). Uses cache keysha1(voiceId|style|text). Skips ifELEVENLABS_API_KEYis 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 ofsrc/brands/{slug}.ts), composition file path.
- Upload to Supabase bucket
- 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 --noEmitfailing — 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 toremotion-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: 3with diff vs expected. - Telegram fail → fall back to
send_telegram_document()with.mp4as attachment.
Reused
render/render.tsrender/voiceover.tsapp/server/telegram_video.py(shared util)config/harness/projects.jsonfor Linear team/project resolution
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=....