From 88d8bad2c4bb4fe2e299cdaf757c8c03eaa5c32a Mon Sep 17 00:00:00 2001 From: Sven Assmann Date: Mon, 28 Mar 2022 16:39:33 +0200 Subject: [PATCH] feat(#36): Add framerate cli option - retyping of u128 to Timecode not everywhere fully clean - impl thread safe frame dropping strategy with comparing image hashes - keeping a linked list of `FrameEssences` --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/capture/frame_comparator.rs | 45 ++++++++++++++++ src/capture/frame_essence.rs | 24 +++++++++ src/capture/mod.rs | 4 ++ src/capture/processor.rs | 96 +++++++-------------------------- src/capture/timecode.rs | 2 +- src/generators/gif.rs | 2 +- src/main.rs | 1 + 9 files changed, 96 insertions(+), 82 deletions(-) create mode 100644 src/capture/frame_comparator.rs create mode 100644 src/capture/frame_essence.rs diff --git a/Cargo.lock b/Cargo.lock index d1a60d5..bc01293 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -813,7 +813,7 @@ dependencies = [ [[package]] name = "t-rec" -version = "0.7.3" +version = "0.7.3-alpha.1" dependencies = [ "anyhow", "blockhash", diff --git a/Cargo.toml b/Cargo.toml index f6164c1..91e42b8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "t-rec" -version = "0.7.3" +version = "0.7.3-alpha.1" authors = ["Sven Assmann "] edition = "2018" license = "GPL-3.0-only" diff --git a/src/capture/frame_comparator.rs b/src/capture/frame_comparator.rs new file mode 100644 index 0000000..d15ebba --- /dev/null +++ b/src/capture/frame_comparator.rs @@ -0,0 +1,45 @@ +use std::collections::LinkedList; + +use crate::capture::{FrameDropStrategy, FrameEssence}; + +pub struct FrameComparator { + last_frames: LinkedList, + strategy: FrameDropStrategy, +} + +impl FrameComparator { + pub fn new(strategy: FrameDropStrategy) -> Self { + Self { + last_frames: LinkedList::new(), + strategy, + } + } +} + +impl FrameComparator { + pub fn should_drop_frame(&mut self, frame_essence: FrameEssence) -> bool { + match self.strategy { + FrameDropStrategy::DoNotDropAny => false, + FrameDropStrategy::DropIdenticalFrames => { + if let Some(FrameEssence { when, what }) = self.last_frames.pop_back() { + if frame_essence.when > when && what == frame_essence.what { + // so the last frame and this one is the same... so let's drop it.. + // but add the current frame + self.last_frames.push_back(frame_essence); + true + } else { + let previous_should_drop_frame = self.should_drop_frame(frame_essence); + // restore the popped frame.. + self.last_frames.push_back(FrameEssence { when, what }); + + previous_should_drop_frame + } + } else { + self.last_frames.push_back(frame_essence); + + false + } + } + } + } +} diff --git a/src/capture/frame_essence.rs b/src/capture/frame_essence.rs new file mode 100644 index 0000000..dc9d25e --- /dev/null +++ b/src/capture/frame_essence.rs @@ -0,0 +1,24 @@ +use blockhash::{blockhash256, Blockhash256}; + +use crate::capture::{Frame, Timecode}; + +#[derive(Eq, PartialEq, Clone)] +pub enum FrameDropStrategy { + DoNotDropAny, + DropIdenticalFrames, +} + +#[derive(Eq, PartialOrd, PartialEq, Clone)] +pub struct FrameEssence { + pub(crate) when: Timecode, + pub(crate) what: Blockhash256, +} + +impl FrameEssence { + pub fn new(frame: &Frame, timecode: &Timecode) -> Self { + Self { + when: timecode.clone(), + what: blockhash256(frame), + } + } +} diff --git a/src/capture/mod.rs b/src/capture/mod.rs index 45765bc..824bdc0 100644 --- a/src/capture/mod.rs +++ b/src/capture/mod.rs @@ -1,9 +1,13 @@ mod frame; +mod frame_comparator; +mod frame_essence; mod framerate; mod processor; mod timecode; pub use frame::*; +pub use frame_comparator::*; +pub use frame_essence::*; pub use framerate::*; pub use processor::*; pub use timecode::*; diff --git a/src/capture/processor.rs b/src/capture/processor.rs index 4d0fa18..a4e0330 100644 --- a/src/capture/processor.rs +++ b/src/capture/processor.rs @@ -1,4 +1,3 @@ -use blockhash::{blockhash256, Blockhash256}; use crossbeam_channel::Receiver; use rayon::ThreadPoolBuilder; use std::borrow::Borrow; @@ -7,39 +6,12 @@ use std::sync::{Arc, Mutex}; use std::time::{Duration, Instant}; use tempfile::TempDir; +use crate::capture::frame_comparator::FrameComparator; +use crate::capture::frame_essence::{FrameDropStrategy, FrameEssence}; use crate::capture::{Framerate, Timecode}; use crate::utils::file_name_for; use crate::{Frame, PlatformApi, Result, WindowId}; -#[derive(Eq, PartialEq, Clone)] -pub enum FrameDropStrategy { - DoNotDropAny, - DropIdenticalFrames, -} - -#[derive(Clone)] -struct FrameComparator { - last_frames: Vec<(Timecode, Timecode, Blockhash256)>, - _strategy: FrameDropStrategy, -} - -impl FrameComparator { - pub fn should_drop_frame(&mut self, timecode: &Timecode, frame: &Frame) -> bool { - let hash = blockhash256(frame); - if let Some((_last_time_code, _other_time_code, last_hash)) = self.last_frames.last() { - let last_eq = last_hash == &hash; - if !last_eq { - self.last_frames.pop(); - self.last_frames.push((timecode.clone(), hash)); - } - last_eq - } else { - self.last_frames.push((timecode.clone(), hash)); - false - } - } -} - /// captures screenshots as file on disk /// collects also the timecodes when they have been captured /// stops once receiving something in rx @@ -55,23 +27,16 @@ pub fn capture_thread( let pool = ThreadPoolBuilder::default().build()?; let duration = Duration::from_secs(1) / *framerate.as_ref(); let start = Instant::now(); - // let mut idle_duration = Duration::from_millis(0); - // let mut last_frame: Option = None; - // let mut identical_frames = 0; let mut last_time = Instant::now(); let api = Arc::new(api); - let comp = Arc::new(Mutex::new(FrameComparator { - last_frames: Vec::new(), - _strategy: frame_drop_strategy.clone(), - })); - // let rx = Arc::new(rx); - // let mut results: Arc>>> = Arc::new(Mutex::new(Vec::new())); + let comparator = Arc::new(Mutex::new(FrameComparator::new( + frame_drop_strategy.clone(), + ))); pool.scope(|s| { loop { let delta = Instant::now().saturating_duration_since(last_time); let sleep_time = duration.sub(delta); - // thread::sleep(sleep_time); // blocks for a timeout if rx.recv_timeout(sleep_time).is_ok() { if pool.current_thread_has_pending_tasks().unwrap_or(false) { @@ -85,53 +50,28 @@ pub fn capture_thread( s.spawn({ let api = api.clone(); let tempdir = tempdir.clone(); - let comp = comp.clone(); + let comp = comparator.clone(); let time_codes = time_codes.clone(); move |_| { let tc: Timecode = now.saturating_duration_since(start).as_millis().into(); - - let frame = api.capture_window_screenshot(win_id); - if let Ok(frame) = frame { + if let Ok(frame) = api.capture_window_screenshot(win_id) { let frame: Frame = frame.into(); - if comp.lock().unwrap().should_drop_frame(&tc, &frame) { - return; + let frame_essence = FrameEssence::new(&frame, &tc); + { + let mut lock = comp.try_lock(); + if let Ok(ref mut mutex) = lock { + if mutex.should_drop_frame(frame_essence) { + return; + } + } else { + dbg!(" locking failed..."); + } } frame.save(&tc, tempdir.borrow(), file_name_for).unwrap(); time_codes.lock().unwrap().push(tc); - // results.borrow_mut().lock().unwrap().push(result); } } - }); - - /* - let image = api.capture_window_screenshot(win_id)?; - if frame_drop_strategy == &FrameDropStrategy::DropIdenticalFrames { - if last_frame.is_some() - && image - .samples - .as_slice() - .eq(last_frame.as_ref().unwrap().samples.as_slice()) - { - identical_frames += 1; - } else { - identical_frames = 0; - } - } - - if identical_frames > 0 { - // let's track now the duration as idle - idle_duration = idle_duration.add(now.duration_since(last_now)); - } else { - if let Err(e) = save_frame(&image, tc, tempdir.lock().unwrap().borrow(), file_name_for) - { - eprintln!("{}", &e); - return Err(e); - } - time_codes.lock().unwrap().push(tc); - last_frame = Some(image); - identical_frames = 0; - } - */ + }); last_time = now; } diff --git a/src/capture/timecode.rs b/src/capture/timecode.rs index 3eb803e..8ba21a8 100644 --- a/src/capture/timecode.rs +++ b/src/capture/timecode.rs @@ -1,4 +1,4 @@ -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)] pub struct Timecode(u32); impl AsRef for Timecode { diff --git a/src/generators/gif.rs b/src/generators/gif.rs index d032ff3..9071f04 100644 --- a/src/generators/gif.rs +++ b/src/generators/gif.rs @@ -37,7 +37,7 @@ pub fn generate_gif_with_convert( println!("🎉 🚀 Generating {target}"); let mut cmd = Command::new(PROGRAM); cmd.arg("-loop").arg("0"); - let mut delay = 0; + let mut delay: u32 = 0; let temp = tempdir.path(); let last_frame_i = time_codes.last(); if last_frame_i.is_none() { diff --git a/src/main.rs b/src/main.rs index 27a795d..faa19aa 100644 --- a/src/main.rs +++ b/src/main.rs @@ -171,6 +171,7 @@ fn main() -> Result<()> { let target = target_file(args.value_of("file").unwrap()); let mut time = Duration::default(); + time_codes.lock().unwrap().sort_unstable(); if should_generate_gif { time += prof! {