Skip to content
← All projects
2026Personal project: designed by me, built by directing AI coding agents

News Brief

When the switch from Google Assistant to Gemini broke Google's news briefing for me on my Android phone, I rebuilt it on my own Google Cloud. The short news bulletins I choose are stitched into one spoken brief with Google-style hand-offs between them. It skips editions I've already played through, and I listen to it every day on my phone through YouTube Music.

PythonCloud RunffmpegText-to-speechIdentity-Aware ProxyWorkload Identity Federation
Only what's neweditions I've already played through are left out; a show with nothing newer gets a one-line “nothing new” instead

The source is private for now. The technical detail above is taken from the repository's README, deploy script and code.

Architecture

Architecture of News Brief, as a pipeline. 1. Curate: a phone-friendly web editor behind Identity-Aware Proxy 2. Schedule: Cloud Scheduler runs a Cloud Run Job every 30 minutes, hourly overnight 3. Build: the newest episode per show, spoken framing, matched loudness, one MP3 with chapters 4. Publish: a private podcast feed holding only the current brief, at an unguessable URL 5. Listen: a small redirect notes when I press play, so the next brief is “since you last listened”
  1. 01

    Curate

    a phone-friendly web editor behind Identity-Aware Proxy

  2. 02

    Schedule

    Cloud Scheduler runs a Cloud Run Job every 30 minutes, hourly overnight

  3. 03

    Build

    the newest episode per show, spoken framing, matched loudness, one MP3 with chapters

  4. 04

    Publish

    a private podcast feed holding only the current brief, at an unguessable URL

  5. 05

    Listen

    a small redirect notes when I press play, so the next brief is “since you last listened”

The problem

Google's briefing did one simple thing well: a single voice command played a handful of short bulletins back to back, each introduced by name and how recent it was. Rebuilding it meant finding the newest edition of each show, saying something sensible when a show has nothing new, and delivering it as a ready-made file that any podcast player can play, so every greeting and hand-off is decided before anyone presses play.

Approach & decisions

  • My data stays out of the code. The show list, its order and settings live in private storage, and the catalogue of suggested shows is a private data file that can be replaced without a rebuild. The code only knows generic kinds of source, so a publisher moving a feed is a data edit, not a release.
  • Every show has a fallback chain, tried in order when a feed fails, has nothing recent or its audio is refused. The BBC's presenter-read UK News Update is only served to UK connections, so it falls back to the World Service five-minute bulletin, then to BBC headlines read aloud.
  • It rebuilds every 30 minutes because the greeting is fixed when the file is built, and each build is kept cheap: a scheduled build whose running order matches the last one publishes nothing. Each episode's loudness is measured once and remembered, then a simple gain and peak limiter are applied: about four times faster than ffmpeg's loudnorm on an hour of audio. Spoken lines are cached for two weeks.
  • Speech is kept within free tiers where it can be: Google Cloud TTS has free monthly characters, and free offline Piper is the self-hosted default and the fallback.
  • An optional AI line, switched on in my deployment with Gemini, rewords the sentence after the greeting. It sees only the weekday, the bank holiday and how many shows have something new. A sentence with an invented name or number, a time of day or sales talk is thrown away and the rules-based line is used instead.
  • Least privilege throughout. The public listening link runs as its own service account that can read one file and update one other, and nothing else; API keys live in Secret Manager; deploys from GitHub are keyless through Workload Identity Federation and limited to the main branch; and saves use conditional writes, so two devices editing at once can't overwrite each other's changes.

Results

  • In daily use: I play it every day on my Android phone, through YouTube Music.
  • Over 200 unit and API tests under strict linting, plus CI jobs set up to build the Docker stack end to end from the real feeds and to check every real feed and audio download daily.
  • Built to run for next to nothing: one scheduler job, services that scale to zero, cached speech, and builds that publish nothing when nothing has changed.

What this honestly is not

  • Not a hosted service. It runs in my own Google Cloud project, for me; anyone else would deploy their own copy.
  • Not for redistribution. Episodes come from their publishers and are stitched into one file for my own listening. The feed is marked itunes:block, sits at an unguessable URL and holds only the current brief; a replaced brief turns to silence after six hours and is later deleted.
  • Not hand-written. I designed it and made the decisions; AI coding agents wrote the code under my direction.

Stack

PythonFlaskCloud Run (job + services)Cloud SchedulerCloud StorageSecret ManagerIdentity-Aware ProxyWorkload Identity FederationffmpegGoogle Cloud TTS / PiperGemini (optional)