Skip to content

andrepaim/distillpod

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

91 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

DistillPod βš—οΈ

A self-hosted, mobile-first podcast app with AI-powered features: transcription, distillations, ad detection, chapter generation, episode chat, and deep research reports. No per-call API costs β€” all AI runs via the Claude CLI through your existing subscription.


Screenshots

Home feed Episode Player Chat Distillation Shared link
Home Episode Player Chat Distills Unauthorized
Latest episodes across subscriptions Episode detail with AI summary Full-screen player with chapters AI-generated insights + Q&A Distillation card with Copy / Delete / Research What a recipient sees when following a shared link

Why I Built This

I was paying for a premium podcast app specifically for its AI distillation feature β€” the ability to extract insights from what I was listening to on the fly. It worked well, but it felt wasteful: I was already running a VPS with the Claude CLI for my own OpenClaw agent, paying for a Claude subscription. Why route that through someone else's SaaS?

So I cut out the middleman.

DistillPod runs entirely on your own server. The AI features β€” distillations, ad detection, chapters, chat, research β€” all go through the Claude CLI using your existing subscription. No separate API key, no extra per-call charges on top of what you already pay, no data leaving your infrastructure to a third-party podcast app, no feature flags behind a paywall.

If you have a VPS and a Claude subscription, you already have everything you need to run this.


Features

  • πŸ“° Home feed β€” unified list of the latest episodes across all subscriptions, sorted by date. Shows distillation count per episode.
  • πŸ” Search β€” find podcasts via the iTunes Search API (no key needed). When the search box is empty, a πŸ€– Suggested for you section surfaces daily AI-generated recommendations based on your listening history.
  • πŸ“š Library β€” browse your subscribed podcasts and their episode lists with transcript status badges.
  • ▢️ Fullscreen Player β€” Spotify-style slide-up player with chapter navigation, ad-free toggle, and distillation controls.
  • βš—οΈ Distill β€” tap at any moment while listening. Captures the last 60 seconds of transcript, calls the Claude CLI, and returns a verbatim quote and a 1–2 sentence insight (~30s).
  • βœ‚οΈ Ad-free audio β€” after transcription, Claude classifies ad segments and ffmpeg cuts them out. Stream the clean version from the player.
  • πŸ“– Chapters β€” Claude generates 4–10 named chapters with timestamps from the full transcript. Tap any chapter to jump directly.
  • πŸ’¬ Episode chat β€” ask questions about any transcribed episode. Claude answers using the full transcript as context. History kept per episode (capped at 50 messages).
  • πŸ”¬ Research β€” trigger a deep research report from any distillation. Claude generates queries, Tavily runs web searches, Claude synthesizes findings into an HTML report. Delivered via Telegram.
  • πŸ“‹ Distillations library β€” all your distillations grouped by episode. Copy, delete, or trigger research from any entry.
  • ⚑ Stale-while-revalidate caching β€” data is cached in localStorage with a 30-minute TTL and refreshed silently in the background.

How It Works

All AI features call the Claude CLI as a subprocess:

result = subprocess.run(
    ["claude", "--print", prompt],
    capture_output=True, text=True
)

The CLI authenticates through your Claude subscription β€” no API key, no per-call billing.

Setup: install the Claude CLI and log in once:

npm install -g @anthropic-ai/claude-code
claude login

AI features summary

Feature Trigger
Distillation (quote + insight) User taps βš—οΈ while listening
Ad detection + audio cut After transcription completes
Chapter generation + summary Daily sync or manual script run
Episode chat User opens chat or sends message
Deep research report User triggers from a distillation
Podcast suggestions Daily cron at 09:00 BRT

Transcription

When you tap Play:

  1. POST /player/play triggers a background download + transcription task
  2. The episode MP3 is downloaded to /media/ (streaming, skips if cached)
  3. faster-whisper transcribes with word_timestamps=True (CPU, medium model by default)
  4. Word-level timestamps saved to transcripts table
  5. Ad detection runs non-fatally after transcription
  6. The βš—οΈ Distill button unlocks when transcript_status = done

Model trade-offs

Model Speed (CPU) Accuracy
base Fastest Good
small Fast Better
medium Moderate Very good (default)
large-v3 Slow Best

Set via WHISPER_MODEL in .env.


Stack

Layer Tech
Frontend React 18 + TypeScript + Vite + Tailwind CSS v3
Client routing React Router v6
State Zustand
Backend FastAPI + uvicorn
Database aiosqlite β€” async SQLite (WAL mode)
Transcription faster-whisper β€” local speech-to-text (CTranslate2, int8)
AI Claude CLI (claude --print)
RSS feedparser
HTTP client httpx
Auth authlib β€” Google OAuth2

