| name | remotion-video |
|---|---|
| description | Create/edit branded videos with Remotion — Liquid Glass overlay, subtitles, key phrases, zooms, CTA. Reusable composition architecture. |
Target: macOS with local Chrome. Project structure supports multiple videos via config-per-video pattern.
- Drop
.mp4intopublic/videos/, CTA image intopublic/images/ - Create
src/compositions/<id>.tsfollowing theVideoCompositionConfigtype - Import and push to
compositionsarray insrc/Root.tsx - Render:
PUPPETEER_EXECUTABLE_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" npx remotion render <id> out/<id>.mp4 --browser-executable="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --concurrency=1 --crf=16
- concurrency=1 required — parallel rendering opens Chrome tabs on sequential ports (3000, 3001, …) that fail. Always use
--concurrency=1. <Video>is visual-only during render — audio must be a separate<Audio>component using the same source file. Without it, output is silent.staticFile()for all public assets —staticFile("/videos/x.mp4"),staticFile("/images/x.png"). No raw string paths.- Google Fonts —
@import url()won't work in headless Chrome. Use@remotion/google-fonts(loadFont) at module init. Expose the loadedfontFamilystring in theme tokens. - TypeScript ~5.5 only — TS 7+ breaks the Remotion bundler (
typescript.sys.readFileundefined). Installtypescript@~5.5.0. - Browser executable — Always pass
--browser-executable="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome"or setPUPPETEER_EXECUTABLE_PATH. The auto-downloaded headless shell is 93MB and slow. - OffthreadVideo — Avoid. Causes
delayRendertimeouts on macOS 14. Use<Video>+<Audio>instead. - Source video graphics — Overlays sit on top of whatever the source MP4 contains. If the original video already has text boxes, icons, or lower-thirds, they WILL compete visually with subtitles and key phrases. Prefer clean-background talking-head footage. Test with a frame extraction before committing to a full render.
- CTA image per video — Each composition needs its own CTA image in
public/images/. Reusing another video's CTA image causes branding mismatch. Create a custom screenshot or graphic for every video's call to action. npm run buildis a full render — It renders LegalTech3 from scratch (~3 min). For verification after code changes, usenpx tsc --noEmit(typecheck) plus a short render:remotion render <id> out/verify.mp4 --frames=0-5 --concurrency=1. Only runnpm run buildwhen you want the final LegalTech3 output.
All components are generic — they receive data via props:
| Component | Props | Role |
|---|---|---|
VideoPlayer |
config: VideoCompositionConfig |
Wires everything together |
LiquidGlassOverlay |
children |
Dark bg #040811 + corner blobs (no frame) |
ZoomController |
moments: ZoomMoment[], children |
Subtle scale transforms (1.03–1.05) |
Subtitles |
segments: SubtitleSegment[] |
Bottom-aligned Inter 300, glass pill bg |
KeyPhrase |
phrases: KeyPhraseData[] |
Playfair italic 700, 55% from top, spring anim |
CTAOverlay |
cta: CTASegment |
Image + bigText + subtext, spring entrance |
For accurate subtitle sync, transcribe with mlx-whisper (Apple Silicon native):
pip3 install mlx-whisper
python3 -c "
import mlx_whisper
r = mlx_whisper.transcribe('audio.wav', language='es')
for s in r['segments']:
print(f\"[{s['start']:.1f}-{s['end']:.1f}] {s['text']}\")
"Then correct the text manually and pair with the accurate timestamps. Break long segments into 2-line max subtitle chunks.
--crf=16 for h264 gives ~2000kbps at 360x640 — good balance of quality/size. --image-format=jpeg for JPEG frame encoding (faster than PNG, same visual quality for video).
See references/config-template.ts for the VideoCompositionConfig shape.
This section is designed to be given verbatim to ANY LLM (ChatGPT, Claude, Gemini, DeepSeek, etc.) as a self-contained prompt chain. Each step builds on the previous one.
The system is a Remotion (React 19 + TypeScript 5.5) pipeline that produces branded 360×640 vertical videos with: Liquid Glass background, synced subtitles, animated key phrases, subtle zooms, and a final CTA overlay. Components are generic — all data lives in a per-video config.
Goal: Get a timestamped transcript of the source video.
Prompt to the LLM:
Transcribe this video's audio using mlx-whisper or whisper. Language: Spanish (or detect). Command:
pip3 install mlx-whisper python3 -c " import mlx_whisper r = mlx_whisper.transcribe('audio.wav', language='es') for s in r['segments']: print(f\"[{s['start']:.1f}-{s['end']:.1f}] {s['text']}\") "Output format:
[start-end] raw transcribed text
Deliverable: Raw transcript file (e.g., assets/transcript_raw.txt).
Reality check: Whisper makes errors, especially with technical terms and non-English languages. ALWAYS do Phase 2 correction.
Goal: Fix Whisper errors while preserving timestamps. Split into subtitle-friendly chunks (max ~2 lines per segment, ~2-4 seconds each).
Prompt to the LLM:
Here is the raw Whisper transcript with timestamps. Do two things:
CORRECT the text: fix transcription errors, restore technical terms, fix grammar. DO NOT modify timestamps.
SPLIT long segments into subtitle-friendly chunks of 2-4 seconds each. Each chunk should be 1-2 spoken lines max. If you split a segment, interpolate the timestamps proportionally.
Context about the video topic: [insert 1-2 sentence description of what the video is about]
Output format:
[start-end] corrected subtitle textRaw transcript: [paste raw transcript here]
Deliverable: Cleaned transcript with 20-40 subtitle segments, ready for the config.
Pacing rule of thumb:
- Spanish: ~10-13 chars/sec spoken
- English: ~12-15 chars/sec spoken
- Each subtitle: 2-4 seconds, 20-50 characters
Goal: Find 3-5 conceptual anchors in the video that deserve visual emphasis. These appear as large Playfair Display italic overlays at 55% screen height.
Prompt to the LLM:
Analyze this corrected transcript from a [duration]s video about [topic].
Identify 3-5 KEY CONCEPTS that are the main takeaways. For each one:
- text: THE CONCEPT IN UPPERCASE (1-4 words, punchy)
- subtext: a brief explanation (4-8 words, Spanish or video language)
- start: when the concept is first introduced (seconds)
- end: when the speaker moves to the next concept (seconds)
Rules:
- Concepts should be spread across the video, not clustered
- start should be ~0.5-1s AFTER the speaker begins introducing it
- end should align with subtitle segment boundaries
- Don't overlap key phrases (end of one < start of next)
- The last key phrase should end before the CTA starts (last ~6-8s)
Transcript: [paste corrected transcript]
Deliverable: 3-5 KeyPhraseData entries with text, subtext, start, end.
Goal: Add subtle scale animations (1.03-1.05x) that match the key phrases to create visual dynamism without being distracting.
Prompt to the LLM:
Using these key phrase segments and the video duration of [N]s, define zoom moments.
Rules:
- Each zoom should cover the same range as its corresponding key phrase
- Scale values: 1.03 for short segments, 1.04 for longer/more important ones
- Between key phrases, zoom returns to 1.0 (handled automatically by ZoomController)
- Last segment (CTA area, last ~10s) should be scale 1.0 (no zoom)
- Add a short intro zoom (1.03, first 5-10s) if the video has a hook
Key phrases: [paste key phrases with timestamps]
Output format:
{ start: X, end: Y, scale: Z.Z }
Deliverable: 4-6 ZoomMoment entries covering the timeline.
Goal: Final overlay with image + text in the last ~6-8 seconds.
Prompt to the LLM:
Define the CTA for the last [N] seconds of the video (start at [CTA_START], end at [VIDEO_END]).
Provide:
- image: filename in public/images/ (e.g., "cta-topic.png")
- bigText: 1-2 words in UPPERCASE, the core action/topic
- subtext: 1 short sentence telling the viewer what to do
The speaker's final message is: [paste the last subtitle segment text]
Deliverable: One CTASegment.
Goal: Assemble everything into a VideoCompositionConfig file.
Prompt to the LLM:
Create the file
src/compositions/[video-id].tsexporting aVideoCompositionConfig:import type { VideoCompositionConfig } from "../types"; export const [videoId]: VideoCompositionConfig = { id: "[VideoId]", videoSrc: "[filename].mp4", // in public/videos/ durationInSeconds: [exact duration], subtitles: [ /* all segments from Phase 2 */ ], keyPhrases: [ /* all entries from Phase 3 */ ], zooms: [ /* all entries from Phase 4 */ ], cta: { /* from Phase 5 */ }, };Use these exact data: [paste all data from previous phases]
Deliverable: Complete config file.
Prompt to the LLM:
Import the new config in
src/Root.tsx:import { [videoId] } from "./compositions/[video-id]";Add it to the
compositionsarray.Place the CTA image in
public/images/.Render:
PUPPETEER_EXECUTABLE_PATH="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ npx remotion render [VideoId] out/[video-id]-branded.mp4 \ --browser-executable="/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ --codec=h264 --image-format=jpeg --crf=16 --concurrency=1
If you want to hand the ENTIRE workflow to an LLM in one shot, use this:
I have a vertical video (360×640, 9:16) at
public/videos/[name].mp4. Its duration is [N] seconds. The topic is: [1-2 sentence description]. The video language is [Spanish/English].Using the Remotion overlay system (Liquid Glass design: #040811 background, Inter 300 subtitles, Playfair Display 700 italic key phrases, subtle 1.03-1.04x zooms, CTA overlay at the end), I need you to produce a complete branded version.
Follow this exact pipeline:
- Transcribe the audio with mlx-whisper and save raw transcript
- Correct transcription errors + split into 2-4s subtitle chunks (keep timestamps)
- Identify 3-5 key concepts → key phrases with text + subtext + timestamps
- Define zoom moments matching key phrases (1.03-1.04x)
- Define CTA for last ~6s (image + bigText + subtext)
- Create
src/compositions/[name].tswith the full VideoCompositionConfig- Register in Root.tsx and render with --concurrency=1 --crf=16
Project details:
- TypeScript ~5.5.0 (NOT 7+)
- React 19 + Remotion 4
- Dimensions: 360×640, 30fps
- Chrome executable: /Applications/Google Chrome.app/Contents/MacOS/Google Chrome
- All public/ assets must use staticFile()
- Video and Audio are separate components
- @remotion/google-fonts for fonts (Inter + Playfair Display), NOT CSS @import
- The video source is [path to mp4]
Start now and work through all 7 phases.