1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
//! # psyche-subtitle-toolkit
//!
//! Extract, translate, and mux ASS, SRT, WebVTT, and PGS subtitles in MKV files.
//! Usable as a standalone CLI or library for media server integration.
//!
//! No cloud required. No telemetry. Every translation provider is opt-in.
//!
//! ## Supported providers
//!
//! | Provider | Struct | Endpoint |
//! |----------|--------|----------|
//! | [Ollama](https://ollama.com) | [`OllamaTranslator`] | `/api/generate` |
//! | [OpenAI](https://platform.openai.com) | [`OpenAiTranslator`] | `/v1/chat/completions` |
//! | [OpenCode Zen](https://opencode.ai/docs/zen/) | [`OpenCodeZenTranslator`] | `/v1/chat/completions` |
//! | [OpenCode Go](https://opencode.ai/docs/go/) | [`OpenCodeGoTranslator`] | `/v1/chat/completions` |
//! | [OpenRouter](https://openrouter.ai) | [`OpenRouterTranslator`] | `/api/v1/chat/completions` |
//! | [Anthropic](https://docs.anthropic.com) | [`AnthropicTranslator`] | `/v1/messages` |
//! | [DeepL](https://www.deepl.com) | [`DeepLTranslator`] | `/v2/translate` |
//! | [Google Translate](https://cloud.google.com/translate) | [`GoogleTranslator`] | `/language/translate/v2` |
//! | [Gemini](https://ai.google.dev/gemini-api/docs) | [`GeminiTranslator`] | `v1beta/models/{model}:generateContent` |
//!
//! ## Quick start (library)
//!
//! Translate an MKV file in-place:
//!
//! ```no_run
//! use std::sync::Arc;
//! use psyche_subtitle_toolkit::{translate_mkv, TranslateMkvOptions, OllamaTranslator, Translator};
//!
//! # async fn example() -> psyche_subtitle_toolkit::Result<()> {
//! let translator: Arc<dyn Translator> = Arc::new(OllamaTranslator::new("llama3.1")?);
//! translate_mkv(
//! TranslateMkvOptions {
//! input: "/media/anime/episode.mkv".into(),
//! target_language: "pt-BR".into(),
//! track_id: None,
//! keep_temp: false,
//! dry_run: false,
//! resume: false,
//! max_concurrent: 1,
//! },
//! translator,
//! ).await?;
//! # Ok(())
//! # }
//! ```
//!
//! Translate ASS content directly (no MKV I/O):
//!
//! ```no_run
//! use std::sync::Arc;
//! use psyche_subtitle_toolkit::{translate_ass, AssSubtitle, OllamaTranslator, Translator};
//!
//! # async fn example() -> psyche_subtitle_toolkit::Result<()> {
//! let ass = AssSubtitle::parse(&std::fs::read_to_string("source.ass")?)?;
//! let translator: Arc<dyn Translator> = Arc::new(OllamaTranslator::new("llama3.1")?);
//! let translated = translate_ass(ass, "pt-BR", 1, translator).await?;
//! std::fs::write("translated.ass", translated.render())?;
//! # Ok(())
//! # }
//! ```
//!
//! Implement a custom provider:
//!
//! ```
//! use async_trait::async_trait;
//! use psyche_subtitle_toolkit::{Translator, TranslationRequest, Result};
//!
//! struct MyTranslator;
//!
//! #[async_trait]
//! impl Translator for MyTranslator {
//! async fn translate(&self, request: TranslationRequest<'_>) -> Result<String> {
//! // Your translation logic here.
//! // request.source_text is numbered: "<1> hello\n<2> world"
//! // Return translated text in the same format.
//! Ok(request.source_text.to_string())
//! }
//! }
//! ```
//!
//! ## Pipeline overview
//!
//! 1. **Inspect** — `mkvmerge -J` identifies and selects a supported subtitle
//! 2. **Extract** — `mkvextract tracks` extracts the selected subtitle
//! 3. **Parse** — format-specific parsers preserve timing, styles, and metadata blocks
//! 4. **Protect tags** — inline ASS/VTT/SRT formatting becomes validated tokens
//! 5. **Chunk** — cues are split according to provider item and byte limits
//! 6. **Translate** — Each chunk sent to the provider (concurrent if `max_concurrent > 1`)
//! 7. **Retry** — transient failures honor `Retry-After` and use jittered backoff
//! 8. **Apply** — Translated text mapped back to cues by ID
//! 9. **Restore tags** — protected inline spans are validated and restored
//! 10. **Mux** — a validated candidate preserves track order and metadata before commit
/// Error types for the subtitle toolkit.
/// MKV container inspection and manipulation (mkvmerge/mkvextract wrappers).
/// OCR pipeline for PGS (bitmap) subtitles via PaddleOCR.
/// Translation pipeline: MKV full-pipeline and subtitle-only translation.
/// Subtitle parsing, chunking, and tag manipulation.
/// Pluggable translation providers and the [`Translator`] trait.
// Error types
pub use ;
// OCR
pub use ;
// MKV inspection
pub use ;
// Pipeline
pub use ;
// Subtitle model
pub use AssSubtitle;
pub use ;
pub use SrtSubtitle;
pub use ;
pub use VttSubtitle;
// Translation providers
pub use AnthropicTranslator;
pub use DeepLTranslator;
pub use GeminiTranslator;
pub use GoogleTranslator;
pub use OllamaTranslator;
pub use OpenAiTranslator;
pub use ;
pub use OpenRouterTranslator;
pub use ;