This repository has no description
0

Configure Feed

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

🔨 Split into features so that WASM builds again

+344 -301
+12 -7
Cargo.toml
··· 21 21 22 22 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 23 23 24 + [features] 25 + vst = ["dep:env_logger", "dep:nih_plug", "dep:ureq"] 26 + web = ["dep:wasm-bindgen", "dep:web-sys"] 27 + mp4 = ["dep:env_logger", "dep:ffmpeg-next", "dep:video-rs"] 28 + 24 29 [dependencies] 25 30 nih_plug = { git = "https://github.com/robbert-vdh/nih-plug.git", features = [ 26 31 "assert_process_allocs", 27 - ] } 32 + ], optional = true } 28 33 anyhow = "1.0.81" 29 34 chrono = "0.4.23" 30 35 docopt = "1.1.1" ··· 40 45 chrono-human-duration = "0.1.1" 41 46 handlebars = "5.1.2" 42 47 tiny_http = "0.12.0" 43 - wasm-bindgen = "0.2.92" 48 + wasm-bindgen = { version = "0.2.92", optional = true } 44 49 getrandom = { version = "0.2", features = ["js"] } 45 - web-sys = { version = "0.3.4", features = [ 50 + web-sys = { version = "0.3.4", optional = true, features = [ 46 51 'Document', 47 52 'Element', 48 53 'HtmlElement', ··· 57 62 roxmltree = "0.19.0" 58 63 strum = { version = "0.26.2", features = ["strum_macros"] } 59 64 strum_macros = "0.26.2" 60 - ureq = "3.0.6" 65 + ureq = { version = "3.0.6", optional = true } 61 66 tiny-skia = "0.11.4" 62 67 resvg = "0.44.0" 63 68 measure_time = "0.9.0" 64 - env_logger = "0.11.6" 69 + env_logger = { version = "0.11.6", optional = true } 65 70 log = "0.4.26" 66 71 ndarray = "0.16.1" 67 72 rgb2yuv420 = "0.2.3" 68 - video-rs = { version = "0.10.3", features = ["ndarray"] } 69 - ffmpeg-next = "7.1.0" 73 + video-rs = { version = "0.10.3", features = ["ndarray"], optional = true } 74 + ffmpeg-next = { version = "7.1.0", optional = true } 70 75 rayon = "1.10.0" 71 76 num_cpus = "1.16.0" 72 77
+2 -1
src/geometry/point.rs
··· 1 + #[cfg(feature = "web")] 1 2 use wasm_bindgen::prelude::*; 2 3 3 4 use crate::Region; 4 5 5 - #[wasm_bindgen] 6 + #[cfg_attr(feature = "web", wasm_bindgen)] 6 7 #[derive(Debug, Clone, Copy, Default, PartialEq)] 7 8 pub struct Point(pub usize, pub usize); 8 9
+2 -1
src/geometry/region.rs
··· 1 1 use crate::{Object, Point}; 2 2 use anyhow::{format_err, Error, Result}; 3 3 use backtrace::Backtrace; 4 + #[cfg(feature = "web")] 4 5 use wasm_bindgen::prelude::*; 5 6 6 - #[wasm_bindgen] 7 + #[cfg_attr(feature = "web", wasm_bindgen)] 7 8 #[derive(Debug, Clone, Default, Copy)] 8 9 pub struct Region { 9 10 pub start: Point,
+4 -3
src/graphics/color.rs
··· 8 8 use serde::Deserialize; 9 9 use strum::IntoEnumIterator; 10 10 use strum_macros::EnumIter; 11 + #[cfg(feature = "web")] 11 12 use wasm_bindgen::prelude::*; 12 13 13 - #[wasm_bindgen] 14 + #[cfg_attr(feature = "web", wasm_bindgen)] 14 15 #[derive(Debug, Clone, Copy, PartialEq, EnumIter)] 15 16 pub enum Color { 16 17 Black, ··· 94 95 } 95 96 } 96 97 97 - #[wasm_bindgen(getter_with_clone)] 98 + #[cfg_attr(feature = "web", wasm_bindgen(getter_with_clone))] 98 99 #[derive(Debug, Deserialize, Clone)] 99 100 pub struct ColorMapping { 100 101 pub black: String, ··· 111 112 pub gray: String, 112 113 } 113 114 114 - #[wasm_bindgen] 115 + #[cfg_attr(feature = "web", wasm_bindgen)] 115 116 impl ColorMapping { 116 117 // wasm_bindegen is not supported on trait impls 117 118 #[allow(clippy::should_implement_trait)]
+4 -3
src/graphics/filter.rs
··· 1 1 use std::hash::Hash; 2 2 3 + #[cfg(feature = "web")] 3 4 use wasm_bindgen::prelude::*; 4 5 5 - #[wasm_bindgen] 6 + #[cfg_attr(feature = "web", wasm_bindgen)] 6 7 #[derive(Debug, Clone, Copy, PartialEq)] 7 8 pub enum FilterType { 8 9 Glow, ··· 10 11 Saturation, 11 12 } 12 13 13 - #[wasm_bindgen] 14 + #[cfg_attr(feature = "web", wasm_bindgen)] 14 15 #[derive(Debug, Clone, Copy)] 15 16 pub struct Filter { 16 17 pub kind: FilterType, 17 18 pub parameter: f32, 18 19 } 19 20 20 - #[wasm_bindgen] 21 + #[cfg_attr(feature = "web", wasm_bindgen)] 21 22 impl Filter { 22 23 pub fn name(&self) -> String { 23 24 match self.kind {
+2 -1
src/graphics/objects.rs
··· 1 1 use crate::{Fill, Filter, Point, Region, Transformation}; 2 + #[cfg(feature = "web")] 2 3 use wasm_bindgen::prelude::*; 3 4 4 5 #[derive(Debug, Clone, PartialEq, Eq)] ··· 113 114 } 114 115 } 115 116 116 - #[wasm_bindgen] 117 + #[cfg_attr(feature = "web", wasm_bindgen)] 117 118 #[derive(Debug, Clone, Copy)] 118 119 pub struct ObjectSizes { 119 120 pub empty_shape_stroke_width: f32,
+2 -1
src/graphics/transform.rs
··· 1 + #[cfg(feature = "web")] 1 2 use wasm_bindgen::prelude::*; 2 3 3 4 use slug::slugify; 4 5 5 - #[wasm_bindgen] 6 + #[cfg_attr(feature = "web", wasm_bindgen)] 6 7 #[derive(Debug, Clone, Copy, PartialEq)] 7 8 pub enum TransformationType { 8 9 Scale,
+2
src/lib.rs
··· 9 9 pub mod rendering; 10 10 pub mod synchronization; 11 11 pub mod video; 12 + 13 + #[cfg(feature = "web")] 12 14 pub mod wasm; 13 15 14 16 pub use geometry::{Angle, Containable, Point, Region};
+17 -1
src/main.rs
··· 1 1 use anyhow::Result; 2 + #[cfg(feature = "vst")] 3 + #[cfg(feature = "mp4")] 4 + use env_logger; 2 5 use measure_time::info_time; 3 6 use shapemaker::{ 4 7 cli::{canvas_from_cli, cli_args}, ··· 8 11 extern crate log; 9 12 10 13 pub fn main() -> Result<()> { 14 + #[cfg(feature = "vst")] 15 + #[cfg(feature = "mp4")] 11 16 env_logger::init(); 12 17 run(cli_args()) 13 18 } ··· 38 43 Err(e) => println!("Error saving image: {}", e), 39 44 } 40 45 } 41 - return Ok(()); 46 + Ok(()) 47 + } else { 48 + run_video(args, canvas) 42 49 } 50 + } 43 51 52 + #[cfg(not(feature = "mp4"))] 53 + fn run_video(_args: cli::Args, _canvas: Canvas) -> Result<()> { 54 + println!("Video rendering is disabled. Enable the mp4 feature to render videos."); 55 + Ok(()) 56 + } 57 + 58 + #[cfg(feature = "mp4")] 59 + fn run_video(args: cli::Args, canvas: Canvas) -> Result<()> { 44 60 let mut video = Video::<()>::new(canvas); 45 61 video.duration_override = args.flag_duration.map(|seconds| seconds * 1000); 46 62 video.start_rendering_at = args.flag_start.unwrap_or_default() * 1000;
+2 -1
src/random/color.rs
··· 1 1 use rand::Rng; 2 + #[cfg(feature = "web")] 2 3 use wasm_bindgen::prelude::*; 3 4 4 5 use crate::Color; 5 6 6 - #[wasm_bindgen] 7 + #[cfg_attr(feature = "web", wasm_bindgen)] 7 8 pub fn random_color(except: Option<Color>) -> Color { 8 9 let all = [ 9 10 Color::Black,
-38
src/rendering/canvas.rs
··· 1 1 use super::renderable::SVGRenderable; 2 2 use crate::graphics::canvas::Canvas; 3 3 use measure_time::{debug_time, info_time}; 4 - use rayon::{ 5 - iter::{IndexedParallelIterator, ParallelIterator}, 6 - slice::ParallelSliceMut, 7 - }; 8 4 use resvg::usvg; 9 5 use std::sync::Arc; 10 6 ··· 124 120 self.png_render_cache = Some(new_svg_contents); 125 121 126 122 Ok(Some(pixmap)) 127 - } 128 - 129 - pub fn pixmap_to_hwc_frame( 130 - &self, 131 - resolution: u32, 132 - pixmap: &tiny_skia::Pixmap, 133 - ) -> anyhow::Result<video_rs::Frame> { 134 - info_time!("pixmap_to_hwc_frame"); 135 - let (width, height) = self.resolution_to_size(resolution); 136 - let (width, height) = (width as usize, height as usize); 137 - let mut data = vec![0u8; height * width * 3]; 138 - 139 - data.par_chunks_exact_mut(3) 140 - .enumerate() 141 - .for_each(|(index, chunk)| { 142 - let x = index % width; 143 - let y = index / width; 144 - 145 - let pixel = pixmap 146 - .pixel(x as u32, y as u32) 147 - .unwrap_or_else(|| panic!("No pixel found at x, y = {x}, {y}")); 148 - 149 - chunk[0] = pixel.red(); 150 - chunk[1] = pixel.green(); 151 - chunk[2] = pixel.blue(); 152 - }); 153 - 154 - Ok(video_rs::Frame::from_shape_vec([height, width, 3], data)?) 155 - } 156 - 157 - pub fn render_to_hwc_frame(&mut self, resolution: u32) -> anyhow::Result<video_rs::Frame> { 158 - let (width, height) = self.resolution_to_size(resolution); 159 - let pixmap = self.render_to_pixmap_no_cache(width, height)?; 160 - self.pixmap_to_hwc_frame(resolution, &pixmap) 161 123 } 162 124 163 125 fn usvg_tree_to_pixmap(
+283
src/video/encoding.rs
··· 1 + extern crate ffmpeg_next as ffmpeg; 2 + use super::{context::Context, engine::milliseconds_to_timestamp, Video}; 3 + use crate::{ui::Log, Canvas, SVGRenderable}; 4 + use anyhow::Result; 5 + use indicatif::ProgressIterator; 6 + use itertools::Itertools; 7 + use measure_time::{debug_time, info_time}; 8 + use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; 9 + use rayon::{iter::IndexedParallelIterator, slice::ParallelSliceMut}; 10 + use std::{ 11 + fs::create_dir_all, 12 + path::{Path, PathBuf}, 13 + str::FromStr, 14 + sync::{Arc, Mutex}, 15 + }; 16 + use video_rs::Time; 17 + 18 + impl Canvas { 19 + pub fn render_to_hwc_frame(&mut self, resolution: u32) -> anyhow::Result<video_rs::Frame> { 20 + let (width, height) = self.resolution_to_size(resolution); 21 + let pixmap = self.render_to_pixmap_no_cache(width, height)?; 22 + self.pixmap_to_hwc_frame(resolution, &pixmap) 23 + } 24 + 25 + pub fn pixmap_to_hwc_frame( 26 + &self, 27 + resolution: u32, 28 + pixmap: &tiny_skia::Pixmap, 29 + ) -> anyhow::Result<video_rs::Frame> { 30 + info_time!("pixmap_to_hwc_frame"); 31 + let (width, height) = self.resolution_to_size(resolution); 32 + let (width, height) = (width as usize, height as usize); 33 + let mut data = vec![0u8; height * width * 3]; 34 + 35 + data.par_chunks_exact_mut(3) 36 + .enumerate() 37 + .for_each(|(index, chunk)| { 38 + let x = index % width; 39 + let y = index / width; 40 + 41 + let pixel = pixmap 42 + .pixel(x as u32, y as u32) 43 + .unwrap_or_else(|| panic!("No pixel found at x, y = {x}, {y}")); 44 + 45 + chunk[0] = pixel.red(); 46 + chunk[1] = pixel.green(); 47 + chunk[2] = pixel.blue(); 48 + }); 49 + 50 + Ok(video_rs::Frame::from_shape_vec([height, width, 3], data)?) 51 + } 52 + } 53 + 54 + impl<AdditionalContext: Default> Video<AdditionalContext> { 55 + fn setup_encoder(&mut self, output_path: &str) -> anyhow::Result<()> { 56 + let (width, height) = self.initial_canvas.resolution_to_size(self.resolution); 57 + 58 + self.encoder = Some(Arc::new(Mutex::new( 59 + video_rs::Encoder::new( 60 + PathBuf::from_str(output_path)?, 61 + video_rs::encode::Settings::preset_h264_yuv420p( 62 + width as usize, 63 + height as usize, 64 + false, 65 + ), 66 + ) 67 + .expect("Failed to build encoder"), 68 + ))); 69 + 70 + Ok(()) 71 + } 72 + 73 + pub fn render_frames(&mut self) -> Result<usize> { 74 + let mut written_frames_count: usize = 0; 75 + let mut context = Context { 76 + frame: 0, 77 + beat: 0, 78 + beat_fractional: 0.0, 79 + timestamp: "00:00:00.000".to_string(), 80 + ms: 0, 81 + bpm: self.syncdata.bpm, 82 + syncdata: &self.syncdata, 83 + extra: AdditionalContext::default(), 84 + later_hooks: vec![], 85 + audiofile: self.audiofile.clone(), 86 + duration_override: self.duration_override, 87 + }; 88 + 89 + let mut canvas = self.initial_canvas.clone(); 90 + 91 + let mut previous_rendered_beat = 0; 92 + let mut previous_rendered_frame = 0; 93 + 94 + let render_ms_range = 0..self.duration_ms() + self.start_rendering_at; 95 + 96 + self.progress_bar.set_length(render_ms_range.len() as u64); 97 + 98 + let mut frames_to_encode: Vec<(Time, String)> = vec![]; 99 + 100 + for _ in render_ms_range 101 + .into_iter() 102 + .progress_with(self.progress_bar.clone()) 103 + { 104 + context.ms += 1_usize; 105 + context.timestamp = milliseconds_to_timestamp(context.ms).to_string(); 106 + context.beat_fractional = (context.bpm * context.ms) as f32 / (1000.0 * 60.0); 107 + context.beat = context.beat_fractional as usize; 108 + context.frame = self.fps * context.ms / 1000; 109 + 110 + self.progress_bar.set_message(context.timestamp.clone()); 111 + 112 + if context.marker() != "" { 113 + self.progress_bar.println(format!( 114 + "{}: marker {}", 115 + context.timestamp, 116 + context.marker() 117 + )); 118 + } 119 + 120 + if context.marker().starts_with(':') { 121 + let marker_text = context.marker(); 122 + let commandline = marker_text.trim_start_matches(':').to_string(); 123 + 124 + for command in &self.commands { 125 + if commandline.starts_with(&command.name) { 126 + let args = commandline 127 + .trim_start_matches(&command.name) 128 + .trim() 129 + .to_string(); 130 + (command.action)(args, &mut canvas, &mut context)?; 131 + } 132 + } 133 + } 134 + 135 + // Render later hooks first, so that for example animations that aren't finished yet get overwritten by next frame's hook, if the next frames touches the same object 136 + // This is way better to cancel early animations such as fading out an object that appears on every note of a stem, if the next note is too close for the fade-out to finish. 137 + 138 + let mut later_hooks_to_delete: Vec<usize> = vec![]; 139 + 140 + for (i, hook) in context.later_hooks.iter().enumerate() { 141 + if (hook.when)(&canvas, &context, previous_rendered_beat) { 142 + (hook.render_function)(&mut canvas, context.ms)?; 143 + if hook.once { 144 + later_hooks_to_delete.push(i); 145 + } 146 + } else if !hook.once { 147 + later_hooks_to_delete.push(i); 148 + } 149 + } 150 + 151 + for i in later_hooks_to_delete { 152 + if i < context.later_hooks.len() { 153 + context.later_hooks.remove(i); 154 + } 155 + } 156 + 157 + for hook in &self.hooks { 158 + if (hook.when)( 159 + &canvas, 160 + &context, 161 + previous_rendered_beat, 162 + previous_rendered_frame, 163 + ) { 164 + (hook.render_function)(&mut canvas, &mut context)?; 165 + } 166 + } 167 + 168 + if context.frame != previous_rendered_frame { 169 + debug_time!("compute_frame"); 170 + frames_to_encode.push(( 171 + Time::from_secs_f64(context.ms as f64 * 1e-3), 172 + canvas 173 + .render_to_svg( 174 + canvas.colormap.clone(), 175 + canvas.cell_size, 176 + canvas.object_sizes, 177 + "", 178 + )? 179 + .to_string(), 180 + )); 181 + 182 + written_frames_count += 1; 183 + 184 + previous_rendered_beat = context.beat; 185 + previous_rendered_frame = context.frame; 186 + } 187 + } 188 + 189 + self.initial_canvas.load_fonts()?; 190 + 191 + self.progress_bar.set_position(0); 192 + self.progress_bar.set_length(frames_to_encode.len() as u64); 193 + self.progress_bar.set_message("Rasterizing"); 194 + 195 + let (hwc_frames_send, hwc_frames_receive) = 196 + std::sync::mpsc::channel::<(Time, video_rs::Frame)>(); 197 + 198 + let resolution = self.resolution; 199 + let pb = self.progress_bar.clone(); 200 + let canvas = self.initial_canvas.clone(); 201 + frames_to_encode.par_iter().for_each(|(time, svg)| { 202 + let (width, height) = canvas.resolution_to_size(resolution); 203 + let pixmap = canvas 204 + .svg_to_pixmap(width, height, svg) 205 + .expect("Failed to render frame"); 206 + 207 + let frame = canvas 208 + .pixmap_to_hwc_frame(resolution, &pixmap) 209 + .expect("Failed to convert pixmap to frame"); 210 + 211 + hwc_frames_send 212 + .send((*time, frame)) 213 + .expect("Failed to send frame"); 214 + 215 + pb.inc(1); 216 + }); 217 + 218 + drop(hwc_frames_send); 219 + 220 + self.progress_bar.set_position(0); 221 + self.progress_bar.set_length(frames_to_encode.len() as u64); 222 + self.progress_bar.set_message("Encoding"); 223 + 224 + for (time, frame) in hwc_frames_receive 225 + .iter() 226 + .sorted_by(|(a, _), (b, _)| a.as_secs_f64().total_cmp(&b.as_secs_f64())) 227 + { 228 + self.encoder 229 + .as_mut() 230 + .expect("Encoder was not initialized") 231 + .lock() 232 + .unwrap() 233 + .encode(&frame, time) 234 + .expect("Failed to encode frame"); 235 + 236 + self.progress_bar.inc(1); 237 + } 238 + 239 + self.progress_bar.finish(); 240 + 241 + Ok(written_frames_count) 242 + } 243 + 244 + pub fn render(&mut self, output_file: String) -> Result<()> { 245 + info_time!("render"); 246 + 247 + // create_dir_all(self.frames_output_directory)?; 248 + // remove_dir_all(self.frames_output_directory)?; 249 + // create_dir(self.frames_output_directory)?; 250 + create_dir_all(Path::new(&output_file).parent().unwrap())?; 251 + 252 + self.setup_encoder(&output_file)?; 253 + 254 + self.progress_bar.set_position(0); 255 + self.progress_bar.set_prefix("Rendering"); 256 + self.progress_bar.set_message(""); 257 + 258 + let frames_written = self.render_frames()?; 259 + 260 + self.encoder 261 + .as_mut() 262 + .expect("Encoder is missing somehow") 263 + .lock() 264 + .unwrap() 265 + .finish()?; 266 + 267 + self.progress_bar.log( 268 + "Rendered", 269 + &format!("{} frames to {}", frames_written, output_file), 270 + ); 271 + 272 + self.progress_bar.set_position(0); 273 + self.progress_bar.set_prefix("Adding"); 274 + self.progress_bar.set_message("audio track"); 275 + 276 + Ok(()) 277 + } 278 + 279 + #[allow(dead_code)] 280 + fn add_audio_track(&mut self, _output_file: String) -> Result<()> { 281 + 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)"); 282 + } 283 + }
+9 -244
src/video/engine.rs
··· 1 - extern crate ffmpeg_next as ffmpeg; 2 1 use super::animation::LayerAnimationUpdateFunction; 3 2 use super::context::Context; 4 3 use crate::synchronization::audio::MusicalDurationUnit; 5 4 use crate::synchronization::midi::MidiSynchronizer; 6 5 use crate::synchronization::sync::{SyncData, Syncable}; 7 6 use crate::ui::{self, setup_progress_bar, Log as _}; 8 - use crate::{Canvas, ColoredObject, SVGRenderable}; 7 + use crate::{Canvas, ColoredObject}; 9 8 use anyhow::Result; 10 9 use chrono::{DateTime, NaiveDateTime}; 11 - use indicatif::{ProgressBar, ProgressIterator}; 12 - use itertools::Itertools; 13 - use measure_time::{debug_time, info_time}; 14 - use rayon::iter::{IntoParallelRefIterator, ParallelIterator}; 15 - use std::str::FromStr; 10 + use indicatif::ProgressBar; 11 + use measure_time::info_time; 12 + #[allow(unused)] 16 13 use std::sync::{Arc, Mutex}; 17 - use std::{ 18 - fmt::Formatter, 19 - fs::create_dir_all, 20 - panic, 21 - path::{Path, PathBuf}, 22 - }; 23 - use video_rs::Time; 14 + use std::{fmt::Formatter, panic, path::PathBuf}; 24 15 25 16 pub type BeatNumber = usize; 26 17 pub type FrameNumber = usize; ··· 51 42 pub duration_override: Option<usize>, 52 43 pub start_rendering_at: usize, 53 44 pub progress_bar: indicatif::ProgressBar, 54 - encoder: Option<Arc<Mutex<video_rs::Encoder>>>, 45 + 46 + #[cfg(feature = "mp4")] 47 + pub encoder: Option<Arc<Mutex<video_rs::Encoder>>>, 55 48 } 56 49 57 50 pub struct Hook<C> { ··· 110 103 duration_override: None, 111 104 start_rendering_at: 0, 112 105 progress_bar: setup_progress_bar(0, ""), 106 + #[cfg(feature = "mp4")] 113 107 encoder: None, 114 108 } 115 - } 116 - 117 - fn setup_encoder(&mut self, output_path: &str) -> anyhow::Result<()> { 118 - let (width, height) = self.initial_canvas.resolution_to_size(self.resolution); 119 - 120 - self.encoder = Some(Arc::new(Mutex::new( 121 - video_rs::Encoder::new( 122 - PathBuf::from_str(output_path)?, 123 - video_rs::encode::Settings::preset_h264_yuv420p( 124 - width as usize, 125 - height as usize, 126 - false, 127 - ), 128 - ) 129 - .expect("Failed to build encoder"), 130 - ))); 131 - 132 - Ok(()) 133 109 } 134 110 135 111 pub fn sync_audio_with(self, sync_data_path: &str) -> Self { ··· 457 433 .expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.") 458 434 } 459 435 460 - pub fn render_frames(&mut self) -> Result<usize> { 461 - let mut written_frames_count: usize = 0; 462 - let mut context = Context { 463 - frame: 0, 464 - beat: 0, 465 - beat_fractional: 0.0, 466 - timestamp: "00:00:00.000".to_string(), 467 - ms: 0, 468 - bpm: self.syncdata.bpm, 469 - syncdata: &self.syncdata, 470 - extra: AdditionalContext::default(), 471 - later_hooks: vec![], 472 - audiofile: self.audiofile.clone(), 473 - duration_override: self.duration_override, 474 - }; 475 - 476 - let mut canvas = self.initial_canvas.clone(); 477 - 478 - let mut previous_rendered_beat = 0; 479 - let mut previous_rendered_frame = 0; 480 - 481 - let render_ms_range = 0..self.duration_ms() + self.start_rendering_at; 482 - 483 - self.progress_bar.set_length(render_ms_range.len() as u64); 484 - 485 - let mut frames_to_encode: Vec<(Time, String)> = vec![]; 486 - 487 - for _ in render_ms_range 488 - .into_iter() 489 - .progress_with(self.progress_bar.clone()) 490 - { 491 - context.ms += 1_usize; 492 - context.timestamp = milliseconds_to_timestamp(context.ms).to_string(); 493 - context.beat_fractional = (context.bpm * context.ms) as f32 / (1000.0 * 60.0); 494 - context.beat = context.beat_fractional as usize; 495 - context.frame = self.fps * context.ms / 1000; 496 - 497 - self.progress_bar.set_message(context.timestamp.clone()); 498 - 499 - if context.marker() != "" { 500 - self.progress_bar.println(format!( 501 - "{}: marker {}", 502 - context.timestamp, 503 - context.marker() 504 - )); 505 - } 506 - 507 - if context.marker().starts_with(':') { 508 - let marker_text = context.marker(); 509 - let commandline = marker_text.trim_start_matches(':').to_string(); 510 - 511 - for command in &self.commands { 512 - if commandline.starts_with(&command.name) { 513 - let args = commandline 514 - .trim_start_matches(&command.name) 515 - .trim() 516 - .to_string(); 517 - (command.action)(args, &mut canvas, &mut context)?; 518 - } 519 - } 520 - } 521 - 522 - // Render later hooks first, so that for example animations that aren't finished yet get overwritten by next frame's hook, if the next frames touches the same object 523 - // This is way better to cancel early animations such as fading out an object that appears on every note of a stem, if the next note is too close for the fade-out to finish. 524 - 525 - let mut later_hooks_to_delete: Vec<usize> = vec![]; 526 - 527 - for (i, hook) in context.later_hooks.iter().enumerate() { 528 - if (hook.when)(&canvas, &context, previous_rendered_beat) { 529 - (hook.render_function)(&mut canvas, context.ms)?; 530 - if hook.once { 531 - later_hooks_to_delete.push(i); 532 - } 533 - } else if !hook.once { 534 - later_hooks_to_delete.push(i); 535 - } 536 - } 537 - 538 - for i in later_hooks_to_delete { 539 - if i < context.later_hooks.len() { 540 - context.later_hooks.remove(i); 541 - } 542 - } 543 - 544 - for hook in &self.hooks { 545 - if (hook.when)( 546 - &canvas, 547 - &context, 548 - previous_rendered_beat, 549 - previous_rendered_frame, 550 - ) { 551 - (hook.render_function)(&mut canvas, &mut context)?; 552 - } 553 - } 554 - 555 - if context.frame != previous_rendered_frame { 556 - debug_time!("compute_frame"); 557 - frames_to_encode.push(( 558 - Time::from_secs_f64(context.ms as f64 * 1e-3), 559 - canvas 560 - .render_to_svg( 561 - canvas.colormap.clone(), 562 - canvas.cell_size, 563 - canvas.object_sizes, 564 - "", 565 - )? 566 - .to_string(), 567 - )); 568 - 569 - written_frames_count += 1; 570 - 571 - previous_rendered_beat = context.beat; 572 - previous_rendered_frame = context.frame; 573 - } 574 - } 575 - 576 - self.initial_canvas.load_fonts()?; 577 - 578 - self.progress_bar.set_position(0); 579 - self.progress_bar.set_length(frames_to_encode.len() as u64); 580 - self.progress_bar.set_message("Rasterizing"); 581 - 582 - let (hwc_frames_send, hwc_frames_receive) = 583 - std::sync::mpsc::channel::<(Time, video_rs::Frame)>(); 584 - 585 - let resolution = self.resolution; 586 - let pb = self.progress_bar.clone(); 587 - let canvas = self.initial_canvas.clone(); 588 - frames_to_encode.par_iter().for_each(|(time, svg)| { 589 - let (width, height) = canvas.resolution_to_size(resolution); 590 - let pixmap = canvas 591 - .svg_to_pixmap(width, height, svg) 592 - .expect("Failed to render frame"); 593 - 594 - let frame = canvas 595 - .pixmap_to_hwc_frame(resolution, &pixmap) 596 - .expect("Failed to convert pixmap to frame"); 597 - 598 - hwc_frames_send 599 - .send((*time, frame)) 600 - .expect("Failed to send frame"); 601 - 602 - pb.inc(1); 603 - }); 604 - 605 - drop(hwc_frames_send); 606 - 607 - self.progress_bar.set_position(0); 608 - self.progress_bar.set_length(frames_to_encode.len() as u64); 609 - self.progress_bar.set_message("Encoding"); 610 - 611 - for (time, frame) in hwc_frames_receive 612 - .iter() 613 - .sorted_by(|(a, _), (b, _)| a.as_secs_f64().total_cmp(&b.as_secs_f64())) 614 - { 615 - self.encoder 616 - .as_mut() 617 - .expect("Encoder was not initialized") 618 - .lock() 619 - .unwrap() 620 - .encode(&frame, time) 621 - .expect("Failed to encode frame"); 622 - 623 - self.progress_bar.inc(1); 624 - } 625 - 626 - self.progress_bar.finish(); 627 - 628 - Ok(written_frames_count) 629 - } 630 - 631 436 pub fn setup_progress_bar(&self) -> ProgressBar { 632 437 ui::setup_progress_bar(self.total_frames() as u64, "Rendering") 633 - } 634 - 635 - pub fn render(&mut self, output_file: String) -> Result<()> { 636 - info_time!("render"); 637 - 638 - // create_dir_all(self.frames_output_directory)?; 639 - // remove_dir_all(self.frames_output_directory)?; 640 - // create_dir(self.frames_output_directory)?; 641 - create_dir_all(Path::new(&output_file).parent().unwrap())?; 642 - 643 - self.setup_encoder(&output_file)?; 644 - 645 - self.progress_bar.set_position(0); 646 - self.progress_bar.set_prefix("Rendering"); 647 - self.progress_bar.set_message(""); 648 - 649 - let frames_written = self.render_frames()?; 650 - 651 - self.encoder 652 - .as_mut() 653 - .expect("Encoder is missing somehow") 654 - .lock() 655 - .unwrap() 656 - .finish()?; 657 - 658 - self.progress_bar.log( 659 - "Rendered", 660 - &format!("{} frames to {}", frames_written, output_file), 661 - ); 662 - 663 - self.progress_bar.set_position(0); 664 - self.progress_bar.set_prefix("Adding"); 665 - self.progress_bar.set_message("audio track"); 666 - 667 - Ok(()) 668 - } 669 - 670 - #[allow(dead_code)] 671 - fn add_audio_track(&mut self, _output_file: String) -> Result<()> { 672 - 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)"); 673 438 } 674 439 } 675 440
+3
src/video/mod.rs
··· 2 2 pub mod context; 3 3 pub mod engine; 4 4 5 + #[cfg(feature = "mp4")] 6 + pub mod encoding; 7 + 5 8 pub use animation::Animation; 6 9 pub use engine::Video;