This repository has no description
0

Configure Feed

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

๐Ÿ’„ Improve progress bars and log messages

+210 -122
+1
Cargo.lock
··· 4595 4595 "nanoid", 4596 4596 "ndarray", 4597 4597 "nih_plug", 4598 + "num-traits", 4598 4599 "once_cell", 4599 4600 "quick-xml", 4600 4601 "rand 0.9.2",
+1
Cargo.toml
··· 109 109 quick-xml = "0.38.3" 110 110 vgv = { git = "https://github.com/gwennlbh/vgvf", version = "0.1.0", optional = true} 111 111 serde-aux = "4.7.0" 112 + num-traits = "0.2.19" 112 113 113 114 114 115 [dev-dependencies]
-10
src/synchronization/midi.rs
··· 223 223 ); 224 224 } 225 225 226 - progressbar.log( 227 - "Detected", 228 - &format!( 229 - "MIDI file {} with {} stems and initial tempo of {} BPM", 230 - source.to_str().unwrap(), 231 - track_names.len(), 232 - tempo_to_bpm(now.tempo) 233 - ), 234 - ); 235 - 236 226 // Convert ticks to absolute 237 227 let mut track_no = 0; 238 228 for track in midifile.tracks.iter() {
+37 -23
src/ui.rs
··· 3 3 use console::Style; 4 4 use indicatif::{ProgressBar, ProgressStyle}; 5 5 use itertools::Itertools; 6 + use num_traits::ops::euclid::Euclid; 6 7 use std::borrow::Cow; 7 8 use std::collections::HashMap; 9 + use std::ops::Range; 8 10 use std::sync::{Arc, Mutex}; 9 11 use std::thread::{self, JoinHandle}; 10 - use std::time; 12 + use std::time::{self, Duration}; 11 13 12 14 pub const PROGRESS_BARS_STYLE: &str = 13 15 "\x1b]9;4;1;{percent}\x1b\\{prefix:>12.bold.cyan} {percent:03}% [{bar:25}] {msg} ({elapsed} ago)"; ··· 96 98 } 97 99 } 98 100 99 - pub trait EngineProgressBar { 100 - fn step_with_engine(&self, progression: EngineProgression); 101 - } 102 - 103 - impl EngineProgressBar for ProgressBar { 104 - fn step_with_engine(&self, progression: EngineProgression) { 105 - let EngineProgression { 106 - ms, 107 - scene_name, 108 - timestamp, 109 - } = progression; 110 - 111 - self.set_position(ms as _); 112 - self.set_message(match scene_name { 113 - Some(scene) => format!("{}: {}", timestamp, scene), 114 - None => format!("{}", timestamp), 115 - }); 116 - } 117 - } 118 - 119 101 pub trait MaybeProgressBar<'a> { 120 102 fn set_message(&'a self, message: impl Into<Cow<'static, str>>); 121 103 fn inc(&'a self, n: u64); ··· 155 137 .join(", ") 156 138 } 157 139 158 - pub fn format_duration(duration: impl IntoTimestamp) -> String { 140 + pub(crate) fn format_timestamp(duration: impl IntoTimestamp) -> String { 159 141 format!( 160 142 "{}", 161 143 DateTime::from_timestamp_millis(duration.as_millis() as i64) ··· 164 146 ) 165 147 } 166 148 167 - trait IntoTimestamp { 149 + pub(crate) fn format_duration(duration: Duration) -> String { 150 + let (hours, rest) = duration.as_millis().div_rem_euclid(&3_600_000); 151 + let (minutes, rest) = rest.div_rem_euclid(&60_000); 152 + let (seconds, milliseconds) = rest.div_rem_euclid(&1_000); 153 + 154 + if hours > 0 { 155 + format!("{} h {:02} m {:02} s", hours, minutes, seconds) 156 + } else if minutes > 0 { 157 + format!("{} m {:02} s", minutes, seconds) 158 + } else if seconds > 0 { 159 + format!("{}.{:03} s", seconds, milliseconds) 160 + } else { 161 + format!("{} ms", milliseconds) 162 + } 163 + } 164 + 165 + pub(crate) fn format_timestamp_range(ms_range: &Range<usize>) -> String { 166 + format!( 167 + "from {} to {}", 168 + format_timestamp(ms_range.start), 169 + format_timestamp(ms_range.end) 170 + ) 171 + } 172 + 173 + pub(crate) fn format_filepath(path: &std::path::Path) -> String { 174 + format!( 175 + "{}{}", 176 + if path.is_relative() { "./" } else { "" }, 177 + path.to_string_lossy() 178 + ) 179 + } 180 + 181 + pub(crate) trait IntoTimestamp { 168 182 fn as_millis(&self) -> usize; 169 183 } 170 184
+2 -1
src/video/context.rs
··· 22 22 pub duration_override: Option<usize>, 23 23 pub current_scene: Option<String>, 24 24 pub scene_started_at_ms: Option<usize>, 25 + pub rendered_frames: usize, 25 26 } 26 27 27 28 impl<C> Context<'_, C> { 28 29 pub fn timestamp(&self) -> String { 29 - ui::format_duration(self.ms).to_string() 30 + ui::format_timestamp(self.ms).to_string() 30 31 } 31 32 32 33 pub fn beat_fractional(&self) -> f32 {
+45 -19
src/video/encoding.rs
··· 1 1 use super::Video; 2 2 use crate::rendering::svg; 3 - use crate::ui::EngineProgressBar; 4 - use crate::video::engine::EngineOutput; 5 - use crate::Canvas; 3 + use crate::ui::{format_duration, format_timestamp}; 4 + use crate::video::engine::{EngineControl, EngineOutput}; 5 + use crate::{ui::Log, Canvas}; 6 6 use anyhow::Result; 7 + use chrono::format; 7 8 use itertools::Itertools; 8 9 use measure_time::debug_time; 9 10 use std::fs::File; ··· 31 32 32 33 let mut file = File::create(&output_file)?; 33 34 34 - self.progress_bar.set_position(0); 35 - self.progress_bar.set_prefix("Rendering"); 36 - self.progress_bar.set_message(""); 35 + let pb = self.progress_bars.encoding.clone(); 36 + pb.set_position(0); 37 + pb.set_length(self.ms_to_frames(self.duration_ms()) as _); 38 + pb.set_prefix("Encoding"); 39 + pb.set_message(format!( 40 + "with VGV, to {}{}", 41 + if output_file.is_relative() { "./" } else { "" }, 42 + output_file.to_string_lossy(), 43 + )); 37 44 38 45 self.initial_canvas.load_fonts()?; 39 46 let initial_canvas = self.initial_canvas.clone(); ··· 41 48 let (width, height) = initial_canvas.resolution_to_size_even(resolution); 42 49 43 50 let (tx, rx) = std::sync::mpsc::sync_channel::<EngineOutput>(10_000); 44 - 45 - let pb = self.progress_bar.clone(); 46 51 47 52 let mut vgv_encoder = vgv::Encoder::new(vgv::Frame::Initialization { 48 53 w: width as _, ··· 66 71 for output in rx.iter() { 67 72 match output { 68 73 EngineOutput::Finished => break, 69 - EngineOutput::Frame(progression, ref svg) => { 70 - pb.step_with_engine(progression); 74 + EngineOutput::Frame(ref svg) => { 75 + pb.inc(1); 71 76 vgv_encoder.encode_svg(match svg { 72 77 svg::Node::Text(text) => text.to_string(), 73 78 svg::Node::SVG(svg) => svg.to_string(), ··· 84 89 vgv_encoder.dump(&mut file); 85 90 }); 86 91 87 - self.render_all_frames(tx)?; 92 + self.render_with_overrides(tx)?; 88 93 89 94 vgv_thread.join().expect("VGV thread panicked"); 95 + 96 + self.progress_bars.encoding.finish(); 90 97 91 98 Ok(()) 92 99 } ··· 135 142 pub fn encode(&mut self, output_file: impl Into<PathBuf>) -> Result<()> { 136 143 debug_time!("encode"); 137 144 145 + self.progress.remove(&self.progress_bars.loading); 146 + 138 147 let output_file: PathBuf = output_file.into(); 139 148 140 149 if output_file.exists() { ··· 149 158 150 159 let mut encoder = self.setup_encoder(&output_file)?; 151 160 152 - self.progress_bar.set_position(0); 153 - self.progress_bar.set_prefix("Rendering"); 154 - self.progress_bar.set_message(""); 161 + let pb = self.progress_bars.encoding.clone(); 162 + 163 + pb.set_length(self.ms_to_frames(self.duration_ms()) as _); 164 + pb.set_message(""); 155 165 156 166 self.initial_canvas.load_fonts()?; 157 167 let initial_canvas = self.initial_canvas.clone(); 158 168 let resolution = self.resolution; 159 169 160 170 let (tx, rx) = std::sync::mpsc::sync_channel::<EngineOutput>(1_000); 161 - 162 - let pb = self.progress_bar.clone(); 163 171 164 172 let encoder_thread = thread::spawn(move || { 165 173 for output in rx.iter() { 166 174 match output { 167 175 EngineOutput::Finished => break, 168 - EngineOutput::Frame(progression, svg) => { 169 - pb.step_with_engine(progression); 176 + EngineOutput::Frame(svg) => { 177 + pb.inc(1); 178 + pb.set_message(format!( 179 + "{}/{} frames", 180 + pb.position(), 181 + pb.length().unwrap() 182 + )); 170 183 encode_frame( 171 184 &mut encoder, 172 185 resolution, ··· 181 194 encoder.stdin.take().unwrap().flush().unwrap(); 182 195 }); 183 196 184 - self.render_all_frames(tx)?; 197 + self.render_with_overrides(tx)?; 185 198 186 199 encoder_thread.join().expect("Encoder thread panicked"); 200 + 201 + self.progress_bars.encoding.finish(); 202 + self.progress_bars.encoding.log( 203 + "Encoded", 204 + &format!( 205 + "video to {}{} in {}", 206 + if output_file.is_relative() { "./" } else { "" }, 207 + output_file.to_string_lossy(), 208 + format_duration(self.progress_bars.encoding.elapsed()) 209 + ), 210 + ); 211 + 212 + self.progress.clear().unwrap(); 187 213 188 214 Ok(()) 189 215 }
+70 -45
src/video/engine.rs
··· 1 1 use super::{context::Context, Video}; 2 2 use crate::rendering::svg; 3 - use crate::{Canvas, SVGRenderable}; 3 + use crate::ui::{format_duration, format_timestamp_range, Log}; 4 + use crate::{ui, Canvas, SVGRenderable}; 4 5 use anyhow::Result; 5 6 use measure_time::debug_time; 6 7 use std::sync::mpsc::SyncSender; ··· 8 9 /// What data is sent to the output by the rendering engine for each rendered frame 9 10 pub enum EngineOutput { 10 11 Finished, 11 - Frame(EngineProgression, svg::Node), 12 + Frame(svg::Node), 12 13 } 13 14 14 15 pub struct EngineProgression { ··· 35 36 ) -> Result<usize> { 36 37 debug_time!("render"); 37 38 38 - let mut rendered_frames_count: usize = 0; 39 39 let mut context = Context { 40 - ms: self.start_rendering_at, 40 + rendered_frames: 0, 41 + ms: 0, 41 42 current_scene: None, 42 43 fps: self.fps, 43 44 syncdata: &self.syncdata, ··· 57 58 let mut previous_rendered_beat = 0; 58 59 let mut previous_rendered_frame = 0; 59 60 60 - let render_ms_range = self.start_rendering_at + 0..self.duration_ms(); 61 + let pb = self.progress_bars.rendering.clone(); 62 + pb.set_prefix("Rendering"); 63 + pb.set_message(""); 64 + pb.set_position(0); 65 + pb.set_length(self.duration_ms() as _); 61 66 62 - self.progress_bar.set_length(render_ms_range.len() as u64); 63 - 64 - for _ in render_ms_range { 65 - context.ms += 1_usize; 67 + for _ in 0..self.total_duration_ms() { 68 + context.ms += 1; 66 69 67 70 let control = controller(&context); 68 71 ··· 81 84 continue; 82 85 } 83 86 84 - if let EngineControl::RenderFromCanvas(new_canvas) = control { 85 - canvas = new_canvas; 86 - } 87 + pb.inc(1); 88 + pb.set_message(match context.current_scene { 89 + Some(ref scene) => format!("{}: {}", context.timestamp(), scene), 90 + None => context.timestamp(), 91 + }); 87 92 88 93 if context.marker().starts_with(':') { 89 94 let marker_text = context.marker(); ··· 134 139 } 135 140 136 141 if !skip_rendering && context.frame() != previous_rendered_frame { 137 - output.send(EngineOutput::Frame( 138 - context.engine_progression(), 139 - canvas.render_to_svg( 140 - canvas.colormap.clone(), 141 - canvas.cell_size, 142 - canvas.object_sizes, 143 - "", 144 - )?, 145 - ))?; 142 + output.send(EngineOutput::Frame(canvas.render_to_svg( 143 + canvas.colormap.clone(), 144 + canvas.cell_size, 145 + canvas.object_sizes, 146 + "", 147 + )?))?; 146 148 147 - rendered_frames_count += 1; 149 + context.rendered_frames += 1; 148 150 149 151 previous_rendered_beat = context.beat(); 150 152 previous_rendered_frame = context.frame(); 151 153 } 152 154 153 155 if stop_after { 154 - println!( 155 - "Stopping rendering as requested after frame {}", 156 - context.frame() 157 - ); 158 156 break; 159 157 } 160 158 } 161 159 162 160 output.send(EngineOutput::Finished)?; 163 161 164 - Ok(rendered_frames_count) 162 + pb.finish(); 163 + pb.log( 164 + "Rendered", 165 + &format!( 166 + "{} frames in {}", 167 + context.rendered_frames, 168 + format_duration(pb.elapsed()) 169 + ), 170 + ); 171 + self.progress.remove(&pb); 172 + 173 + Ok(context.rendered_frames) 165 174 } 166 175 167 - pub fn render_single_frame( 168 - &self, 169 - frame_no: usize, 170 - ) -> Result<(String, svg::Node)> { 176 + pub fn render_single_frame(&self, frame_no: usize) -> Result<svg::Node> { 171 177 debug_time!("render_single_frame"); 172 178 let (tx, rx) = std::sync::mpsc::sync_channel::<EngineOutput>(2); 173 179 ··· 175 181 if ctx.frame() == frame_no { 176 182 EngineControl::Finish 177 183 } else if ctx.frame() < frame_no { 178 - EngineControl::Skip 184 + EngineControl::Walk 179 185 } else { 180 186 EngineControl::Stop 181 187 } ··· 185 191 for output in rx.iter() { 186 192 match output { 187 193 EngineOutput::Finished => break, 188 - EngineOutput::Frame(progression, svg) => { 189 - return Ok((progression.timestamp, svg)) 190 - } 194 + EngineOutput::Frame(svg) => return Ok(svg), 191 195 } 192 196 } 193 197 ··· 196 200 )); 197 201 } 198 202 199 - pub fn render_all_frames( 203 + pub fn render_everything( 200 204 &self, 201 205 output: SyncSender<EngineOutput>, 202 206 ) -> Result<usize> { 203 207 self.render(output, |_| EngineControl::Render) 204 208 } 209 + 210 + pub fn render_with_overrides( 211 + &self, 212 + output: SyncSender<EngineOutput>, 213 + ) -> Result<usize> { 214 + let start = self.start_rendering_at; 215 + let actual_ms_range = start..(start + self.duration_ms()); 216 + let full_ms_range = 0..self.total_duration_ms(); 217 + 218 + if actual_ms_range != full_ms_range { 219 + self.progress_bars 220 + .rendering 221 + .log("Constrained", &format_timestamp_range(&actual_ms_range)); 222 + } 223 + 224 + self.render(output, |ctx| { 225 + if actual_ms_range.contains(&ctx.ms) { 226 + EngineControl::Render 227 + } else if ctx.ms > actual_ms_range.end { 228 + EngineControl::Stop 229 + } else { 230 + EngineControl::Skip 231 + } 232 + }) 233 + } 205 234 } 206 235 207 236 /// Tells the rendering engine what to do with a frame 208 237 pub enum EngineControl { 209 238 /// Don't run hooks or anything on this frame 210 - Ignore, 211 - /// Skip to the next frame, don't render this one 212 239 Skip, 240 + /// Skip to the next frame, don't render this one 241 + Walk, 213 242 /// Render this frame as usual 214 243 Render, 215 244 /// Render this frame and stop rendering afterwards 216 245 Finish, 217 246 /// Don't render this frame and stop rendering 218 247 Stop, 219 - /// Set canvas and then render this frame 220 - RenderFromCanvas(Canvas), 221 248 } 222 249 223 250 impl EngineControl { 224 251 pub fn render_this_one(&self) -> bool { 225 252 match self { 226 - EngineControl::RenderFromCanvas(_) 227 - | EngineControl::Render 228 - | EngineControl::Finish => true, 229 - EngineControl::Ignore | EngineControl::Skip | EngineControl::Stop => { 253 + EngineControl::Render | EngineControl::Finish => true, 254 + EngineControl::Skip | EngineControl::Walk | EngineControl::Stop => { 230 255 false 231 256 } 232 257 } ··· 234 259 235 260 pub fn run_hooks_on_this_one(&self) -> bool { 236 261 match self { 237 - EngineControl::Ignore => false, 262 + EngineControl::Skip => false, 238 263 _ => true, 239 264 } 240 265 }
+1 -1
src/video/hooks.rs
··· 58 58 render_function: &'static RenderFunction<AdditionalContext>, 59 59 ) -> Self { 60 60 self.with_hook(Hook { 61 - when: Box::new(move |_, context, _, _| context.frame() == 0), 61 + when: Box::new(move |_, context, _, _| context.rendered_frames == 0), 62 62 render_function: Box::new(render_function), 63 63 }) 64 64 }
+6 -5
src/video/server.rs
··· 10 10 11 11 impl VideoServer { 12 12 pub fn new<C: 'static + Default>(video: Arc<Video<C>>) -> Self { 13 - video.progress_bar.finish(); 13 + video.progress.clear(); 14 14 15 15 let router = Router::new() 16 16 .route("/", routing::get(async || Html(PREVIEW_HTML))) ··· 26 26 println!("Frame number requested: {number}"); 27 27 28 28 match video.render_single_frame(number) { 29 - Ok((timecode, svg)) => svg.to_string().replace( 30 - "</svg>", 31 - &format!(r#"<meta name="shapemaker:timecode" content="{timecode}" /></svg>"#) 32 - ), 29 + // Ok((timecode, svg)) => svg.to_string().replace( 30 + // "</svg>", 31 + // &format!(r#"<meta name="shapemaker:timecode" content="{timecode}" /></svg>"#) 32 + // ), 33 + Ok(svg) => svg.to_string(), 33 34 Err(err) => format!("{err:?}"), 34 35 } 35 36 }),
+47 -18
src/video/video.rs
··· 4 4 midi::MidiSynchronizer, 5 5 sync::{SyncData, Syncable}, 6 6 }, 7 - ui::{self, display_counts, Log}, 7 + ui::{self, display_counts, format_duration, format_filepath, Log}, 8 8 video::hooks::{AttachHooks, CommandAction, Hook}, 9 9 Canvas, Scene, 10 10 }; ··· 26 26 } 27 27 } 28 28 29 + pub struct VideoProgressBars { 30 + pub loading: indicatif::ProgressBar, 31 + pub rendering: indicatif::ProgressBar, 32 + pub encoding: indicatif::ProgressBar, 33 + } 34 + 29 35 pub struct Video<C> { 30 36 pub fps: usize, 31 37 pub initial_canvas: Canvas, ··· 38 44 pub resolution: u32, 39 45 pub duration_override: Option<usize>, 40 46 pub start_rendering_at: usize, 41 - pub progress_bar: indicatif::ProgressBar, 47 + pub progress_bars: VideoProgressBars, 48 + pub progress: indicatif::MultiProgress, 42 49 } 43 50 44 51 impl<C: Default> AttachHooks<C> for Video<C> { ··· 57 64 58 65 impl<C: Default> Video<C> { 59 66 pub fn new(canvas: Canvas) -> Self { 67 + let progress_bars = VideoProgressBars { 68 + loading: ui::setup_progress_bar(0, "Loading"), 69 + rendering: ui::setup_progress_bar(0, "Rendering"), 70 + encoding: ui::setup_progress_bar(0, "Encoding"), 71 + }; 72 + 73 + let progress = indicatif::MultiProgress::new(); 74 + progress.add(progress_bars.loading.clone()); 75 + progress.add(progress_bars.rendering.clone()); 76 + progress.add(progress_bars.encoding.clone()); 77 + 60 78 Self { 61 79 fps: 30, 62 80 initial_canvas: canvas, ··· 69 87 audiofile: PathBuf::new(), 70 88 duration_override: None, 71 89 start_rendering_at: 0, 72 - progress_bar: ui::setup_progress_bar(0, ""), 90 + progress_bars, 91 + progress, 73 92 } 74 93 } 75 94 ··· 77 96 debug_time!("sync_audio_with"); 78 97 79 98 let file_path: PathBuf = sync_data_path.into(); 80 - let pb = Some(&self.progress_bar); 99 + let pb = Some(&self.progress_bars.loading); 81 100 82 101 let syncdata = match file_path.extension().and_then(|s| s.to_str()) { 83 102 Some("mid" | "midi") => { ··· 89 108 _ => panic!("Unsupported sync data format"), 90 109 }; 91 110 92 - self.progress_bar.finish(); 93 - self.progress_bar.log( 111 + let pb = pb.unwrap(); 112 + 113 + pb.finish(); 114 + pb.log( 94 115 "Loaded", 95 116 &format!( 96 - "{} from {file_path:?}", 97 - display_counts(HashMap::from([ 117 + "{things} from {path} in {elapsed}", 118 + path = format_filepath(&file_path), 119 + elapsed = format_duration(pb.elapsed()), 120 + things = display_counts(HashMap::from([ 98 121 ("markers", syncdata.markers.len()), 122 + ("stems", syncdata.stems.len()), 99 123 ( 100 124 "notes", 101 125 syncdata ··· 108 132 ), 109 133 ); 110 134 135 + if let Some(bpm) = syncdata.bpm { 136 + pb.log( 137 + "BPM", 138 + &format!("set to {bpm} from {}", format_filepath(&file_path)), 139 + ); 140 + } 141 + 111 142 return Self { 112 143 syncdata: self.syncdata.union(syncdata), 113 144 ..self 114 145 }; 115 146 } 116 147 117 - pub fn total_frames(&self) -> usize { 118 - self.fps * (self.duration_ms() + self.start_rendering_at) / 1000 148 + pub fn ms_to_frames(&self, ms: usize) -> usize { 149 + self.fps * ms / 1000 119 150 } 120 151 152 + // Duration of the video, taking into account a possible duration override. 121 153 pub fn duration_ms(&self) -> usize { 122 - if let Some(duration_override) = self.duration_override { 123 - return duration_override; 124 - } 154 + self.duration_override.unwrap_or(self.total_duration_ms()) 155 + } 125 156 157 + /// Duration of the video, without taking into account a possible duration override. 158 + pub fn total_duration_ms(&self) -> usize { 126 159 self.syncdata 127 160 .stems 128 161 .values() 129 162 .map(|stem| stem.duration_ms) 130 163 .max() 131 - .expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.") 132 - } 133 - 134 - pub fn setup_progress_bar(&self) -> ProgressBar { 135 - ui::setup_progress_bar(self.total_frames() as u64, "Rendering") 164 + .expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.") 136 165 } 137 166 138 167 /// Adds hooks from the given scene to the video.