Built
MediaOS long-form library for the Mistlewick brand: two-hour narrated episodes with generated artwork

MediaOS

In progress

One operator, a whole media studio.

RoleSolo build
Brands4, since October 2026
Reach20,000+ views
StackNext.js, Supabase, FFmpeg

MediaOS is the tool I built to run content brands on my own. It is not open source and not a product for sale: the idea is to build the machine once, then spend a few hours a week supervising it.

Today it runs four brands across four YouTube channels, one Instagram account and one TikTok account, and what it has published has passed 20,000 views.

It has two pipelines. One takes a long video, transcribes it, picks the moments worth clipping, reframes them to vertical around the faces, and burns in captions. The other starts from a premise and writes a long-form episode chapter by chapter, narrates it, mixes it over a soundscape and renders the video.

Around those sit the parts that make it a system: a queue that shows what is running and what is held and why, a review step before anything is published, a posting calendar, a comment inbox with drafted replies, and reach next to what each post cost. The heavy work, transcription, narration and rendering, runs on my own machine.

What it does

  • Runs four brands on six accounts, with 20,000+ views so far.
  • Long video in, scored and captioned vertical clips out.
  • Long-form episodes: script, narration, mix and render, one chapter at a time.
  • Dubs a finished episode into five more languages.
  • A durable worker with a queue, daily limits and a kill switch per brand.
  • Review, schedule and publish from one calendar, with analytics and unit economics per post.

Under the hood

Architecture

One Next.js 16 app with Hono API routes, one durable worker process, and Supabase Postgres as the single source of truth: about 80 tables over 95 migrations, with row level security on every one. The code is a monorepo of 23 packages (transcription, reframe, captions, render, story, tts, audio, publishing, policy, money and more) plus four special engines.

All work is a row in a jobs table, one row per pipeline stage. A stage handler returns its artifact and the runner enqueues the next stage, so every step can be retried on its own and no media work ever runs inside a request. The worker runs the clip pipeline, the long-form pipeline and one loop per subsystem (publish, analytics, discovery, inbox, policy) in a single process, and drains cleanly on the first Ctrl-C.

The machine learning side is Python, kept as small CLI programs the worker runs as subprocesses: JSON on stdout, logs on stderr, no sidecar server to keep alive.

The clip pipeline

Import, transcribe, curate, refine, render. Transcription is Whisper large-v3-turbo with word timestamps behind three interchangeable backends: mlx on the Apple GPU, whisper.cpp, and faster-whisper on CPU or CUDA. On my Mac the mlx path does 40 minutes of audio in under two minutes.

Curate decides per video how to pick moments. If there is enough real speech it picks by what is said. If there is not, as with a game trailer, it switches to picking by what is on screen and cuts on shot changes. Claude then scores every candidate against a rubric for hook, flow and value, and the rubric, the cost and the prompt version are stored next to each result.

Clips are reframed to 9:16 with face-aware crop plans (MediaPipe, YOLO and YuNet), captioned with word-level karaoke timing, loudness normalised and rendered with FFmpeg and Remotion. A clean language pass masks swearing in the captions, the headline and the post copy at render time, without touching the stored transcript.

Long-form on rented GPUs

A long-form episode goes script, narrate, mix, render. Claude writes an outline and then the chapters, up to four scripts at once. Narration runs on GPUs rented by the second from Modal: one short-lived app per episode, several GPUs rendering at the same time, each container loading the voice model once and streaming passages back as they finish. The first version took 97 minutes for a two-hour episode, so I rebuilt it around that design with a target of about 20 minutes.

Nothing is trusted because a model produced it. Every narrated passage is transcribed back and compared with the script. Rare names get a second check with a phoneme recogniser, because a speech-to-text model will happily spell a correct name four different ways. A passage that fails is rendered again with a new seed while the GPUs keep working on later chapters.

The mix lays the narration over a soundscape and masters to podcast loudness (-16 LUFS, -1 dBTP). A finished episode can be dubbed into Spanish, German, French, Portuguese and Italian through the same narrate, mix and render stages.

Rented compute is treated as something that can bill me by accident. Every run is a short-lived app with timeouts and no retries, stopped on success, failure and timeout, with a sweep on worker start that only ever stops apps it started itself, and a spending limit per video.

Artwork and voices

Thumbnails and episode artwork come from open image models running on the same rented GPUs. I picked the default by blind comparison on real episodes: same scenes, same prompts, model names hidden. Claude writes the thumbnail concepts and the real renderer sets the title over each picture.

Narrator voices get the same treatment. A voice optimizer renders a test passage every way worth trying, measures each take for loudness and sibilance, drops the clearly worse ones, and lets me pick the rest by ear in blind pairs. Every pick is a new version of the narrator, never an overwrite.

Running it unattended

Each brand has its own review mode, daily limits, a pause and a kill switch, and a ledger of what today cost. Publishing goes through one adapter interface, so the same code path can post for real or to a fixture.

The whole app can be started in a read-only verification mode where every provider that could publish or spend is forced to its fixture and the database client refuses writes. That is how I check a change against real data without risking a post.

Quality gates

Every model output and every external payload is parsed with a Zod schema before anything uses it. A change has to pass type checking, type-aware lint with zero suppressions allowed, more than 600 test files, Python tests, 86 database tests in pgTAP and Playwright runs against the real app.

When a review finds a bug, the fix is not finished until a lint rule, a test or a database check makes that class of bug impossible to ship again.

A look inside

MediaOS clip review for the Clutch Signal brand: a captioned vertical clip with its score and transcript, approved or declined from the keyboard
MediaOS clips stream for the Clutch Signal brand: scored, captioned vertical clips, approved and scheduled
MediaOS publish week for the Pixel Heist brand: approved clips placed on YouTube slots
MediaOS dialogue shorts for the Briefly Human brand: one-minute scripted shorts, scheduled