Podcast audio → YouTube video — automated, local, Apple Silicon. WhisperX + MLX-LLM + Remotion + FastAPI/HTMX WebGUI.
  • Python 40.2%
  • HTML 22.1%
  • CSS 21.2%
  • TypeScript 12.7%
  • JavaScript 3.6%
  • Other 0.2%
Find a file
Johannes Kaindl bf9991724d
chore: gitignore .claude/ (CORE-AGENT-05)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 17:48:58 +02:00
.forgejo/issue_template docs: align repo metadata + docs with jkaindl house style 2026-05-27 11:06:17 +02:00
.gitea/issue_template chore(repo): add .gitea issue templates (bug + feature + config) 2026-05-27 13:50:43 +02:00
.github/workflows ci: bump checkout/setup-node to v5 (Node 24) 2026-06-15 17:25:50 +02:00
design-concepts docs(webgui): add design-concepts mockups — HTML/CSS reference for integration 2026-05-20 21:56:36 +02:00
docs docs: paused-after-transcribe screenshot + fix stale visualizer docs/scripts 2026-06-15 16:41:35 +02:00
prompts chore: drop stale absolute paths from usage examples 2026-05-22 18:26:15 +02:00
tests refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
tools/screenshots docs: paused-after-transcribe screenshot + fix stale visualizer docs/scripts 2026-06-15 16:41:35 +02:00
visualizer docs: paused-after-transcribe screenshot + fix stale visualizer docs/scripts 2026-06-15 16:41:35 +02:00
webgui build: pre-commit hooks + Makefile (PROF-PY-05/06) 2026-06-15 13:55:52 +02:00
.editorconfig build(py): adopt uv + pyproject; editorconfig TOML→4-space 2026-06-15 13:46:13 +02:00
.gitignore chore: gitignore .claude/ (CORE-AGENT-05) 2026-06-15 17:48:58 +02:00
.pre-commit-config.yaml build: pre-commit hooks + Makefile (PROF-PY-05/06) 2026-06-15 13:55:52 +02:00
.python-version docs: rewrite README in English, add standard project files 2026-05-22 18:48:04 +02:00
auth_youtube.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
CHANGELOG.md ci: run the full gate on push/PR + CI badge (CORE-META-02) 2026-06-15 17:22:00 +02:00
CLA.md docs: dual-license (LICENSING + CLA), German README, canonical badges 2026-06-15 17:07:37 +02:00
CONTRIBUTING.md docs: dual-license (LICENSING + CLA), German README, canonical badges 2026-06-15 17:07:37 +02:00
download_models.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
generate_meta.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
LICENSE chore: prepare repository for public release 2026-05-22 18:25:16 +02:00
LICENSE-DOCS build: pre-commit hooks + Makefile (PROF-PY-05/06) 2026-06-15 13:55:52 +02:00
LICENSING.md docs: dual-license (LICENSING + CLA), German README, canonical badges 2026-06-15 17:07:37 +02:00
Makefile build: pre-commit hooks + Makefile (PROF-PY-05/06) 2026-06-15 13:55:52 +02:00
pipeline.py docs: Diátaxis user manual + README link (CORE-META-04) 2026-06-15 14:18:08 +02:00
pipeline_core.py style: apply ruff format + autofix across the codebase 2026-06-15 13:46:26 +02:00
playlists.example.json chore: prepare repository for public release 2026-05-22 18:25:16 +02:00
pyproject.toml build(py): adopt uv + pyproject; editorconfig TOML→4-space 2026-06-15 13:46:13 +02:00
pytest.ini refactor(core): extract pipeline_core.py — shared helpers for TUI + WebGUI 2026-05-20 22:53:07 +02:00
README.de.md ci: run the full gate on push/PR + CI badge (CORE-META-02) 2026-06-15 17:22:00 +02:00
README.md ci: run the full gate on push/PR + CI badge (CORE-META-02) 2026-06-15 17:22:00 +02:00
render_video.py docs: paused-after-transcribe screenshot + fix stale visualizer docs/scripts 2026-06-15 16:41:35 +02:00
SECURITY.md docs: align repo metadata + docs with jkaindl house style 2026-05-27 11:06:17 +02:00
transcribe.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
transcript_diff.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
transcript_editor.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
transcript_history.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
transcript_segment_ops.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
transcript_word_ops.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
tui.py style: apply ruff format + autofix across the codebase 2026-06-15 13:46:26 +02:00
tui.tcss feat: import whisper-pipeline (Python + Remotion visualizer) ex 20_Claude/26-001 + ShadowVault 26-001/05-visuals — Cluster-A Code-Split (26-017 Vault-Konsolidierung) 2026-05-19 17:19:56 +02:00
tui_app.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
tui_cmd.py style: apply ruff format + autofix across the codebase 2026-06-15 13:46:26 +02:00
tui_progress.py style: apply ruff format + autofix across the codebase 2026-06-15 13:46:26 +02:00
upload_youtube.py refactor(types): satisfy mypy strict + remaining ruff rules (PROF-PY-03) 2026-06-15 13:53:45 +02:00
uv.lock build(py): adopt uv + pyproject; editorconfig TOML→4-space 2026-06-15 13:46:13 +02:00
webgui.py style: apply ruff format + autofix across the codebase 2026-06-15 13:46:26 +02:00

