Skip to content

Commit 4e7d9fa

Browse files
committed
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`
1 parent 39afa54 commit 4e7d9fa

File tree

9 files changed

+96
-82
lines changed

9 files changed

+96
-82
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "t-rec"
3-
version = "0.7.3"
3+
version = "0.7.3-alpha.1"
44
authors = ["Sven Assmann <sven.assmann.it@gmail.com>"]
55
edition = "2018"
66
license = "GPL-3.0-only"

src/capture/frame_comparator.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
use std::collections::LinkedList;
2+
3+
use crate::capture::{FrameDropStrategy, FrameEssence};
4+
5+
pub struct FrameComparator {
6+
last_frames: LinkedList<FrameEssence>,
7+
strategy: FrameDropStrategy,
8+
}
9+
10+
impl FrameComparator {
11+
pub fn new(strategy: FrameDropStrategy) -> Self {
12+
Self {
13+
last_frames: LinkedList::new(),
14+
strategy,
15+
}
16+
}
17+
}
18+
19+
impl FrameComparator {
20+
pub fn should_drop_frame(&mut self, frame_essence: FrameEssence) -> bool {
21+
match self.strategy {
22+
FrameDropStrategy::DoNotDropAny => false,
23+
FrameDropStrategy::DropIdenticalFrames => {
24+
if let Some(FrameEssence { when, what }) = self.last_frames.pop_back() {
25+
if frame_essence.when > when && what == frame_essence.what {
26+
// so the last frame and this one is the same... so let's drop it..
27+
// but add the current frame
28+
self.last_frames.push_back(frame_essence);
29+
true
30+
} else {
31+
let previous_should_drop_frame = self.should_drop_frame(frame_essence);
32+
// restore the popped frame..
33+
self.last_frames.push_back(FrameEssence { when, what });
34+
35+
previous_should_drop_frame
36+
}
37+
} else {
38+
self.last_frames.push_back(frame_essence);
39+
40+
false
41+
}
42+
}
43+
}
44+
}
45+
}

src/capture/frame_essence.rs

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
use blockhash::{blockhash256, Blockhash256};
2+
3+
use crate::capture::{Frame, Timecode};
4+
5+
#[derive(Eq, PartialEq, Clone)]
6+
pub enum FrameDropStrategy {
7+
DoNotDropAny,
8+
DropIdenticalFrames,
9+
}
10+
11+
#[derive(Eq, PartialOrd, PartialEq, Clone)]
12+
pub struct FrameEssence {
13+
pub(crate) when: Timecode,
14+
pub(crate) what: Blockhash256,
15+
}
16+
17+
impl FrameEssence {
18+
pub fn new(frame: &Frame, timecode: &Timecode) -> Self {
19+
Self {
20+
when: timecode.clone(),
21+
what: blockhash256(frame),
22+
}
23+
}
24+
}

src/capture/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
11
mod frame;
2+
mod frame_comparator;
3+
mod frame_essence;
24
mod framerate;
35
mod processor;
46
mod timecode;
57

68
pub use frame::*;
9+
pub use frame_comparator::*;
10+
pub use frame_essence::*;
711
pub use framerate::*;
812
pub use processor::*;
913
pub use timecode::*;

src/capture/processor.rs

Lines changed: 18 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
use blockhash::{blockhash256, Blockhash256};
21
use crossbeam_channel::Receiver;
32
use rayon::ThreadPoolBuilder;
43
use std::borrow::Borrow;
@@ -7,39 +6,12 @@ use std::sync::{Arc, Mutex};
76
use std::time::{Duration, Instant};
87
use tempfile::TempDir;
98

9+
use crate::capture::frame_comparator::FrameComparator;
10+
use crate::capture::frame_essence::{FrameDropStrategy, FrameEssence};
1011
use crate::capture::{Framerate, Timecode};
1112
use crate::utils::file_name_for;
1213
use crate::{Frame, PlatformApi, Result, WindowId};
1314

14-
#[derive(Eq, PartialEq, Clone)]
15-
pub enum FrameDropStrategy {
16-
DoNotDropAny,
17-
DropIdenticalFrames,
18-
}
19-
20-
#[derive(Clone)]
21-
struct FrameComparator {
22-
last_frames: Vec<(Timecode, Timecode, Blockhash256)>,
23-
_strategy: FrameDropStrategy,
24-
}
25-
26-
impl FrameComparator {
27-
pub fn should_drop_frame(&mut self, timecode: &Timecode, frame: &Frame) -> bool {
28-
let hash = blockhash256(frame);
29-
if let Some((_last_time_code, _other_time_code, last_hash)) = self.last_frames.last() {
30-
let last_eq = last_hash == &hash;
31-
if !last_eq {
32-
self.last_frames.pop();
33-
self.last_frames.push((timecode.clone(), hash));
34-
}
35-
last_eq
36-
} else {
37-
self.last_frames.push((timecode.clone(), hash));
38-
false
39-
}
40-
}
41-
}
42-
4315
/// captures screenshots as file on disk
4416
/// collects also the timecodes when they have been captured
4517
/// stops once receiving something in rx
@@ -55,23 +27,16 @@ pub fn capture_thread(
5527
let pool = ThreadPoolBuilder::default().build()?;
5628
let duration = Duration::from_secs(1) / *framerate.as_ref();
5729
let start = Instant::now();
58-
// let mut idle_duration = Duration::from_millis(0);
59-
// let mut last_frame: Option<ImageOnHeap> = None;
60-
// let mut identical_frames = 0;
6130
let mut last_time = Instant::now();
6231
let api = Arc::new(api);
63-
let comp = Arc::new(Mutex::new(FrameComparator {
64-
last_frames: Vec::new(),
65-
_strategy: frame_drop_strategy.clone(),
66-
}));
67-
// let rx = Arc::new(rx);
68-
// let mut results: Arc<Mutex<Vec<Result<()>>>> = Arc::new(Mutex::new(Vec::new()));
32+
let comparator = Arc::new(Mutex::new(FrameComparator::new(
33+
frame_drop_strategy.clone(),
34+
)));
6935

7036
pool.scope(|s| {
7137
loop {
7238
let delta = Instant::now().saturating_duration_since(last_time);
7339
let sleep_time = duration.sub(delta);
74-
// thread::sleep(sleep_time);
7540
// blocks for a timeout
7641
if rx.recv_timeout(sleep_time).is_ok() {
7742
if pool.current_thread_has_pending_tasks().unwrap_or(false) {
@@ -85,53 +50,28 @@ pub fn capture_thread(
8550
s.spawn({
8651
let api = api.clone();
8752
let tempdir = tempdir.clone();
88-
let comp = comp.clone();
53+
let comp = comparator.clone();
8954
let time_codes = time_codes.clone();
9055
move |_| {
9156
let tc: Timecode = now.saturating_duration_since(start).as_millis().into();
92-
93-
let frame = api.capture_window_screenshot(win_id);
94-
if let Ok(frame) = frame {
57+
if let Ok(frame) = api.capture_window_screenshot(win_id) {
9558
let frame: Frame = frame.into();
96-
if comp.lock().unwrap().should_drop_frame(&tc, &frame) {
97-
return;
59+
let frame_essence = FrameEssence::new(&frame, &tc);
60+
{
61+
let mut lock = comp.try_lock();
62+
if let Ok(ref mut mutex) = lock {
63+
if mutex.should_drop_frame(frame_essence) {
64+
return;
65+
}
66+
} else {
67+
dbg!(" locking failed...");
68+
}
9869
}
9970
frame.save(&tc, tempdir.borrow(), file_name_for).unwrap();
10071
time_codes.lock().unwrap().push(tc);
101-
// results.borrow_mut().lock().unwrap().push(result);
10272
}
10373
}
104-
});
105-
106-
/*
107-
let image = api.capture_window_screenshot(win_id)?;
108-
if frame_drop_strategy == &FrameDropStrategy::DropIdenticalFrames {
109-
if last_frame.is_some()
110-
&& image
111-
.samples
112-
.as_slice()
113-
.eq(last_frame.as_ref().unwrap().samples.as_slice())
114-
{
115-
identical_frames += 1;
116-
} else {
117-
identical_frames = 0;
118-
}
119-
}
120-
121-
if identical_frames > 0 {
122-
// let's track now the duration as idle
123-
idle_duration = idle_duration.add(now.duration_since(last_now));
124-
} else {
125-
if let Err(e) = save_frame(&image, tc, tempdir.lock().unwrap().borrow(), file_name_for)
126-
{
127-
eprintln!("{}", &e);
128-
return Err(e);
129-
}
130-
time_codes.lock().unwrap().push(tc);
131-
last_frame = Some(image);
132-
identical_frames = 0;
133-
}
134-
*/
74+
});
13575

13676
last_time = now;
13777
}

src/capture/timecode.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#[derive(Clone, Debug)]
1+
#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq)]
22
pub struct Timecode(u32);
33

44
impl AsRef<u32> for Timecode {

src/generators/gif.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ pub fn generate_gif_with_convert(
3737
println!("🎉 🚀 Generating {target}");
3838
let mut cmd = Command::new(PROGRAM);
3939
cmd.arg("-loop").arg("0");
40-
let mut delay = 0;
40+
let mut delay: u32 = 0;
4141
let temp = tempdir.path();
4242
let last_frame_i = time_codes.last();
4343
if last_frame_i.is_none() {

src/main.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,7 @@ fn main() -> Result<()> {
171171

172172
let target = target_file(args.value_of("file").unwrap());
173173
let mut time = Duration::default();
174+
time_codes.lock().unwrap().sort_unstable();
174175

175176
if should_generate_gif {
176177
time += prof! {

0 commit comments

Comments
 (0)