{"id":42037815,"url":"https://github.com/johnpc/subsyncarr","last_synced_at":"2026-05-17T01:11:26.055Z","repository":{"id":274070527,"uuid":"921807025","full_name":"johnpc/subsyncarr","owner":"johnpc","description":null,"archived":false,"fork":false,"pushed_at":"2025-02-03T02:49:16.000Z","size":1249,"stargazers_count":204,"open_issues_count":12,"forks_count":5,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-10-10T15:19:27.235Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnpc.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2025-01-24T16:48:06.000Z","updated_at":"2025-10-07T00:12:16.000Z","dependencies_parsed_at":"2025-01-24T21:15:30.712Z","dependency_job_id":null,"html_url":"https://github.com/johnpc/subsyncarr","commit_stats":null,"previous_names":["johnpc/subsyncarr"],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/johnpc/subsyncarr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnpc%2Fsubsyncarr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnpc%2Fsubsyncarr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnpc%2Fsubsyncarr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnpc%2Fsubsyncarr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnpc","download_url":"https://codeload.github.com/johnpc/subsyncarr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnpc%2Fsubsyncarr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28767049,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-26T03:54:34.369Z","status":"ssl_error","status_checked_at":"2026-01-26T03:54:33.031Z","response_time":59,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-01-26T05:11:20.232Z","updated_at":"2026-05-17T01:11:26.048Z","avatar_url":"https://github.com/johnpc.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Subsyncarr\n\nAn 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).\n\n**Docker Hub:** [mrorbitman/subsyncarr](https://hub.docker.com/r/mrorbitman/subsyncarr)\n\n## Features\n\n### Core Functionality\n\n- **Automated Subtitle Synchronization** - Syncs subtitles for your entire media library or specific folders.\n- **Multiple Sync Engines** - Uses ffsubsync, autosubsync, and alass for maximum compatibility and success rate\n- **Scheduled Processing** - Runs on a configurable cron schedule (default: daily at midnight) and on container startup\n- **Parallel Processing** - Configure concurrent subtitle processing for faster library syncing\n- **Skip Already Synced Files** - Avoids re-processing files that already have synchronized subtitles or where an engine repeatedly fails.\n- **Processing History** - View past runs with detailed statistics, results, and logs\n- **Configuration Dashboard** - View current settings, monitored paths, and schedule status\n- **Configurable Timeouts** - Set per-engine timeout limits to prevent hung processes\n- **Log Management** - Configurable retention policies with automatic trimming and deletion\n- **Non Destructive** - Creates new files for each engine so no original files are altered. Allows easy switching between engines while watching content.\n\n## Quick Start\n\n### Using Docker Compose (Recommended)\n\n1. **Create a docker-compose.yaml file** with the following content:\n\n```yaml\nname: subsyncarr\n\nservices:\n  subsyncarr:\n    image: mrorbitman/subsyncarr:latest\n    container_name: subsyncarr\n    ports:\n      - '3000:3000' # Web UI\n    volumes:\n      # Mount your media directories\n      - /path/to/movies:/movies\n      - /path/to/tv:/tv\n      - /path/to/anime:/anime\n      - ./data:/app/data # Persist database across restarts\n    restart: unless-stopped\n    deploy:\n      resources:\n        limits:\n          memory: 768M # Hard limit\n        reservations:\n          memory: 128M # Minimum guaranteed memory\n    environment:\n      - TZ=Etc/UTC # Replace with your own timezone\n      - PUID=1000\n      - PGID=10\n      - CRON_SCHEDULE=0 0 * * * # Runs every day at midnight by default\n      - SCAN_PATHS=/movies,/tv # Comma-separated paths to scan\n      - EXCLUDE_PATHS=/movies/temp,/tv/downloads # Optional: exclude directories\n      - MAX_CONCURRENT_SYNC_TASKS=1 # Number of parallel processing tasks\n      - INCLUDE_ENGINES=ffsubsync,autosubsync,alass # Engines to use\n```\n\n2. **Update the configuration:**\n\n   - Replace `/path/to/movies`, `/path/to/tv`, etc. with your actual media paths\n   - Update `TZ` to your timezone (e.g., `America/New_York`, `Europe/London`)\n   - Update `PUID` and `PGID` to match your user (run `id` command to find these)\n   - Adjust `SCAN_PATHS` to match your mounted volumes\n\n3. **Start the container:**\n\n```bash\ndocker compose up -d\n```\n\n4. **Access the Web UI:**\n\nOpen your browser to [http://localhost:3000](http://localhost:3000) or whatever port you've mapped to inside docker.\n\n### Using Docker Run\n\n```bash\ndocker run -d \\\n  --name subsyncarr \\\n  -p 3000:3000 \\\n  -v /path/to/movies:/movies \\\n  -v /path/to/tv:/tv \\\n  -v ./data:/app/data \\\n  -e TZ=Etc/UTC \\\n  -e PUID=1000 \\\n  -e PGID=10 \\\n  -e CRON_SCHEDULE=\"0 0 * * *\" \\\n  -e SCAN_PATHS=/movies,/tv \\\n  -e MAX_CONCURRENT_SYNC_TASKS=1 \\\n  mrorbitman/subsyncarr:latest\n```\n\n## Configuration\n\n### Core Configuration\n\n| Variable                    | Default                       | Description                                                                      |\n| --------------------------- | ----------------------------- | -------------------------------------------------------------------------------- |\n| `SCAN_PATHS`                | `/scan_dir`                   | Comma-separated directories to scan for SRT files (must be mounted as volumes)   |\n| `EXCLUDE_PATHS`             | _(none)_                      | Comma-separated directories to exclude from scanning                             |\n| `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. |\n| `CRON_SCHEDULE`             | `0 0 * * *`                   | Cron expression for sync schedule (daily at midnight), or `disabled` to turn off |\n| `MAX_CONCURRENT_SYNC_TASKS` | `1`                           | Number of subtitle files to process in parallel (higher = faster but more CPU)   |\n| `INCLUDE_ENGINES`           | `ffsubsync,autosubsync,alass` | Which sync engines to use (comma-separated)                                      |\n| `SYNC_TIMEOUT`              | _(none)_                      | Timeout in seconds per sync operation (overrides SYNC_ENGINE_TIMEOUT_MS)         |\n| `SYNC_ENGINE_TIMEOUT_MS`    | `1800000`                     | Timeout for each sync engine in milliseconds (30 min default)                    |\n| `NODE_OPTIONS`             | `--max-old-space-size=512`    | Node.js options, used here to set memory limit (in MB)                           |\n| `WEB_PORT`                 | `3000`                        | Port for the web UI                                                              |\n| `WEB_HOST`                 | `127.0.0.1`                   | Host to bind the web UI to (`0.0.0.0` to expose externally)                     |\n| `TZ`                        | _(system)_                    | Timezone for logging and cron scheduling (e.g., `America/New_York`)              |\n| `PUID`                      | `1000`                        | User ID for file permissions (run `id -u` to find yours)                         |\n| `PGID`                      | `1000`                        | Group ID for file permissions (run `id -g` to find yours)                        |\n\n### Database \u0026 Log Configuration\n\n| Variable                           | Default                        | Description                                 |\n| ---------------------------------- | ------------------------------ | ------------------------------------------- |\n| `DB_PATH`                          | `/app/data/subsyncarr.db` | SQLite database location                    |\n| `LOG_BUFFER_SIZE`                  | `1000`                         | Ring buffer size for in-memory logs         |\n| `RETENTION_KEEP_RUNS_DAYS`         | `30`                           | Keep complete runs for N days               |\n| `RETENTION_TRIM_LOGS_DAYS`         | `7`                            | Trim logs after N days (keeps summary only) |\n| `RETENTION_MAX_LOG_SIZE`           | `10000`                        | Max size for trimmed logs in bytes          |\n| `RETENTION_CLEANUP_INTERVAL_HOURS` | `24`                           | How often to run cleanup (in hours)         |\n\n### Timeout Configuration\n\nThe `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.\n\nExample configuration:\n\n```yaml\nenvironment:\n  - SYNC_ENGINE_TIMEOUT_MS=3600000 # 60 minutes for large files\n```\n\n\n### Directory Structure\n\nYour media directory should be organized with video files and their corresponding subtitle files using matching names:\n\n```txt\n/movies\n├── Movie Title (2024).mkv\n├── Movie Title (2024).srt          # Will be synchronized\n├── Movie Title (2024).ffsubsync.srt # Generated output\n└── Another Movie.mp4\n    └── Another Movie.srt\n\n/tv\n├── Show Name/\n│   ├── Season 01/\n│   │   ├── Show.S01E01.mkv\n│   │   └── Show.S01E01.srt\n```\n\nThe app follows standard naming conventions compatible with Plex, Jellyfin, Emby, and Bazarr.\n\n## Web UI\n\nSubsyncarr Plus includes a comprehensive web-based monitoring interface accessible at `http://localhost:3000` after starting the container.\n\n### UI Features\n\n**Real-time Monitoring:**\n\n- Live progress bars showing current processing status\n- File-by-file status updates via WebSocket\n- Engine-level detail (see which sync engine is running)\n- Current and queued files display\n\n**Manual Control:**\n\n- **Start Full Run** - Process all configured directories immediately\n- **Scan Specific Path** - Process a custom directory on demand\n- **Stop Processing** - Cancel all remaining files in current run\n- **Skip File** - Cancel processing for individual files\n\n**File Management:**\n\n- View completed and skipped files\n- Clear processed files from the UI\n- Track file status (pending, processing, completed, skipped, error)\n- See matched video files for each subtitle\n\n**Processing History:**\n\n- Sortable run history table with timestamps\n- Per-run statistics (total, completed, skipped, failed counts)\n- Engine-level results summary with notation:\n  - **F** = ffsubsync result\n  - **Au** = autosubsync result\n  - **Al** = alass result\n- Duration tracking for each run\n- View detailed logs for any past run with copy-to-clipboard functionality\n\n**Configuration Dashboard:**\n\n- Display of monitored paths and excluded paths\n- Schedule status with next run time\n- Human-readable cron schedule translation\n\n### Database Persistence\n\nProcessing history is stored in SQLite and persists across container restarts. Ensure the data volume is mounted:\n\n```yaml\nvolumes:\n  - ./data:/app/data # Database and logs stored here\n```\n\n## Advanced Features\n\n### Auto-Skip on Repeated Failures\n\nThe 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`.\n\n### Memory Management\n\nOptimized for low-memory environments with:\n\n- Configurable memory limits (768MB default, 128MB minimum)\n- SQLite optimizations for low RAM usage\n- File-based logging with buffering to reduce memory pressure\n- Automatic database vacuuming and cleanup\n- Ring buffer for in-memory logs\n\n### Log Retention \u0026 Cleanup\n\nAutomatic cleanup keeps your database size manageable:\n\n- Complete runs retained for 30 days (configurable)\n- Logs trimmed after 7 days, keeping only summaries\n- Runs beyond retention period are automatically deleted\n- Cleanup runs every 24 hours (configurable)\n\n## Troubleshooting\n\n### View Container Logs\n\n```bash\ndocker logs -f subsyncarr\n```\n\n### Check Web UI Logs\n\nDetailed processing logs are available in the Web UI under \"Processing History\" - click on any run to view full logs.\n\n### Permission Issues\n\nIf you encounter permission errors, ensure `PUID` and `PGID` match your host user:\n\n```bash\nid -u  # Get your user ID\nid -g  # Get your group ID\n```\n\nThen update your docker-compose.yaml with these values.\n\n\u003e **Note:** Do not use the `user:` directive in docker-compose or `--user` in docker run. The container must start as root so the entrypoint can configure file permissions using `PUID`/`PGID`, then drops to the unprivileged user automatically via `gosu`.\n\n### Memory Issues\n\nIf the container is being killed due to OOM (Out Of Memory):\n\n1. Reduce `MAX_CONCURRENT_SYNC_TASKS` to 1\n2. Increase memory limit in `NODE_OPTIONS` (e.g., `--max-old-space-size=1024`)\n3. Increase memory limit in docker-compose.yaml\n4. Reduce `SYNC_ENGINE_TIMEOUT_MS` for faster timeouts\n4. Exclude large files or problematic directories with `EXCLUDE_PATHS`\n\n### Files Not Being Processed\n\nCheck that:\n\n1. Your subtitle files are named to match video files (e.g., `movie.mkv` and `movie.srt`)\n2. `SCAN_PATHS` matches your mounted volumes\n3. Files haven't already been synced (check for `.ffsubsync.srt` files)\n4. Files aren't being auto-skipped due to repeated failures (check skip status in Web UI)\n\n## Docker Hub\n\nPull the latest image:\n\n```bash\ndocker pull mrorbitman/subsyncarr:latest\n```\n\n**Docker Hub Repository:** [mrorbitman/subsyncarr](https://hub.docker.com/r/mrorbitman/subsyncarr)\n\n## Contributing\n\nIssues and pull requests are welcome! Please report bugs or suggest features via GitHub Issues.\n\n## License\n\nSee LICENSE file for details.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnpc%2Fsubsyncarr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnpc%2Fsubsyncarr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnpc%2Fsubsyncarr/lists"}