A fully git-based, headless video and audio podcast platform. No web interfaces, no databases, no CMS. Content lives in a git repository, media assets in S3-compatible storage, and everything is controlled from the terminal and text editor. AI coding tools handle the heavy lifting of implementation.
- Full server application with database, background workers, transcoding pipeline — heavy operational burden
- Content is locked inside PeerTube's database; backups and migrations are complex
- Customization means forking a large Node.js codebase
- Overkill for a single-creator setup
- Fediverse integration is valuable, but can be achieved independently with much less complexity
- PHP/Laravel app with MySQL/Postgres dependency — another server to maintain
- Web interface is the primary workflow; API exists but is secondary
- Good Podcast 2.0 and ActivityPub support, but these are just XML tags and JSON endpoints that can be generated statically
- Ties you to their data model and update cycle
Both tools solve the "I need a server application" problem. But with modern static site generation, S3-compatible storage, and edge workers, there is no need for a server application. The content workflow should be: edit text files → run a script → publish. Everything else is unnecessary complexity for a single creator.
┌─────────────────────────────────────────────────────┐
│ Git Repository │
│ (Source of Truth for everything) │
│ │
│ content/episodes/ Markdown + frontmatter │
│ subtitles/ Whisper-generated .vtt files │
│ chapters/ JSON chapter files │
│ scripts/ Build & publish tooling │
│ feeds/ Generated RSS/XML (build output)│
│ site/ Optional minimal Astro site │
└──────────────┬──────────────────────────────────────┘
│ git lfs → originals
▼
┌──────────────────────┐ ┌──────────────────────────┐
│ Bucket 1: Originals │ │ Bucket 2: Delivery │
│ (Backblaze B2) │ │ (Backblaze B2 + CF) │
│ │ │ │
│ - Raw video files │ │ - HLS segments │
│ - Git LFS backend │ │ - MP3/Opus audio │
│ - Private │ │ - Thumbnails │
│ │ │ - Subtitles │
│ │ │ - Soundbite clips │
│ │ │ - Public via Cloudflare │
└──────────────────────┘ └──────────────────────────┘
│
┌─────────┴─────────┐
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ RSS Feeds │ │ Optional: │
│ │ │ Minimal Website │
│ - Audio │ │ (Astro/CF Pages) │
│ - Video │ │ │
│ │ │ ActivityPub │
│ Podcast 2.0 │ │ endpoints │
│ compatible │ │ (CF Worker) │
└──────────────┘ └──────────────────┘
Backblaze B2 is significantly cheaper than S3 or R2 for storage (~$6/TB/month). Through the Cloudflare Bandwidth Alliance, egress from B2 through Cloudflare is free. This makes it the most cost-effective option for video hosting.
Bucket 1 — Originals: Also serves as Git LFS backend. Contains raw recordings. Private, not publicly accessible. This is the archive and backup.
Bucket 2 — Delivery: Contains transcoded, optimized assets. Public, served through Cloudflare CDN. All URLs in RSS feeds and on the website point here.
Git LFS is configured with a custom transfer agent pointing to B2 instead of GitHub's LFS (which is expensive). This means git clone pulls the full repo including original media files. The same B2 bucket serves double duty as LFS storage and raw archive.
Each episode is a directory in the git repo:
content/episodes/2025-02-05-interview-topic/
├── index.md # Metadata, description, shownotes
├── subtitles.de.vtt # German subtitles (Whisper-generated)
├── subtitles.en.vtt # English subtitles (optional)
├── chapters.json # Podcast 2.0 chapter format
└── thumbnail.jpg # Episode artwork
---
title: "Episode Title"
date: 2025-02-05
type: conversation | screencast | tutorial | talk
slug: interview-topic
# Feed routing
feeds:
audio: true # Include in audio podcast feed
video: true # Include in video podcast feed
# Media references (URLs in delivery bucket)
audio:
mp3:
url: https://cdn.example.com/episodes/interview-topic/audio.mp3
size: 48234567
opus:
url: https://cdn.example.com/episodes/interview-topic/audio.opus
size: 24117283
video:
hls: https://cdn.example.com/episodes/interview-topic/master.m3u8
mp4:
1080p:
url: https://cdn.example.com/episodes/interview-topic/1080p.mp4
size: 2048234567
720p:
url: https://cdn.example.com/episodes/interview-topic/720p.mp4
size: 1024117283
duration: "01:23:45"
# Podcast 2.0 metadata
season: 1
episode: 5
persons:
- name: "Host Name"
role: host
- name: "Guest Name"
role: guest
location:
name: "Remote"
soundbites:
- start: "00:15:30"
end: "00:16:15"
title: "Key insight about open source"
tags: [open-source, interview, ai]
---
Episode description and shownotes in Markdown.
Supports full Markdown including links, code blocks, etc.| Type | Audio Feed | Video Feed | Notes |
|---|---|---|---|
conversation |
✅ (audio extract) | ✅ (video) | Talking heads — audio is the primary format, video is optional extra |
screencast |
❌ | ✅ | Visuals are essential, audio-only makes no sense |
tutorial |
❌ | ✅ | Same as screencast |
talk |
✅ (audio extract) | ✅ (video) | Conference talks — usually work as audio too |
This is controlled by the feeds.audio and feeds.video flags in frontmatter.
Two separate RSS/XML feeds are generated at build time. Both fully implement the Podcast 2.0 namespace (xmlns:podcast="https://podcastindex.org/namespace/1.0").
Standard podcast feed. Enclosure points to MP3. Additional podcast:alternateEnclosure for Opus. Only includes episodes where feeds.audio: true.
Enclosure points to MP4 (most compatible). Additional podcast:alternateEnclosure for HLS streaming. Only includes episodes where feeds.video: true.
Channel-level:
podcast:locked— Prevent unauthorized claiming (important without a big host)podcast:guid— Persistent global identifier, survives domain/URL changespodcast:funding— Link to support/donation pagepodcast:person— Show hostspodcast:medium—podcastfor audio feed,videofor video feed
Item-level:
podcast:chapters— URL to JSON chapters filepodcast:transcript— URL to VTT subtitle filepodcast:person— Per-episode guestspodcast:soundbite— Start time + duration for audio clipspodcast:alternateEnclosure— Multiple formats (MP3/Opus, MP4/HLS)podcast:season+podcast:episode— Numberingpodcast:location— Optional geo-tagging
After generation, submit feeds to:
- podcastindex.org — Open index, propagates to many apps
- Apple Podcasts — Via Podcasts Connect
- Spotify — Via Spotify for Podcasters (separate submission)
All media processing runs locally on a Mac Studio (Apple Silicon). No cloud transcoding needed for a low-volume single-creator setup (a few videos per month).
- FFmpeg with VideoToolbox hardware acceleration
- Whisper (OpenAI, runs locally) for subtitle generation
- b2 CLI or rclone for B2 uploads
- Node.js or Python for feed generation script
- ImageMagick/FFmpeg for thumbnail and soundbite audiogram generation
- Transcode video → HLS segments (multiple resolutions) + fallback MP4
- Extract audio → MP3 (192kbps) + Opus (96kbps) for conversation/talk episodes
- Generate subtitles → Whisper outputs VTT, commit to git
- Generate soundbite clips → FFmpeg cuts audio segment, optionally generates audiogram video (waveform + title overlay) for social sharing
- Generate thumbnails → Extract frame or use provided image, resize for feed requirements
- Upload to delivery bucket → Sync transcoded assets to B2 Bucket 2 via Cloudflare
- Update frontmatter → Fill in URLs and file sizes (or script does this automatically)
- Generate feeds → Build audio.xml and video.xml from content directory
- Deploy → Push feeds (and optional site) to Cloudflare Pages or B2
- Use
h264_videotoolbox/hevc_videotoolboxfor hardware-accelerated encoding on Apple Silicon - HLS output: 10-second segments, generate master playlist for adaptive bitrate
- For MVP: single resolution MP4 is fine, HLS with multiple resolutions is a later optimization
A very lightweight Astro site on Cloudflare Pages. Not strictly necessary (feeds are the primary distribution), but useful for:
- Shareable URLs for individual episodes (link someone to a specific video)
- SEO (static HTML ranks well, use schema.org
VideoObjectandPodcastEpisode) - Embedded video player (hls.js or Video.js)
- Show notes rendered from Markdown
- Episode archive/listing
This is intentionally minimal — a single template for episode pages, a listing page, and an index. No design overhead. Content comes from the same git repo.
These endpoints can be static JSON files generated at build time:
/.well-known/webfinger— Account discovery/activitypub/actor— Actor profile (type: Service or Person)/activitypub/outbox— Ordered collection of Create activities for each episode
This allows Fediverse users to find the account and see published content. No interaction possible yet.
A small Cloudflare Worker (~200-300 lines) handles:
- Inbox POST endpoint — Receives Follow requests, Likes, Announces (boosts)
- HTTP Signature verification — Required by ActivityPub spec
- Follower storage — Cloudflare KV or D1 for the follower list
- Push notifications — On new episode publish, send Create activities to all followers
PeerTube uses ActivityPub objects of type Video with specific extensions. Generating PeerTube-compatible JSON-LD would allow PeerTube instances to natively federate and display the content. This is an advanced feature and not required for launch.
- Git repo structure with content model
- Shell script: transcode video (single resolution MP4, no HLS yet)
- Shell script: extract audio (MP3)
- Shell script: generate subtitles with Whisper
- Shell script: upload assets to B2
- Feed generator: audio RSS feed with Podcast 2.0 tags
- Feed generator: video RSS feed with Podcast 2.0 tags
- Submit feeds to podcastindex.org
- Makefile or master script tying it all together
- HLS transcoding with multiple resolutions (adaptive bitrate)
- Opus audio as alternate enclosure
- Chapter marks support (JSON chapters file → podcast:chapters tag)
- Soundbite clip generation + audiogram for social sharing
- Minimal Astro website with hls.js player
- Deploy site to Cloudflare Pages
- schema.org structured data for SEO
- Static ActivityPub endpoints (webfinger, actor, outbox)
- Cloudflare Worker for inbox/follow handling
- Push new episodes to Fediverse followers
- PeerTube-compatible Video objects
- Automated chapter generation (AI-based, from transcript)
- Multi-language subtitle generation
- Clip/highlight extraction (AI-suggested soundbites)
- Analytics (privacy-respecting, maybe Plausible or simple CF Workers analytics)
- Comments via ActivityPub replies
- Git is the source of truth. Everything except binary media lives in the repo.
git clone= full backup minus raw video files (those are in B2/LFS). - No databases. Feeds and site are statically generated from Markdown frontmatter.
- No web interfaces. All workflows happen in terminal and text editor. AI tools can assist with any step.
- Portable and exit-friendly. Standard formats everywhere (Markdown, RSS, VTT, JSON, MP4). Zero lock-in.
- Local-first processing. Transcoding, subtitle generation, feed building all run locally. No cloud dependencies for the build pipeline.
- Progressive enhancement. Start with feeds only, add website later, add Fediverse later. Each phase is independently useful.