A self-hosted BBC iPlayer & BBC Sounds download manager. Rust/Axum backend Β· Ember.js web UI Β· SQLite persistence Β· Docker deployment.
Warning
A UK TV licence is required to access BBC iPlayer TV content legally.
- Search BBC iPlayer and BBC Sounds programmes (TV and radio) via
get_iplayer - Series drill-down β expand any show on the search page to browse all series and episodes; queue an entire series or individual episodes
- Queue management β add, remove, cancel, retry, reorder downloads
- Background worker pool β configurable concurrent downloads
- Exponential-backoff retries β automatically retry failed downloads up to a configurable limit (2 s β 4 s β 8 s β¦)
- Scheduled downloads β specify a future date/time per item
- Live progress β WebSocket push updates (progress bar, speed, ETA)
- Basic auth β token-based login, multi-user support
- Settings UI β configure output directory, quality, tools, proxy, concurrency and retry limits
# 1. Clone
git clone https://github.com/you/tapedeck && cd tapedeck
# 2. Configure
cp .env.example .env
# β Edit .env: set SECRET, ADMIN_PASSWORD, DOWNLOAD_DIR
# 3. Run
docker compose up -d
# 4. Open
open http://localhost:3000Log in with the ADMIN_USERNAME / ADMIN_PASSWORD you set in .env
(defaults: admin / changeme).
WG.Dockerfile is a variant that bakes a WireGuard VPN tunnel into the container, starting it before the tapedeck app. This is useful if you want all get_iplayer traffic to egress through a VPN without configuring the host network.
- A WireGuard configuration file named
wg0.confsomewhere on the host β it is mounted into the container at runtime, so private keys never appear in an image layer. - The container must be granted the
NET_ADMINcapability (and optionallySYS_MODULEif thewireguardkernel module is not already loaded on the host).
These are the changes needed to make the docker-compose setup with Wireguard.
services:
tapedeck:
build:
dockerfile: WG.Dockerfile
cap_add:
- NET_ADMIN
- SYS_MODULE
sysctls:
- net.ipv4.conf.all.src_valid_mark=1
volumes:
- ./wg0.conf:/etc/wireguard/wg0.conf:roOn startup the container (running as root) mounts and reads /etc/wireguard/wg0.conf, calls wg-quick up wg0 to bring up the WireGuard interface, then drops to the unprivileged tapedeck user to run the application. Because the config is mounted at runtime rather than copied into the image, private keys never appear in any image layer and it is safe to push the image to a registry.
All settings come from environment variables (or a .env file β copy .env.example to get started):
| Variable | Default | Description |
|---|---|---|
SECRET |
(required in prod) | HMAC signing secret β generate with openssl rand -hex 32 |
ADMIN_USERNAME |
admin |
Initial admin username, seeded on first boot |
ADMIN_PASSWORD |
changeme |
Initial admin password, seeded on first boot |
DOWNLOAD_DIR |
./downloads |
Host directory where downloaded programmes are stored (mounted as /downloads in the container) |
MAX_CONCURRENT |
5 |
Maximum simultaneous downloads |
MAX_DOWNLOAD_RETRIES |
5 |
Times to retry a failed download (0 = no retries); backs off exponentially (2 s β 4 s β 8 s β¦) |
PROXY |
(empty) | Optional HTTP proxy URL passed to get_iplayer |
BIND |
0.0.0.0:3000 |
HTTP listen address |
DATABASE_URL |
/data/tapedeck.db |
SQLite path inside the container |
OUTPUT_DIR |
/downloads |
Download destination inside the container |
GET_IPLAYER_PATH |
/usr/local/bin/get_iplayer |
Path to the get_iplayer binary |
FFMPEG_PATH |
/usr/bin/ffmpeg |
Path to ffmpeg |
Runtime settings (output dir, quality, retry limit, concurrency) can also be updated via the Settings page in the UI and are stored in the database; they take effect on the next download attempt.
The Default Quality setting accepts the following values:
| Value | TV (--tv-quality) |
Radio (--radio-quality) |
|---|---|---|
best |
fhd |
high |
good |
hd |
standard |
worst |
mobile |
low |
Raw get_iplayer values (1080p, 720p, sd, etc.) can also be entered directly.
cd backend
cargo run
# binds to http://localhost:3000
# API at http://localhost:3000/api
# WS at ws://localhost:3000/ws?token=<token>Requires get_iplayer (v3.36+) and ffmpeg to be in PATH.
cd frontend
npm install
npm start # dev server on http://localhost:4200 (proxies /api β localhost:3000)Production build output goes to frontend/dist/, which is served as static files by the Rust server.
| Method | Path | Description |
|---|---|---|
POST |
/api/auth/login |
Login β { token, user_id, username } |
GET |
/api/queue |
List queue; ?status=&page=&per_page= |
POST |
/api/queue |
Add item |
GET |
/api/queue/:id |
Get item |
DELETE |
/api/queue/:id |
Cancel / remove |
POST |
/api/queue/:id/retry |
Retry failed/cancelled |
POST |
/api/queue/reorder |
Bulk reprioritise |
GET |
/api/search?q=&type=tv|radio |
Search programmes |
GET |
/api/search/episodes?pid=&type=tv|radio |
List all episodes for a brand/series PID |
POST |
/api/search/refresh |
Refresh programme cache |
GET |
/api/settings |
List all settings |
PUT |
/api/settings/:key |
Update one setting |
PATCH |
/api/settings |
Bulk update settings |
GET |
/api/users |
List users |
GET |
/api/users/me |
Current user |
POST |
/api/users |
Create user |
DELETE |
/api/users/:id |
Delete user |
PUT |
/api/users/:id/password |
Change password |
All endpoints except /api/auth/login require Authorization: Bearer <token>.
Connect to ws://<host>/ws?token=<token> for real-time events:
For DASH streams (progress: 0), the UI shows an indeterminate animated bar until the download completes. A heartbeat event is also emitted every 30 seconds with the elapsed time so the UI stays live.
tapedeck/
βββ backend/ Rust/Axum service
β βββ src/
β β βββ main.rs Entry point
β β βββ config.rs Environment config (incl. MAX_DOWNLOAD_RETRIES)
β β βββ auth.rs Password hashing + token auth
β β βββ db.rs SQLite pool + migrations
β β βββ models.rs Shared types + DTOs
β β βββ queue.rs Background worker pool + retry logic
β β βββ iplayer.rs get_iplayer subprocess wrapper + episode parser
β β βββ state.rs Shared Axum state
β β βββ routes/
β β βββ mod.rs Router assembly
β β βββ queue.rs Queue endpoints
β β βββ search.rs Search + episode-listing endpoints
β β βββ settings.rs Settings CRUD
β β βββ users.rs User management
β β βββ ws.rs WebSocket handler
β βββ migrations/
β βββ 001_initial.sql
β βββ 002_max_retries_setting.sql
βββ frontend/ Ember.js SPA
β βββ app/
β β βββ app.js
β β βββ router.js
β β βββ services/
β β β βββ api.js HTTP client (incl. fetchEpisodes)
β β β βββ socket.js WebSocket client
β β βββ routes/ Route classes
β β βββ controllers/ Controller classes (search: series drill-down)
β β βββ helpers/
β β β βββ includes.js
β β βββ templates/ Handlebars templates
β β βββ styles/
β β βββ app.css
β βββ config/
βββ Dockerfile
βββ docker-compose.yml
βββ .env.example
GNU General Public License v3.0 or later (GPL-3.0-or-later)