Getting started
Install
Container (recommended)
This is how the tool is meant to run: watch is the image’s default command, so
the container fetches on an interval and stays out of the way.
services:
subtrace:
image: ghcr.io/alexraskin/subtrace:latest
container_name: subtrace
environment:
PUID: 1000 # owner of your media files
PGID: 1000
TZ: America/Phoenix
SUBTRACE_OPENSUBTITLES_API_KEY: ${SUBTRACE_OPENSUBTITLES_API_KEY}
SUBTRACE_OPENSUBTITLES_USERNAME: ${SUBTRACE_OPENSUBTITLES_USERNAME}
SUBTRACE_OPENSUBTITLES_PASSWORD: ${SUBTRACE_OPENSUBTITLES_PASSWORD}
SUBTRACE_SUBDL_API_KEY: ${SUBTRACE_SUBDL_API_KEY}
volumes:
- ./subtrace-config:/config # subtrace.toml + state database
- /mnt/media:/media # must be read-write
restart: unless-stopped.srt files next to the videos, so it cannot.Then write the config the container will read:
mkdir -p subtrace-config
docker run --rm ghcr.io/alexraskin/subtrace:latest config init > subtrace-config/subtrace.toml
$EDITOR subtrace-config/subtrace.toml # library paths are the ones *inside* the container
docker compose up -d subtraceBinary
git clone https://github.com/alexraskin/subtrace && cd subtrace
mise run install # -> /usr/local/bin
# or: mise run install-user -> ~/.local/binThe binary is static and cgo-free (the SQLite driver is pure Go), so it has no runtime dependencies.
First config
subtrace config init > subtrace.tomlThat writes a fully commented reference file. The parts you must set:
[[library]]
path = "/media/Movies"
kind = "movie" # "movie" or "tv"
[[library]]
path = "/media/TV Shows"
kind = "tv"
[subtitles]
languages = ["en"]
[providers]
order = ["opensubtitles", "subdl"]
[providers.opensubtitles]
api_key = "" # or SUBTRACE_OPENSUBTITLES_API_KEY
username = "" # or SUBTRACE_OPENSUBTITLES_USERNAME
password = "" # or SUBTRACE_OPENSUBTITLES_PASSWORD
[providers.subdl]
api_key = "" # or SUBTRACE_SUBDL_API_KEYAt least one [[library]] block is required — subtrace refuses to start
without one. Roots must not overlap or be listed twice. See
Configuration for every key, and Providers
for where the credentials come from.
First run
subtrace config validate # parse + check, no network
subtrace scan # walk the library, record what is missing, no networkscan works before any credentials exist, so it is the fastest way to confirm
subtrace sees your files the way you expect:
video files 8412
have subtitles 6011
missing subtitles 2401
took 12.4s
movie 1204
tv 7208
sample of what is missing:
[en] /media/Movies/Heat (1995) {tmdb-949}/Heat (1995) Bluray-1080p.mkvIf missing subtitles is implausibly high or low, the cause is almost always
scan configuration — see Ignore patterns
and Troubleshooting.
Then fetch a handful, and look at what landed on disk:
subtrace fetch --limit 5
subtrace statusUse --dry-run on any command to do everything except write files.
Once you are happy with the results, leave it running:
subtrace watch # fetch, sleep run.interval, repeatWhat happens next
The first fetch will not finish your library, and it is not supposed to.
Provider quotas are much smaller than a real library, so subtrace works through
it across many runs and remembers everything in SQLite. See Pacing.