Skip to content
cpliegerPublic

About

Get subtitles found, timed and saved next to every video in your Sonarr and Radarr library

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

19 stars

Watchers

0 watching

Forks

Latest commit

 

History

1,523 Commits

Folders and files

subflux

Image Size Platforms base: Distroless Mutation SBOM

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.

⚠️ Alpha software

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.

What it does

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.

Who it is for

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.

Quick start

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"
  1. Create the settings folder for user 1000 with sudo install -d -o 1000 -g 1000 /opt/appdata/subflux. If your .env sets PUID and PGID, use those numbers.
  2. Replace /path/to/media with your media folder, and /media with the root folder Sonarr and Radarr show under Settings, then Media Management. If they use two root folders, add one line for each.
  3. Run docker compose up -d.
  4. 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.
  5. Create the admin account.
  6. 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.
  7. 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.

Configuration reference

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

Security

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.

Troubleshooting

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.

Monitoring

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.

Documentation

  • Configuration lists every setting and command, for anyone writing config.yaml by 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.

Credits

  • 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.

Contributing

Issues and pull requests are welcome. See CONTRIBUTING.md.

Disclaimer

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.

License

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.

About

Get subtitles found, timed and saved next to every video in your Sonarr and Radarr library

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

19 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages