use super::Video; use crate::Timestamp; use crate::ui::{Log, Pretty}; use crate::video::encoders::Encoder; use crate::video::engine::{EngineControl, EngineController, EngineOutput}; use anyhow::{Result, anyhow}; use itertools::Itertools; use measure_time::debug_time; use std::path::PathBuf; use std::thread; impl Video { pub fn encode( &mut self, output_file: impl Into + Clone, ) -> Result { let actual_ms_range = self.constrained_ms_range(); if actual_ms_range != self.total_ms_range() { self.progress_bars.rendering.log( "Constrained", &Timestamp::from_ms_range(&actual_ms_range).pretty(), ); } self.encode_controlled(output_file, &move |ctx| { if actual_ms_range.contains(&ctx.ms) { EngineControl::Render } else if ctx.ms > actual_ms_range.end { EngineControl::Stop } else { EngineControl::Skip } }) } pub fn encode_controlled( &mut self, output_file: impl Into + Clone, engine_controller: &EngineController, ) -> Result { debug_time!("encode"); let encoder = self.setup_encoder(output_file.clone())?; let encoder_name = encoder.name(); let result = self.encode_with(encoder, engine_controller); let _ = notify_rust::Notification::new() .appname("Shapemaker") .summary(&match result { Ok(_) => format!("{} is ready", &output_file.into().pretty()), Err(_) => format!("{} failed", &output_file.into().pretty()), }) .body(&match result { Err(ref e) => format!("Encoding failed: {e}"), Ok(time_taken) => format!( "Encoded with {encoder_name} in {}", time_taken.pretty() ), }) .show(); result } pub fn encode_with( &mut self, mut encoder: Box, engine_controller: &EngineController, ) -> Result { debug_time!("encode_with"); self.progress.remove(&self.progress_bars.loading); let pb = self.progress_bars.encoding.clone(); pb.set_length(self.ms_to_frames(self.duration_ms()) as _); pb.set_message(""); let (tx, rx) = std::sync::mpsc::sync_channel::(1_000); #[cfg(feature = "hotpath")] let (tx, rx) = hotpath::channel!((tx, rx), capacity = 1_000, log = true); let parallelism = std::thread::available_parallelism() .map(|n| n.get()) .unwrap_or(1); pb.log( "Starting", &format!("encoder with parallelism of {parallelism}"), ); let encoder_thread = thread::spawn(move || -> Result { for outputs in &rx.iter().chunks(parallelism) { match encoder.encode_frames(outputs.collect())? { std::ops::ControlFlow::Break(_) => break, _ => (), } } let time_taken = pb.elapsed(); let finish_message = encoder.finish_message(time_taken); encoder.finish()?; pb.finish(); pb.log("Encoded", &finish_message); Ok(time_taken) }); self.render(tx, engine_controller)?; let time_taken = encoder_thread .join() .map_err(|e| anyhow!("Encoder thread panicked: {e:?}")) .flatten()?; let _ = self.progress.clear(); Ok(time_taken) } #[allow(dead_code)] fn add_audio_track(&mut self, _output_file: String) -> Result<()> { 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)" ); } }