#![allow(clippy::manual_range_contains)] #![warn(rust_2018_idioms)] #![forbid(elided_lifetimes_in_paths, unsafe_code)] mod iotro; mod render; mod time; use crate::time::{parse_date, parse_time, Date, Time}; use camino::Utf8PathBuf as PathBuf; use clap::Parser; use rational::Rational; use serde::{Deserialize, Serialize}; use serde_with::{serde_as, DisplayFromStr, FromInto}; use std::{ collections::BTreeSet, fmt::Display, fs, io::{self, BufRead as _, Write} }; #[derive(Debug, Parser)] struct Args { #[clap(short = 'C', long, default_value = ".")] directory: PathBuf, #[clap(short = 'c', long, default_value = "23ws-malo")] course: String } #[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[derive(Clone, Copy, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] enum Resolution { /// 640x360 nHD, /// 1280x720 HD, /// 1920x1080 FullHD, /// 2560x1440 WQHD, /// 3840x2160 UHD } impl Resolution { fn width(self) -> usize { match self { Self::nHD => 640, Self::HD => 1280, Self::FullHD => 1920, Self::WQHD => 2560, Self::UHD => 3840 } } fn height(self) -> usize { match self { Self::nHD => 360, Self::HD => 720, Self::FullHD => 1080, Self::WQHD => 1440, Self::UHD => 2160 } } } #[derive(Deserialize, Serialize)] struct Project { lecture: ProjectLecture, source: ProjectSource, progress: ProjectProgress } #[serde_as] #[derive(Deserialize, Serialize)] struct ProjectLecture { course: String, #[serde_as(as = "DisplayFromStr")] date: Date } #[serde_as] #[derive(Deserialize, Serialize)] struct ProjectSource { files: Vec, #[serde_as(as = "DisplayFromStr")] first_file_start: Time, #[serde_as(as = "DisplayFromStr")] last_file_end: Time, metadata: Option } #[serde_as] #[derive(Deserialize, Serialize)] struct ProjectSourceMetadata { /// The duration of the source video. #[serde_as(as = "DisplayFromStr")] source_duration: Time, /// The FPS of the source video. #[serde_as(as = "FromInto<(i128, i128)>")] source_fps: Rational, /// The time base of the source video. source_tbn: u32, /// The resolution of the source video. source_res: Resolution, /// The sample rate of the source audio. source_sample_rate: u32 } #[derive(Default, Deserialize, Serialize)] struct ProjectProgress { preprocessed: bool, rendered: bool, transcoded: BTreeSet } fn ask_time(question: impl Display) -> Time { let mut stdout = io::stdout().lock(); let mut stdin = io::stdin().lock(); writeln!(stdout, "{question}").unwrap(); let mut line = String::new(); loop { line.clear(); write!(stdout, "> ").unwrap(); stdout.flush().unwrap(); stdin.read_line(&mut line).unwrap(); let line = line.trim(); match parse_time(line) { Ok(time) => return time, Err(err) => writeln!(stdout, "Invalid Input {line:?}: {err}").unwrap() } } } fn main() { let args = Args::parse(); // process arguments let directory = args.directory.canonicalize_utf8().unwrap(); let course = args.course; // let's see if we need to initialise the project let project_path = directory.join("project.toml"); let project = if project_path.exists() { toml::from_slice(&fs::read(&project_path).unwrap()).unwrap() } else { let dirname = directory.file_name().unwrap(); let date = parse_date(dirname).expect("Directory name is not in the expected format"); let mut files = Vec::new(); for entry in directory.read_dir_utf8().unwrap() { let entry = entry.unwrap(); let name = entry.file_name(); let lower = name.to_ascii_lowercase(); if (lower.ends_with(".mp4") || lower.ends_with(".mts")) && entry.file_type().unwrap().is_file() { files.push(String::from(name)); } } files.sort_unstable(); assert!(!files.is_empty()); println!("I found the following source files: {files:?}"); let first_file_start = ask_time(format_args!( "Please take a look at the file {} and tell me the first second you want included", files.first().unwrap() )); let last_file_end = ask_time(format_args!( "Please take a look at the file {} and tell me the last second you want included", files.last().unwrap() )); let project = Project { lecture: ProjectLecture { course, date }, source: ProjectSource { files, first_file_start, last_file_end, metadata: None }, progress: Default::default() }; fs::write(&project_path, toml::to_string(&project).unwrap().as_bytes()).unwrap(); project }; println!("{}", toml::to_string(&project).unwrap()); // render(&directory, &project).unwrap(); }