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.
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.
cp config.example.yaml config.yaml
cp .env.example .env
Edit .env with your OpenAI, Sonarr, and Radarr keys.
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.
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.
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:
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.
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.
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.
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.
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.
