Please check the build logs for more information.
See Builds for ideas on how to fix a failed build, or Metadata for how to configure docs.rs builds.
If you believe this is docs.rs' fault, open an issue.
psyche-subtitle-toolkit
Extract, translate, and mux ASS, SRT, WebVTT, and PGS (bitmap) subtitles in MKV files. Usable as a standalone CLI or Rust library.
No cloud required. No telemetry. Every translation provider is opt-in.
Features
- Extract ASS, SRT, WebVTT, and PGS subtitle tracks from MKV files via mkvmerge/mkvextract
- Translate subtitle dialogue through 9 pluggable providers
- Protect inline ASS, SRT, and WebVTT formatting during translation
- Automatic chunking by each provider's item and UTF-8 byte limits
- Concurrent chunk translation with configurable parallelism
- Retry transient failures with
Retry-After, exponential backoff, and jitter; HTTP 503 retries for up to 2 hours with a 60-second delay cap - Validate the candidate MKV and preserve track order, name, and flags before replacement
- Process single files or entire directories
- Translate standalone
.ass,.srt, and.vttfiles without MKV (viatranslate-subtitle;translate-assremains an alias) - OCR PGS bitmap subtitles via pinned, SHA-256-verified PaddleOCR PP-OCRv5 models
- Resume interrupted translations with a versioned input/configuration manifest
Supported Providers
| Provider | Flag | Auth | --parallel |
Notes |
|---|---|---|---|---|
| Ollama | --provider ollama |
None | 3 | Default. Any Ollama model. |
| Anthropic | --provider anthropic |
environment or --api-key |
2 | Messages API. Custom endpoint via --anthropic-url. |
| OpenAI | --provider openai |
environment or --api-key |
2 | Compatible with OpenAI-style endpoints. |
| OpenCode Zen | --provider opencode-zen |
environment or --api-key |
2 | Native Chat Completions integration; default https://opencode.ai/zen. |
| OpenCode Go | --provider opencode-go |
environment or --api-key |
2 | Native Chat Completions integration; default https://opencode.ai/zen/go. |
| OpenRouter | --provider openrouter |
environment or --api-key |
2 | Unified hosted-model API. |
| DeepL | --provider deepl |
environment or --api-key |
5 | Free or pro endpoint. |
| Google Translate | --provider google |
environment or --api-key |
10 | Translation v2 API. |
| Gemini | --provider gemini |
environment or --api-key |
2 | generateContent API. |
The --parallel column shows recommended concurrency for each provider.
For credentials, prefer PSYCHE_SUBTITLE_API_KEY or the provider-specific variable: OPENAI_API_KEY, OPENCODE_ZEN_API_KEY, OPENCODE_GO_API_KEY, OPENROUTER_API_KEY, ANTHROPIC_API_KEY, DEEPL_API_KEY, GOOGLE_API_KEY, or GEMINI_API_KEY. --api-key remains available for compatibility but can be visible in shell history and process listings.
OpenCode Zen and Go expose multiple wire protocols. These providers support the models advertised at /v1/chat/completions; models exposed only through /responses or /messages are not accepted by this adapter yet.
Installation
Or build from source:
Requirements
mkvmergeandmkvextractfrom MKVToolNix must be in yourPATH.
CLI Usage
Inspect MKV tracks
Output shows all tracks with a * marking the track that translation will auto-select:
* track 2: type=subtitles codec=SubStationAlpha language=eng name=HIDIVE_English
track 3: type=subtitles codec=SubStationAlpha language=jpn name=
Translate subtitles
# Ollama (default, local)
# OpenAI (set OPENAI_API_KEY or PSYCHE_SUBTITLE_API_KEY first)
# OpenCode Zen (set OPENCODE_ZEN_API_KEY or PSYCHE_SUBTITLE_API_KEY first)
# OpenCode Go (set OPENCODE_GO_API_KEY or PSYCHE_SUBTITLE_API_KEY first)
# DeepL (free tier)
# Google Translate
# Gemini
# OpenRouter (free model)
Translate standalone subtitle files
# ASS file
# SRT file (auto-detected by extension or content)
# WebVTT file (auto-detected by extension or WEBVTT header)
Resume interrupted translations
If a batch run is interrupted (crash, network failure), restart with --resume to skip already-translated files:
# First run — interrupted at file 15/20
# Restart — skips files 1-14, continues from 15
Progress is saved to a versioned JSON manifest and auto-deleted when all files complete. It records the target, provider/model identity, track choice, canonical path, size, and modification time. Each file is marked pending before translation; if the MKV changes before the completion checkpoint is written, resume stops for manual inspection instead of risking a second remux. A stale manifest from a different run is rejected instead of silently mixing outputs.
Full options
-i, --input <INPUT> MKV file or directory containing MKV files
--to <TO> Target language code (e.g. pt-BR, en, ja)
--provider <PROVIDER> Translation backend [default: ollama]
--track <TRACK> Specific subtitle track ID to translate
--model <MODEL> Model name [default: llama3.1]
--ollama-url <URL> Ollama base URL [default: http://localhost:11434]
--openai-url <URL> OpenAI base URL [default: https://api.openai.com]
--opencode-zen-url <URL> OpenCode Zen base URL [default: https://opencode.ai/zen]
--opencode-go-url <URL> OpenCode Go base URL [default: https://opencode.ai/zen/go]
--anthropic-url <URL> Anthropic base URL [default: https://api.anthropic.com]
--api-key <KEY> API key; environment variables are preferred
--deepl-url <URL> DeepL base URL [default: https://api-free.deepl.com]
--keep-temp Preserve extracted and translated subtitle files
--dry-run Show what would be translated without modifying files
--resume Save progress and skip already-translated files on restart
--parallel <N> Max concurrent chunk translations [default: 1]
Library Usage
Add to your Cargo.toml:
[]
= { = "../psyche-subtitle-toolkit" }
Translate an MKV file
use Arc;
use ;
# async
Translate ASS content directly
use Arc;
use ;
# async
Implement a custom provider
use async_trait;
use ;
How It Works
- Inspect --
mkvmerge -Jidentifies tracks and selects the ASS, SRT, or VTT subtitle - Extract --
mkvextract trackspulls the ASS file to a temp directory - Parse -- The format parser preserves timing, styles, identifiers, and VTT metadata blocks
- Protect tags -- Inline formatting is replaced by validated opaque tokens
- Chunk -- Cues are split according to provider item and byte limits
- Translate -- Each chunk is sent to the provider as
<N> textnumbered lines (concurrent if--parallel > 1) - Retry -- Only transient failures and malformed model output are retried; permanent 4xx errors fail immediately, while HTTP 503 keeps retrying for up to 2 hours with at most 60 seconds between attempts
- Apply -- Translated text is mapped back to cues by ID
- Restore tags -- Protected formatting is checked for presence/order and restored
- Mux -- A temporary MKV is inspected before a rollback-capable replacement
Testing
Provider tests use wiremock to mock HTTP endpoints -- no real API calls.
Release Notes
v0.4.0
- First-class OpenCode providers —
--provider opencode-zenand--provider opencode-gowith dedicated endpoints (--opencode-zen-url,--opencode-go-url; defaults tohttps://opencode.ai/zenandhttps://opencode.ai/zen/go) - Retry hardening — transient/permanent error classification,
Retry-Aftersupport, exponential backoff with jitter and a 60-second delay cap; HTTP 503 retries for up to a 2-hour budget - Safer MKV muxing — the candidate MKV is inspected before replacement, preserving track order, name, and flags; rollback-capable swap
- Versioned resume manifests — record target language, provider/model identity, track choice, canonical path, size, and mtime; modified inputs stop resume instead of risking a second remux, and stale manifests are rejected
- Per-provider chunk limits — chunks sized by each provider's item count and UTF-8 byte budget instead of a fixed 200 lines
- Environment-based API keys — prefer
PSYCHE_SUBTITLE_API_KEYor provider-specific variables (OPENAI_API_KEY,OPENROUTER_API_KEY,ANTHROPIC_API_KEY,DEEPL_API_KEY,GOOGLE_API_KEY,GEMINI_API_KEY,OPENCODE_ZEN_API_KEY,OPENCODE_GO_API_KEY);--api-keyremains available - Richer provider errors — errors retain HTTP status, retryability, and
Retry-After; transport failures never expose credentials embedded in URLs - PGS OCR hardening — pinned, SHA-256-verified PaddleOCR PP-OCRv5 models with bounded rendering and cache hashing
translate-assrenamed totranslate-subtitle(the old name still works as an alias)
v0.3.0
- PGS OCR — bitmap subtitle recognition via PaddleOCR PP-OCRv5 (auto-downloads models)
- PGS track auto-detection in MKV files
- New dependencies:
pgs-rs,ocr-rs,image,imageproc
v0.2.0
- SRT support — parse, translate, and render SubRip subtitles
- WebVTT support — parse, translate, and render WebVTT subtitles
- Anthropic provider — Messages API (
/v1/messages) with custom endpoint support - Remove
--source-lang— all providers auto-detect source language, making the flag redundant - Format auto-detection —
translate-assCLI auto-detects ASS/SRT/VTT by extension or content - MKV format priority — ASS > SRT > VTT when multiple subtitle tracks exist
- Refactored pipeline —
translate_document()helper shared by ASS, SRT, and VTT pipelines
v0.1.0
Initial release:
- 7 translation providers (Ollama, OpenAI, OpenRouter, DeepL, Google, Gemini)
--parallel Nfor concurrent chunk translation--resumefor interrupted batch recovery--dry-runto preview without modifying files- Retry with exponential backoff on HTTP and malformed output errors
- DeepL/Google batch mode (per-line array elements)
- 200 lines per chunk
- Progress output to stderr
translate-asssubcommand for standalone subtitle files