sportsozzy

Ozzy Sports πŸŽ™οΈ

An AI sports commentator for live streams. Point it at a live sport stream β€” like an NOS livestream β€” and it replaces the dry, boring official commentary with a live commentator that’s half Aussie larrikin, half weary observational stand-up, while keeping the crowd and ambient stadium sound from the original broadcast.

Commentary is generated with OpenAI models: a vision model watches frames from the stream and writes the next line in character, and the gpt-4o-mini-tts speech model speaks it with a rowdy, off-the-cuff commentator delivery (via the TTS instructions parameter).

How it works

 stream page URL ──▢ /api/resolve ──▢ .m3u8 URL
                                        β”‚
        browser ◀── /api/proxy (CORS) β”€β”€β”˜        (hls.js plays it; canvas can capture frames)
           β”‚
           β”œβ”€ every N seconds: capture 2–3 frames ──▢ POST /api/commentary
           β”‚                                             β”‚  vision model β†’ commentary text
           β”‚                                             β”‚  gpt-4o-mini-tts β†’ mp3
           β”‚                       β—€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
           β”‚
           └─ Web Audio mixer:
                original stereo ──┐
                L βˆ’ R (voice-cancelled ambience) ─────▢ crossfade ─▢ duck ─▢ πŸ”Š
                Ozzy's TTS lines ──────────────────────────────────────────▢ πŸ”Š

Keeping the ambient sound: broadcast commentators are mixed dead-centre, while crowd and ambience are stereo. The player subtracts the right channel from the left (karaoke-style centre-channel cancellation), which removes most of the original commentator’s voice but keeps the stereo atmosphere. A slider crossfades between the original mix and the voice-cancelled mix, and the whole ambient bed automatically ducks while Ozzy is talking.

Setup

npm install
export OPENAI_API_KEY=sk-...
npm start
# open http://localhost:3210

Paste a stream URL, click Find stream, then click the Watch with Ozzy Sports commentary link. That link (/watch.html?src=…) is shareable with anyone who can reach your server. On the watch page, press Start Ozzy Sports (a click is required β€” browsers demand a user gesture before audio can play).

Configuration (env vars)

Variable Default Purpose
OPENAI_API_KEY β€” Required.
OPENAI_VISION_MODEL gpt-4o-mini Model that watches frames and writes commentary.
OPENAI_TTS_MODEL gpt-4o-mini-tts OpenAI speech model.
OPENAI_TTS_VOICE ash TTS voice (ash, verse, ballad, coral, …).
OPENAI_BASE_URL https://api.openai.com/v1 Override for proxies/compatible APIs.
PORT 3210 HTTP port.
APP_PASSWORD β€” If set, the whole site requires this password (HTTP Basic auth, any username). Strongly recommended for public deployments β€” the commentary endpoint spends your OpenAI credits.
ALLOW_PRIVATE_NETWORKS β€” Set to 1 to let the resolver/proxy fetch private/localhost addresses (local development only; they are blocked by default to prevent SSRF).

In-player controls

Hosted version: ozzysports.live (GitHub Pages)

The docs/ folder is a fully static build of Ozzy Sports that runs entirely in the browser, deployed to GitHub Pages by .github/workflows/pages.yml on every push to main. Differences from the server version:

One-time setup

  1. Push to main (or run the β€œDeploy GitHub Pages” workflow manually). The workflow enables Pages itself (enablement: true) and the site appears at https://pas-mllr.github.io/sportsozzy/.
  2. docs/CNAME already declares ozzysports.live. At your domain registrar, add:

    Type Name Value
    A @ 185.199.108.153
    A @ 185.199.109.153
    A @ 185.199.110.153
    A @ 185.199.111.153
    AAAA @ 2606:50c0:8000::153
    AAAA @ 2606:50c0:8001::153
    AAAA @ 2606:50c0:8002::153
    AAAA @ 2606:50c0:8003::153
    CNAME www pas-mllr.github.io
  3. In Settings β†’ Pages, confirm the custom domain shows ozzysports.live, wait for the DNS check and certificate, then tick Enforce HTTPS.

Notes & limitations