This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

at main 5.1 kB View raw
1use crate::{ 2 Video, 3 rendering::rasterization::{create_pixmap, paint_svg_on_pixmap}, 4 ui::Pretty, 5 video::{encoders::Encoder, engine::EngineOutput}, 6}; 7use anyhow::{Result, anyhow}; 8use measure_time::debug_time; 9use rayon::prelude::*; 10use std::{fs::File, io::Write, ops::ControlFlow, path::PathBuf, sync::Arc}; 11 12pub struct FFMpegEncoder { 13 progress: indicatif::ProgressBar, 14 process: std::process::Child, 15 output_size: (u32, u32), 16 fontdb: Option<Arc<resvg::usvg::fontdb::Database>>, 17 destination: PathBuf, 18} 19 20impl<C: Default> Video<C> { 21 pub fn setup_ffmpeg_encoder( 22 &self, 23 width: u32, 24 height: u32, 25 output_path: PathBuf, 26 ) -> Result<FFMpegEncoder> { 27 debug_time!("setup_encoder"); 28 let output_path: PathBuf = output_path.into(); 29 30 let mut command = std::process::Command::new("ffmpeg"); 31 command 32 // Audio // 33 // Take non-0 starting point into account 34 .args(["-ss", &self.start_rendering_at.seconds_string()]) 35 // File 36 .args(["-i", self.audiofile.to_str().unwrap()]) 37 // 38 // Video // 39 // Raw video input 40 .args(["-f", "rawvideo"]) 41 // RGBA Pixels 42 .args(["-pixel_format", "rgba"]) 43 // Dimensions 44 .args(["-video_size", &format!("{width}x{height}")]) 45 // FPS 46 .args(["-framerate", &self.fps.to_string()]) 47 // Input from pipe 48 .args(["-i", "-"]) 49 .stdin(std::process::Stdio::piped()) 50 // 51 // Mapping // 52 // Audio from first input 53 .args(["-map", "0:a"]) 54 // Video from second input 55 .args(["-map", "1:v"]) 56 // Use shortest stream for final duration 57 .arg("-shortest") 58 // 59 // Output // 60 // Use 4:2:0 (4:4:4 is not widely supported) 61 .args(["-pix_fmt", "yuv420p"]) 62 // Write to file 63 .arg(output_path.to_str().unwrap()) 64 // Debug ffmpeg too if shapemaker is debugging 65 .args([ 66 "-loglevel", 67 (if log::log_enabled!(log::Level::Debug) { 68 "debug" 69 } else { 70 "error" 71 }), 72 ]) 73 // Put stdout/stderr here so that it doesn't mess with progress bars 74 .stdout(File::create("ffmpeg_stdout.log")?) 75 .stderr(File::create("ffmpeg_stderr.log")?); 76 77 let commandline = format!("{:?}", &command); 78 79 Ok(FFMpegEncoder { 80 destination: output_path.clone(), 81 fontdb: self.initial_canvas.fontdb.clone(), 82 output_size: (width, height), 83 progress: self.progress_bars.encoding.clone(), 84 process: command 85 .spawn() 86 .map_err(|e| anyhow!("Could not run {commandline}: {e:?}",))?, 87 }) 88 } 89} 90 91impl Encoder for FFMpegEncoder { 92 fn name(&self) -> String { 93 "FFMpeg".into() 94 } 95 96 fn encode_frames( 97 &mut self, 98 outputs: Vec<EngineOutput>, 99 ) -> Result<ControlFlow<()>> { 100 let (width, height) = self.output_size; 101 102 let mut rasterizations: Vec<_> = outputs 103 .par_iter() 104 .filter_map(|output| match output { 105 EngineOutput::Finished => None, 106 EngineOutput::Frame { index, size, svg } => Some({ 107 debug_time!("encode_frame"); 108 // Make sure that width and height are divisible by 2, as the encoder requires it 109 110 let mut pixmap = create_pixmap(width, height); 111 112 match paint_svg_on_pixmap( 113 pixmap.as_mut(), 114 &svg.to_string(), 115 *size, 116 &self.fontdb, 117 ) { 118 Ok(..) => Some(Ok((index, pixmap.data().to_vec()))), 119 Err(e) => Some(Err(e)), 120 } 121 }), 122 }) 123 .collect(); 124 125 rasterizations.sort_by_cached_key(|r| match r { 126 Some(Ok((index, _))) => **index, 127 _ => 0, 128 }); 129 130 for rasterization in rasterizations { 131 match rasterization { 132 None => return Ok(ControlFlow::Break(())), 133 Some(Ok((index, data))) => { 134 self.process.stdin.as_mut().unwrap().write_all(&data)?; 135 self.progress.inc(1); 136 self.progress.set_message(format!("{index}th frame",)); 137 } 138 Some(Err(e)) => return Err(e), 139 } 140 } 141 142 Ok(ControlFlow::Continue(())) 143 } 144 145 fn finish(&mut self) -> Result<()> { 146 self.process.stdin.take().unwrap().flush().unwrap(); 147 Ok(()) 148 } 149 150 fn finish_message(&self, time_elapsed: std::time::Duration) -> String { 151 format!( 152 "video to {} in {}", 153 self.destination.pretty(), 154 time_elapsed.pretty() 155 ) 156 } 157}