This repository has no description
1mod scenes;
2
3use anyhow::anyhow;
4use rand::{SeedableRng, rngs::SmallRng};
5use shapemaker::{ui::Log, *};
6use std::{fs, path::PathBuf, time::Duration};
7
8pub struct State {
9 bass_pattern_at: Region,
10 kick_color: Color,
11 rng: SmallRng,
12 kick_counter: u32,
13}
14
15impl Default for State {
16 fn default() -> Self {
17 Self {
18 bass_pattern_at: Region::from_topleft(Point(1, 1), (2, 2)).unwrap(),
19 kick_color: Color::White,
20 rng: SmallRng::seed_from_u64(0),
21 kick_counter: 0,
22 }
23 }
24}
25
26#[tokio::main]
27pub async fn main() {
28 let mut canvas = Canvas::with_layers(vec![]);
29
30 canvas.set_grid_size(16, 9);
31 canvas.colormap = ColorMapping {
32 black: "#000000".into(),
33 white: "#ffffff".into(),
34 red: "#cf0a2b".into(),
35 green: "#22e753".into(),
36 blue: "#2734e6".into(),
37 yellow: "#f8e21e".into(),
38 orange: "#f05811".into(),
39 purple: "#6a24ec".into(),
40 brown: "#a05634".into(),
41 pink: "#e92e76".into(),
42 gray: "#81a0a8".into(),
43 cyan: "#4fecec".into(),
44 };
45
46 let mut video = Video::<State>::new(canvas);
47 let mut args = pico_args::Arguments::from_env();
48
49 video.duration_override = args
50 .value_from_str("--duration")
51 .ok()
52 .map(Duration::from_secs);
53
54 if video.duration_override.is_some_and(|d| d.is_zero()) {
55 video.duration_override = None;
56 }
57
58 video.start_rendering_at = args
59 .value_from_str("--start")
60 .ok()
61 .map(Timestamp::from_seconds)
62 .unwrap_or_default();
63
64 video.resolution = args.value_from_str("--resolution").ok().unwrap_or(480);
65 video.fps = args.value_from_str("--fps").ok().unwrap_or(30);
66
67 video.audiofile = PathBuf::from("schedule-hell.wav");
68 video = video
69 // Sync inputs //
70 .sync_audio_with("schedule-hell.midi")
71 .sync_audio_with("schedule-hell.wav")
72 // Scenes //
73 .with_scene(scenes::starry_sky())
74 .with_init_scene(scenes::intro())
75 .with_marked_scene(scenes::first_break())
76 .assign_scene_to("end of first break", "starry sky")
77 .assign_scene_to("second break", "intro")
78 // Credits //
79 .when_remaining(10, &|canvas, _| {
80 let world = canvas.world_region;
81 canvas.root().set(
82 "credits text",
83 Object::Text(
84 world.start.translated(2, 2),
85 "Postamble / Schedule Hell".into(),
86 12.0,
87 )
88 .colored(Color::White),
89 );
90 Ok(())
91 });
92
93 let destination: String = args
94 .free_from_str()
95 .unwrap_or(String::from("schedule-hell.mp4"));
96
97 if destination.starts_with("localhost:") {
98 video.serve(&destination).await;
99 } else {
100 let result = if destination.ends_with(".svg") {
101 let render_ahead = 10;
102
103 let frame_no = destination
104 .trim_end_matches(".svg")
105 .parse::<usize>()
106 .expect("Provide a integer when rendering a frame");
107
108 video.progress_bars.loading.log(
109 "Constrained",
110 &format!(
111 "to frame #{frame_no}, with {render_ahead}-frame context"
112 ),
113 );
114
115 video.render_frame(frame_no, render_ahead).and_then(|svg| {
116 fs::write(destination, svg.to_string())
117 .map_err(|e| anyhow!("{e:?}"))
118 })
119 } else {
120 video.encode(destination).map(|_| ())
121 };
122
123 match result {
124 Ok(_) => (),
125 Err(e) => {
126 let _ = video.progress.clear();
127 ().log_error("Failed", &format!("{e:?}"));
128 }
129 };
130 }
131}