This repository has no description
0

Configure Feed

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

1use super::Video; 2use crate::ui::{Log, Pretty}; 3use crate::video::encoders::Encoder; 4use crate::video::encoders::vgv::VGVTranscodeMode; 5use crate::video::engine::EngineOutput; 6use anyhow::{Result, anyhow}; 7use measure_time::debug_time; 8use std::path::PathBuf; 9use std::thread; 10 11impl<AdditionalContext: Default> Video<AdditionalContext> { 12 pub fn encode( 13 &mut self, 14 output_file: impl Into<PathBuf> + Clone, 15 ) -> Result<std::time::Duration> { 16 debug_time!("encode"); 17 18 let encoder = self.setup_encoder(output_file.clone())?; 19 let encoder_name = encoder.name(); 20 21 let time_taken = self.encode_with(encoder)?; 22 23 let _ = notify_rust::Notification::new() 24 .appname("Shapemaker") 25 .summary(&format!("{} is ready", &output_file.into().pretty())) 26 .body(&format!( 27 "Encoded with {encoder_name} in {}", 28 time_taken.pretty() 29 )) 30 .show(); 31 32 Ok(time_taken) 33 } 34 35 fn setup_encoder( 36 &mut self, 37 output_path: impl Into<PathBuf>, 38 ) -> Result<Box<dyn Encoder + Send>> { 39 let (width, height) = 40 self.initial_canvas.resolution_to_size_even(self.resolution); 41 42 let destination = output_path.into(); 43 let pb = &self.progress_bars.encoding; 44 45 if destination.exists() { 46 std::fs::remove_file(&destination)?; 47 } 48 49 std::fs::create_dir_all( 50 &destination 51 .parent() 52 .expect("Given output file has no parent"), 53 )?; 54 55 Ok(match destination.full_extension() { 56 ".vgv.html" => { 57 self.progress_bars.encoding.log( 58 "Selecting", 59 &format!( 60 "VGV encoder with HTML transcoding as {} ends with .vgv.html", 61 destination.pretty(), 62 ), 63 ); 64 65 Box::new(self.setup_vgv_encoder( 66 VGVTranscodeMode::ToHTML, 67 width as _, 68 height as _, 69 &self.initial_canvas, 70 destination, 71 )?) 72 } 73 ".vgv" => { 74 self.progress_bars.encoding.log( 75 "Selecting", 76 &format!( 77 "VGV encoder as {} ends with .vgv (use .vgv.html for HTML transcoding)", 78 destination.pretty(), 79 ), 80 ); 81 82 Box::new(self.setup_vgv_encoder( 83 VGVTranscodeMode::None, 84 width as _, 85 height as _, 86 &self.initial_canvas, 87 destination, 88 )?) 89 } 90 _ => { 91 pb.log( 92 "Selecting", 93 &format!( 94 "FFMpeg encoder as {} ends with {}", 95 destination.pretty(), 96 destination.full_extension() 97 ), 98 ); 99 100 self.initial_canvas.load_fonts()?; 101 Box::new(self.setup_ffmpeg_encoder(width, height, destination)?) 102 } 103 }) 104 } 105 106 pub fn encode_with( 107 &mut self, 108 mut encoder: Box<dyn Encoder + Send>, 109 ) -> Result<std::time::Duration> { 110 debug_time!("encode_with"); 111 112 self.progress.remove(&self.progress_bars.loading); 113 114 let pb = self.progress_bars.encoding.clone(); 115 116 pb.set_length(self.ms_to_frames(self.duration_ms()) as _); 117 pb.set_message(""); 118 119 let (tx, rx) = std::sync::mpsc::sync_channel::<EngineOutput>(1_000); 120 121 let encoder_thread = 122 thread::spawn(move || -> Result<std::time::Duration> { 123 for output in rx.iter() { 124 match output { 125 EngineOutput::Finished => break, 126 EngineOutput::Frame { .. } => { 127 pb.inc(1); 128 pb.set_message(encoder.progress_message( 129 pb.position(), 130 pb.length().unwrap(), 131 )); 132 } 133 } 134 135 encoder.encode_frame(output)?; 136 } 137 138 let time_taken = pb.elapsed(); 139 let finish_message = encoder.finish_message(time_taken); 140 141 encoder.finish()?; 142 143 pb.finish(); 144 pb.log("Encoded", &finish_message); 145 146 Ok(time_taken) 147 }); 148 149 self.render_with_overrides(tx)?; 150 151 let time_taken = encoder_thread 152 .join() 153 .map_err(|e| anyhow!("Encoder thread panicked: {e:?}")) 154 .flatten()?; 155 156 let _ = self.progress.clear(); 157 158 Ok(time_taken) 159 } 160 161 #[allow(dead_code)] 162 fn add_audio_track(&mut self, _output_file: String) -> Result<()> { 163 todo!( 164 "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)" 165 ); 166 } 167} 168 169// Because .extension() sucks 170 171trait FullExtension { 172 fn full_extension(&self) -> &str; 173} 174 175impl FullExtension for PathBuf { 176 fn full_extension(&self) -> &str { 177 let filename = self 178 .file_name() 179 .and_then(|f| f.to_str()) 180 .unwrap_or_default(); 181 let parts: Vec<&str> = filename.split('.').collect(); 182 if parts.len() <= 1 { 183 "" 184 } else { 185 &filename[filename.find('.').unwrap()..] 186 } 187 } 188}