subflux finds, downloads and times subtitles for the shows and movies in your Sonarr and Radarr library, and saves them next to each video. It works only through Sonarr and Radarr and does not scan folders on its own.
subflux is under active development and has not reached version 1.0. Any update can change the config format, the saved state, the API or the behavior. Pin a specific image tag instead of latest, and read the release notes before you upgrade.
The dev branch is mid-rebuild. Its image serves a placeholder page, /api/health and /metrics, and does none of the work below until the rebuild lands. Run a released main tag.
subflux gets subtitles in your languages onto your whole library:
- Checks Sonarr and Radarr every 30 seconds and fetches subtitles for each new download.
- Scans the library once a day to fill gaps and upgrade recent subtitles.
- Fixes each download's timing to match a subtitle track in the video, or optionally its audio.
- Shows coverage on a web page, where you can search, pick and re-time subtitles by hand.
subflux is built for people who run Sonarr, Radarr or both on a Docker host and want subtitles without picking them by hand. It picks languages from each file's audio track and can fetch forced and hearing-impaired versions. A 52,000-episode library runs in 1 GB of memory.
You need a Sonarr or Radarr instance with its API key, and your media mounted writable at the paths they use. subflux searches thirteen subtitle sites:
| Site | What it finds | What it needs |
|---|---|---|
| OpenSubtitles | Movie and TV subtitles | An account and an API key |
| Gestdown | TV subtitles from Addic7ed | No account |
| SubSource | Movie and TV subtitles | An API key |
| SubDL | Movie and TV subtitles | An API key |
| BetaSeries | English and French TV subtitles | An API key |
| AnimeTosho | Anime subtitles from the releases it indexes | No account |
| YIFY Subtitles | Movie subtitles | No account |
| HDBits | Subtitles of HDBits.org torrents | An account and its passkey |
| TsukiHime | Anime subtitles from the releases it indexes | No account |
| Jimaku | Japanese subtitles, mostly for Japanese drama and film | An API key |
| Subs.ro | Romanian subtitles, and some in eight other languages | An API key |
| Assrt | Chinese and English subtitles | An API token |
| SubX | Spanish subtitles from the Subdivx catalogue | An API key |
Every site but two is read through its official API or published feed. The exceptions are YIFY Subtitles, which has no API, and SubX, which re-serves Subdivx, a site with no API. subflux reads both sparingly: it honours each host's robots.txt, sends at most four requests per search and pauses the site for at least an hour after any refusal.
Anime episode mapping comes from animap, where you can report wrong or missing anime subtitles caused by a mapping.
Consider Bazarr if you want a native install on Windows, macOS or a Raspberry Pi, or a site such as Titlovi or Napiprojekt. It supports 184 subtitle languages and Whisper speech-to-text.
subflux is free software under the AGPL-3.0-or-later license.
The image is on GitHub Container Registry and Docker Hub, for amd64 and arm64. This is the compose.yaml in this repository.
services:
subflux:
image: ghcr.io/cplieger/subflux:latest
container_name: subflux
restart: unless-stopped
# subflux finishes its shutdown within 8 seconds; this leaves it room before Docker kills it.
stop_grace_period: 10s
# Run "sudo install -d -o 1000 -g 1000 /opt/appdata/subflux" before the first start,
# or the container restarts in a loop. If .env sets PUID and PGID, use those numbers.
user: "${PUID:-1000}:${PGID:-1000}"
ports:
- "8374:8374"
volumes:
- "/opt/appdata/subflux:/config" # settings and saved state
# Put the path Sonarr and Radarr show for your media on the right, writable by the user above.
- "/path/to/media:/media"- Create the settings folder for user 1000 with
sudo install -d -o 1000 -g 1000 /opt/appdata/subflux. If your.envsetsPUIDandPGID, use those numbers. - Replace
/path/to/mediawith your media folder, and/mediawith the root folder Sonarr and Radarr show under Settings, then Media Management. If they use two root folders, add one line for each. - Run
docker compose up -d. - From another device on your network, open the host's address on port 8374 in a browser, for example
http://192.0.2.10:8374. - Create the admin account.
- In the setup wizard, enter Sonarr's address as you open it from another device, not
localhost, and the API key from the General page of Sonarr's Settings. Do the same for Radarr. - Enter the right-hand paths from step 2 as media roots, pick your subtitle sites and languages, then click Finish.
Run docker logs subflux. You should see HTTP server listening.
Every setting is in the web page's Settings dialog and is saved to /config/config.yaml. A save applies at once, with no restart. To write the file yourself, see Configuration.
| Key | Default | Description |
|---|---|---|
sources[] |
(none) | The Sonarr and Radarr instances to read, at most 16, each with an id, a type (sonarr or radarr), its url and its api_key |
languages |
required | The subtitle languages to fetch. List at least one target in default or a rule |
providers.<name> |
(none) | The subtitle sites to search, with their account settings. Until a site is listed, subflux searches none |
scan.change_interval |
30s |
How often subflux checks each source for new imports |
scan.whole_interval |
24h |
Time between the end of one full library scan and the start of the next |
search.min_fit |
other |
The lowest fit class downloaded automatically: other, same_source, same_release or exact |
search.upgrade_window_days |
7 |
For how many days after a download a better fitting subtitle replaces it. 0 turns these upgrades off |
sources[].exclude_tags[] |
[no-subflux] |
Tags whose shows and movies subflux skips |
timing.audio_fallback |
false |
Also try the video's speech when its subtitle tracks cannot verify a download's timing |
post_processing.strip_hi |
false |
Remove hearing-impaired annotations such as [music] and speaker labels such as JOHN:. Overlapping annotations end at the first closing bracket, so (a[b)c] keeps c], and a speaker label never takes the next line |
post_processing.strip_tags |
true |
Remove <i>, <b>, <u>, <s> and <font> tags. Any other tag, such as <iframe>, stays as text |
trusted_proxies |
[] |
Your reverse proxy's address, so the real client address is logged and rate-limited |
allowed_hosts |
[] |
The host names subflux answers for. Leave empty to accept any |
logging.level |
info |
debug, info, warn or error |
Every other setting, the command line and environment references in config.yaml are in Configuration.
| Variable | Description | Default |
|---|---|---|
PUID, PGID |
The user and group the container runs as, read by compose.yaml |
1000 |
| Mount | Description |
|---|---|
/config |
config.yaml and the subflux.bolt database |
/media |
Your media, where subflux writes subtitle files next to each video |
| Port | Description |
|---|---|
8374 |
The web page, the API and /metrics |
Create the admin account right after the first start. Until one exists, the first visitor to the page creates it. Keep port 8374 on your own network, or put a reverse proxy with HTTPS in front of it and set trusted_proxies and allowed_hosts. /metrics and /api/health answer without a login.
Logins use passwords, passkeys or OIDC, and an admin can create API keys for scripts. Saved site passwords and API keys are never sent back to the browser. Passkeys cover your whole domain, so with subflux at subflux.example.com the browser offers them on every example.com host. Setting auth.disable_auth turns login off and treats every request as an admin.
The image has no shell and runs as the user in compose.yaml. subflux checks each subtitle site's address before it connects. Security covers reverse proxies, passkeys and what the image contains.
The healthcheck runs subflux health, which passes while the web server is running, set up or not. Docker checks every 30 seconds after a 15-second start period and marks the container unhealthy after 3 failures in a row. A wrong API key or an unreachable Sonarr instance keeps it healthy, so you can fix it on the web page. Docker's health state notifies nobody: to hear that subflux is down, load the alert rules below or point an uptime prober at /api/health.
- An alert says subtitles cannot be written in a folder. subflux pauses work there and retries every 5 minutes. Make the folder writable for the container user.
- An alert says a media root is unavailable. The folder is missing, empty or cannot be opened. Mount it, or fix the root folder in Sonarr or Radarr or the source's
path_mappings. - An alert says a subtitle site was disabled. The site rejected your credentials three times. Save new ones or press the site's Test button.
- A share is unmounted. subflux keeps its records, shows an alert naming the path and holds new imports there until the share returns.
How subflux works explains each case in full.
subflux serves Prometheus metrics at /metrics and writes JSON logs. Prometheus alert rules ship in alerts/promql.yaml. Monitoring and alerts lists the metrics and the rules and shows how to load them.
- Configuration lists every setting and command, for anyone writing
config.yamlby hand. - How subflux works covers fit classes, timing, credential handling and the limits.
- Security covers reverse proxies, passkeys and what the image contains.
- Monitoring and alerts lists the metrics, the alert rules and the kinds of problem each rule pages for.
- Database maintenance covers format resets, a database that cannot be opened and compaction.
- FFmpeg and x264 are built into the image to read subtitle and audio tracks and to stream the preview in the timing editor.
- The timing engine is a Go port of the alignment algorithm in alass by @kaegi.
- The audio timing uses a port of the voice-activity detector from the WebRTC project, re-tuned for film audio.
- Chinese and English subtitle search is provided by Assrt.
Issues and pull requests are welcome. See CONTRIBUTING.md.
This project is built with care and follows security best practices, but it is intended for personal / self-hosted use. No guarantees of fitness for production environments. Use at your own risk.
This project was built with AI assistance. The human maintainer defines architecture, supervises implementation, and makes all final decisions.
AGPL-3.0-or-later. See LICENSE. The image carries the license text of every bundled component under /usr/share/licenses/.
The image bundles two upstream programs built from source, both GPL-2.0-or-later: FFmpeg (the FFMPEG_VERSION pin in the Dockerfile, fetched as the n<version> tag archive of https://github.com/FFmpeg/FFmpeg) and x264 (the X264_COMMIT pin, cloned from https://code.videolan.org/videolan/x264.git). The build applies no patches; this repository's Dockerfile, together with those two pinned sources, is the complete build recipe, which is how the GPL source offer for the shipped binaries is met.
Three upstream projects are ported rather than bundled: into internal/subsync, the alignment algorithms of alass (GPL-3.0-or-later, which section 13 of the GPLv3 lets an AGPL-3.0 program contain) and the voice activity detector of WebRTC (BSD-3-Clause); into internal/subtitleenc/icu, the charset detector of ICU (Unicode-3.0). The release-name patterns in internal/release are imported from Sonarr and Radarr (GPL-3.0) and TRaSH-Guides (MIT). Their license texts, with the files that carry the ported and imported code, are in THIRD_PARTY_NOTICES.md.