A modern, self-hosted podcast aggregator (podcatcher) web application built with Django, HTMX, and Tailwind CSS.
- Podcast Discovery: Browse and search thousands of podcasts with full-text search
- Smart Feed Updates: Intelligent RSS feed parsing with conditional requests and dynamic scheduling
- Episode Playback: In-browser audio player with playback position tracking
- Personalized Recommendations: Algorithm-based podcast suggestions based on your subscriptions
- Bookmarks & History: Save favorite episodes and track listening progress
- Email Notifications: Optional digests of new episodes from subscribed podcasts
- Progressive Web App: Installable as a mobile/desktop app
- OAuth Support: Sign in with GitHub or Google
- Backend: Django 6.0 (Python 3.14)
- Frontend: HTMX, AlpineJS, Tailwind CSS
- Database: PostgreSQL 18 with full-text search
- Cache: Redis 8
- RSS Parsing: lxml (XPath) with Pydantic validation
- Image Processing: Pillow with WebP compression
- Python 3.14 (install via
uv python install 3.14.xif needed) - uv - Fast Python package installer
- just - Command runner (optional but recommended)
- Docker & Docker Compose (required for development services)
-
Clone the repository
git clone https://github.com/yourusername/radiofeed-app.git cd radiofeed-app -
Start development services (do this first!)
just start
This starts Docker containers via
docker-compose.ymlfor:- PostgreSQL 18 (port 5432)
- Redis 8 (port 6379)
- Mailpit for email testing (UI: http://localhost:8025)
These services must be running before proceeding with the next steps.
-
Create environment file
cp .env.example .env
The
.env.examplefile contains required development settings (e.g.,DEBUG=true,USE_DEBUG_TOOLBAR=true) that are configured to work with the Docker Compose services. -
Install dependencies
just install
This will:
- Install Python dependencies with uv
- Set up pre-commit hooks
- Download NLTK data for text processing
-
Run database migrations
just dj migrate
-
Create a superuser
just dj createsuperuser
-
Start the development server
just serve
The app will be available at http://localhost:8000
The .env.example file contains default development settings configured to work with the docker-compose.yml services. Simply copy it to .env as shown in step 3 above.
If you need to customize settings (e.g., using local PostgreSQL/Redis instead of Docker, or adding OAuth credentials):
- Edit your
.envfile to customize:DATABASE_URL- PostgreSQL connection string (default works with Docker Compose)REDIS_URL- Redis connection string (default works with Docker Compose)SECRET_KEY- Django secret key (auto-generated in development)DEBUG- Enable debug mode (set totruefor development)USE_DEBUG_TOOLBAR- Enable Django Debug Toolbar (set totruefor development)- OAuth credentials for GitHub/Google login (optional)
just # List all available commands
just serve # Run dev server + Tailwind compiler
just lint # Run code formatters and linters
just typecheck # Run type checker (basedpyright)
just dj <command> # Run Django management commandsThe project maintains 100% code coverage:
just test # Run all tests with coverage
just test radiofeed/podcasts # Test specific module
just test-e2e # Run end-to-end tests with Playwright
just tw # Watch mode - auto-run on changesPre-commit hooks run automatically on commit:
just precommit run --all-files # Run manually
just precommitupdate # Update hook versionsHooks include:
- Ruff - Fast Python linting and formatting
- basedpyright - Type checking
- djhtml/djlint - Django template formatting
- rustywind - Tailwind class sorting
- gitleaks - Secret detection
- commitlint - Conventional commit messages
The app doesn't include a web UI for adding podcasts yet. Use the Django admin or management commands:
# Via admin
just dj createsuperuser
# Then visit http://localhost:8000/admin/
# Via shell
just dj shell
>>> from radiofeed.podcasts.models import Podcast
>>> Podcast.objects.create(rss="https://example.com/feed.xml")This project uses Django tasks for running background jobs without Celery. The just dj command can be used to run these tasks:
just dj db_workerNOTE: ensure task runner is running.
Podcast feeds are parsed via management commands (typically run via cron):
just dj parse_podcast_feeds --limit 360 # Parse up to 360 scheduled podcasts
just dj send_episode_updates # Email new episode notifications
just dj create_podcast_recommendations # Generate podcast recommendationsThe parser features:
- Conditional HTTP requests (ETag/If-Modified-Since)
- Dynamic scheduling based on episode frequency
- Exponential backoff on errors
- Bulk episode upserts for efficiency
- Support for iTunes, Google Play, and Podcast Index namespaces
For production deployment to Hetzner Cloud with Cloudflare CDN/SSL, see the Deployment Guide. It should be possible to deploy to any Docker compatible hosting provider with some adjustments to the deployment scripts.
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes following the code style (Ruff will enforce this)
- Write tests to maintain 100% coverage
- Commit using conventional commits (
feat:,fix:,docs:, etc.) - Push to your fork and submit a pull request
Pre-commit hooks will run automatically to ensure code quality.
MIT License - see LICENSE file for details.
- Issues: GitHub Issues
- Security: See SECURITY.md for reporting vulnerabilities
- RSS feed parsing inspired by various podcast aggregators
- Uses excellent open-source libraries (Django, HTMX, PostgreSQL)
- Thanks to all contributors!