← All projects

Live Caption

A live-captioning operator I built to put English subtitles over Gujarati devotional performances — and drove solo at a 3,000–5,000-person event.

Composited result from the actual event — captions on the venue screen

The problem

A large devotional program with a globally-scattered, mixed-language audience (much of it non-native Gujarati) — staged skits and live speeches would land for only part of the room.

The design bet: at a live event, auto-transcription on a venue network is a liability (latency, mangled names, no recovery), so a human operator who knows the material drives captions. I sat with the AV team, learned how their mixing pipeline consumed a feed, and built exactly the clean caption layer they overlay.

The origin leads; the generalization closes — nothing is Gujarati-specific. Any language, any live event.

What I built

  • Two operating modes switched live — Step Mode advances a pre-parsed script cue-by-cue (for staged skits); Typing Mode types captions live (for unscripted speakers).
  • Two surfaces — a Display View (audience-facing: captions on a green background for AV to key) and a Console (all operator controls, never on feed).
  • Script import + parser — handles dialogue/transcript formats with auto sentence/clause splitting and short-line merging.
  • Prep-time overflow flagging — any cue that would clip is caught before you go on air.
  • Setup + Calibration Mode — font/size/color/band geometry, with a calibration overlay so AV can size their crop.

How it works

Stack — React 19 + React Router (routes split the Console from the Display View), TypeScript, Vite, Tailwind, self-hosted fonts (zero runtime network), and Vitest. No backend — a deliberate reliability choice.

Architecture — operator input → a parser produces cues → a session reducer holds show state → state crosses to the Display View via the browser (localStorage + BroadcastChannel, same machine, two windows) → the Display View renders cues on a fixed virtual canvas over a green background → the AV team keys out the green and overlays on the venue screens. (The keying is the AV team's system, not this app.)

Console Show tab — Step/Typing controls + cue list w/ a red overflow flag
Display View output — captions on green
Architecture diagram — operator → app → AV keys + overlays → venue screens
The hard problem — engineering backward from "one operator, live, in front of thousands, no second takes"

Captions must never shrink and never clip; the operator must never be surprised on-air. Worked example:

① Measure, don't guess — measure the real pixel width of text in the exact display font via an offscreen canvas (not character-counting). ② The detail that proves it was thought through — it waits for the custom font to load before measuring (else it measures a fallback font and every width is wrong). ③ Warn at prep, not on-air — any cue that would wrap past the band is flagged red in the Console during prep ("N overflows to fix"); change the style and every cue re-checks instantly.

④ Graceful failure — if something slips through live, the extra line grows upward from a fixed baseline (never shrinks/clips). ⑤ A deliberate tradeoff — wrapping is word-boundary only, so a single token wider than the band (a URL/hashtag) wouldn't wrap; intentional, because caption content is spoken prose where words always fit and mid-word hyphenation hurts readability for a case that never occurs.

Honest altitude: the clever call is measure-in-pixels + warn-at-prep + font-load timing; greedy word-wrap and upward-spill are standard. The codebase also carries ADRs and a reducer/selector Vitest seam.