Automatically detect and remove advertisements from podcast audio files using Whisper transcription and LLM analysis.
AdNihilator is a podcast ad detection and removal system that:
- Transcribes podcast audio using OpenAI's Whisper model
- Detects sponsor segments using keyword heuristics and LLM refinement
- Removes ads with frame-accurate ffmpeg splicing
- Provides both a standalone CLI and a web service architecture
AdNihilator uses a three-tier detection strategy, choosing the cheapest effective method:
Tier 1 - Description Timestamps (Free)
- Extracts ad timestamps from episode descriptions when available
- Supports formats:
(00:29-01:15), chapter markers,1h02mnotation - Skips transcription entirely when high-confidence timestamps found
Tier 2 - Gemini Audio Detection (~$0.10/episode)
- Uses Google's Gemini 2.0 Flash for direct audio analysis
- ~60 seconds processing vs 5-12 minutes for Whisper
- No transcription needed - analyzes audio directly
- May make a rare, targeted OpenAI text-refinement call when keyword over-detection produces an implausibly long (>10 min) merged ad span. This fires on a small minority of episodes; the cost estimate above is provisional for those until measured live.
Tier 3 - Whisper + LLM (~$0.004/episode)
- Whisper transcribes audio to timestamped text segments
- Keyword detection scores segments using sponsor names and ad patterns
- OpenAI GPT refines candidates into precise ad boundaries
Final Step: ffmpeg removes ad segments with frame-accurate cuts
When using OpenAI LLM refinement, AdNihilator uses an optimized two-pass approach:
- Pass 1: Fast segment-level transcription of the full episode
- Pass 2: High-quality word-level transcription of only detected ad regions
This provides ~2.5x speedup compared to transcribing the entire episode with word timestamps.
AdNihilator specifically looks for ads in common placement zones:
- Pre-roll (first 2 minutes): House ads, network promos, sponsor reads
- Outro (last 2 minutes): Post-roll ads, dynamically inserted sponsors
- Process individual audio files locally
- Output JSON with ad timestamps and confidence scores
- Splice out ads to create clean audio files
- Subscribe to podcast RSS feeds
- Auto-process new episodes with a worker daemon
- Generate ad-free RSS feeds for podcast apps
- Store processed audio on Cloudflare R2
- Gemini audio detection: Direct audio analysis without transcription (fastest, ~$0.10/episode)
- External transcripts: Fast processing using podcast-provided transcripts (Substack, Lex Fridman)
- Description timestamps: Extract ad markers from episode descriptions (free)
- Sponsor-aware: Extracts sponsor names from episode descriptions for better accuracy
- Confidence scoring: Each ad gets a 0-1 confidence score for filtering
- Multi-region detection: Pre-roll, mid-roll, and outro ad placement
- LLM cost tracking: Track and display detection costs per episode in the UI
- Python 3.11+
- ffmpeg: Required for audio processing
# macOS brew install ffmpeg # Ubuntu/Debian apt-get install ffmpeg
git clone https://github.com/yourusername/adnihilator.git
cd adnihilator
pip install -e .Before first use, download a Whisper model:
adnihilator download-model smallAvailable models: tiny, base, small, medium, large (larger = more accurate but slower)
adnihilator detect podcast.mp3 --out results.jsonThis runs heuristic detection only (no LLM). Fast but less accurate.
export OPENAI_API_KEY="sk-..."
adnihilator detect podcast.mp3 --llm-provider openai --out results.jsonUses GPT-4.1-mini to refine ad boundaries. More accurate, requires OpenAI API key.
adnihilator splice results.json --out clean.mp3Creates a new audio file with all detected ads removed.
adnihilator detect podcast.mp3 --llm-provider openai --splice --out clean.mp3Create adnihilator.toml in your working directory:
[llm]
provider = "openai"
model = "gpt-4o-mini" # or "gpt-4o"
api_key_env = "OPENAI_API_KEY"
[gemini]
# Enable Gemini 2.0 Flash for audio-based ad detection
# Faster (~60s) but more expensive (~$0.10/episode) than Whisper+LLM
enabled = true
api_key_env = "GEMINI_API_KEY"
model = "gemini-2.0-flash-exp"
[detect]
heuristic_threshold = 0.4 # Heuristic sensitivity (lower = more sensitive)
context_segments_before = 2 # Include 2 segments before ad for context
context_segments_after = 2 # Include 2 segments after ad for context
[transcribe]
model = "small" # Whisper model size
device = "cpu" # or "cuda" for GPUDetection Priority: When Gemini is enabled, the worker uses this order:
- Description timestamps (if high-confidence timestamps found)
- External transcript (if
source_urlavailable) - Gemini audio detection (if enabled and API key set)
- Whisper + LLM refinement (fallback)
For automated podcast processing, you can deploy the web service + worker architecture.
Web Service (VPS) Worker Daemon (Local/GPU)
├── FastAPI app ├── Claims jobs from API
├── SQLite database ├── Transcribes audio
├── RSS feed management ├── Detects & removes ads
└── Admin UI └── Uploads to R2 storage
On your VPS:
cd adnihilator
pip install -e .
# Set environment variables
export ADMIN_USERNAME="admin"
export ADMIN_PASSWORD="your-password"
export WORKER_API_KEY="your-worker-secret"
export DATABASE_PATH="data/adnihilator.db"
export R2_PUBLIC_URL="https://pub-xxxxx.r2.dev"
# Optional: stuck-job recovery timeout in seconds (default 7200 = 2h)
export WORKER_STUCK_TIMEOUT_SECONDS="7200"
# Run with uvicorn
uvicorn web.app:app --host 0.0.0.0 --port 8001 --workers 2See deploy/adnihilator.service for a systemd service example.
On your local machine (with GPU recommended):
# Copy and edit the example script
cp scripts/run-worker.sh.example scripts/run-worker.sh
# Edit run-worker.sh with your credentials
# Start the worker
./scripts/run-worker.shOr run directly:
export API_URL="https://your-server.com"
export WORKER_API_KEY="your-worker-secret"
export OPENAI_API_KEY="sk-..."
export GEMINI_API_KEY="AIza..." # Optional: for Gemini audio detection
export R2_ACCESS_KEY="..."
export R2_SECRET_KEY="..."
export R2_BUCKET="adnihilator"
export R2_ENDPOINT="https://....r2.cloudflarestorage.com"
python -m adnihilator.cli worker --daemon --interval 60Visit https://your-server.com and:
- Login with admin credentials
- Add podcast RSS feeds
- Episodes are auto-queued for processing
- Get your ad-free RSS feed URL
Detect ads in an audio file.
adnihilator detect INPUT [OPTIONS]Options:
--out FILE: Output JSON file path--whisper-model MODEL,-m: Whisper model (tiny/base/small/medium/large)--device DEVICE,-d: Processing device (cpu/cuda)--llm-provider PROVIDER,-l: LLM provider (none/openai)--config FILE,-c: Config file path--splice: Automatically splice out ads after detection--confidence-threshold FLOAT: Minimum confidence to remove (default: 0.35)
Remove detected ads from audio.
adnihilator splice DETECTION_JSON [OPTIONS]Options:
--out FILE: Output audio file path--confidence-threshold FLOAT: Minimum confidence to remove (default: 0.35)
Download a Whisper model.
adnihilator download-model MODELArguments:
MODEL: Model size (tiny/base/small/medium/large)
Detection results are saved as JSON:
{
"audio_path": "podcast.mp3",
"duration": 3247.5,
"segments": [
{
"index": 0,
"start": 0.0,
"end": 5.2,
"text": "Welcome to the podcast",
"words": [...]
}
],
"candidates": [
{
"start": 120.5,
"end": 165.3,
"trigger_keywords": ["sponsor", "promo_code"],
"heuristic_score": 0.75,
"sponsors_found": ["BetterHelp"]
}
],
"ad_spans": [
{
"start": 122.1,
"end": 163.8,
"confidence": 0.95,
"sponsor": "BetterHelp"
}
],
"model_info": {
"whisper_model": "small",
"llm_provider": "openai",
"llm_model": "gpt-4.1-mini"
}
}pytest
pytest tests/test_sponsors.py -v # Single test file
pytest -k "test_extract" -v # Tests matching patternadnihilator/
├── adnihilator/ # Core library
│ ├── cli.py # CLI commands
│ ├── transcribe.py # Whisper transcription
│ ├── external_transcript.py # External transcript fetching
│ ├── ad_timestamps.py # Extract timestamps from descriptions
│ ├── gemini_audio.py # Gemini audio detection client
│ ├── sponsors.py # Sponsor extraction from HTML
│ ├── ad_keywords.py # Heuristic detection
│ ├── ad_llm.py # LLM refinement
│ ├── two_pass.py # Two-pass optimization
│ ├── splice.py # Audio splicing
│ └── config.py # Configuration
├── web/ # Web service (optional)
│ ├── app.py # FastAPI application
│ ├── models.py # SQLAlchemy models (with LLM tracking)
│ ├── routes/ # API endpoints
│ ├── services/ # RSS sync, R2 upload
│ └── templates/ # Admin UI
├── worker/ # Worker daemon (optional)
│ ├── daemon.py # Job processing loop (three-tier detection)
│ ├── client.py # API client
│ └── r2.py # R2 upload
├── scripts/ # Utility scripts
│ └── migrate_add_llm_fields.py # Database migration
└── tests/ # Test suite
- Low confidence: Increase the threshold with
--confidence-threshold 0.5 - No LLM: Heuristic-only mode is less accurate. Use
--llm-provider openai - Dynamic ad insertion: Some ads are inserted by podcast networks and may be harder to detect
With LLM refinement:
- High confidence (>0.7): ~95% precision
- Medium confidence (0.5-0.7): ~85% precision
- Low confidence (<0.5): ~60-70% precision
Without LLM (heuristic only): ~70% precision overall
Yes! Use heuristic-only mode:
adnihilator detect podcast.mp3 --out results.jsonThis is faster and free, but less accurate than LLM refinement.
- CLI mode: Everything runs locally. Transcripts never leave your machine unless you enable LLM refinement.
- LLM mode: Transcripts are sent to OpenAI for ad detection. Do not use on sensitive content.
- Web service: Designed for self-hosting. You control all data.
- Description timestamps: Free (parses episode descriptions)
- Whisper transcription: Free (runs locally)
- Gemini audio detection: ~$0.10 per episode (~60 seconds processing)
- OpenAI LLM refinement: ~$0.004 per episode with GPT-4o-mini
- Mega-span refinement: rare extra GPT-4o-mini call on the few episodes whose keyword detection yields a >10 min merged span (capped per episode)
- Cloudflare R2 storage: ~$0.015/GB/month + minimal egress fees
| Method | Cost/Episode | Processing Time | Accuracy |
|---|---|---|---|
| Description timestamps | Free | Instant | High (when available) |
| Gemini 2.0 Flash | ~$0.10 | ~60 seconds | High |
| Whisper + GPT-4o-mini | ~$0.004 | 5-12 minutes | High |
| Whisper only (no LLM) | Free | 5-12 minutes | Medium |
The web UI tracks and displays LLM costs per episode so you can monitor spending.
AdNihilator works with any podcast, but has special optimizations for:
- Substack podcasts: Fast transcript fetching (no Whisper needed)
- Lex Fridman Podcast: Fast transcript fetching from lexfridman.com
- All other podcasts via Whisper transcription
MIT
Contributions welcome! Please:
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Submit a pull request
Built with:
- faster-whisper - Fast Whisper transcription
- Google Gemini - Audio-based ad detection
- OpenAI API - LLM refinement
- FastAPI - Web service
- ffmpeg - Audio processing