Skip to content

Commit

Permalink
feat(#36): Add framerate cli option
Browse files Browse the repository at this point in the history
- 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
  • Loading branch information
sassman committed Feb 15, 2022
1 parent 027f156 commit a4cc8d6
Show file tree
Hide file tree
Showing 8 changed files with 147 additions and 46 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ e2e_tests = []
section = "x11"
depends = "imagemagick"
extended-description = """## Features
- Screenshotting your terminal with 4 frames per second (every 250ms)
- Screenshotting your terminal with 4/8 frames per second
- Generates high quality small sized animated gif images
- **Build-In idle frames detection and optimization** (for super fluid
presentations)
Expand Down
80 changes: 54 additions & 26 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ Blazingly fast terminal recorder that generates animated gif images for the web
![demo](./docs/demo.gif)

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

```sh
t-rec 0.7.0
t-rec 0.7.1
Sven Assmann <[email protected]>
Blazingly fast terminal recorder that generates animated gif images for the web written in rust.

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

OPTIONS:
-b, --bg <bg> Background color when decors are used [default: transparent]
[possible values: white, black, transparent]
-d, --decor <decor> Decorates the animation with certain, mostly border effects
[default: none] [possible values: shadow, none]
-e, --end-pause <s | ms | m> to specify the pause time at the end of the animation, that
time the gif will show the last frame
-h, --help Print help information
-l, --ls-win If you want to see a list of windows available for recording
by their id, you can set env var 'WINDOWID' or `--win-id` to
record this specific window only
-m, --video Generates additionally to the gif a mp4 video of the recording
-M, --video-only Generates only a mp4 video and not gif
-n, --natural If you want a very natural typing experience and disable the
idle detection and sampling optimization
-q, --quiet Quiet mode, suppresses the banner: 'Press Ctrl+D to end
recording'
-s, --start-pause <s | ms | m> to specify the pause time at the start of the animation, that
time the gif will show the first frame
-v, --verbose Enable verbose insights for the curious
-V, --version Print version information
-w, --win-id <win-id> Window Id (see --ls-win) that should be captured, instead of
the current terminal
-b, --bg <bg>
Background color when decors are used [default: transparent] [possible values: white,
black, transparent]

-d, --decor <decor>
Decorates the animation with certain, mostly border effects [default: none] [possible
values: shadow, none]

-e, --end-pause <s | ms | m>
Specify the pause time at the end of the animation, that time the gif will show the last
frame

-f, --framerate <frames per second>
Increase the screen capturing rate (framerate) [default: 4] [possible values: 4, 8]

-h, --help
Print help information

-l, --ls-win
If you want to see a list of windows available for recording by their id, you can set
env var 'WINDOWID' or `--win-id` to record this specific window only

-m, --video
Generates additionally to the gif a mp4 video of the recording

-M, --video-only
Generates only a mp4 video and not gif

-n, --natural
If you want a very natural typing experience and disable the idle detection and sampling
optimization

-o, --output <file>
Specify the output file (without extension) [default: t-rec]

-q, --quiet
Quiet mode, suppresses the banner: 'Press Ctrl+D to end recording'

-s, --start-pause <s | ms | m>
Specify the pause time at the start of the animation, that time the gif will show the
first frame

-v, --verbose
Enable verbose insights for the curious

-V, --version
Print version information

-w, --win-id <win-id>
Window Id (see --ls-win) that should be captured, instead of the current terminal
```
### Disable idle detection & optimization
Expand Down
18 changes: 13 additions & 5 deletions src/capture.rs → src/capture/capture.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use anyhow::{Context, Result};
use anyhow::Context;
use image::save_buffer;
use image::ColorType::Rgba8;
use std::borrow::Borrow;
Expand All @@ -8,8 +8,15 @@ use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use tempfile::TempDir;

use crate::capture::Framerate;
use crate::utils::file_name_for;
use crate::{ImageOnHeap, PlatformApi, WindowId};
use crate::{ImageOnHeap, PlatformApi, Result, WindowId};

#[derive(Eq, PartialEq)]
pub enum FrameDropStrategy {
DoNotDropAny,
DropIdenticalFrames,
}

/// captures screenshots as file on disk
/// collects also the timecodes when they have been captured
Expand All @@ -20,9 +27,10 @@ pub fn capture_thread(
win_id: WindowId,
time_codes: Arc<Mutex<Vec<u128>>>,
tempdir: Arc<Mutex<TempDir>>,
force_natural: bool,
frame_drop_strategy: &FrameDropStrategy,
framerate: &Framerate,
) -> Result<()> {
let duration = Duration::from_millis(250);
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<ImageOnHeap> = None;
Expand All @@ -37,7 +45,7 @@ pub fn capture_thread(
let effective_now = now.sub(idle_duration);
let tc = effective_now.saturating_duration_since(start).as_millis();
let image = api.capture_window_screenshot(win_id)?;
if !force_natural {
if frame_drop_strategy == &FrameDropStrategy::DropIdenticalFrames {
if last_frame.is_some()
&& image
.samples
Expand Down
29 changes: 29 additions & 0 deletions src/capture/framerate.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
use std::fmt::{Display, Formatter};

#[derive(Clone)]
pub struct Framerate(u32);

impl Framerate {
pub fn new(f: u32) -> Self {
Self(f)
}
}

impl Display for Framerate {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
let framerate = self.0;
write!(f, "framerate {framerate} [fps]")
}
}

impl From<u32> for Framerate {
fn from(fr: u32) -> Self {
Self(fr)
}
}

impl AsRef<u32> for Framerate {
fn as_ref(&self) -> &u32 {
&self.0
}
}
5 changes: 5 additions & 0 deletions src/capture/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
mod capture;
mod framerate;

pub use capture::*;
pub use framerate::*;
17 changes: 14 additions & 3 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ pub fn launch() -> ArgMatches {
.required(false)
.short('e')
.long("end-pause")
.help("to specify the pause time at the end of the animation, that time the gif will show the last frame"),
.help("Specify the pause time at the end of the animation, that time the gif will show the last frame"),
)
.arg(
Arg::new("start-pause")
Expand All @@ -100,7 +100,7 @@ pub fn launch() -> ArgMatches {
.required(false)
.short('s')
.long("start-pause")
.help("to specify the pause time at the start of the animation, that time the gif will show the first frame"),
.help("Specify the pause time at the start of the animation, that time the gif will show the first frame"),
)
.arg(
Arg::new("file")
Expand All @@ -109,7 +109,18 @@ pub fn launch() -> ArgMatches {
.short('o')
.long("output")
.default_value("t-rec")
.help("to specify the output file (without extension)"),
.help("Specify the output file (without extension)"),
)
.arg(
Arg::new("framerate")
.value_name("frames per second")
.takes_value(true)
.required(false)
.short('f')
.long("framerate")
.default_value("4")
.possible_values(&["4", "8"])
.help("Increase the screen capturing rate (framerate)"),
)
.arg(
Arg::new("program")
Expand Down
41 changes: 30 additions & 11 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
mod capture;
mod cli;
mod common;
mod decor_effect;
mod generators;
mod tips;
mod utils;

mod capture;
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
mod utils;
#[cfg(target_os = "windows")]
mod win;

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

use crate::capture::capture_thread;
use crate::capture::{capture_thread, FrameDropStrategy, Framerate};
use crate::utils::{sub_shell_thread, target_file, DEFAULT_EXT, MOVIE_EXT};
use anyhow::{bail, Context};
use clap::ArgMatches;
Expand Down Expand Up @@ -64,7 +64,8 @@ fn main() -> Result<()> {
if args.is_present("list-windows") {
return ls_win();
}

let quiet = args.is_present("quiet");
let verbose = args.is_present("verbose");
let program: String = {
if args.is_present("program") {
args.value_of("program").unwrap().to_owned()
Expand All @@ -77,13 +78,22 @@ fn main() -> Result<()> {
let mut api = setup()?;
api.calibrate(win_id)?;

let force_natural = args.is_present("natural-mode");
let frame_drop_strategy = args
.is_present("natural-mode")
.then(|| FrameDropStrategy::DoNotDropAny)
.unwrap_or_else(|| FrameDropStrategy::DropIdenticalFrames);
let should_generate_gif = !args.is_present("video-only");
let should_generate_video = args.is_present("video") || args.is_present("video-only");
let (start_delay, end_delay) = (
parse_delay(args.value_of("start-pause"), "start-pause")?,
parse_delay(args.value_of("end-pause"), "end-pause")?,
);
let framerate = args
.value_of("framerate")
.unwrap()
.parse::<u32>()
.map(|f| Framerate::new(f))
.context("Invalid value for framerate")?;

if should_generate_gif {
check_for_gif()?;
Expand All @@ -101,26 +111,35 @@ fn main() -> Result<()> {
let photograph = {
let tempdir = tempdir.clone();
let time_codes = time_codes.clone();
let force_natural = force_natural;
let framerate = framerate.clone();
thread::spawn(move || -> Result<()> {
capture_thread(&rx, api, win_id, time_codes, tempdir, force_natural)
capture_thread(
&rx,
api,
win_id,
time_codes,
tempdir,
&frame_drop_strategy,
&framerate,
)
})
};
let interact = thread::spawn(move || -> Result<()> { sub_shell_thread(&program).map(|_| ()) });

clear_screen();
if args.is_present("verbose") {
if verbose {
println!(
"Frame cache dir: {:?}",
tempdir.lock().expect("Cannot lock tempdir resource").path()
);
let fr = format!(" @ {}", &framerate);
if let Some(window) = window_name {
println!("Recording window: {:?}", window);
println!("Recording window: {window}{fr}");
} else {
println!("Recording window id: {}", win_id);
println!("Recording window id: {win_id}{fr}");
}
}
if args.is_present("quiet") {
if quiet {
println!();
} else {
println!("[t-rec]: Press Ctrl+D to end recording");
Expand Down
1 change: 1 addition & 0 deletions src/tips.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const TIPS: &[&str] = &[
"To add a pause at the beginning of the gif loop, use e.g. option `-s 500ms` option",
"To prevent cutting out stall frames, checkout the `-n` option",
"To remove the shadow around the gif, use the `-d none` option",
"To double the capturing framerate, use the option `-f 8`",
"For a mp4 video, use the `-m` option",
"To suppress the 'Ctrl+D' banner, use the `-q` option",
];
Expand Down

0 comments on commit a4cc8d6

Please sign in to comment.