Skip to content

Commit

Permalink
Add option to render jpeg frames instead of gif
Browse files Browse the repository at this point in the history
  • Loading branch information
sandorex committed Jan 20, 2025
1 parent 5592b97 commit ee7a2b6
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 43 deletions.
39 changes: 22 additions & 17 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ fontdb = "0.22.0"
fontdue = "0.7"
gifski = "1"
imgref = "1"
jpeg-encoder = "0.6.0"
log = "0.4"
reqwest = { version = "0.12.8", default-features = false, features = ["blocking", "rustls-tls-native-roots", "gzip"] }
resvg = { version = "0.44.0", features = ["text"] } # TODO remove default features
Expand Down
95 changes: 72 additions & 23 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
use anyhow::{anyhow, Result};
use anyhow::{anyhow, Context, Result};
use clap::ArgEnum;
use gifski::progress::{ProgressReporter, ProgressBar, NoProgress};
use log::info;
use std::fmt::{Debug, Display};
use std::io::{BufRead, Write};
use std::io::BufRead;
use std::fs::File;
use std::{iter, thread, time::Instant};
mod asciicast;
mod events;
Expand Down Expand Up @@ -36,6 +38,8 @@ pub struct Config {
pub speed: f64,
pub theme: Option<Theme>,
pub show_progress_bar: bool,
pub output_frames: bool,
pub output_filename: String,
}

impl Default for Config {
Expand All @@ -55,6 +59,8 @@ impl Default for Config {
speed: DEFAULT_SPEED,
theme: Default::default(),
show_progress_bar: true,
output_frames: false,
output_filename: "".into(),
}
}
}
Expand Down Expand Up @@ -117,7 +123,7 @@ impl Display for Theme {
}
}

pub fn run<I: BufRead, O: Write + Send>(input: I, output: O, config: Config) -> Result<()> {
pub fn run<I: BufRead>(input: I, config: Config) -> Result<()> {
let (header, events) = asciicast::open(input)?;

let terminal_size = (
Expand Down Expand Up @@ -188,30 +194,73 @@ pub fn run<I: BufRead, O: Write + Send>(input: I, output: O, config: Config) ->
let (collector, writer) = gifski::new(settings)?;
let start_time = Instant::now();

thread::scope(|s| {
let writer_handle = s.spawn(move || {
if config.show_progress_bar {
let mut pr = gifski::progress::ProgressBar::new(count);
let result = writer.write(output, &mut pr);
pr.finish();
println!();
result
} else {
let mut pr = gifski::progress::NoProgress {};
writer.write(output, &mut pr)
}
});
if config.output_frames {
// check for existance of the output directory to provide a clear error message
if std::fs::exists(&config.output_filename)? {
return Err(anyhow!("Frame output directory {:?} already exists", config.output_filename));
}

// create the directory in advance
std::fs::create_dir(&config.output_filename)?;

let mut pr: Box<dyn ProgressReporter> = if config.show_progress_bar {
Box::new(ProgressBar::new(count))
} else {
Box::new(NoProgress {})
};

for (i, (time, lines, cursor)) in frames.enumerate() {
for (i, (_, lines, cursor)) in frames.enumerate() {
let image = renderer.render(lines, cursor);
let time = if i == 0 { 0.0 } else { time };
collector.add_frame_rgba(i, image, time + config.last_frame_duration)?;
let buf = image.pixels().flat_map(|x| [x.r, x.g, x.g, x.b]).collect::<Vec<_>>();

let frame_file_path = std::path::PathBuf::new()
.join(&config.output_filename)
.join(format!("{}.jpeg", i));

// TODO argument for jpeg quality?
let encoder = jpeg_encoder::Encoder::new_file(&frame_file_path, 90)
.with_context(|| anyhow!("Error creating frame file at {:?}", frame_file_path))?;

// TODO remove unwraps
encoder.encode(&buf,
width.try_into().unwrap(),
height.try_into().unwrap(),
jpeg_encoder::ColorType::Rgba
)
.with_context(|| anyhow!("Error while encoding frame {}", i))?;

if !pr.increase() {
break;
}
}
} else {
let mut output = File::create(&config.output_filename)?;

thread::scope(|s| {
let writer_handle = s.spawn(move || {
if config.show_progress_bar {
let mut pr = ProgressBar::new(count);
let result = writer.write(&mut output, &mut pr);
pr.finish();
println!();
result
} else {
let mut pr = NoProgress {};
writer.write(output, &mut pr)
}
});

for (i, (time, lines, cursor)) in frames.enumerate() {
let image = renderer.render(lines, cursor);
let time = if i == 0 { 0.0 } else { time };
collector.add_frame_rgba(i, image, time + config.last_frame_duration)?;
}

drop(collector);
writer_handle.join().unwrap()?;
Result::<()>::Ok(())
})?;
drop(collector);
writer_handle.join().unwrap()?;
Result::<()>::Ok(())
})?;
}

info!(
"rendering finished in {}s",
Expand Down
11 changes: 8 additions & 3 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,13 @@ struct Cli {
/// asciicast path/filename or URL
input_filename_or_url: String,

/// GIF path/filename
/// GIF path/filename (output directory if outputting frames)
output_filename: String,

/// Output each frame as separate JPEG
#[clap(long)]
output_frames: bool,

/// Select frame rendering backend
#[clap(long, arg_enum, default_value_t = agg::Renderer::default())]
renderer: agg::Renderer,
Expand Down Expand Up @@ -178,9 +182,10 @@ fn main() -> Result<()> {
speed: cli.speed,
theme: cli.theme.map(|theme| theme.0),
show_progress_bar: true,
output_frames: cli.output_frames,
output_filename: cli.output_filename,
};

let input = BufReader::new(reader(&cli.input_filename_or_url)?);
let mut output = File::create(&cli.output_filename)?;
agg::run(input, &mut output, config)
agg::run(input, config)
}

0 comments on commit ee7a2b6

Please sign in to comment.