Skip to content

Multi-Engine Sync

Sublarr aligns subtitles to video through a multi-engine orchestrator rather than a single CLI call. Engines run in a configured order; the orchestrator early-exits on the first sane success and writes every attempt to an audit table.

EngineInstalled by defaultWhat it does
ffsubsync✅ bundled in the Docker imageSpeech-detection-based sync against the video’s audio track
alass❌ needs the alass binary on the hostReference-subtitle-based sync (uses another subtitle as the time base)

Check live status at Settings → Sync Engines or via:

Terminal window
curl -s -H "X-API-Key: $SUBLARR_API_KEY" \
http://<host>:5765/api/v1/sync/engines

The endpoint returns each engine’s name, available flag (is the binary installed?), configured timeout_s, and the global sanity_threshold_ms (default 60 000 ms = 60 s).

The SyncOrchestrator runs engines in order. An engine result is rejected and the next engine is tried when:

  1. is_available() returns False — the binary isn’t installed
  2. engine.sync() raises an exception
  3. The returned offset exceeds the sanity_threshold_ms — an obvious mis-sync result is treated as a failure

If all engines fail, the subtitle is saved without sync and a WARNING is logged. The sublarr_sync_all_failed_total Prometheus counter increments so operators can alert on chronic failures.

Every engine attempt (successful or not) writes a row to the sync_job_runs table:

ColumnMeaning
engineEngine name (ffsubsync, alass, …)
statusok, error, insanity_reject, skipped, failure
offset_msDetected shift in milliseconds (NULL when engine didn’t complete)
duration_msTime spent in that engine
subtitle_path / video_pathInputs
reasonShort error description (64 char max)
created_atTimestamp

Query recent runs:

Terminal window
curl -s -H "X-API-Key: $SUBLARR_API_KEY" \
"http://<host>:5765/api/v1/sync/runs?limit=50"

Download the static binary from alass releases and mount it into the container’s PATH, e.g. in docker-compose.yml:

services:
sublarr:
volumes:
- /path/to/alass:/usr/local/bin/alass:ro

After a container restart, GET /api/v1/sync/engines will show alass: available=true.

Successful sync fires the after_sync trigger of the post-processing pipeline — you can chain a Plex/Emby/Jellyfin refresh or a webhook notification right after the subtitle lands.

The orchestrator is deliberately engine-agnostic. Future additions (NanoSync, LLM-assisted sync) drop in as new files under backend/services/sync_engines/ and are appended to the chain — no orchestrator changes needed.