this repo has no description
3
fork

Configure Feed

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

⚗️ Pipe raw frames to ffmpeg cli in parallel while rendering

authored by

Gwenn Le Bihan and committed by
Gwenn Le Bihan
39ab8c0e b94c9bb4

+117 -99
+33 -8
Cargo.lock
··· 1148 1148 1149 1149 [[package]] 1150 1150 name = "env_logger" 1151 - version = "0.11.6" 1151 + version = "0.11.8" 1152 1152 source = "registry+https://github.com/rust-lang/crates.io-index" 1153 - checksum = "dcaee3d8e3cfc3fd92428d477bc97fc29ec8716d180c0d74c643bb26166660e0" 1153 + checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f" 1154 1154 dependencies = [ 1155 1155 "anstream", 1156 1156 "anstyle", 1157 1157 "env_filter", 1158 - "humantime", 1158 + "jiff 0.2.5", 1159 1159 "log", 1160 1160 ] 1161 1161 ··· 1749 1749 dependencies = [ 1750 1750 "bstr", 1751 1751 "itoa", 1752 - "jiff", 1752 + "jiff 0.1.16", 1753 1753 "thiserror 2.0.12", 1754 1754 ] 1755 1755 ··· 2839 2839 ] 2840 2840 2841 2841 [[package]] 2842 + name = "jiff" 2843 + version = "0.2.5" 2844 + source = "registry+https://github.com/rust-lang/crates.io-index" 2845 + checksum = "c102670231191d07d37a35af3eb77f1f0dbf7a71be51a962dcd57ea607be7260" 2846 + dependencies = [ 2847 + "jiff-static", 2848 + "log", 2849 + "portable-atomic", 2850 + "portable-atomic-util", 2851 + "serde", 2852 + ] 2853 + 2854 + [[package]] 2855 + name = "jiff-static" 2856 + version = "0.2.5" 2857 + source = "registry+https://github.com/rust-lang/crates.io-index" 2858 + checksum = "4cdde31a9d349f1b1f51a0b3714a5940ac022976f4b49485fc04be052b183b4c" 2859 + dependencies = [ 2860 + "proc-macro2", 2861 + "quote", 2862 + "syn 2.0.98", 2863 + ] 2864 + 2865 + [[package]] 2842 2866 name = "jiff-tzdb" 2843 2867 version = "0.1.4" 2844 2868 source = "registry+https://github.com/rust-lang/crates.io-index" ··· 3737 3761 3738 3762 [[package]] 3739 3763 name = "portable-atomic" 3740 - version = "1.6.0" 3764 + version = "1.11.0" 3741 3765 source = "registry+https://github.com/rust-lang/crates.io-index" 3742 - checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" 3766 + checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e" 3743 3767 3744 3768 [[package]] 3745 3769 name = "portable-atomic-util" ··· 3823 3847 3824 3848 [[package]] 3825 3849 name = "quote" 3826 - version = "1.0.36" 3850 + version = "1.0.40" 3827 3851 source = "registry+https://github.com/rust-lang/crates.io-index" 3828 - checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" 3852 + checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" 3829 3853 dependencies = [ 3830 3854 "proc-macro2", 3831 3855 ] ··· 4243 4267 name = "schedule-hell-backbone" 4244 4268 version = "0.1.0" 4245 4269 dependencies = [ 4270 + "env_logger", 4246 4271 "ffmpeg-next", 4247 4272 "rand 0.9.0", 4248 4273 "shapemaker",
+1
examples/schedule-hell-backbone/Cargo.toml
··· 4 4 edition = "2024" 5 5 6 6 [dependencies] 7 + env_logger = "0.11.8" 7 8 ffmpeg-next = "7.1.0" 8 9 rand = "0.9.0" 9 10 shapemaker = { version = "1.2.2", path = "../.." }
+5 -4
examples/schedule-hell-backbone/src/main.rs
··· 5 5 const SEED: u64 = 0; 6 6 7 7 fn main() { 8 + env_logger::init(); 8 9 let mut canvas = Canvas::new(vec![ 9 10 "flickers_occlusions", 10 11 "flickers", ··· 25 26 26 27 let mut video = Video::<Ctx>::new(canvas); 27 28 video.audiofile = "../schedule-hell/schedule-hell.flac".into(); 28 - video.resolution = 720; 29 - video.duration_override = Some(3_000); 29 + video.fps = 60; 30 + video.resolution = 480; 31 + video.duration_override = Some(30_000); 30 32 video 31 33 .sync_audio_with("../schedule-hell/schedule-hell.midi") 32 - .each_n_frame(10, &|canvas, ctx| { 33 - // canvas.set_background(ctx.extra.rng.random()); 34 + .init(&|canvas, ctx| { 34 35 backbone(&mut ctx.extra.rng, canvas); 35 36 Ok(()) 36 37 })
+77 -82
src/video/encoding.rs
··· 1 - extern crate ffmpeg_next as ffmpeg; 2 1 use super::{context::Context, engine::milliseconds_to_timestamp, Video}; 3 2 use crate::rendering::stringify_svg; 4 - use crate::{ui::Log, Canvas, SVGRenderable}; 3 + use crate::{Canvas, SVGRenderable}; 5 4 use anyhow::Result; 6 5 use indicatif::ProgressIterator; 7 6 use measure_time::debug_time; 8 7 use rayon::iter::ParallelIterator; 9 8 use rayon::{iter::IndexedParallelIterator, slice::ParallelSliceMut}; 10 - use std::sync::MutexGuard; 11 - use std::{ 12 - fs::create_dir_all, 13 - path::{Path, PathBuf}, 14 - str::FromStr, 15 - sync::{Arc, Mutex}, 16 - }; 9 + use std::io::Write; 10 + use std::sync::mpsc::Sender; 11 + use std::thread; 12 + use std::{fs::create_dir_all, path::PathBuf}; 17 13 use video_rs::Time; 18 14 19 15 impl Canvas { ··· 59 55 fn setup_encoder( 60 56 &mut self, 61 57 output_path: impl Into<PathBuf>, 62 - ) -> anyhow::Result<()> { 58 + ) -> anyhow::Result<std::process::Child> { 63 59 debug_time!("setup_encoder"); 64 60 let output_path: PathBuf = output_path.into(); 65 61 66 62 let (width, height) = 67 63 self.initial_canvas.resolution_to_size_even(self.resolution); 68 64 69 - let settings = video_rs::encode::Settings::preset_h264_yuv420p( 70 - width as usize, 71 - height as usize, 72 - false, 73 - ); 74 - 75 - ffmpeg_next::encoder::find_by_name("libx264") 76 - .expect("Failed to find libx264 encoder"); 77 - 78 - self.encoder = Some(Arc::new(Mutex::new( 79 - video_rs::Encoder::new(output_path, settings) 80 - .expect("Failed to build encoder"), 81 - ))); 82 - 83 - Ok(()) 65 + Ok(std::process::Command::new("ffmpeg") 66 + .arg("-i") 67 + .arg(self.audiofile.to_str().unwrap()) 68 + .arg("-f") 69 + .arg("rawvideo") 70 + .arg("-pixel_format") 71 + .arg("rgba") 72 + .arg("-video_size") 73 + .arg(format!("{width}x{height}")) 74 + .arg("-framerate") 75 + .arg(format!("{}", self.fps)) 76 + .arg("-i") 77 + .arg("-") 78 + .arg("-map") 79 + .arg("0:a") 80 + .arg("-map") 81 + .arg("1:v") 82 + .arg("-shortest") 83 + .arg(output_path.to_str().unwrap()) 84 + .arg("-loglevel") 85 + .arg("error") 86 + .stdin(std::process::Stdio::piped()) 87 + .spawn()?) 84 88 } 85 89 86 - pub fn render_frames(&mut self) -> Result<usize> { 90 + pub fn render_frames( 91 + &self, 92 + output: Sender<(video_rs::Time, String)>, 93 + ) -> Result<usize> { 87 94 debug_time!("render_frames"); 88 95 let mut written_frames_count: usize = 0; 89 96 let mut context = Context { ··· 109 116 110 117 self.progress_bar.set_length(render_ms_range.len() as u64); 111 118 112 - let mut frames_to_encode: Vec<(Time, String)> = vec![]; 113 - 114 119 for _ in render_ms_range 115 120 .into_iter() 116 121 .progress_with(self.progress_bar.clone()) ··· 142 147 .trim_start_matches(&command.name) 143 148 .trim() 144 149 .to_string(); 145 - (command.action)(args, &mut canvas, &mut context)?; 150 + (command.action)(args, &mut canvas, &mut context) 151 + .unwrap(); 146 152 } 147 153 } 148 154 } ··· 154 160 155 161 for (i, hook) in context.later_hooks.iter().enumerate() { 156 162 if (hook.when)(&canvas, &context, previous_rendered_beat) { 157 - (hook.render_function)(&mut canvas, context.ms)?; 163 + (hook.render_function)(&mut canvas, context.ms).unwrap(); 158 164 if hook.once { 159 165 later_hooks_to_delete.push(i); 160 166 } ··· 176 182 previous_rendered_beat, 177 183 previous_rendered_frame, 178 184 ) { 179 - (hook.render_function)(&mut canvas, &mut context)?; 185 + (hook.render_function)(&mut canvas, &mut context).unwrap(); 180 186 } 181 187 } 182 188 183 189 if context.frame != previous_rendered_frame { 184 - frames_to_encode.push(( 190 + output.send(( 185 191 Time::from_secs_f64(context.ms as f64 * 1e-3), 186 - stringify_svg(canvas.render_to_svg( 187 - canvas.colormap.clone(), 188 - canvas.cell_size, 189 - canvas.object_sizes, 190 - "", 191 - )?), 192 + stringify_svg( 193 + canvas 194 + .render_to_svg( 195 + canvas.colormap.clone(), 196 + canvas.cell_size, 197 + canvas.object_sizes, 198 + "", 199 + ) 200 + .unwrap(), 201 + ), 192 202 )); 193 203 194 204 written_frames_count += 1; ··· 198 208 } 199 209 } 200 210 201 - self.initial_canvas.load_fonts()?; 202 - 203 - self.progress_bar.set_position(0); 204 - self.progress_bar.set_length(frames_to_encode.len() as u64); 205 - self.progress_bar.set_message("Encoding"); 206 - 207 - for (time, svg) in frames_to_encode 208 - .into_iter() 209 - .progress_with(self.progress_bar.clone()) 210 - { 211 - encode_frame( 212 - self.encoder 213 - .as_mut() 214 - .expect("Encoder was not initalized") 215 - .lock() 216 - .unwrap(), 217 - self.resolution, 218 - time, 219 - &canvas, 220 - &svg, 221 - )?; 222 - } 223 - 224 - self.progress_bar.finish(); 225 - 226 211 Ok(written_frames_count) 227 212 } 228 213 ··· 231 216 232 217 let output_file: PathBuf = output_file.into(); 233 218 234 - // create_dir_all(self.frames_output_directory)?; 235 - // remove_dir_all(self.frames_output_directory)?; 236 - // create_dir(self.frames_output_directory)?; 237 219 create_dir_all(&output_file.parent().unwrap())?; 238 220 239 - self.setup_encoder(&output_file)?; 221 + let mut encoder = self.setup_encoder(&output_file)?; 240 222 241 223 self.progress_bar.set_position(0); 242 224 self.progress_bar.set_prefix("Rendering"); 243 225 self.progress_bar.set_message(""); 244 226 245 - let frames_written = self.render_frames()?; 227 + self.initial_canvas.load_fonts()?; 228 + let initial_canvas = self.initial_canvas.clone(); 229 + let resolution = self.resolution; 230 + 231 + let (tx, rx) = std::sync::mpsc::channel(); 246 232 247 - self.encoder 248 - .as_mut() 249 - .expect("Encoder is missing somehow") 250 - .lock() 251 - .unwrap() 252 - .finish()?; 233 + thread::spawn(move || { 234 + for (time, svg) in rx.into_iter() { 235 + encode_frame( 236 + &mut encoder, 237 + resolution, 238 + time, 239 + &initial_canvas, 240 + &svg, 241 + ) 242 + .unwrap(); 243 + } 244 + 245 + encoder.stdin.take().unwrap().flush().unwrap(); 246 + }); 253 247 254 - self.progress_bar.log( 255 - "Rendered", 256 - &format!("{} frames to {:?}", frames_written, output_file), 257 - ); 248 + self.render_frames(tx)?; 258 249 259 250 self.progress_bar.set_position(0); 260 251 self.progress_bar.set_prefix("Adding"); ··· 270 261 } 271 262 272 263 fn encode_frame( 273 - mut encoder: MutexGuard<video_rs::Encoder>, 264 + encoder: &mut std::process::Child, 274 265 resolution: u32, 275 266 timestamp: Time, 276 267 canvas: &Canvas, ··· 281 272 let (width, height) = canvas.resolution_to_size_even(resolution); 282 273 283 274 let pixmap = canvas.svg_to_pixmap(width, height, svg)?; 284 - let frame = 285 - canvas.pixmap_to_hwc_frame((width as usize, height as usize), &pixmap)?; 286 - Ok(encoder.encode(&frame, timestamp)?) 275 + // Send frame 276 + encoder.stdin.as_mut().unwrap().write_all(&pixmap.data())?; 277 + 278 + // let frame = 279 + // canvas.pixmap_to_hwc_frame((width as usize, height as usize), &pixmap)?; 280 + // Ok(encoder.encode(&frame, timestamp)?) 281 + Ok(()) 287 282 }
+1 -5
src/video/engine.rs
··· 19 19 20 20 pub type RenderFunction<C> = 21 21 dyn Fn(&mut Canvas, &mut Context<C>) -> anyhow::Result<()>; 22 + 22 23 pub type CommandAction<C> = 23 24 dyn Fn(String, &mut Canvas, &mut Context<C>) -> anyhow::Result<()>; 24 25 ··· 46 47 pub duration_override: Option<usize>, 47 48 pub start_rendering_at: usize, 48 49 pub progress_bar: indicatif::ProgressBar, 49 - 50 - #[cfg(feature = "mp4")] 51 - pub encoder: Option<Arc<Mutex<video_rs::Encoder>>>, 52 50 } 53 51 54 52 pub struct Hook<C> { ··· 107 105 duration_override: None, 108 106 start_rendering_at: 0, 109 107 progress_bar: setup_progress_bar(0, ""), 110 - #[cfg(feature = "mp4")] 111 - encoder: None, 112 108 } 113 109 } 114 110