Architecture

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                         User (browser)                       β”‚
β”‚              https://your-domain.example.com                 β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                           β”‚ HTTPS (Apache reverse proxy)
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚              FastAPI app  β€” port 8124 (localhost)            β”‚
β”‚                                                             β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”  β”‚
β”‚  β”‚  Static files  β”‚  β”‚           API Routers             β”‚  β”‚
β”‚  β”‚  (React SPA)   β”‚  β”‚  /auth  /podcasts  /player        β”‚  β”‚
β”‚  β”‚  /assets/**    β”‚  β”‚  /gists  /chat  /research         β”‚  β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
                                      β”‚
              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
              β”‚                       β”‚                       β”‚
   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”
   β”‚   SQLite DB      β”‚   β”‚   Media files     β”‚   β”‚  Claude CLI     β”‚
   β”‚ distillpod.db    β”‚   β”‚  /media/*.mp3     β”‚   β”‚ claude --print  β”‚
   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component breakdown

Component Responsibility
frontend/ React SPA β€” UI only, all state on backend
backend/main.py FastAPI entry point, serves built frontend and reports
backend/routers/auth.py Google OAuth2 flow, session cookie management
backend/routers/podcasts.py Search, subscribe, episode listing, suggestions
backend/routers/player.py Play trigger, audio streaming, transcript status, chapters
backend/routers/gists.py Create, list, delete distillations
backend/routers/chat.py Episode Q&A β€” init, message, history
backend/routers/research.py Trigger + poll research reports
backend/services/downloader.py Async MP3 download to /media/
backend/services/transcriber.py faster-whisper, word-level timestamps, async background task
backend/services/snip_engine.py Timestamp window lookup + Claude distillation
backend/services/ad_detector.py Claude ad classification + ffmpeg audio surgery
backend/services/chapterizer.py Claude chapter + summary generation
backend/services/researcher.py Multi-turn research pipeline: Claude + Tavily β†’ HTML report
backend/services/rss.py RSS feed parsing
backend/services/podcast_index.py PodcastIndex API wrapper
backend/database.py SQLite connection, schema init, WAL mode
backend/config.py All settings via env vars (pydantic-settings)

Self-hosting

Requirements

  • Python 3.10+
  • Node.js 18+
  • ffmpeg (for ad-free audio generation)
  • Claude CLI installed and authenticated
  • A VPS with a few GB of RAM (faster-whisper medium uses ~1.5GB)

Quick start

git clone https://github.com/your-username/distillpod.git
cd distillpod
cp .env.example .env       # edit with your settings

# Backend
cd backend
pip install -r requirements.txt
uvicorn main:app --host 127.0.0.1 --port 8124

# Frontend (production build)
cd ../frontend
npm install
npm run build              # outputs to frontend/dist/

FastAPI serves frontend/dist/ at / automatically.

Environment variables

Copy .env.example to backend/.env and edit:

Variable Required Description
GOOGLE_CLIENT_ID Yes Google OAuth2 client ID
GOOGLE_CLIENT_SECRET Yes Google OAuth2 client secret
ALLOWED_EMAILS Yes Comma-separated email allowlist
SESSION_SECRET Yes JWT signing secret (openssl rand -hex 32)
PUBLIC_URL Yes Public-facing domain (CORS, OAuth redirect, report URLs)
PODCAST_INDEX_API_KEY No Podcast Index API key for richer metadata
PODCAST_INDEX_SECRET No Podcast Index API secret
TAVILY_API_KEY No Enables deep research reports
TELEGRAM_BOT_TOKEN No Telegram notifications
TELEGRAM_CHAT_ID No Telegram chat ID
WHISPER_MODEL No Whisper model size: base, small, medium (default), large-v3
MEDIA_DIR No Path for downloaded MP3s (default: media/)
REPORTS_DIR No Path for HTML research reports (default: reports/)

Development

Run backend and frontend with hot reload in separate terminals:

# Terminal 1 β€” backend
cd backend
uvicorn main:app --host 127.0.0.1 --port 8124 --reload

# Terminal 2 β€” frontend
cd frontend
npm install
npm run dev   # http://localhost:5173

Deploy (systemd)

Service file

[Unit]
Description=DistillPod β€” AI-powered podcast player
After=network.target

[Service]
Type=simple
User=root
WorkingDirectory=/path/to/distillpod/backend
ExecStart=/usr/bin/python3 -m uvicorn main:app --host 127.0.0.1 --port 8124
Restart=always
RestartSec=5
EnvironmentFile=/path/to/distillpod/backend/.env

[Install]
WantedBy=multi-user.target

Reverse proxy (Apache)

ProxyPreserveHost On
ProxyPass / http://127.0.0.1:8124/
ProxyPassReverse / http://127.0.0.1:8124/

Scheduled jobs

Two daily cron jobs run as background scripts:

Job Schedule Script What it does
distillpod-daily-sync 03:00 BRT scripts/daily-sync.py RSS fetch β†’ download β†’ transcribe β†’ ad detection β†’ chapterization
distillpod-suggest 09:00 BRT scripts/suggest-podcasts.py Claude generates queries β†’ iTunes search β†’ 4 suggestions stored

The daily sync pipeline per subscription:

  1. Reset stuck processing episodes
  2. Fetch latest 5 RSS episodes
  3. Download recent episodes (≀48h old)
  4. Transcribe with faster-whisper
  5. Detect + remove ads (non-fatal)
  6. Generate chapters + episode summary
  7. Telegram alert on errors

Testing

Backend (pytest)

cd /path/to/distillpod
python3 -m pytest tests/ -v

Tests run against an in-memory SQLite DB. Auth bypassed via test session cookie.

E2E (Playwright)

cd frontend
npx playwright test

Mobile viewport (390x844), Chromium. Requires TEST_MODE=true in .env for auth bypass.

Test files: navigation, home feed, search, library, player (fullscreen, gists, chapters, ad-free, chat), gists library, caching, SPA routing.


License

MIT

About

Self-hosted podcast app with instant transcript snipping. No cloud costs.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors