-
-
Landing page
-
Architecture
-
Workspace, empty canvas
-
Pipeline: Bilingual subtitles
-
Subtitle demo: after run
-
Subtitle demo: editable English transcript
-
Subtitle demo: editable Chinese transcript
-
Subtitled result video
-
Subtitle demo: downloadable result video
-
Pipeline: landscape video to vertical reels
-
Vertical frame demo: after run
-
Chat & event trace
-
Docs page
-
Deployed on Replit
Inspiration
Editing a video for social — subtitles, a bilingual translation, a vertical reframe for Reels — is a small task that still means wrestling with a timeline editor or memorizing FFmpeg incantations. The intent is one sentence ("add English and Chinese subtitles and burn them in"), but the execution is a dozen fiddly steps.
We kept seeing agent demos that generate a single blob of output and hoped it was right. Video is unforgiving: one wrong filter and you crop someone's head off, or the Chinese renders as tofu boxes. We wanted an agent you could see think — one that plans an explicit, inspectable pipeline before touching a single frame, so you can watch each step, check its output, and change it in plain English. That's CineDAG: describe the edit, and it builds and runs a multi-agent pipeline you can actually read.
What it does
CineDAG turns a natural-language request into a Resource/Operation DAG — a graph that strictly alternates media artifacts and agent steps — then builds it live on a canvas and runs it for real.
- Describe an edit ("bilingual subtitles, burned in" / "make it a vertical 9:16 short with auto-captions"). Google's ADK + Gemini plan a pipeline node-by-node.
- Watch it build on a React Flow canvas:
Source → Audio Extractor → Audio → Transcriber → Subtitles → Subtitle Burner → Final. Every step is a node you can inspect. - Run it — FFmpeg does the deterministic media work; Gemini does the multimodal work (transcription, translation, content-preserving vertical reframe). Nodes light up as they execute.
- Inspect every step — open any node's output tab (e.g. the generated
.srtwith the exact English + Chinese lines), scrub the final video, and download it from the player menu. - Keep chatting to refine — follow-up messages update the same pipeline in place instead of starting over.
It ships with sample flows (bilingual subtitle burn, vertical reframe + auto-captions) and runs the whole thing on Replit.
How we built it
- Frontend: Next.js 15 (App Router) + React 19 + Tailwind, with React Flow (
@xyflow/react) for the pipeline canvas and dagre for auto-layout. State is plain React around one Builder component; results stream to the UI over SSE. - Agent layer: the Google Agent Development Kit for TypeScript (
@google/adk) drives Gemini (gemini-3.5-flash). OneLlmAgentreturns a strict Resource/Operation JSON DAG; other Gemini calls handle transcription, translation, and reframe analysis. - Backend resolution: a per-request priority chain — BYOK (browser-supplied key) → Vertex AI (ADC) → server env key → a canned sample fallback so the UI is always demoable.
- Execution: everything runs through one
ExecutorServiceinterface with two backends —LocalFfmpegRunner(child-process FFmpeg) andReplitCloudRunner(remote HTTP). Operations carry anenginetag (ffmpeg/gemini/tts) that decides where they run. Swapping engines is one env var. - Media: FFmpeg with libass burns subtitles; a blurred-background fit does content-preserving vertical reframes (nothing cropped). Outputs are written to disk and served back through a Range-aware
/api/file/*route. - Deploy: the full app runs on Replit with
EXECUTION_MODE=local, so FFmpeg executes inside the Repl — build, run, and preview all live on one public URL. We imported and iterated on the project with Replit Agent.
Challenges we ran into
- The ADK's quiet contract. A string
instructiontriggers{var}template injection — our prompts contain literal{in}/{out}braces, which blew up with "Context variable not found." Switching to a function-form instruction fixed it. And errors surface as events (errorCode/finishReason), not thrown exceptions, so "empty response" was really a masked error until we surfaced the event text. - Thinking models eating the budget. On Gemini 2.5+/3.x, hidden reasoning consumed the output budget and returned empty JSON. Setting
thinkingConfig.thinkingBudget = 0gave the whole budget back to the structured output we actually wanted. - A minifier that silently dropped a filter. Next's SWC minifier mis-folded a template-literal FFmpeg filter and dropped a segment (
,gblur=...[bg];) from the production bundle — a bug that only appeared innext build, not dev. We found it by grepping the compiled route and rewrote the filter as a single plain string. - Chinese rendered as tofu boxes on Replit. The transcript was correct, but Replit's Nix container has no CJK font, so libass drew
.notdefboxes. We installednoto-fonts-cjk-sansviareplit.nixand forced the family throughforce_style(env-gated so local rendering is unchanged). - Legacy alignment quirks. libass
force_styleuses legacy SSA alignment, not the ASS numpad —Alignment=8renders in the middle, not the top. We verified empirically and pinned2/6. - Replit deploy failing at Promote. The deployment run command used
pnpm, butcorepackis only enabled in the build phase — so the run phase found nopnpm, opened no port, and failed the health check. Calling thenextbinary directly and binding0.0.0.0:$PORTfixed it.
Accomplishments that we're proud of
- An agent whose plan is visible and inspectable — not a black box. You can read the graph, open any step's output, and trust the result.
- Iterative refinement — follow-up chat edits the existing pipeline in place instead of regenerating from scratch.
- A genuinely pluggable execution engine (local FFmpeg ↔ Replit cloud) behind one interface, swappable with a single env var.
- Content-preserving vertical reframe — fits the whole frame over a blurred background, so nothing gets cropped.
- It always demos — with no API key it falls back to a sample pipeline, so the UI never dead-ends.
What we learned
- Read the library's source, not just its README — the ADK's function-vs-string instruction behavior and event-based errors weren't documented where we looked, and grepping
node_modulesbeat guessing. - Production-only bugs are real: the SWC minifier issue never showed in dev. Always test the actual
next build. - Fonts and encodings are where "it works on my machine" goes to die — CJK glyphs, libass alignment, and the deploy container's font set all bit us.
- Making an agent's reasoning structural (an explicit DAG) rather than freeform text makes it dramatically easier to verify, debug, and edit.
What's next for CineDAG
- Real AI dubbing — the
ttsengine is currently stubbed; wire up voice synthesis over the translated captions. - More operations — highlight-reel extraction, B-roll insertion, background music, silence trimming.
- Parallel execution — the DAG already encodes independence; run non-dependent branches concurrently.
- Shareable pipelines — export/import already works as JSON; add a gallery of community pipelines as starting points.
- Longer-form video — chunked transcription and streaming renders for clips beyond a few minutes.
Built With
- ai-agent
- ffmpeg
- gemini
- google-adk
- libass
- multi-agent
- next.js
- node.js
- radix-ui
- react
- replit
- server-sent-events
- speech-to-text
- tailwindcss
- typescript
- vertex-ai
Log in or sign up for Devpost to join the conversation.