podcast-to-youtube

English · Deutsch

License: AGPL v3 Docs: CC BY-SA 4.0 Codeberg Release CI Tests Python Platform: macOS Status: Active

Automated end-to-end pipeline: podcast audio → finished YouTube video, running locally on Apple Silicon.

Target platform: Apple Silicon Mac, macOS 15+. Mac-only by design.

Status: v1.0.0 — first public release. The full four-phase pipeline runs end-to-end; the WebGUI is the primary interface. Mac Silicon, AGPL-3.0.

The WebGUI start screen: an audio-path field, the visualizer, language, model and speaker options, and the Start pipeline button.

Drop an episode, pick the options, start the pipeline — everything runs locally.


About

A single audio file (.m4a / .mp3 / .wav) becomes a private YouTube video. Everything runs locally on Mac hardware — transcription with WhisperX, metadata generation with a local MLX-served LLM, video rendering with Remotion. The only network call is the YouTube upload itself.


Release status

For full per-release notes see CHANGELOG.md.

Version Date Headline
v1.0.0 2026-05-22 Initial public release — four-phase pipeline (transcribe · metadata · render · upload), WebGUI + TUI, 64 tests.

What it does

Four phases, one pipeline:

flowchart TD
    A[Audio · m4a / mp3 / wav] --> B[Transcribe · WhisperX]
    B --> C[Metadata · local LLM]
    C --> D[Render · Remotion]
    D --> E[Upload · YouTube Data API]
    E --> F[Private video]
  1. Transcribe — WhisperX produces a word-level transcript (JSON / SRT / TXT) with speaker labels.
  2. Metadata — a local MLX LLM generates the YouTube title, description, tags and chapters.
  3. Render — Remotion renders a 1920×1080 MP4 with an audio visualiser.
  4. Upload — the YouTube Data API v3 publishes the video as private. Upload is a manual, explicit step.

Quick start

git clone https://codeberg.org/jkaindl/podcast-to-youtube.git
cd podcast-to-youtube

# Python environment (uv reads pyproject.toml + uv.lock, creates .venv)
uv sync                       # add --extra transcribe for the WhisperX phase
source .venv/bin/activate

# system tools
brew install ffmpeg

# Remotion dependencies (once)
cd visualizer && npm install && cd ..

# YouTube OAuth (once — needs a real terminal for the browser flow)
python auth_youtube.py

# launch the WebGUI
python webgui.py

The WebGUI opens at http://localhost:8765.

Two external prerequisites: a local MLX server on port 8080 serving the metadata LLM, and a Google Cloud OAuth client (client_secrets.json, Desktop App, with the YouTube Data API v3 enabled). upload_youtube.py prints the Google Cloud setup steps if client_secrets.json is missing.


WebGUI

python webgui.py starts a FastAPI + HTMX interface and opens the browser at http://localhost:8765.

Pick an audio file, choose the options, click Start pipeline. The run page streams the live log and phase progress over Server-Sent Events. After the render phase the MP4 preview plays inline. Upload is never automatic — choose the visibility (private / unlisted) and click Upload to YouTube.

WebGUI during a live run: the render phase in progress, the pipeline log streaming, and the transcript and YouTube-metadata previews already populated.
Live run — phase stepper, streaming log, transcript & metadata as they land.
WebGUI ready-to-upload state: the rendered video preview with the upload card, where you choose private or unlisted visibility before confirming the upload.
Ready to upload — render preview ready; you pick visibility and confirm. Never automatic.
WebGUI finished state: all four phases complete, the rendered video, and a card linking to the uploaded YouTube video.
Done — all four phases complete, video rendered and uploaded.
Key Action
Ctrl+R Open the start-pipeline dialog
Ctrl/Cmd+Z (on edit page) Undo the last editor action — native field-undo wins while a text input is focused
Ctrl/Cmd+S (on edit page) Save & return to the run page

Transcript editor

Whisper occasionally mistypes names, jargon, and foreign words. Rather than re-running the whole pipeline, the editor lets the transcript be corrected between phases and reruns only what changed.

The transcript editor: a bulk speaker-rename form, then one card per segment with a timestamp, a speaker dropdown, an editable text field, and merge/split controls. An Undo dropdown sits top-right; Save & Return / Save & Continue / Cancel actions sit at the bottom. Saving invalidates the metadata and render phases so they re-run with the corrected transcript.
Transcript editor — per-segment text and speaker edits, merge/split, bulk speaker rename, and undo. Saving reruns only the affected phases.

Two ways in:

  • Pause after transcribe — tick the Pause after transcribe for editing checkbox on the start form. The pipeline stops after Whisper finishes; the run page surfaces an Edit Transcript button.
  • Edit anytime — every run with a transcript exposes an Edit transcript link on the run page. Saving an edit resets the meta + render phases to pending; click the phase indicator to re-run them with the corrected transcript.

A run paused after transcription: the phase stepper shows Transcribe done with Metadata, Render and Upload pending, and a 'Pipeline paused after Transcribe' card offers 'Edit Transcript' or 'Continue without editing', with the transcript preview below.
Pause after transcribe — the pipeline stops after Whisper; edit the transcript, then continue with Meta + Render.

What the editor can do:

  • Segment text — fix mistyped names, jargon, foreign words.
  • Speaker re-labelling — change the speaker per segment, or bulk-rename SPEAKER_00Anna across the whole transcript.
  • Merge / split segments — combine two consecutive segments or split one at the cursor position.
  • Word-level edits/runs/<stem>/edit/words opens a per-word table for finer-grained corrections.
  • Diff view/runs/<stem>/diff shows original vs. current, word-by-word.
  • Undo — every action snapshots the prior state. The Undo dropdown shows the recent history; Ctrl/Cmd+Z reverts the last action.

The first save creates a one-time <stem>.whisperx.original.json backup. Snapshots accumulate in output/<stem>/snapshots/, auto-trimmed to the 20 newest.


CLI

The pipeline also runs headless:

source .venv/bin/activate

# full run — transcribe, metadata, render, upload
python pipeline.py podcast.m4a

# skip the upload
python pipeline.py podcast.m4a --skip-upload

# pick a visualiser
python pipeline.py podcast.m4a --viz dialogue --skip-upload
python pipeline.py podcast.m4a --viz monologue --skip-upload

# speaker diarization (requires accepting the pyannote terms on huggingface.co)
python pipeline.py podcast.m4a --hf-token $HF_TOKEN

python pipeline.py --help

A Textual TUI is kept as a fallback frontend: python tui.py podcast.m4a.

Output lands in output/<stem>/:

  • <stem>.whisperx.json — word-level transcript with speaker labels
  • <stem>.whisperx.original.json — pristine backup, created the first time the transcript is edited (see Transcript editor)
  • <stem>.srt — subtitles
  • <stem>.txt — plain-text transcript
  • <stem>.youtube-meta.json — title, description, tags, chapters
  • <stem>-<viz>.mp4 — the finished video (1920×1080, 30 fps)
  • snapshots/<unix-ts>.json — per-mutation undo snapshots written by the editor; auto-trimmed to the 20 newest

