this repo has no description
3
fork

Configure Feed

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

🚧 it works!!!!!

authored by

Gwenn Le Bihan and committed by
Gwen Le Bihan
23974955 8bc4ba4f

+113 -133
+1 -1
Cargo.toml
··· 63 63 measure_time = "0.9.0" 64 64 env_logger = "0.11.6" 65 65 log = "0.4.26" 66 - video-rs = { version = "0.10.3", features = ["ndarray"] } 67 66 ndarray = "0.16.1" 68 67 rgb2yuv420 = "0.2.3" 68 + video-rs = { version = "0.10.3", features = ["ndarray"] } 69 69 70 70 71 71 [dev-dependencies]
+1 -1
Justfile
··· 21 21 cp shapemaker ~/.local/bin/ 22 22 23 23 example-video out="out.mp4" args='': 24 - ./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 480 {{args}} 24 + RUST_BACKTRACE=1 ./shapemaker video --colors colorschemes/palenight.css {{out}} --sync-with fixtures/schedule-hell.midi --audio fixtures/schedule-hell.flac --grid-size 16x10 --resolution 480 {{args}} --duration 10 25 25 26 26 example-image out="out.png" args='': 27 27 ./shapemaker image --colors colorschemes/palenight.css --resolution 1400 {{out}} {{args}}
+68 -17
src/canvas.rs
··· 4 4 use itertools::Itertools as _; 5 5 use measure_time::info_time; 6 6 use rand::Rng; 7 - use rgb2yuv420::convert_rgb_to_yuv420p; 8 7 9 8 use crate::{ 10 9 layer::Layer, objects::Object, random_color, Angle, Color, ColorMapping, ColoredObject, ··· 421 420 422 421 pub fn resolution_to_size(&self, resolution: u32) -> (u32, u32) { 423 422 let aspect_ratio = self.aspect_ratio(); 424 - let (height, width) = if aspect_ratio > 1.0 { 423 + if aspect_ratio > 1.0 { 425 424 // landscape: resolution is width 426 - (resolution, (resolution as f32 * aspect_ratio) as u32) 425 + (resolution, (resolution as f32 / aspect_ratio) as u32) 427 426 } else { 428 427 // portrait: resolution is height 429 - ((resolution as f32 / aspect_ratio) as u32, resolution) 430 - }; 428 + ((resolution as f32 * aspect_ratio) as u32, resolution) 429 + } 431 430 } 432 431 433 432 // previous_frame_at gives path to the previously rendered frame, which allows to copy on cache hits instead of having to re-write bytes again ··· 626 625 info_time!("render_to_hwc_frame"); 627 626 let (width, height) = self.resolution_to_size(resolution); 628 627 let pixmap = self.render_to_pixmap_no_cache(width, height)?; 629 - let rgb_pixels: [u8] = pixmap 630 - .pixels() 631 - .iter() 632 - .map(|pixel| [pixel.red(), pixel.green(), pixel.blue()]) 633 - .flatten() 634 - .collect(); 628 + Ok(video_rs::Frame::from_shape_fn( 629 + (pixmap.height() as usize, pixmap.width() as usize, 3), 630 + |(y, x, c)| { 631 + let pixel = pixmap 632 + .pixel(x as u32, y as u32) 633 + .expect(&format!("No pixel found at x, y = {x}, {y}")); 634 + match c { 635 + 0 => pixel.red(), 636 + 1 => pixel.green(), 637 + 2 => pixel.blue(), 638 + _ => unreachable!(), 639 + } 640 + }, 641 + )) 642 + // let mut rgb_pixels = ndarray::Array3::from_shape_vec( 643 + // [pixmap.width() as usize, pixmap.height() as usize, 3], 644 + // pixmap 645 + // .pixels() 646 + // .iter() 647 + // .map(|pixel| [pixel.red(), pixel.green(), pixel.blue()]) 648 + // .flatten() 649 + // .collect(), 650 + // )?; 635 651 636 - Ok(video_rs::Frame::from(convert_rgb_to_yuv420p( 637 - &rgb_pixels, 638 - width, 639 - height, 640 - tiny_skia::BYTES_PER_PIXEL, 641 - ))) 652 + // // Go from WHC to HWC 653 + // rgb_pixels = rgb_pixels.permuted_axes([1, 0, 2]); 654 + 655 + // println!("rgb_pixels: {:?}", rgb_pixels.shape()); 656 + 657 + // // Flatten before giving to YUV conversion 658 + // let rgb_pixels_flat = rgb_pixels.into_flat(); 659 + 660 + // println!("rgb_pixels_flat: {:?}", rgb_pixels_flat.len()); 661 + 662 + // let yuv_pixels = convert_rgb_to_yuv420p( 663 + // &rgb_pixels_flat 664 + // .as_slice() 665 + // .expect("Failed to convert HWC RGB data to a slice for YUV420p conversion"), 666 + // pixmap.height(), 667 + // pixmap.width(), 668 + // 3, 669 + // ); 670 + 671 + // println!("yuv_pixels: {:?}", yuv_pixels.len()); 672 + 673 + // // info_time!("render_to_hwc_frame -> convert_rgb_to_yuv420p"); 674 + // // let yuv_pixels = convert_rgb_to_yuv420p( 675 + // // pixmap.data(), 676 + // // pixmap.width(), 677 + // // pixmap.height(), 678 + // // tiny_skia::BYTES_PER_PIXEL, 679 + // // ); 680 + 681 + // // println!("yuv_pixels: {:?}", yuv_pixels.len()); 682 + 683 + // info_time!("render_to_hwc_frame -> create_frame"); 684 + 685 + // let frame_data = ndarray::Array3::from_shape_vec( 686 + // [pixmap.height() as usize, pixmap.width() as usize, 3], 687 + // yuv_pixels, 688 + // )?; 689 + 690 + // println!("frame_data: {:?}", frame_data.shape()); 691 + 692 + // Ok(video_rs::Frame::from(frame_data)) 642 693 } 643 694 644 695 fn usvg_tree_to_pixmap(
+1
src/main.rs
··· 34 34 let mut video = Video::<()>::new(canvas); 35 35 video.duration_override = args.flag_duration.map(|seconds| seconds * 1000); 36 36 video.start_rendering_at = args.flag_start.unwrap_or_default() * 1000; 37 + video.resolution = args.flag_resolution.unwrap_or(1920); 37 38 video.fps = args.flag_fps.unwrap_or(30); 38 39 video.audiofile = args 39 40 .flag_audio
+42 -114
src/video.rs
··· 1 - use std::process; 1 + use std::collections::HashMap; 2 + use std::str::FromStr; 2 3 use std::{ 3 4 fmt::Formatter, 4 - fs::{create_dir, create_dir_all, remove_dir_all}, 5 + fs::create_dir_all, 5 6 panic, 6 7 path::{Path, PathBuf}, 7 8 }; ··· 14 15 15 16 use crate::{ 16 17 sync::SyncData, 17 - ui::{self, format_log_msg, setup_progress_bar, Log as _}, 18 + ui::{self, setup_progress_bar, Log as _}, 18 19 Canvas, ColoredObject, Context, LayerAnimationUpdateFunction, MidiSynchronizer, 19 20 MusicalDurationUnit, Syncable, 20 21 }; ··· 35 36 /// Arguments: canvas, context, previous rendered beat 36 37 pub type LaterHookCondition<C> = dyn Fn(&Canvas, &Context<C>, BeatNumber) -> bool; 37 38 38 - #[derive(Debug)] 39 39 pub struct Video<C> { 40 40 pub fps: usize, 41 41 pub initial_canvas: Canvas, ··· 93 93 } 94 94 } 95 95 96 - fn is_binary_installed(binary: &str) -> bool { 97 - process::Command::new("which") 98 - .arg(binary) 99 - .output() 100 - .map(|output| output.status.success()) 101 - .unwrap_or(false) 102 - } 103 - 104 96 impl<AdditionalContext: Default> Video<AdditionalContext> { 105 97 pub fn new(canvas: Canvas) -> Self { 106 98 Self { ··· 120 112 } 121 113 } 122 114 123 - fn setup_encoder(&self, output_path: String) -> Self { 124 - self.encoder = video_rs::Encoder::new( 125 - output_path, 126 - video_rs::encode::Settings::preset_h264_yuv420p( 127 - self.initial_canvas.width(), 128 - self.initial_canvas.height(), 129 - true, 130 - ), 131 - ) 115 + fn setup_encoder(&mut self, output_path: &str) -> anyhow::Result<()> { 116 + let (width, height) = self.initial_canvas.resolution_to_size(self.resolution); 117 + 118 + self.encoder = Some( 119 + video_rs::Encoder::new( 120 + PathBuf::from_str(output_path)?, 121 + video_rs::encode::Settings::preset_h264_yuv420p( 122 + width as usize, 123 + height as usize, 124 + false, 125 + ), 126 + ) 127 + .expect("Failed to build encoder"), 128 + ); 129 + 130 + Ok(()) 132 131 } 133 132 134 133 pub fn sync_audio_with(self, sync_data_path: &str) -> Self { ··· 154 153 panic!("Unsupported sync data format"); 155 154 } 156 155 157 - pub fn build_video(&self, render_to: &str) -> Result<()> { 158 - let mut command = std::process::Command::new("ffmpeg"); 159 - 160 - command 161 - .args(["-hide_banner", "-loglevel", "error"]) 162 - .args(["-framerate", &self.fps.to_string()]) 163 - .args(["-pattern_type", "glob"]) // not available on Windows 164 - .args([ 165 - "-i", 166 - &format!( 167 - "{}/*.png", 168 - self.frames_output_directory, 169 - // self.total_frames().to_string().len() 170 - ), 171 - ]) 172 - .args([ 173 - "-ss", 174 - &format!("{}", self.start_rendering_at as f32 / 1000.0), 175 - ]); 176 - 177 - if !self.audiofile.to_str().unwrap().is_empty() { 178 - if !self.audiofile.exists() { 179 - return Err(anyhow::format_err!( 180 - "Audio file {} does not exist", 181 - self.audiofile.to_str().unwrap() 182 - )); 183 - } 184 - command.args(["-i", self.audiofile.to_str().unwrap()]); 185 - // so that vscode can read the video file with sound lmao 186 - command.args(["-acodec", "mp3"]); 187 - } 188 - 189 - command 190 - .args(["-t", &format!("{}", self.duration_ms() as f32 / 1000.0)]) 191 - .args(["-c:v", "libx264"]) 192 - .args(["-pix_fmt", "yuv420p"]) 193 - .arg("-y") 194 - .arg(render_to); 195 - 196 - match command.output() { 197 - Err(e) => Err(anyhow::format_err!("Failed to execute ffmpeg: {}", e)), 198 - Ok(r) => { 199 - println!("{}", std::str::from_utf8(&r.stdout).unwrap()); 200 - println!("{}", std::str::from_utf8(&r.stderr).unwrap()); 201 - Ok(()) 202 - } 203 - } 204 - } 205 - 206 - fn frame_output_path(&self, frame_no: usize) -> String { 207 - format!( 208 - "{}/{:0width$}.png", 209 - self.frames_output_directory, 210 - frame_no, 211 - width = self.total_frames().to_string().len() 212 - ) 213 - } 214 - 215 156 pub fn with_hook(self, hook: Hook<AdditionalContext>) -> Self { 216 157 let mut hooks = self.hooks; 217 158 hooks.push(hook); ··· 514 455 .expect("No audio sync data provided. Use .sync_audio_with() to load a MIDI file, or provide a duration override.") 515 456 } 516 457 517 - pub fn render_layers_in(&self, output_directory: String) -> Result<()> { 518 - for composition in self 519 - .initial_canvas 520 - .layers 521 - .iter() 522 - .map(|l| vec![l.name.as_str()]) 523 - { 524 - self.render(format!( 525 - "{}/{}.mov", 526 - output_directory, 527 - composition.join("+") 528 - ))?; 529 - } 530 - Ok(()) 531 - } 532 - 533 458 // Saves PNG frames to disk. Returns number of frames written. 534 - pub fn render_frames(&self, progress_bar: &ProgressBar) -> Result<usize> { 459 + pub fn render_frames(&mut self) -> Result<usize> { 535 460 let mut written_frames_count: usize = 0; 536 461 let mut context = Context { 537 462 frame: 0, ··· 566 491 context.beat = context.beat_fractional as usize; 567 492 context.frame = self.fps * context.ms / 1000; 568 493 569 - progress_bar.set_message(context.timestamp.clone()); 494 + self.progress_bar.set_message(context.timestamp.clone()); 570 495 571 496 if context.marker() != "" { 572 - progress_bar.println(format!( 497 + self.progress_bar.println(format!( 573 498 "{}: marker {}", 574 499 context.timestamp, 575 500 context.marker() ··· 630 555 // self.resolution, 631 556 // Some(&self.frame_output_path(previous_rendered_frame)), 632 557 // )?; 633 - self.encoder.expect("Encoder was not initialized").encode( 634 - &canvas.render_to_hwc_frame(self.resolution), 635 - Time::from_secs_f64(context.ms as f64 * 1e-3), 636 - )?; 558 + self.encoder 559 + .as_mut() 560 + .expect("Encoder was not initialized") 561 + .encode( 562 + &canvas.render_to_hwc_frame(self.resolution)?, 563 + Time::from_secs_f64(context.ms as f64 * 1e-3), 564 + )?; 637 565 638 566 written_frames_count += 1; 639 567 ··· 649 577 ui::setup_progress_bar(self.total_frames() as u64, "Rendering") 650 578 } 651 579 652 - pub fn render(&self, output_file: String) -> Result<()> { 580 + pub fn render(&mut self, output_file: String) -> Result<()> { 653 581 info_time!("render"); 654 582 655 583 // create_dir_all(self.frames_output_directory)?; ··· 657 585 // create_dir(self.frames_output_directory)?; 658 586 create_dir_all(Path::new(&output_file).parent().unwrap())?; 659 587 660 - self.setup_encoder(output_file); 588 + self.setup_encoder(&output_file)?; 661 589 662 590 self.progress_bar.set_position(0); 663 591 self.progress_bar.set_prefix("Rendering"); 664 592 self.progress_bar.set_message(""); 665 593 666 - let frames_written = self.render_frames(&self.progress_bar)?; 594 + let frames_written = self.render_frames()?; 667 595 668 - self.progress_bar 669 - .log("Rendered", &format!("{} frames to SVG", frames_written)); 596 + self.encoder 597 + .as_mut() 598 + .expect("Encoder is missing somehow") 599 + .finish()?; 670 600 671 - let spinner = ui::Spinner::start("Building", "video"); 672 - let result = self.build_video(&output_file); 673 - spinner.end(&format_log_msg( 674 - "Built", 675 - &format!("video to {}", output_file), 676 - )); 601 + self.progress_bar.log( 602 + "Rendered", 603 + &format!("{} frames to {}", frames_written, output_file), 604 + ); 677 605 678 - result 606 + Ok(()) 679 607 } 680 608 } 681 609