An automated subtitle synchronization tool that runs as a Docker container. It continuously monitors your media directories for video files with out-of-sync subtitles and automatically synchronizes them using three sync engines (ffsubsync, autosubsync, and alass).
Docker Hub: mrorbitman/subsyncarr
- Automated Subtitle Synchronization - Syncs subtitles for your entire media library or specific folders.
- Multiple Sync Engines - Uses ffsubsync, autosubsync, and alass for maximum compatibility and success rate
- Scheduled Processing - Runs on a configurable cron schedule (default: daily at midnight) and on container startup
- Parallel Processing - Configure concurrent subtitle processing for faster library syncing
- Skip Already Synced Files - Avoids re-processing files that already have synchronized subtitles or where an engine repeatedly fails.
- Processing History - View past runs with detailed statistics, results, and logs
- Configuration Dashboard - View current settings, monitored paths, and schedule status
- Configurable Timeouts - Set per-engine timeout limits to prevent hung processes
- Log Management - Configurable retention policies with automatic trimming and deletion
- SRT and ASS Subtitles - Syncs SubRip (
.srt) and Advanced SubStation Alpha (.ass) subtitles. Outputs keep the input's format (e.g.movie.en.ffsubsync.ass). - Non Destructive - Creates new files for each engine so no original files are altered. Allows easy switching between engines while watching content.
- Create a docker-compose.yaml file with the following content:
name: subsyncarr
services:
subsyncarr:
image: mrorbitman/subsyncarr:latest
container_name: subsyncarr
ports:
- '3000:3000' # Web UI
volumes:
# Mount your media directories
- /path/to/movies:/movies
- /path/to/tv:/tv
- /path/to/anime:/anime
- ./data:/app/data # Persist database across restarts
restart: unless-stopped
deploy:
resources:
limits:
memory: 768M # Hard limit
reservations:
memory: 128M # Minimum guaranteed memory
environment:
- TZ=Etc/UTC # Replace with your own timezone
- PUID=1000
- PGID=10
- CRON_SCHEDULE=0 0 * * * # Runs every day at midnight by default
- SCAN_PATHS=/movies,/tv # Comma-separated paths to scan
- EXCLUDE_PATHS=/movies/temp,/tv/downloads # Optional: exclude directories
- MAX_CONCURRENT_SYNC_TASKS=1 # Number of parallel processing tasks
- INCLUDE_ENGINES=ffsubsync,autosubsync,alass # Engines to use-
Update the configuration:
- Replace
/path/to/movies,/path/to/tv, etc. with your actual media paths - Update
TZto your timezone (e.g.,America/New_York,Europe/London) - Update
PUIDandPGIDto match your user (runidcommand to find these) - Adjust
SCAN_PATHSto match your mounted volumes
- Replace
-
Start the container:
docker compose up -d- Access the Web UI:
Open your browser to http://localhost:3000 or whatever port you've mapped to inside docker.
docker run -d \
--name subsyncarr \
-p 3000:3000 \
-v /path/to/movies:/movies \
-v /path/to/tv:/tv \
-v ./data:/app/data \
-e TZ=Etc/UTC \
-e PUID=1000 \
-e PGID=10 \
-e CRON_SCHEDULE="0 0 * * *" \
-e SCAN_PATHS=/movies,/tv \
-e MAX_CONCURRENT_SYNC_TASKS=1 \
mrorbitman/subsyncarr:latest| Variable | Default | Description |
|---|---|---|
SCAN_PATHS |
/scan_dir |
Comma-separated directories to scan for subtitle files (must be mounted as volumes) |
EXCLUDE_PATHS |
(none) | Comma-separated directories to exclude from scanning |
SYNC_LANGUAGES |
(none) | Comma-separated language codes to sync (e.g., en,de). Only syncs subtitles with matching language tags in the filename (e.g., movie.en.srt). If not set, all subtitles are synced. |
CRON_SCHEDULE |
0 0 * * * |
Cron expression for sync schedule (daily at midnight), or disabled to turn off |
MAX_CONCURRENT_SYNC_TASKS |
1 |
Number of subtitle files to process in parallel (higher = faster but more CPU) |
INCLUDE_ENGINES |
ffsubsync,autosubsync,alass |
Which sync engines to use (comma-separated) |
FFSUBSYNC_SUFFIX |
ffsubsync |
Custom suffix for ffsubsync output files (e.g., sdh → movie.en.sdh.srt) |
AUTOSUBSYNC_SUFFIX |
autosubsync |
Custom suffix for autosubsync output files (e.g., cc → movie.en.cc.srt) |
ALASS_SUFFIX |
alass |
Custom suffix for alass output files (e.g., forced → movie.en.forced.srt) |
SYNC_TIMEOUT |
(none) | Timeout in seconds per sync operation (overrides SYNC_ENGINE_TIMEOUT_MS) |
SYNC_ENGINE_TIMEOUT_MS |
1800000 |
Timeout for each sync engine in milliseconds (30 min default) |
NODE_OPTIONS |
--max-old-space-size=512 |
Node.js options, used here to set memory limit (in MB) |
WEB_PORT |
3000 |
Port for the web UI |
WEB_HOST |
127.0.0.1 |
Host to bind the web UI to (0.0.0.0 to expose externally) |
BASE_PATH |
(none) | Reverse-proxy subpath for the web UI (/subsyncarr for https://example.com/subsyncarr/) |
TZ |
(system) | Timezone for logging and cron scheduling (e.g., America/New_York) |
PUID |
1000 |
User ID for file permissions (run id -u to find yours) |
PGID |
1000 |
Group ID for file permissions (run id -g to find yours) |
| Variable | Default | Description |
|---|---|---|
DB_PATH |
/app/data/subsyncarr.db |
SQLite database location |
LOG_BUFFER_SIZE |
1000 |
Ring buffer size for in-memory logs |
RETENTION_KEEP_RUNS_DAYS |
30 |
Keep complete runs for N days |
RETENTION_TRIM_LOGS_DAYS |
7 |
Trim logs after N days (keeps summary only) |
RETENTION_MAX_LOG_SIZE |
10000 |
Max size for trimmed logs in bytes |
RETENTION_CLEANUP_INTERVAL_HOURS |
24 |
How often to run cleanup (in hours) |
The SYNC_ENGINE_TIMEOUT_MS environment variable controls how long each sync engine can run before being terminated. This prevents hung processes from blocking the queue.
On timeout the whole process tree of the engine is killed (the shell, ffsubsync/alass and the ffmpeg processes they spawn). The image runs tini as PID 1 so any process that still gets orphaned is reaped instead of lingering as a <defunct> zombie.
Example configuration:
environment:
- SYNC_ENGINE_TIMEOUT_MS=3600000 # 60 minutes for large filesYour media directory should be organized with video files and their corresponding subtitle files using matching names:
/movies
├── Movie Title (2024).mkv
├── Movie Title (2024).srt # Will be synchronized
├── Movie Title (2024).ffsubsync.srt # Generated output
└── Another Movie.mp4
└── Another Movie.srt
/tv
├── Show Name/
│ ├── Season 01/
│ │ ├── Show.S01E01.mkv
│ │ └── Show.S01E01.srt.ass subtitles are handled the same way and produce .ass outputs (e.g. Movie Title (2024).en.ass → Movie Title (2024).en.ffsubsync.ass). Generated outputs are never picked up as inputs.
Engine support by format:
| Engine | .srt |
.ass |
|---|---|---|
| ffsubsync | Yes | Yes |
| alass | Yes | Yes |
| autosubsync | Yes | No (skipped for .ass with a log message) |
The app follows standard naming conventions compatible with Plex, Jellyfin, Emby, and Bazarr.
Subsyncarr Plus includes a comprehensive web-based monitoring interface accessible at http://localhost:3000 after starting the container.
Real-time Monitoring:
- Live progress bars showing current processing status
- File-by-file status updates via WebSocket
- Engine-level detail (see which sync engine is running)
- Current and queued files display
Manual Control:
- Start Full Run - Process all configured directories immediately
- Scan Specific Path - Process a custom directory on demand
- Stop Processing - Cancel all remaining files in current run
- Skip File - Cancel processing for individual files
File Management:
- View completed and skipped files
- Clear processed files from the UI
- Track file status (pending, processing, completed, skipped, error)
- See matched video files for each subtitle
Processing History:
- Sortable run history table with timestamps
- Per-run statistics (total, completed, skipped, failed counts)
- Engine-level results summary with notation:
- F = ffsubsync result
- Au = autosubsync result
- Al = alass result
- Duration tracking for each run
- View detailed logs for any past run with copy-to-clipboard functionality
Configuration Dashboard:
- Display of monitored paths and excluded paths
- Schedule status with next run time
- Human-readable cron schedule translation
Processing history is stored in SQLite and persists across container restarts. Ensure the data volume is mounted:
volumes:
- ./data:/app/data # Database and logs stored hereThe app intelligently tracks failures for each file/engine combination. After 3 consecutive failures, that engine will be automatically skipped for that specific file, preventing wasted processing time. You can reset skip status via the API endpoint /api/skip-status/reset.
Optimized for low-memory environments with:
- Configurable memory limits (768MB default, 128MB minimum)
- SQLite optimizations for low RAM usage
- File-based logging with buffering to reduce memory pressure
- Automatic database vacuuming and cleanup
- Ring buffer for in-memory logs
Automatic cleanup keeps your database size manageable:
- Complete runs retained for 30 days (configurable)
- Logs trimmed after 7 days, keeping only summaries
- Runs beyond retention period are automatically deleted
- Cleanup runs every 24 hours (configurable)
docker logs -f subsyncarrDetailed processing logs are available in the Web UI under "Processing History" - click on any run to view full logs.
If you encounter permission errors, ensure PUID and PGID match your host user:
id -u # Get your user ID
id -g # Get your group IDThen update your docker-compose.yaml with these values.
Note: Do not use the
user:directive in docker-compose or--userin docker run. The container must start as root so the entrypoint can configure file permissions usingPUID/PGID, then drops to the unprivileged user automatically viagosu.
If the container is being killed due to OOM (Out Of Memory):
- Reduce
MAX_CONCURRENT_SYNC_TASKSto 1 - Increase memory limit in
NODE_OPTIONS(e.g.,--max-old-space-size=1024) - Increase memory limit in docker-compose.yaml
- Reduce
SYNC_ENGINE_TIMEOUT_MSfor faster timeouts - Exclude large files or problematic directories with
EXCLUDE_PATHS
Check that:
- Your subtitle files are named to match video files (e.g.,
movie.mkvandmovie.srtormovie.ass). Supported video extensions are.mkv,.mp4,.avi,.movand.ts. SCAN_PATHSmatches your mounted volumes- Files haven't already been synced (check for
.ffsubsync.srt/.ffsubsync.assfiles) - Files aren't being auto-skipped due to repeated failures (check skip status in Web UI)
Pull the latest image:
docker pull mrorbitman/subsyncarr:latestDocker Hub Repository: mrorbitman/subsyncarr
Issues and pull requests are welcome! Please report bugs or suggest features via GitHub Issues.
MIT — see LICENSE.