Skip to content

gwa99a9/streamrip-subs

Repository files navigation

streamrip-subs icon

streamrip-subs

Self-hosted subtitle extraction and translation for Jellyfin.
Scan your media library, extract embedded subtitles, translate to any language — fully automated.

Python FastAPI Docker Docker Pulls License

⚡ Built as a quick homelab project with AI assistance. Works great with Jellyfin, Radarr, and Sonarr.


What it does

streamrip-subs is a lightweight API server that runs in Docker alongside your media stack. It:

  1. Scans your media library and indexes every video file with its embedded subtitle streams
  2. Extracts text-based subtitle streams to .srt files using ffmpeg — named exactly how Jellyfin expects
  3. Translates extracted subtitles to any language using Google Translate (free, no API key needed)
  4. Serves a web dashboard so you can do all of the above without touching a terminal

It runs a nightly auto-scan, supports Jellyfin webhooks for instant detection of new media, and tracks all jobs with progress bars and cancellation support.


Features

  • Web UI dashboard — library overview, subtitle status per file, one-click extract and translate, live job progress
  • Async library scan — concurrent ffprobe scanning with configurable parallelism
  • Smart extraction — correct Jellyfin filename format (movie.en.srt, movie.en.sdh.srt, movie.en.forced.srt)
  • Codec detection — automatically identifies image-based subtitles (PGS/VOBsub/DVB) and skips them gracefully with a clear warning instead of silently failing
  • Multi-language translation — translate to Sinhala, Tamil, Hindi, French, or any Google Translate language
  • Batch translation with retry — translates in batches of 40 blocks, with recursive batch-splitting on count mismatch and exponential backoff on network errors
  • Job system — every scan/extract/translate is a tracked job with status, progress, error reporting, and kill/cancel support
  • Duplicate prevention — won't start a second job if one is already running for the same file
  • Auto-scan scheduler — nightly cron-style scan via APScheduler, configurable via env vars
  • Jellyfin webhook — receives Jellyfin "Item Added" events and triggers a scan automatically
  • Auto-cleanup — removes DB entries for files deleted from disk on every scan
  • Job purge — one-click cleanup of old completed jobs via API or UI
  • SQLite + WAL — fast, concurrent, zero-configuration database

Tech stack

Component Technology
API server FastAPI + Uvicorn
Database SQLite with WAL mode
Video probing ffprobe (part of ffmpeg)
Subtitle extraction ffmpeg
Translation Google Translate (free unofficial API)
HTTP client httpx (async, retry + backoff)
Scheduler APScheduler
Container Docker

Quick start

Option A — Docker Hub (recommended)

# 1. Pull the image
docker pull gwa99a9/streamrip-subs:latest

# 2. Download the compose file
curl -O https://raw.githubusercontent.com/gwa99a9/streamrip-subs/main/docker-compose.yml

# 3. Edit your media path and timezone (see Configuration below)
nano docker-compose.yml

# 4. Start
docker compose up -d

Option B — Build from source

git clone https://github.com/gwa99a9/streamrip-subs.git
cd streamrip-subs

# Edit docker-compose.yml with your paths
nano docker-compose.yml

docker compose up -d --build

5. Open the dashboard

http://localhost:5200

6. Scan your library

Click Scan library in the dashboard, or:

curl -X POST http://localhost:5200/scan

Configuration

Edit docker-compose.yml — the only required changes are the volume paths:

volumes:
  - /your/media/path:/media:rw          # your Jellyfin media folder
  - /your/appdata/streamrip-subs:/data  # where the database is stored

All environment variables

Variable Default Description
TZ Europe/London Timezone for logs and scheduler
SCAN_CONCURRENCY 4 Parallel ffprobe processes during scan
SCAN_CRON_HOUR 3 Hour for nightly auto-scan (24h)
SCAN_CRON_MINUTE 0 Minute for nightly auto-scan
DEFAULT_TRANSLATE_LANG en Default translation target language code
PYTHONUNBUFFERED 1 Real-time log output

Project structure

streamrip-subs/
├── Dockerfile
├── docker-compose.yml
├── .env.example
├── .gitignore
├── README.md
├── CONTRIBUTING.md
├── .github/
│   ├── workflows/
│   │   └── docker.yml        # auto-build and push to Docker Hub on push/tag
│   └── ISSUE_TEMPLATE/
└── backend/
    ├── app.py                # FastAPI routes, job management, web UI, scheduler, webhook
    ├── scanner_async.py      # Async file walker, ffprobe, ffmpeg extraction, codec detection
    ├── translator.py         # SRT parser, Google Translate batching with recursive split fallback
    ├── db.py                 # SQLite schema, migrations, helpers
    └── requirements.txt      # Pinned Python dependencies

Usage

Typical workflow

# 1. Scan your library (or let the nightly auto-scan do it)
curl -X POST http://localhost:5200/scan

# 2. Find a movie's file_id
curl "http://localhost:5200/search?q=MovieName"
# → [{"id": 42, "path": "/media/movies/MovieName/..."}]

# 3. Check what subtitle streams it has
curl http://localhost:5200/file/42/subtitle-status

# 4. Extract English subtitles
curl -X POST "http://localhost:5200/extract-sub/42?lang=en"
# → {"job_id": "abc-123", "reused": false}

