This repository has no description
1use super::Video;
2use crate::Timestamp;
3use crate::ui::{Log, Pretty};
4use crate::video::encoders::Encoder;
5use crate::video::engine::{EngineControl, EngineController, EngineOutput};
6use anyhow::{Result, anyhow};
7use itertools::Itertools;
8use measure_time::debug_time;
9use std::path::PathBuf;
10use std::thread;
11
12impl<C: Default> Video<C> {
13 pub fn encode(
14 &mut self,
15 output_file: impl Into<PathBuf> + Clone,
16 ) -> Result<std::time::Duration> {
17 let actual_ms_range = self.constrained_ms_range();
18 if actual_ms_range != self.total_ms_range() {
19 self.progress_bars.rendering.log(
20 "Constrained",
21 &Timestamp::from_ms_range(&actual_ms_range).pretty(),
22 );
23 }
24
25 self.encode_controlled(output_file, &move |ctx| {
26 if actual_ms_range.contains(&ctx.ms) {
27 EngineControl::Render
28 } else if ctx.ms > actual_ms_range.end {
29 EngineControl::Stop
30 } else {
31 EngineControl::Skip
32 }
33 })
34 }
35
36 pub fn encode_controlled(
37 &mut self,
38 output_file: impl Into<PathBuf> + Clone,
39 engine_controller: &EngineController<C>,
40 ) -> Result<std::time::Duration> {
41 debug_time!("encode");
42
43 let encoder = self.setup_encoder(output_file.clone())?;
44 let encoder_name = encoder.name();
45
46 let result = self.encode_with(encoder, engine_controller);
47
48 let _ = notify_rust::Notification::new()
49 .appname("Shapemaker")
50 .summary(&match result {
51 Ok(_) => format!("{} is ready", &output_file.into().pretty()),
52 Err(_) => format!("{} failed", &output_file.into().pretty()),
53 })
54 .body(&match result {
55 Err(ref e) => format!("Encoding failed: {e}"),
56 Ok(time_taken) => format!(
57 "Encoded with {encoder_name} in {}",
58 time_taken.pretty()
59 ),
60 })
61 .show();
62
63 result
64 }
65
66 pub fn encode_with(
67 &mut self,
68 mut encoder: Box<dyn Encoder + Send>,
69 engine_controller: &EngineController<C>,
70 ) -> Result<std::time::Duration> {
71 debug_time!("encode_with");
72
73 self.progress.remove(&self.progress_bars.loading);
74
75 let pb = self.progress_bars.encoding.clone();
76
77 pb.set_length(self.ms_to_frames(self.duration_ms()) as _);
78 pb.set_message("");
79
80 let (tx, rx) = std::sync::mpsc::sync_channel::<EngineOutput>(1_000);
81
82 #[cfg(feature = "hotpath")]
83 let (tx, rx) = hotpath::channel!((tx, rx), capacity = 1_000, log = true);
84
85 let parallelism = std::thread::available_parallelism()
86 .map(|n| n.get())
87 .unwrap_or(1);
88
89 pb.log(
90 "Starting",
91 &format!("encoder with parallelism of {parallelism}"),
92 );
93
94 let encoder_thread =
95 thread::spawn(move || -> Result<std::time::Duration> {
96 for outputs in &rx.iter().chunks(parallelism) {
97 match encoder.encode_frames(outputs.collect())? {
98 std::ops::ControlFlow::Break(_) => break,
99 _ => (),
100 }
101 }
102
103 let time_taken = pb.elapsed();
104 let finish_message = encoder.finish_message(time_taken);
105
106 encoder.finish()?;
107
108 pb.finish();
109 pb.log("Encoded", &finish_message);
110
111 Ok(time_taken)
112 });
113
114 self.render(tx, engine_controller)?;
115
116 let time_taken = encoder_thread
117 .join()
118 .map_err(|e| anyhow!("Encoder thread panicked: {e:?}"))
119 .flatten()?;
120
121 let _ = self.progress.clear();
122
123 Ok(time_taken)
124 }
125
126 #[allow(dead_code)]
127 fn add_audio_track(&mut self, _output_file: String) -> Result<()> {
128 todo!(
129 "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)"
130 );
131 }
132}