Subtitler
Setup · Docker or executable

Getting started

Docker is the recommended install for library scanning. The executable can also generate subtitles for a single file without Sonarr, Radarr, or a config file.

StandaloneGenerate one file

Use this path when you only want subtitles for one video and do not need the daemon.

terminal
export OPENAI_API_KEY=...
go build -o subtitler ./cmd/subtitler
./subtitler generate -langs en,pt-PT "/path/to/movie.mkv"

If config.yaml is absent and -config is not passed, generate uses built-in defaults, reads OPENAI_API_KEY from the environment, and writes .subtitler.<language>.srt sidecars next to the video.

Passing -config custom.yaml makes the config explicit. If that file is missing, the command fails instead of silently falling back.

01Copy the example files

Start from the bundled examples, then fill in your API keys.

terminal
cp config.example.yaml config.yaml
cp .env.example .env

Edit .env with your OpenAI, Sonarr, and Radarr keys.

.env
OPENAI_API_KEY=your-openai-api-key
SONARR_API_KEY=your-sonarr-api-key
RADARR_API_KEY=your-radarr-api-key
PUID=1000
PGID=1000

Set PUID / PGID to the user that owns your media (id -u and id -g) so sidecars are written with the right permissions.

02Connect Sonarr and Radarr

Set URLs in config.yaml that are reachable from the Subtitler container. The keys are read from .env.

config.yaml
sonarr:
  url: http://sonarr:8989
  api_key: ${SONARR_API_KEY}

radarr:
  url: http://radarr:7878
  api_key: ${RADARR_API_KEY}

Container names like http://sonarr:8989 work when everything shares a Docker network. Otherwise use a reachable host or IP.

03Mount the media paths

Subtitler asks Sonarr and Radarr for media paths, so the container must see those exact paths. Mount your library at the same path the ARR apps report.

docker-compose.example.yaml
volumes:
  - ./config.yaml:/etc/subtitler/config.yaml:ro
  - ./data:/data
  # Match the path Sonarr/Radarr report, e.g.:
  - /mnt/media:/mnt/media

If the ARR apps report a path the container can't match exactly, translate it with path_mappings:

config.yaml
subtitles:
  path_mappings:
    - from: /movies
      to: /media/movies

04Pick languages and strategy

Set the languages you want and how aggressively to fill them. missing_only writes a target language only when no matching sidecar exists. By default, timings come from the preferred source audio track.

config.yaml
subtitles:
  required_languages:
    - en
    - pt-PT
  source_audio_languages:
    - en
    - auto
  source_subtitle_language: en
  strategy: missing_only        # missing_only | generated_only | force
  embedded:
    action: ignore              # ignore | extract
  cleanup:
    external_subtitles: keep    # keep | quarantine | delete

Generated files are written next to the video as movie.subtitler.en.srt, one per language.

05Validate safely

The example config ships with dry_run: true. Keep it until the logs show the files and actions you expect.

doctor checks connectivity and tooling; scan reports what it would do.

terminal
docker compose --env-file .env -f docker-compose.example.yaml pull
docker compose --env-file .env -f docker-compose.example.yaml \
  run --rm subtitler doctor -config /etc/subtitler/config.yaml
docker compose --env-file .env -f docker-compose.example.yaml \
  run --rm subtitler scan -config /etc/subtitler/config.yaml

06Run the daemon

When the dry-run looks right, set dry_run: false. The example keeps a low max_jobs_per_scan so the library fills in gradually instead of all at once.

config.yaml
dry_run: false
processing:
  scan_interval: 30m
  max_jobs_per_scan: 1   # 0 = unlimited

The example compose runs the daemon command by default, so a plain up -d starts it.

terminal
docker compose --env-file .env -f docker-compose.example.yaml up -d
docker compose --env-file .env -f docker-compose.example.yaml logs -f subtitler

The daemon repeats the scan every scan_interval, processing only the missing-subtitle jobs it finds.