- Python 40.2%
- HTML 22.1%
- CSS 21.2%
- TypeScript 12.7%
- JavaScript 3.6%
- Other 0.2%
|
|
||
|---|---|---|
| .forgejo/issue_template | ||
| .gitea/issue_template | ||
| .github/workflows | ||
| design-concepts | ||
| docs | ||
| prompts | ||
| tests | ||
| tools/screenshots | ||
| visualizer | ||
| webgui | ||
| .editorconfig | ||
| .gitignore | ||
| .pre-commit-config.yaml | ||
| .python-version | ||
| auth_youtube.py | ||
| CHANGELOG.md | ||
| CLA.md | ||
| CONTRIBUTING.md | ||
| download_models.py | ||
| generate_meta.py | ||
| LICENSE | ||
| LICENSE-DOCS | ||
| LICENSING.md | ||
| Makefile | ||
| pipeline.py | ||
| pipeline_core.py | ||
| playlists.example.json | ||
| pyproject.toml | ||
| pytest.ini | ||
| README.de.md | ||
| README.md | ||
| render_video.py | ||
| SECURITY.md | ||
| transcribe.py | ||
| transcript_diff.py | ||
| transcript_editor.py | ||
| transcript_history.py | ||
| transcript_segment_ops.py | ||
| transcript_word_ops.py | ||
| tui.py | ||
| tui.tcss | ||
| tui_app.py | ||
| tui_cmd.py | ||
| tui_progress.py | ||
| upload_youtube.py | ||
| uv.lock | ||
| webgui.py | ||
podcast-to-youtube
English · Deutsch
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.
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]
- Transcribe — WhisperX produces a word-level transcript (JSON / SRT / TXT) with speaker labels.
- Metadata — a local MLX LLM generates the YouTube title, description, tags and chapters.
- Render — Remotion renders a 1920×1080 MP4 with an audio visualiser.
- 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.
| 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.
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+renderphases topending; click the phase indicator to re-run them with the corrected transcript.
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_00→Annaacross the whole transcript. - Merge / split segments — combine two consecutive segments or split one at the cursor position.
- Word-level edits —
/runs/<stem>/edit/wordsopens a per-word table for finer-grained corrections. - Diff view —
/runs/<stem>/diffshows original vs. current, word-by-word. - Undo — every action snapshots the prior state. The Undo dropdown shows the recent history;
Ctrl/Cmd+Zreverts 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 (defaulthttp://localhost:8080/v1)MLX_MODEL— the local LLM model idHF_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.jsonschema, 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.


