Neon Bulletin — Automated AI-Anchored Daily Newscast (Python, ffmpeg, 3D VRM)

Case study · October 2026

Neon Bulletin — Automated AI-Anchored Daily Newscast (Python, ffmpeg, 3D VRM)

Overview

Neon Bulletin is a daily newscast that produces itself. Every morning at 5 a.m. a job on my Mac pulls the overnight headlines, picks the stories, writes a two-anchor script, voices it, finds licensed photos and video, renders 3D anchors standing beside a studio wall screen, and assembles a finished 1080p episode with captions, a thumbnail, and an upload-ready description for YouTube and Rumble. The anchors are AI and the show says so on screen and out loud. The October 16, 2026 episode ran eight minutes across nine stories, and the pipeline built it in about 13.5 minutes, unattended.

The Challenge

A script is easy. A show people would trust is hard. Every fact had to trace back to a named outlet, and every image had to carry a license that permits reuse, with the credit on screen. Sensitive material (children in tragedy stories, graphic violence, unconfirmed accusations) needed hard rules, not judgment calls made at render time. Timing had to come from real voice audio so the anchors' mouths, cuts, and graphics lined up. And it had to run unattended while leaving me final say before anything went public.

What I Built

  • Story selection — RSS feeds from a configured outlet list, filtered to the last 36 hours, plus business feeds and a standing election beat that adds targeted Google News searches. An LLM picks the rundown and extracts the 5 Ws for each story.
  • License-gated media search — Photo and video search across Wikimedia Commons, Openverse, NASA, and Pixabay. A license gate admits only public domain, CC0, CC BY, CC BY-SA, and Pixabay content. A second model pass ranks and captions what survives, and every shot gets a FILE PHOTO / FILE VIDEO / ILLUSTRATIVE label plus a source credit box.
  • Event footage and sound bites — For stories where one moment is the news, the system searches this week's YouTube video, lays out time-stamped contact sheets, has a vision model find the moment, and cuts 20 seconds or less. Sound bites use Whisper word timestamps to cut a verbatim 5 to 20 second quote, capped at four per show with speaker, date, and source on screen.
  • Voices — Chatterbox TTS on my own GPU server, cloned from synthetic reference voices with clean (Apache-2.0) provenance, loudness-normalized to -16 LUFS and cached per line.
  • 3D studio — Four VRM anchors rotate by date, always one woman and one man. They render in headless Chrome over the DevTools protocol with an explicit frame clock, so the output is frame-exact however slow the render runs. Pillow draws lower thirds, the ticker, a 5W "Rundown" board, and a pre-market markets board; ffmpeg assembles a cold open, ad breaks read by a separate announcer voice.
  • Tips and breaking news — I send links to a Telegram bot. A tip runs as a story only when Google News shows outlet coverage; otherwise it goes to On Our Radar, and sensitive unconfirmed tips never air. Breaking news builds a one-story special report on the same rule.
  • Publishing — A YouTube Data API uploader written without Google's client libraries. It posts episodes as private so I review and publish them, and it packages a Rumble description alongside.

Results

In the first two days the pipeline produced two full daily episodes (8.1 and 8.0 minutes) and two breaking-news specials (82 and 94 seconds), all rendered at 1920x1080 with full media credits in the description. The entire system is about 4,900 lines of Python and JavaScript in 25 files. It retries when the script model returns malformed JSON and pings me on Telegram when an episode is ready.

Tech Stack

Python, ffmpeg, Pillow, NumPy, three.js / VRM, headless Chrome (DevTools Protocol), Chatterbox TTS, Kokoro, Whisper, LLMs via OpenRouter, yt-dlp, YouTube Data API v3, Wikimedia Commons / Openverse / NASA / Pixabay APIs, Yahoo Finance, Telegram Bot API, launchd

Built by Mike Cunningham · October 15, 2026

Your project next

Need something built like this?