# 5. Poll the job
curl http://localhost:5200/jobs/abc-123
# → {"status": "done (2 extracted)", ...}

# 6. Translate to Sinhala
curl -X POST "http://localhost:5200/translate-sub/42?source_lang=en&target_lang=si"

# 7. Poll translation progress
curl http://localhost:5200/jobs/<job_id>

Jellyfin webhook setup

streamrip-subs can automatically scan when new media is added to Jellyfin:

  1. In Jellyfin: Dashboard → Plugins → Catalog → install the Webhook plugin
  2. Dashboard → Webhooks → Add Generic Destination
  3. URL: http://<your-host>:5200/webhook/jellyfin
  4. Enable event: Item Added
  5. Save

API reference

Files

Method Endpoint Description
GET /files?limit=100&offset=0 List all indexed video files
GET /file/{file_id} Get details for a single file
GET /file/{file_id}/subtitle-status Subtitle streams, disk status, job history
GET /subs/{file_id} List subtitle streams for a file
GET /search?q=... Search files by path substring

Actions

Method Endpoint Description
POST /scan Start a full library scan
POST /extract-sub/{file_id}?lang=en Extract subtitle stream to .srt
POST /translate-sub/{file_id}?source_lang=en&target_lang=si Translate .srt to target language

Jobs

Method Endpoint Description
GET /jobs List recent jobs (optional ?status=running)
GET /jobs/{job_id} Get status and progress of a job
POST /jobs/{job_id}/cancel Cancel and kill a running job
DELETE /jobs/purge?older_than_days=7 Delete old completed/errored jobs

System

Method Endpoint Description
GET /health Health check + next auto-scan time
GET /scheduler Auto-scan schedule info
GET /stats File, subtitle, and job counts
GET /languages Supported translation language codes
GET /docs Swagger UI (interactive API explorer)
POST /webhook/jellyfin Jellyfin webhook receiver

Subtitle format compatibility

Text-based ✅ (extractable to .srt)

Codec Name Notes
subrip SRT Perfect — direct copy
ass / ssa ASS/SSA Text extracted, styling stripped
webvtt WebVTT Text extracted, cue tags stripped
mov_text MP4 text Apple/iTunes — works fine
microdvd MicroDVD Frame timing converted
ttml / dfxp TTML Text extracted, XML styling stripped

Image-based ❌ (cannot extract to .srt — needs OCR)

Codec Name Common source
hdmv_pgs_subtitle PGS Blu-ray rips
dvd_subtitle VOBsub DVD rips
dvb_subtitle DVB TV broadcast rips
xsub XSUB Old DivX files

Image-based streams are detected automatically. The UI shows a red image only badge and disables the Extract button with a tooltip. The job log shows a clear skip message rather than a silent ffmpeg failure.

For OCR conversion of PGS/VOBsub:


Subtitle naming (Jellyfin compatible)

Type Filename
Regular MovieName.en.srt
SDH (hearing impaired) MovieName.en.sdh.srt
Forced (foreign parts) MovieName.en.forced.srt
Translated MovieName.si.srt

Supported languages

Any Google Translate language code works. Common ones:

Code Language Code Language
si Sinhala ja Japanese
ta Tamil ko Korean
hi Hindi zh Chinese
fr French ar Arabic
de German pt Portuguese
es Spanish ru Russian

Full list: GET /languages


Maintenance

# View logs (filter polling noise)
docker logs streamrip-subs -f 2>&1 | grep -v "GET /jobs\|GET /stats\|GET /health"

# Rebuild after code changes
docker compose up -d --build

# Purge old jobs
curl -X DELETE "http://localhost:5200/jobs/purge?older_than_days=7"

# Wipe database and start fresh
docker stop streamrip-subs
rm /your/appdata/streamrip-subs/media.db
docker start streamrip-subs

Troubleshooting

PermissionError: /app/app.py

chmod 644 backend/*.py && chmod 755 backend/

sqlite3.OperationalError: no such column: error

docker exec -it streamrip-subs sqlite3 /data/media.db "ALTER TABLE jobs ADD COLUMN error TEXT;"
docker restart streamrip-subs

Scan finds 0 files

docker exec streamrip-subs ls /media   # check the volume mount

Extract button disabled for all files
Run a fresh scan — codec detection populates on scan and the buttons re-enable automatically.

Blu-ray files show many streams but nothing extracts
Blu-ray rips use PGS (image-based) subtitles — see the compatibility table above.


Publishing to Docker Hub

# Build and tag
docker build -t gwa99a9/streamrip-subs:latest .
docker build -t gwa99a9/streamrip-subs:1.0.0 .

# Push
docker push gwa99a9/streamrip-subs:latest
docker push gwa99a9/streamrip-subs:1.0.0

Automated builds via GitHub Actions are configured in .github/workflows/docker.yml — every push to main builds and pushes automatically. Add these secrets to your GitHub repo:

  • DOCKERHUB_USERNAMEgwa99a9
  • DOCKERHUB_TOKEN → your Docker Hub access token

Contributing

PRs welcome! See CONTRIBUTING.md for dev setup and areas that need work most.


License

MIT — do whatever you want with it.


Built for homelab use · Works with Jellyfin · No cloud required
Docker Hub · Issues

About

Self-hosted subtitle extraction and translation for Jellyfin. Scan your media library, extract embedded subtitle streams, translate to any language — fully automated.

Topics

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors