@@ -6,11 +6,13 @@ mod theme;
66mod vt;
77
88use std:: fmt:: { Debug , Display } ;
9- use std:: io:: { BufRead , Write } ;
9+ use std:: fs:: File ;
10+ use std:: io:: BufRead ;
1011use std:: { iter, thread, time:: Instant } ;
1112
12- use anyhow:: { anyhow, Result } ;
13+ use anyhow:: { anyhow, Context , Result } ;
1314use clap:: ArgEnum ;
15+ use gifski:: progress:: { NoProgress , ProgressBar , ProgressReporter } ;
1416use log:: info;
1517
1618use crate :: asciicast:: Asciicast ;
@@ -40,6 +42,8 @@ pub struct Config {
4042 pub speed : f64 ,
4143 pub theme : Option < Theme > ,
4244 pub show_progress_bar : bool ,
45+ pub output_frames : bool ,
46+ pub output_filename : String ,
4347}
4448
4549impl Default for Config {
@@ -59,6 +63,8 @@ impl Default for Config {
5963 speed : DEFAULT_SPEED ,
6064 theme : Default :: default ( ) ,
6165 show_progress_bar : true ,
66+ output_frames : false ,
67+ output_filename : "" . to_string ( ) ,
6268 }
6369 }
6470}
@@ -129,7 +135,7 @@ impl Display for Theme {
129135 }
130136}
131137
132- pub fn run < I : BufRead , O : Write + Send > ( input : I , output : O , config : Config ) -> Result < ( ) > {
138+ pub fn run < I : BufRead > ( input : I , config : Config ) -> Result < ( ) > {
133139 let Asciicast { header, events, .. } = asciicast:: open ( input) ?;
134140
135141 if header. term_cols == 0 || header. term_rows == 0 {
@@ -188,50 +194,84 @@ pub fn run<I: BufRead, O: Write + Send>(input: I, output: O, config: Config) ->
188194
189195 let ( width, height) = renderer. pixel_size ( ) ;
190196
191- info ! ( "gif dimensions: {}x{}" , width, height) ;
197+ info ! ( "output dimensions: {}x{}" , width, height) ;
192198
193- let repeat = if config. no_loop {
194- gifski:: Repeat :: Finite ( 0 )
195- } else {
196- gifski:: Repeat :: Infinite
197- } ;
199+ let start_time = Instant :: now ( ) ;
198200
199- let settings = gifski:: Settings {
200- width : Some ( width as u32 ) ,
201- height : Some ( height as u32 ) ,
202- fast : true ,
203- repeat,
204- ..Default :: default ( )
205- } ;
201+ if config. output_frames {
202+ let dir_path = std:: path:: PathBuf :: from ( & config. output_filename ) ;
206203
207- let ( collector, writer) = gifski:: new ( settings) ?;
208- let start_time = Instant :: now ( ) ;
204+ // create directory if it does not already exist
205+ if !dir_path. try_exists ( ) ? {
206+ std:: fs:: create_dir ( & dir_path) ?;
207+ }
209208
210- thread:: scope ( |s| {
211- let writer_handle = s. spawn ( move || {
212- if config. show_progress_bar {
213- let mut pr = gifski:: progress:: ProgressBar :: new ( count) ;
214- let result = writer. write ( output, & mut pr) ;
215- pr. finish ( ) ;
216- println ! ( ) ;
217- result
218- } else {
219- let mut pr = gifski:: progress:: NoProgress { } ;
220- writer. write ( output, & mut pr)
221- }
222- } ) ;
209+ // make sure output directory is empty
210+ if !dir_path. read_dir ( ) ?. next ( ) . is_none ( ) {
211+ return Err ( anyhow ! ( "Output directory is not empty" ) ) ;
212+ }
213+
214+ let mut pr: Box < dyn ProgressReporter > = if config. show_progress_bar {
215+ Box :: new ( ProgressBar :: new ( count) )
216+ } else {
217+ Box :: new ( NoProgress { } )
218+ } ;
223219
224220 for ( i, frame) in frames. enumerate ( ) {
225- let ( time, lines, cursor) = frame?;
226- let image = renderer. render ( lines, cursor) ;
227- let time = if i == 0 { 0.0 } else { time } ;
228- collector. add_frame_rgba ( i, image, time + config. last_frame_duration ) ?;
221+ let ( _, lines, cursor) = frame?;
222+
223+ let svg = renderer. render_pixmap ( lines, cursor) ;
224+ svg. save_png ( dir_path. join ( format ! ( "{}.png" , i) ) )
225+ . with_context ( || anyhow ! ( "Could not encode frame {i}" ) ) ?;
226+
227+ if !pr. increase ( ) {
228+ return Err ( anyhow ! ( "Progress bar interrupted" ) ) ;
229+ }
229230 }
231+ } else {
232+ let repeat = if config. no_loop {
233+ gifski:: Repeat :: Finite ( 0 )
234+ } else {
235+ gifski:: Repeat :: Infinite
236+ } ;
237+
238+ let settings = gifski:: Settings {
239+ width : Some ( width as u32 ) ,
240+ height : Some ( height as u32 ) ,
241+ fast : true ,
242+ repeat,
243+ ..Default :: default ( )
244+ } ;
245+
246+ let ( collector, writer) = gifski:: new ( settings) ?;
247+ let output = File :: create ( & config. output_filename ) ?;
248+
249+ thread:: scope ( |s| {
250+ let writer_handle = s. spawn ( move || {
251+ if config. show_progress_bar {
252+ let mut pr = gifski:: progress:: ProgressBar :: new ( count) ;
253+ let result = writer. write ( output, & mut pr) ;
254+ pr. finish ( ) ;
255+ println ! ( ) ;
256+ result
257+ } else {
258+ let mut pr = gifski:: progress:: NoProgress { } ;
259+ writer. write ( output, & mut pr)
260+ }
261+ } ) ;
262+
263+ for ( i, frame) in frames. enumerate ( ) {
264+ let ( time, lines, cursor) = frame?;
265+ let image = renderer. render ( lines, cursor) ;
266+ let time = if i == 0 { 0.0 } else { time } ;
267+ collector. add_frame_rgba ( i, image, time + config. last_frame_duration ) ?;
268+ }
230269
231- drop ( collector) ;
232- writer_handle. join ( ) . unwrap ( ) ?;
233- Result :: < ( ) > :: Ok ( ( ) )
234- } ) ?;
270+ drop ( collector) ;
271+ writer_handle. join ( ) . unwrap ( ) ?;
272+ Result :: < ( ) > :: Ok ( ( ) )
273+ } ) ?;
274+ }
235275
236276 info ! (
237277 "rendering finished in {}s" ,
0 commit comments