about summary refs log tree commit diff
path: root/src/app.rs
diff options
context:
space:
mode:
authorMalte Voos <git@mal.tc>2026-01-01 19:26:01 +0100
committerMalte Voos <git@mal.tc>2026-01-04 00:38:38 +0100
commitc8b942b1fbe8fdab1db0e0f56d3ed86a7486b578 (patch)
treecf344838c96ad9bd7bd97d0216c43d6a858f4a60 /src/app.rs
parent80a1c8234fc5b6f56bd1f2df4e6118e57631f523 (diff)
downloadlleap-c8b942b1fbe8fdab1db0e0f56d3ed86a7486b578.tar.gz
lleap-c8b942b1fbe8fdab1db0e0f56d3ed86a7486b578.zip
cache extracted subtitles & deepl translations HEAD main
Diffstat (limited to 'src/app.rs')
-rw-r--r--src/app.rs91
1 files changed, 72 insertions, 19 deletions
diff --git a/src/app.rs b/src/app.rs
index 49efd49..35a501e 100644
--- a/src/app.rs
+++ b/src/app.rs
@@ -1,4 +1,5 @@
 use adw::prelude::*;
+use cached::{DiskCache, IOCached};
 use relm4::{WorkerController, prelude::*};
 
 use crate::{
@@ -13,13 +14,15 @@ use crate::{
     subtitle_view::{SubtitleView, SubtitleViewMsg, SubtitleViewOutput},
     subtitles::{
         MetadataCollection, SUBTITLE_TRACKS, StreamIndex, SubtitleCue, SubtitleTrack,
-        TrackMetadata,
-        extraction::{SubtitleExtractor, SubtitleExtractorMsg, SubtitleExtractorOutput},
+        SubtitleTrackCollection, TrackMetadata,
+        extraction::{
+            ExtractionArgs, SubtitleExtractor, SubtitleExtractorMsg, SubtitleExtractorOutput,
+        },
         state::SubtitleState,
     },
     transcript::{Transcript, TranscriptMsg, TranscriptOutput},
-    translation::{DeeplTranslator, deepl::DeeplTranslatorMsg},
-    util::Tracker,
+    translation::{DeeplTranslator, TRANSLATIONS, deepl::DeeplTranslatorMsg},
+    util::{self, Tracker},
 };
 
 pub struct App {
@@ -34,6 +37,9 @@ pub struct App {
     open_url_dialog: Controller<OpenDialog>,
     subtitle_selection_dialog: Option<Controller<SubtitleSelectionDialog>>,
 
+    subtitle_extraction_args: Option<ExtractionArgs>,
+    subtitle_cache: DiskCache<ExtractionArgs, SubtitleTrackCollection>,
+
     primary_subtitle_state: SubtitleState,
     secondary_subtitle_state: SubtitleState,
 
@@ -153,6 +159,8 @@ impl SimpleComponent for App {
             },
         );
 
+        let subtitle_cache = util::make_cache("subtitles");
+
         let model = Self {
             root: root.clone(),
             player,
@@ -165,6 +173,9 @@ impl SimpleComponent for App {
             open_url_dialog,
             subtitle_selection_dialog: None,
 
+            subtitle_extraction_args: None,
+            subtitle_cache,
+
             primary_subtitle_state: SubtitleState::default(),
             secondary_subtitle_state: SubtitleState::default(),
 
@@ -193,6 +204,14 @@ impl SimpleComponent for App {
             }
             AppMsg::SubtitleExtractionComplete => {
                 log::info!("Subtitle extraction complete");
+                if let Some(ref args) = self.subtitle_extraction_args {
+                    if let Err(e) = self
+                        .subtitle_cache
+                        .cache_set(args.clone(), SUBTITLE_TRACKS.read().clone())
+                    {
+                        log::error!("error caching extracted subtitles: {}", e);
+                    }
+                }
             }
             AppMsg::ApplySubtitleSettings(settings) => {
                 self.primary_subtitle_state
@@ -249,6 +268,8 @@ impl SimpleComponent for App {
                 mut metadata,
                 whisper_stream_index,
             } => {
+                self.reset();
+
                 if let Some(ix) = whisper_stream_index {
                     let audio_metadata = metadata.audio.get(&ix).unwrap();
                     let subs_metadata = TrackMetadata {
@@ -263,17 +284,33 @@ impl SimpleComponent for App {
                     metadata.subtitles.insert(ix, subs_metadata);
                 }
 
-                self.player
-                    .sender()
-                    .send(PlayerMsg::SetUrl(url.clone()))
-                    .unwrap();
-                self.extractor
-                    .sender()
-                    .send(SubtitleExtractorMsg::ExtractFromUrl {
-                        url,
-                        whisper_stream_index,
-                    })
-                    .unwrap();
+                let extraction_args = ExtractionArgs {
+                    url: url.clone(),
+                    whisper_stream_index,
+                };
+
+                match self.subtitle_cache.cache_get(&extraction_args) {
+                    Ok(Some(track_collection)) => {
+                        log::debug!("subtitle cache hit");
+                        *(SUBTITLE_TRACKS.write()) = track_collection;
+                    }
+                    Ok(None) => {
+                        log::debug!("subtitle cache miss");
+                        self.extractor
+                            .sender()
+                            .send(SubtitleExtractorMsg::Extract(extraction_args.clone()))
+                            .unwrap();
+                    }
+                    Err(e) => {
+                        log::error!("error querying subtitle cache: {}", e);
+                        self.extractor
+                            .sender()
+                            .send(SubtitleExtractorMsg::Extract(extraction_args.clone()))
+                            .unwrap();
+                    }
+                }
+
+                self.subtitle_extraction_args = Some(extraction_args);
 
                 let subtitle_selection_dialog = SubtitleSelectionDialog::builder()
                     .launch((self.root.clone().into(), metadata))
@@ -283,12 +320,28 @@ impl SimpleComponent for App {
                         }
                     });
                 self.subtitle_selection_dialog = Some(subtitle_selection_dialog);
+
+                self.player.sender().send(PlayerMsg::SetUrl(url)).unwrap();
             }
         }
     }
 }
 
 impl App {
+    fn reset(&mut self) {
+        SUBTITLE_TRACKS.write().clear();
+        TRANSLATIONS.write().clear();
+
+        self.subtitle_selection_dialog = None;
+        self.subtitle_extraction_args = None;
+        self.primary_subtitle_state = SubtitleState::default();
+        self.secondary_subtitle_state = SubtitleState::default();
+        self.autopaused = false;
+        self.hovering_primary_cue = false;
+
+        // TODO also clear transcript?
+    }
+
     fn update_subtitle_states(&mut self, position: gst::ClockTime) {
         self.update_primary_subtitle_state(position);
         self.update_secondary_subtitle_state(position);
@@ -356,10 +409,10 @@ impl App {
 fn update_subtitle_state(state: &mut SubtitleState, position: gst::ClockTime) {
     if let Some(stream_ix) = state.stream_ix {
         let lock = SUBTITLE_TRACKS.read();
-        let track = lock.get(&stream_ix).unwrap();
-
-        update_last_time_ix(&track.start_times, &mut state.last_started_cue_ix, position);
-        update_last_time_ix(&track.end_times, &mut state.last_ended_cue_ix, position);
+        if let Some(track) = lock.get(&stream_ix) {
+            update_last_time_ix(&track.start_times, &mut state.last_started_cue_ix, position);
+            update_last_time_ix(&track.end_times, &mut state.last_ended_cue_ix, position);
+        }
     }
 }