This repository has no description
0

Configure Feed

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

1use super::{hooks::milliseconds_to_timestamp, Video}; 2use crate::rendering::svg; 3use crate::Canvas; 4use anyhow::Result; 5use itertools::Itertools; 6use measure_time::debug_time; 7use std::fs::File; 8use std::io::Write; 9use std::thread; 10use std::time::Duration; 11use std::{fs::create_dir_all, path::PathBuf}; 12 13impl<AdditionalContext: Default> Video<AdditionalContext> { 14 pub fn encode_to_vgv( 15 &mut self, 16 output_file: impl Into<PathBuf>, 17 ) -> Result<()> { 18 debug_time!("encode_to_vgv"); 19 let output_file: PathBuf = output_file.into(); 20 21 if output_file.exists() { 22 std::fs::remove_file(&output_file)?; 23 } 24 25 create_dir_all( 26 &output_file 27 .parent() 28 .expect("Given output file has no parent"), 29 )?; 30 31 let mut file = File::create(&output_file)?; 32 33 self.progress_bar.set_position(0); 34 self.progress_bar.set_prefix("Rendering"); 35 self.progress_bar.set_message(""); 36 37 self.initial_canvas.load_fonts()?; 38 let initial_canvas = self.initial_canvas.clone(); 39 let resolution = self.resolution; 40 let (width, height) = initial_canvas.resolution_to_size_even(resolution); 41 42 let (tx, rx) = 43 std::sync::mpsc::sync_channel::<(Duration, svg::Node)>(1_000); 44 45 let pb = self.progress_bar.clone(); 46 47 let mut vgv_encoder = vgv::Encoder::new( 48 vgv::InitializationParameters { 49 w: width as _, 50 h: height as _, 51 d: (1000.0 / self.fps as f64) as _, 52 bg: initial_canvas 53 .background 54 .unwrap_or_default() 55 .render(&initial_canvas.colormap), 56 }, 57 format!( 58 r#"width={w} height={h} viewBox="-{pad} -{pad} {w} {h}""#, 59 w = initial_canvas.width(), 60 h = initial_canvas.height(), 61 pad = initial_canvas.canvas_outer_padding 62 ), 63 ); 64 65 let vgv_thread = thread::spawn(move || { 66 for (time, svg) in rx.iter() { 67 if svg.is_empty() { 68 break; 69 } 70 71 vgv_encoder.encode_svg(match svg { 72 svg::Node::Text(text) => text, 73 svg::Node::SVG(svg) => svg, 74 svg::Node::Element(element) => element 75 .children 76 .iter() 77 .map(|child| child.to_string()) 78 .join(""), 79 }); 80 81 pb.set_position(time.as_millis() as _); 82 pb.set_message(milliseconds_to_timestamp(time.as_millis() as _)); 83 } 84 85 vgv_encoder.dump(&mut file); 86 }); 87 88 self.render_all_frames(tx)?; 89 90 vgv_thread.join().expect("VGV thread panicked"); 91 92 Ok(()) 93 } 94 95 fn setup_encoder( 96 &mut self, 97 output_path: impl Into<PathBuf>, 98 ) -> anyhow::Result<std::process::Child> { 99 debug_time!("setup_encoder"); 100 let output_path: PathBuf = output_path.into(); 101 102 let (width, height) = 103 self.initial_canvas.resolution_to_size_even(self.resolution); 104 105 Ok(std::process::Command::new("ffmpeg") 106 .arg("-i") 107 .arg(self.audiofile.to_str().unwrap()) 108 .arg("-f") 109 .arg("rawvideo") 110 .arg("-pixel_format") 111 .arg("rgba") 112 .arg("-video_size") 113 .arg(format!("{width}x{height}")) 114 .arg("-framerate") 115 .arg(format!("{}", self.fps)) 116 .arg("-i") 117 .arg("-") 118 .arg("-map") 119 .arg("0:a") 120 .arg("-map") 121 .arg("1:v") 122 .arg("-shortest") 123 .arg(output_path.to_str().unwrap()) 124 .arg("-loglevel") 125 .arg(if log::log_enabled!(log::Level::Debug) { 126 "debug" 127 } else { 128 "error" 129 }) 130 .stdin(std::process::Stdio::piped()) 131 .stdout(File::create("ffmpeg_stdout.log")?) 132 .stderr(File::create("ffmpeg_stderr.log")?) 133 .spawn()?) 134 } 135 136 pub fn encode(&mut self, output_file: impl Into<PathBuf>) -> Result<()> { 137 debug_time!("encode"); 138 139 let output_file: PathBuf = output_file.into(); 140 141 if output_file.exists() { 142 std::fs::remove_file(&output_file)?; 143 } 144 145 create_dir_all( 146 &output_file 147 .parent() 148 .expect("Given output file has no parent"), 149 )?; 150 151 let mut encoder = self.setup_encoder(&output_file)?; 152 153 self.progress_bar.set_position(0); 154 self.progress_bar.set_prefix("Rendering"); 155 self.progress_bar.set_message(""); 156 157 self.initial_canvas.load_fonts()?; 158 let initial_canvas = self.initial_canvas.clone(); 159 let resolution = self.resolution; 160 161 let (tx, rx) = 162 std::sync::mpsc::sync_channel::<(Duration, svg::Node)>(1_000); 163 164 let pb = self.progress_bar.clone(); 165 166 let encoder_thread = thread::spawn(move || { 167 for (time, svg) in rx.iter() { 168 if svg.is_empty() { 169 break; 170 } 171 172 encode_frame( 173 &mut encoder, 174 resolution, 175 time, 176 &initial_canvas, 177 &svg.to_string(), 178 ) 179 .unwrap(); 180 181 pb.set_position(time.as_millis() as _); 182 pb.set_message(milliseconds_to_timestamp(time.as_millis() as _)); 183 } 184 185 encoder.stdin.take().unwrap().flush().unwrap(); 186 }); 187 188 self.render_all_frames(tx)?; 189 190 encoder_thread.join().expect("Encoder thread panicked"); 191 192 Ok(()) 193 } 194 195 #[allow(dead_code)] 196 fn add_audio_track(&mut self, _output_file: String) -> Result<()> { 197 todo!("Look into https://github.com/zmwangx/rust-ffmpeg/blob/master/examples/transcode-x264.rs and maybe contribute to video-rs (see https://github.com/oddity-ai/video-rs/issues/44)"); 198 } 199} 200 201fn encode_frame( 202 encoder: &mut std::process::Child, 203 resolution: u32, 204 _timestamp: Duration, 205 canvas: &Canvas, 206 svg: &String, 207) -> anyhow::Result<()> { 208 debug_time!("encode_frame"); 209 // Make sure that width and height are divisible by 2, as the encoder requires it 210 let (width, height) = canvas.resolution_to_size_even(resolution); 211 212 let pixmap = canvas.svg_to_pixmap(width, height, svg)?; 213 // Send frame 214 encoder.stdin.as_mut().unwrap().write_all(&pixmap.data())?; 215 216 // let frame = 217 // canvas.pixmap_to_hwc_frame((width as usize, height as usize), &pixmap)?; 218 // Ok(encoder.encode(&frame, timestamp)?) 219 Ok(()) 220}