--- title: Marquee emoji: ๐ŸŽ™๏ธ colorFrom: purple colorTo: pink sdk: gradio sdk_version: 6.18.0 app_file: app.py license: apache-2.0 short_description: Turn any clip into a broadcast starring your friends tags: - build-small-hackathon --- # Marquee ๐ŸŽ™๏ธ โ€” *Everyone's a headliner.* Drop a clip of friends doing anything. Marquee recognizes who's in it, you give each person a stage name, pick a show style, and an AI commentator calls the action play-by-play, by name, in sync โ€” as a broadcast you can play and export. ## โš™๏ธ Running it on a Space (read this first) - **Hardware must be ZeroGPU.** The commentary model runs inside `@spaces.GPU`, which only has a GPU on **Nvidia ZeroGPU** hardware. On a plain CPU Space the 7B VLM has no GPU and is effectively unusable. Set it in **Settings โ†’ Hardware โ†’ ZeroGPU**. - **No secrets needed at runtime.** Everything downloads from public sources (Qwen from the HF Hub, the face models from Intel's OMZ storage). Do **not** commit any `HF_TOKEN`/`.env` โ€” if you need one for `gradio deploy`, keep it local and add it as a **Space secret**, never a file in the repo. - **Nothing to pre-download.** `download_models()` fetches the three tiny OpenVINO IR files at first boot (~5 MB total). First boot also warms up Qwen. ## How it's wired (the important part) The backend is a **`gradio.Server`** (a FastAPI subclass โ€” Gradio 6). It gives us ZeroGPU + the gradio runtime while letting our own routes take priority. The custom UI is a **static HTML page** at `/`. The CPU-only scan is a plain `fetch()` route; the GPU commentary is an `@app.api()` endpoint called through the **Gradio JS client**, which is what wires it into the queue and lets ZeroGPU detect the `@spaces.GPU` function. No iframe, no SSR fighting us for `/`. ``` Static UI (Marquee.html + marquee.css + marquee.js) โ”€โ”€โ–บ served at "/" โ”‚ fetch() for scan ยท Gradio JS client for generate โ–ผ FastAPI (app.py) โ”œโ”€ POST /api/scan (file) -> normalize 720p -> detect+cluster faces โ”‚ -> session + roster (face crops as data URLs) โ”œโ”€ GET /video/{sid} -> streams the normalized mp4 โ””โ”€ @app.api /generate (session, names, vibe)-> key events -> name-grounded frames -> Qwen2.5-VL (ZeroGPU) -> {t,text} script ``` Heavy per-session state (identities, motion, normalized video path) lives in a server-side `SESSIONS` dict keyed by the `session_id` the scan call returns, so the two stateless client calls never round-trip embeddings. Face recognition is used **only** to feed names to the VLM โ€” boxes are never shown. The broadcast player reads the real normalized `