Skip to content

Commit

Permalink
feat(drop-idle-frames): implement idle frame detection and dropping
Browse files Browse the repository at this point in the history
 - relates to issue #10
 - identifies identical frames and drops them
 - calculates also a proper offset for the duration ths is dropped together with the frame
 - add a comand line flag `-n` or `--natural` to disable this mechanism
 - adjust the README and the `docs/demo.gif` to illustrate a very fluid installtion of t-rec (where normally it would take more idle frames)
  • Loading branch information
sassman committed Oct 12, 2020
1 parent b19ada8 commit 7f22d3c
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "t-rec"
version = "0.1.2"
version = "0.2.0"
authors = ["Sven Assmann <[email protected]>"]
edition = "2018"
license = "GPL-3.0-only"
Expand Down
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Blazingly fast terminal recorder that generates animated gif images for the web

- Screenshotting your terminal with 4 frames per second (every 250ms)
- Generates high quality small sized animated gif images
- **Build-In idle frames detection and optimization** (for super fluid presentations)
- Runs (only) on MacOS
- Uses native efficient APIs
- Runs without any cloud service and entirely offline
Expand Down Expand Up @@ -48,7 +49,37 @@ or with specifying a different program to launch
❯ t-rec /bin/sh
```

### Hidden Gems
### Full Options

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

USAGE:
t-rec [FLAGS] [shell or program to launch]

FLAGS:
-h, --help Prints 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' to record this specific window only.
-n, --natural If you want a very natural typing experience and disable the idle detection and sampling
optimization.
-V, --version Prints version information

ARGS:
<shell or program to launch> If you want to start a different program than $SHELL you can pass it here. For
example '/bin/sh'
```
### Disable idle detection & optimization
If you are not happy with the idle detection and optimization, you can disable it with the `-n` or `--natural` parameter.
By doing so, you would get the very natural timeline of typing and recording as you do it.
In this case there will be no optimizations performed.
## Hidden Gems
You can record not only the terminal but also every other window. There 2 ways to do so:
Expand All @@ -58,9 +89,9 @@ You can record not only the terminal but also every other window. There 2 ways t
```sh
❯ TERM_PROGRAM="google chrome" t-rec
tmp path: "/var/folders/m8/084p1v0x4770rpwpkrgl5b6h0000gn/T/trec-74728.rUxBx3ohGiQ2"
Frame cache dir: "/var/folders/m8/084p1v0x4770rpwpkrgl5b6h0000gn/T/trec-74728.rUxBx3ohGiQ2"
Press Ctrl+D to end recording
[src/window_id.rs:122] window_owner = "Google Chrome 2"
Recording Window: "Google Chrome 2"
```
this is how it looks then:
Expand All @@ -79,7 +110,7 @@ Code | 27600
# set the WINDOWID variable and run t-rec
❯ WINDOWID=27600 t-rec
tmp path: "/var/folders/m8/084p1v0x4770rpwpkrgl5b6h0000gn/T/trec-77862.BMYiHNRWqv9Y"
Frame cache dir: "/var/folders/m8/084p1v0x4770rpwpkrgl5b6h0000gn/T/trec-77862.BMYiHNRWqv9Y"
Press Ctrl+D to end recording
```
Expand Down
Binary file modified docs/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
9 changes: 2 additions & 7 deletions src/any/mod.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use tempfile::TempDir;
use crate::ImageOnHeap;

pub fn get_window_id_for(_terminal: String) -> Option<u32> {
unimplemented!("there is only an impl for MacOS")
Expand All @@ -8,12 +8,7 @@ pub fn ls_win() {
unimplemented!("there is only an impl for MacOS")
}

pub fn screenshot_and_save(
_win_id: u32,
_time_code: u128,
_tempdir: &TempDir,
_file_name_for: fn(&u128, &str) -> String,
) -> anyhow::Result<()> {
pub fn capture_window_screenshot(_win_id: u32) -> anyhow::Result<ImageOnHeap> {
unimplemented!("there is only an impl for MacOS")
}

Expand Down
9 changes: 9 additions & 0 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,15 @@ pub fn launch<'a>() -> ArgMatches<'a> {
.author(crate_authors!())
.about(crate_description!())
.setting(AppSettings::AllowMissingPositional)
.arg(
Arg::with_name("natural-mode")
.value_name("natural")
.takes_value(false)
.required(false)
.short("n")
.long("natural")
.help("If you want a very natural typing experience and disable the idle detection and sampling optimization.")
)
.arg(
Arg::with_name("list-windows")
.value_name("list all visible windows with name and id")
Expand Down
2 changes: 1 addition & 1 deletion src/macos/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ mod core_foundation_sys_patches;
mod screenshot;
mod window_id;

pub use screenshot::screenshot_and_save;
pub use screenshot::capture_window_screenshot;
pub use window_id::{get_window_id_for, ls_win};
23 changes: 4 additions & 19 deletions src/macos/screenshot.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
use crate::ImageOnHeap;
use anyhow::{Context, Result};
use core_graphics::display::*;
use core_graphics::image::CGImageRef;
use image::flat::SampleLayout;
use image::{save_buffer, ColorType, FlatSamples};
use tempfile::TempDir;
use image::{ColorType, FlatSamples};

///
/// grabs a screenshot by window id and
/// saves it as a tga file
pub fn screenshot_and_save(
win_id: u32,
time_code: u128,
tempdir: &TempDir,
file_name_for: fn(&u128, &str) -> String,
) -> Result<()> {
pub fn capture_window_screenshot(win_id: u32) -> Result<ImageOnHeap> {
let (w, h, channels, raw_data) = {
let image = unsafe {
CGDisplay::screenshot(
Expand Down Expand Up @@ -51,12 +43,5 @@ pub fn screenshot_and_save(
color_hint: Some(color),
};

save_buffer(
tempdir.path().join(file_name_for(&time_code, "tga")),
&buffer.samples,
w,
h,
color,
)
.context("Cannot save frame")
Ok(ImageOnHeap::new(buffer))
}
69 changes: 63 additions & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
#[cfg(target_os = "macos")]
mod macos;
#[cfg(target_os = "macos")]
use macos::{get_window_id_for, ls_win, screenshot_and_save};
use macos::*;

#[cfg(not(target_os = "macos"))]
mod any;
#[cfg(not(target_os = "macos"))]
use any::{get_window_id_for, ls_win, screenshot_and_save};
use any::*;

mod cli;
use crate::cli::launch;

use crate::macos::capture_window_screenshot;
use anyhow::Context;
use anyhow::Result;
use image::{save_buffer, FlatSamples};
use std::borrow::Borrow;
use std::ffi::OsStr;
use std::ops::{Add, Sub};
use std::process::{Command, ExitStatus, Output};
use std::sync::mpsc::Receiver;
use std::sync::{mpsc, Arc, Mutex};
use std::time::{Duration, Instant};
use std::{env, thread};
use tempfile::TempDir;

pub type ImageOnHeap = Box<FlatSamples<Vec<u8>>>;

#[cfg(target_os = "linux")]
fn main() -> Result<(), std::io::Error> {
unimplemented!("We're super sorry, right now t-rec is only supporting MacOS.\nIf you'd like to contribute checkout:\n\nhttps://github.com/sassman/t-rec-rs/issues/1\n")
Expand All @@ -48,6 +53,8 @@ fn main() -> Result<()> {
}
};

let force_natural = args.is_present("natural-mode");

check_for_imagemagick()?;

// the nice thing is the cleanup on drop
Expand All @@ -59,7 +66,10 @@ fn main() -> Result<()> {
let photograph = {
let tempdir = tempdir.clone();
let time_codes = time_codes.clone();
thread::spawn(move || -> Result<()> { capture_thread(&rx, time_codes, tempdir) })
let force_natural = force_natural;
thread::spawn(move || -> Result<()> {
capture_thread(&rx, time_codes, tempdir, force_natural)
})
};
let interact = thread::spawn(move || -> Result<()> { sub_shell_thread(&program).map(|_| ()) });

Expand Down Expand Up @@ -102,23 +112,70 @@ fn capture_thread(
rx: &Receiver<()>,
time_codes: Arc<Mutex<Vec<u128>>>,
tempdir: Arc<Mutex<TempDir>>,
force_natural: bool,
) -> Result<()> {
let win_id = current_win_id()?;
let duration = Duration::from_millis(250);
let start = Instant::now();
let mut idle_duration = Duration::from_millis(0);
let mut last_frame: Option<ImageOnHeap> = None;
let mut identical_frames = 0;
let mut last_now = Instant::now();
loop {
// blocks for a timeout
if rx.recv_timeout(duration).is_ok() {
break;
}
let tc = Instant::now().saturating_duration_since(start).as_millis();
time_codes.lock().unwrap().push(tc);
screenshot_and_save(win_id, tc, tempdir.lock().unwrap().borrow(), file_name_for)?
let now = Instant::now();
let effective_now = now.sub(idle_duration);
let tc = effective_now.saturating_duration_since(start).as_millis();
let image = capture_window_screenshot(win_id)?;
if !force_natural {
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 {
save_frame(&image, tc, tempdir.lock().unwrap().borrow(), file_name_for)?;
time_codes.lock().unwrap().push(tc);
last_frame = Some(image);
identical_frames = 0;
}
last_now = now;
}

Ok(())
}

///
/// saves a frame as a tga file
pub fn save_frame(
image: &ImageOnHeap,
time_code: u128,
tempdir: &TempDir,
file_name_for: fn(&u128, &str) -> String,
) -> Result<()> {
save_buffer(
tempdir.path().join(file_name_for(&time_code, "tga")),
&image.samples,
image.layout.width,
image.layout.height,
image.color_hint.unwrap(),
)
.context("Cannot save frame")
}

///
/// starts the main program and keeps interacting with the user
/// blocks until termination
Expand Down

0 comments on commit 7f22d3c

Please sign in to comment.