shinkai-translator 0.1.3

CLI tool for translating video subtitles with LLMs through OpenAI-compatible APIs, with native PGS OCR
pub mod config;
pub mod domain;
pub mod error;
pub mod formats;
pub mod media;
pub mod ocr;
pub mod pipeline;
pub mod providers;

use std::path::Path;
use std::sync::Arc;

use tokio::fs;

pub use config::{AppConfig, LoadedConfig, ToolConfig, TranslationDefaults, default_config_path};
pub use domain::{
    AssClassificationPolicy, CueClassification, CueDisposition, CueKind, ProviderConfig,
    SubtitleClassificationEntry, SubtitleClassificationReport, SubtitleClassificationSummary,
    SubtitleCue, SubtitleDocument, SubtitleFormat, ThinkingMode, TranslationOptions,
    TranslationResult,
};
pub use error::TranslatorError;
pub use formats::{classify_document, parse_subtitle, render_subtitle};
pub use media::{
    ExternalToolConfig, MediaInputKind, PgsExtractionResult, SelectedSubtitleStream,
    StreamDisposition, VideoSubtitleJob, count_subtitle_streams, detect_input_kind,
    extract_pgs_stream, is_supported_input_path, mux_translated_subtitle, prepare_video_subtitle_job,
};
pub use ocr::{
    OcrDebugResult, PgsOcrConfig, PgsOcrLanguage, cleanup_fragmentary_subtitle_document,
    debug_pgs_ocr, ocr_pgs_file_to_srt,
};
pub use providers::{
    OpenAiCompatibleProvider, ProviderCapabilities, TranslatedItem, TranslationBatch,
    TranslationBatchItem, TranslationBatchOutput, TranslationProvider,
};

#[derive(Clone)]
pub struct Translator {
    provider: Arc<dyn TranslationProvider>,
}

impl Translator {
    pub fn new(provider: Arc<dyn TranslationProvider>) -> Self {
        Self { provider }
    }

    pub async fn translate_document(
        &self,
        document: &SubtitleDocument,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        pipeline::translate::translate_document(self.provider.as_ref(), document, options).await
    }

    pub async fn translate_str(
        &self,
        source: &str,
        format: SubtitleFormat,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        self.translate_source(source, Some(format), None, options)
            .await
    }

    pub async fn translate_source(
        &self,
        source: &str,
        format_hint: Option<SubtitleFormat>,
        source_name: Option<&Path>,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        let document = formats::parse_subtitle(source, format_hint, source_name)?;
        self.translate_document(&document, options).await
    }

    pub async fn translate_file_path(
        &self,
        path: impl AsRef<Path>,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        let path = path.as_ref();
        let source = fs::read_to_string(path).await?;
        let format_hint = SubtitleFormat::detect_from_path(path);
        self.translate_source(&source, format_hint, Some(path), options)
            .await
    }
}

#[cfg(feature = "blocking")]
pub struct BlockingTranslator {
    translator: Translator,
    runtime: tokio::runtime::Runtime,
}

#[cfg(feature = "blocking")]
impl BlockingTranslator {
    pub fn new(translator: Translator) -> Result<Self, TranslatorError> {
        let runtime = tokio::runtime::Builder::new_multi_thread()
            .enable_all()
            .build()
            .map_err(|error| TranslatorError::RuntimeInit(error.to_string()))?;

        Ok(Self {
            translator,
            runtime,
        })
    }

    pub fn translate_document(
        &self,
        document: &SubtitleDocument,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        self.runtime
            .block_on(self.translator.translate_document(document, options))
    }

    pub fn translate_str(
        &self,
        source: &str,
        format: SubtitleFormat,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        self.runtime
            .block_on(self.translator.translate_str(source, format, options))
    }

    pub fn translate_file_path(
        &self,
        path: impl AsRef<Path>,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        self.runtime
            .block_on(self.translator.translate_file_path(path, options))
    }

    pub fn translate_source(
        &self,
        source: &str,
        format_hint: Option<SubtitleFormat>,
        source_name: Option<&Path>,
        options: &TranslationOptions,
    ) -> Result<TranslationResult, TranslatorError> {
        self.runtime
            .block_on(self.translator.translate_source(source, format_hint, source_name, options))
    }
}