Skip to content

Commit a4cc8d6

Browse files
committed
feat(#36): Add framerate cli option
- add a framerate cli argument `-f | --framerate` - limit the possible framerates to 4 and 8 for now - introduce new wrapper types for better readability - adjust the docs
1 parent 027f156 commit a4cc8d6

File tree

8 files changed

+147
-46
lines changed

8 files changed

+147
-46
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ e2e_tests = []
5252
section = "x11"
5353
depends = "imagemagick"
5454
extended-description = """## Features
55-
- Screenshotting your terminal with 4 frames per second (every 250ms)
55+
- Screenshotting your terminal with 4/8 frames per second
5656
- Generates high quality small sized animated gif images
5757
- **Build-In idle frames detection and optimization** (for super fluid
5858
presentations)

README.md

Lines changed: 54 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@ Blazingly fast terminal recorder that generates animated gif images for the web
1818
![demo](./docs/demo.gif)
1919

2020
## Features
21-
- Screenshotting your terminal with 4 frames per second (every 250ms)
22-
- Generates high quality small sized animated gif images or mp4 videos
21+
- Screenshotting your terminal with 4/8 frames per second
22+
- Generates high-quality small-sized animated gif images or mp4 videos
2323
- **Build-In idle frames detection and optimization** (for super fluid presentations)
2424
- Applies (can be disabled) border decor effects like drop shadow
25-
- Runs on MacOS and Linux
25+
- Runs on macOS and Linux
2626
- Uses native efficient APIs
2727
- Runs without any cloud service and entirely offline
2828
- No issues with terminal sizes larger than 80x24
@@ -123,7 +123,7 @@ t-rec /bin/sh
123123
### Full Options
124124

125125
```sh
126-
t-rec 0.7.0
126+
t-rec 0.7.1
127127
Sven Assmann <sven.assmann.it@gmail.com>
128128
Blazingly fast terminal recorder that generates animated gif images for the web written in rust.
129129

@@ -135,28 +135,56 @@ ARGS:
135135
pass it here. For example '/bin/sh'
136136

137137
OPTIONS:
138-
-b, --bg <bg> Background color when decors are used [default: transparent]
139-
[possible values: white, black, transparent]
140-
-d, --decor <decor> Decorates the animation with certain, mostly border effects
141-
[default: none] [possible values: shadow, none]
142-
-e, --end-pause <s | ms | m> to specify the pause time at the end of the animation, that
143-
time the gif will show the last frame
144-
-h, --help Print help information
145-
-l, --ls-win If you want to see a list of windows available for recording
146-
by their id, you can set env var 'WINDOWID' or `--win-id` to
147-
record this specific window only
148-
-m, --video Generates additionally to the gif a mp4 video of the recording
149-
-M, --video-only Generates only a mp4 video and not gif
150-
-n, --natural If you want a very natural typing experience and disable the
151-
idle detection and sampling optimization
152-
-q, --quiet Quiet mode, suppresses the banner: 'Press Ctrl+D to end
153-
recording'
154-
-s, --start-pause <s | ms | m> to specify the pause time at the start of the animation, that
155-
time the gif will show the first frame
156-
-v, --verbose Enable verbose insights for the curious
157-
-V, --version Print version information
158-
-w, --win-id <win-id> Window Id (see --ls-win) that should be captured, instead of
159-
the current terminal
138+
-b, --bg <bg>
139+
Background color when decors are used [default: transparent] [possible values: white,
140+
black, transparent]
141+
142+
-d, --decor <decor>
143+
Decorates the animation with certain, mostly border effects [default: none] [possible
144+
values: shadow, none]
145+
146+
-e, --end-pause <s | ms | m>
147+
Specify the pause time at the end of the animation, that time the gif will show the last
148+
frame
149+
150+
-f, --framerate <frames per second>
151+
Increase the screen capturing rate (framerate) [default: 4] [possible values: 4, 8]
152+
153+
-h, --help
154+
Print help information
155+
156+
-l, --ls-win
157+
If you want to see a list of windows available for recording by their id, you can set
158+
env var 'WINDOWID' or `--win-id` to record this specific window only
159+
160+
-m, --video
161+
Generates additionally to the gif a mp4 video of the recording
162+
163+
-M, --video-only
164+
Generates only a mp4 video and not gif
165+
166+
-n, --natural
167+
If you want a very natural typing experience and disable the idle detection and sampling
168+
optimization
169+
170+
-o, --output <file>
171+
Specify the output file (without extension) [default: t-rec]
172+
173+
-q, --quiet
174+
Quiet mode, suppresses the banner: 'Press Ctrl+D to end recording'
175+
176+
-s, --start-pause <s | ms | m>
177+
Specify the pause time at the start of the animation, that time the gif will show the
178+
first frame
179+
180+
-v, --verbose
181+
Enable verbose insights for the curious
182+
183+
-V, --version
184+
Print version information
185+
186+
-w, --win-id <win-id>
187+
Window Id (see --ls-win) that should be captured, instead of the current terminal
160188
```
161189
162190
### Disable idle detection & optimization

src/capture.rs renamed to src/capture/capture.rs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use anyhow::{Context, Result};
1+
use anyhow::Context;
22
use image::save_buffer;
33
use image::ColorType::Rgba8;
44
use std::borrow::Borrow;
@@ -8,8 +8,15 @@ use std::sync::{Arc, Mutex};
88
use std::time::{Duration, Instant};
99
use tempfile::TempDir;
1010

11+
use crate::capture::Framerate;
1112
use crate::utils::file_name_for;
12-
use crate::{ImageOnHeap, PlatformApi, WindowId};
13+
use crate::{ImageOnHeap, PlatformApi, Result, WindowId};
14+
15+
#[derive(Eq, PartialEq)]
16+
pub enum FrameDropStrategy {
17+
DoNotDropAny,
18+
DropIdenticalFrames,
19+
}
1320

1421
/// captures screenshots as file on disk
1522
/// collects also the timecodes when they have been captured
@@ -20,9 +27,10 @@ pub fn capture_thread(
2027
win_id: WindowId,
2128
time_codes: Arc<Mutex<Vec<u128>>>,
2229
tempdir: Arc<Mutex<TempDir>>,
23-
force_natural: bool,
30+
frame_drop_strategy: &FrameDropStrategy,
31+
framerate: &Framerate,
2432
) -> Result<()> {
25-
let duration = Duration::from_millis(250);
33+
let duration = Duration::from_secs(1) / *framerate.as_ref();
2634
let start = Instant::now();
2735
let mut idle_duration = Duration::from_millis(0);
2836
let mut last_frame: Option<ImageOnHeap> = None;
@@ -37,7 +45,7 @@ pub fn capture_thread(
3745
let effective_now = now.sub(idle_duration);
3846
let tc = effective_now.saturating_duration_since(start).as_millis();
3947
let image = api.capture_window_screenshot(win_id)?;
40-
if !force_natural {
48+
if frame_drop_strategy == &FrameDropStrategy::DropIdenticalFrames {
4149
if last_frame.is_some()
4250
&& image
4351
.samples

src/capture/framerate.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
use std::fmt::{Display, Formatter};
2+
3+
#[derive(Clone)]
4+
pub struct Framerate(u32);
5+
6+
impl Framerate {
7+
pub fn new(f: u32) -> Self {
8+
Self(f)
9+
}
10+
}
11+
12+
impl Display for Framerate {
13+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
14+
let framerate = self.0;
15+
write!(f, "framerate {framerate} [fps]")
16+
}
17+
}
18+
19+
impl From<u32> for Framerate {
20+
fn from(fr: u32) -> Self {
21+
Self(fr)
22+
}
23+
}
24+
25+
impl AsRef<u32> for Framerate {
26+
fn as_ref(&self) -> &u32 {
27+
&self.0
28+
}
29+
}

src/capture/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
mod capture;
2+
mod framerate;
3+
4+
pub use capture::*;
5+
pub use framerate::*;

src/cli.rs

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ pub fn launch() -> ArgMatches {
9191
.required(false)
9292
.short('e')
9393
.long("end-pause")
94-
.help("to specify the pause time at the end of the animation, that time the gif will show the last frame"),
94+
.help("Specify the pause time at the end of the animation, that time the gif will show the last frame"),
9595
)
9696
.arg(
9797
Arg::new("start-pause")
@@ -100,7 +100,7 @@ pub fn launch() -> ArgMatches {
100100
.required(false)
101101
.short('s')
102102
.long("start-pause")
103-
.help("to specify the pause time at the start of the animation, that time the gif will show the first frame"),
103+
.help("Specify the pause time at the start of the animation, that time the gif will show the first frame"),
104104
)
105105
.arg(
106106
Arg::new("file")
@@ -109,7 +109,18 @@ pub fn launch() -> ArgMatches {
109109
.short('o')
110110
.long("output")
111111
.default_value("t-rec")
112-
.help("to specify the output file (without extension)"),
112+
.help("Specify the output file (without extension)"),
113+
)
114+
.arg(
115+
Arg::new("framerate")
116+
.value_name("frames per second")
117+
.takes_value(true)
118+
.required(false)
119+
.short('f')
120+
.long("framerate")
121+
.default_value("4")
122+
.possible_values(&["4", "8"])
123+
.help("Increase the screen capturing rate (framerate)"),
113124
)
114125
.arg(
115126
Arg::new("program")

src/main.rs

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1+
mod capture;
12
mod cli;
23
mod common;
34
mod decor_effect;
45
mod generators;
56
mod tips;
7+
mod utils;
68

7-
mod capture;
89
#[cfg(target_os = "linux")]
910
mod linux;
1011
#[cfg(target_os = "macos")]
1112
mod macos;
12-
mod utils;
1313
#[cfg(target_os = "windows")]
1414
mod win;
1515

@@ -27,7 +27,7 @@ use crate::decor_effect::{apply_big_sur_corner_effect, apply_shadow_effect};
2727
use crate::generators::{check_for_gif, check_for_mp4, generate_gif, generate_mp4};
2828
use crate::tips::show_tip;
2929

30-
use crate::capture::capture_thread;
30+
use crate::capture::{capture_thread, FrameDropStrategy, Framerate};
3131
use crate::utils::{sub_shell_thread, target_file, DEFAULT_EXT, MOVIE_EXT};
3232
use anyhow::{bail, Context};
3333
use clap::ArgMatches;
@@ -64,7 +64,8 @@ fn main() -> Result<()> {
6464
if args.is_present("list-windows") {
6565
return ls_win();
6666
}
67-
67+
let quiet = args.is_present("quiet");
68+
let verbose = args.is_present("verbose");
6869
let program: String = {
6970
if args.is_present("program") {
7071
args.value_of("program").unwrap().to_owned()
@@ -77,13 +78,22 @@ fn main() -> Result<()> {
7778
let mut api = setup()?;
7879
api.calibrate(win_id)?;
7980

80-
let force_natural = args.is_present("natural-mode");
81+
let frame_drop_strategy = args
82+
.is_present("natural-mode")
83+
.then(|| FrameDropStrategy::DoNotDropAny)
84+
.unwrap_or_else(|| FrameDropStrategy::DropIdenticalFrames);
8185
let should_generate_gif = !args.is_present("video-only");
8286
let should_generate_video = args.is_present("video") || args.is_present("video-only");
8387
let (start_delay, end_delay) = (
8488
parse_delay(args.value_of("start-pause"), "start-pause")?,
8589
parse_delay(args.value_of("end-pause"), "end-pause")?,
8690
);
91+
let framerate = args
92+
.value_of("framerate")
93+
.unwrap()
94+
.parse::<u32>()
95+
.map(|f| Framerate::new(f))
96+
.context("Invalid value for framerate")?;
8797

8898
if should_generate_gif {
8999
check_for_gif()?;
@@ -101,26 +111,35 @@ fn main() -> Result<()> {
101111
let photograph = {
102112
let tempdir = tempdir.clone();
103113
let time_codes = time_codes.clone();
104-
let force_natural = force_natural;
114+
let framerate = framerate.clone();
105115
thread::spawn(move || -> Result<()> {
106-
capture_thread(&rx, api, win_id, time_codes, tempdir, force_natural)
116+
capture_thread(
117+
&rx,
118+
api,
119+
win_id,
120+
time_codes,
121+
tempdir,
122+
&frame_drop_strategy,
123+
&framerate,
124+
)
107125
})
108126
};
109127
let interact = thread::spawn(move || -> Result<()> { sub_shell_thread(&program).map(|_| ()) });
110128

111129
clear_screen();
112-
if args.is_present("verbose") {
130+
if verbose {
113131
println!(
114132
"Frame cache dir: {:?}",
115133
tempdir.lock().expect("Cannot lock tempdir resource").path()
116134
);
135+
let fr = format!(" @ {}", &framerate);
117136
if let Some(window) = window_name {
118-
println!("Recording window: {:?}", window);
137+
println!("Recording window: {window}{fr}");
119138
} else {
120-
println!("Recording window id: {}", win_id);
139+
println!("Recording window id: {win_id}{fr}");
121140
}
122141
}
123-
if args.is_present("quiet") {
142+
if quiet {
124143
println!();
125144
} else {
126145
println!("[t-rec]: Press Ctrl+D to end recording");

src/tips.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const TIPS: &[&str] = &[
66
"To add a pause at the beginning of the gif loop, use e.g. option `-s 500ms` option",
77
"To prevent cutting out stall frames, checkout the `-n` option",
88
"To remove the shadow around the gif, use the `-d none` option",
9+
"To double the capturing framerate, use the option `-f 8`",
910
"For a mp4 video, use the `-m` option",
1011
"To suppress the 'Ctrl+D' banner, use the `-q` option",
1112
];

0 commit comments

Comments
 (0)