Scripts

Script Purpose
pipeline.py Orchestrates all four phases
transcribe.py WhisperX: audio → JSON / SRT / TXT
generate_meta.py MLX LLM: transcript → YouTube metadata
render_video.py Remotion: audio + transcript → MP4
upload_youtube.py YouTube Data API v3: MP4 → private video
auth_youtube.py One-time OAuth authorisation
download_models.py Pre-fetch all models for offline use

Configuration

File Contents
client_secrets.json Google OAuth credentials (not committed)
.youtube_token.pickle Cached OAuth token (not committed)
playlists.json Playlist auto-assignment — copy from playlists.example.json
.env Optional environment variables

Environment variables:

  • MLX_BASE_URL — base URL of the local LLM server (default http://localhost:8080/v1)
  • MLX_MODEL — the local LLM model id
  • HF_TOKEN — Hugging Face token for speaker diarization

Offline use

python download_models.py pre-fetches the Whisper and alignment models so the pipeline runs without internet (except the upload). --hf-token adds the diarization model; --status shows the cache state.


Test suite

uv run pytest tests/ -q        # or: make test

151 unit and integration tests covering the shared pipeline core, the probe and run-history helpers, the job runner, every WebGUI route, and the four transcript-editor modules (transcript_editor, transcript_segment_ops, transcript_word_ops, transcript_history, transcript_diff). Runs in ~3 s on Apple Silicon.


Documentation

The full user manual lives in docs/manual/, organised by Diátaxis:

  • Tutorial — your first video, start to finish.
  • How-to guides — focused recipes (headless runs, transcript editing, offline models, upload visibility).
  • Reference — every CLI flag, WebGUI route, the run-state.json schema, configuration + environment.
  • Explanation — the four-phase architecture and the design rationale.

Project layout

pipeline.py            Orchestrator — four phases, writes run-state.json
pipeline_core.py       Shared helpers (TUI + WebGUI)
transcribe.py          WhisperX step
generate_meta.py       Metadata step (local MLX LLM)
render_video.py        Render step (Remotion)
upload_youtube.py      Upload step (YouTube Data API v3)
transcript_editor.py        Editor V1 — load / save / regen / invalidate
transcript_segment_ops.py   Editor — merge, split, change_speaker, bulk_rename
transcript_word_ops.py      Editor — load_words_flat, save_word_edits
transcript_history.py       Editor — snapshot, undo_last, cleanup_snapshots
transcript_diff.py          Editor — compute_segment_diff vs .original.json
webgui/                FastAPI app — routes, job runner, SSE, templates, static
webgui.py              WebGUI entry point
tui*.py                Textual TUI (fallback frontend)
visualizer/            Remotion project (Node) — the video renderer
tests/                 pytest suite
docs/                  Design specs, implementation plans, screenshots
tools/                 Dev tooling (e.g. screenshot regeneration)
pyproject.toml         Project metadata, deps, and ruff/mypy/pytest config (uv)
uv.lock                Pinned dependency lockfile
Makefile               Standard targets (install / check / serve / …)

Contributing

Issues and pull requests are welcome at Codeberg — the issue templates in .forgejo/issue_template/ prompt for everything that's needed. For larger changes, open an issue first. See CONTRIBUTING.md for the development workflow and SECURITY.md for security-sensitive reports.


Project status

Actively maintained by a single contributor. Apple Silicon focus — the pipeline is Mac-only by design. Cross-platform pull requests are accepted but not actively driven.


License

Code: AGPL-3.0-or-later (LICENSE). Documentation: CC BY-SA 4.0 (LICENSE-DOCS).

The AGPL network clause keeps modifications to a networked deployment open-source. A commercial license is available for uses that cannot meet the AGPL terms — see LICENSING.md. External contributions are accepted under the CLA.md.


Copyright (C) 2026 Johannes Kaindl.