Skip to content
Getting started

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
The media mount must be read-write. Media servers usually mount libraries read-only; subtrace writes .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 subtrace

Binary

git clone https://github.com/alexraskin/subtrace && cd subtrace
mise run install          # -> /usr/local/bin
# or: mise run install-user  -> ~/.local/bin

The binary is static and cgo-free (the SQLite driver is pure Go), so it has no runtime dependencies.

First config

subtrace config init > subtrace.toml

That 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_KEY

At 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 network

scan 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.mkv

If 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 status

Use --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, repeat

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