use std::{collections::BTreeMap, fmt::Display, time::Duration}; use crate::util; use cached::proc_macro::io_cached; use deepl::DeepLApi; use deepl::ModelType; use relm4::prelude::*; use crate::{ settings::Settings, subtitles::{SUBTITLE_TRACKS, StreamIndex}, translation::TRANSLATIONS, }; pub struct DeeplTranslator { stream_ix: Option, next_cues_to_translate: BTreeMap, } #[derive(Debug)] pub enum DeeplTranslatorMsg { SelectTrack(Option), // this is only used to drive the async translation DoTranslate, } impl AsyncComponent for DeeplTranslator { type Init = (); type Input = DeeplTranslatorMsg; type Output = (); type CommandOutput = (); type Root = (); type Widgets = (); async fn init( _init: Self::Init, _root: Self::Root, sender: relm4::AsyncComponentSender, ) -> AsyncComponentParts { let model = Self { stream_ix: None, next_cues_to_translate: BTreeMap::new(), }; sender.input(DeeplTranslatorMsg::DoTranslate); AsyncComponentParts { model, widgets: () } } async fn update( &mut self, message: Self::Input, sender: AsyncComponentSender, _root: &Self::Root, ) { match message { DeeplTranslatorMsg::SelectTrack(stream_ix) => { self.stream_ix = stream_ix; } DeeplTranslatorMsg::DoTranslate => self.do_translate(sender).await, } } fn init_root() -> Self::Root { () } } impl DeeplTranslator { async fn do_translate(&mut self, sender: AsyncComponentSender) { if let Some(stream_ix) = self.stream_ix { let next_cue_to_translate = self.next_cues_to_translate.entry(stream_ix).or_insert(0); while let Some(cue) = { SUBTITLE_TRACKS .read() .get(&stream_ix) .and_then(|stream| stream.texts.get(*next_cue_to_translate)) .cloned() } { match translate_text(TranslateRequest { input: cue.clone(), source_lang: None, // TODO target_lang: deepl::Lang::EN, // TODO }) .await { Ok(translated) => { TRANSLATIONS .write() .entry(stream_ix) .or_insert(Vec::new()) .push(translated); *next_cue_to_translate = *next_cue_to_translate + 1; } Err(e) => { log::error!("error fetching translation: {}", e) } }; } } // check every second for new cues to be translated relm4::tokio::time::sleep(Duration::from_secs(1)).await; sender.input(DeeplTranslatorMsg::DoTranslate); } } #[derive(Clone)] struct TranslateRequest { input: String, source_lang: Option, target_lang: deepl::Lang, } impl Display for TranslateRequest { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, "{:?}-{}:{}", self.source_lang, self.target_lang, self.input ) } } #[io_cached( disk = true, create = r#"{ util::make_cache("deepl_translations") }"#, map_error = r#"|err| err"# )] async fn translate_text(req: TranslateRequest) -> anyhow::Result { let deepl = DeepLApi::with(&Settings::default().deepl_api_key()).new(); let mut requester = deepl.translate_text(req.input, req.target_lang); requester.model_type(ModelType::PreferQualityOptimized); if let Some(source_lang) = req.source_lang { requester.source_lang(source_lang); } let translated = requester.await?.translations.pop().unwrap().text; // try to respect deepl's rate-limit relm4::tokio::time::sleep(Duration::from_millis(500)).await; Ok(translated) }