render_video/src/main.rs

195 lines
4.5 KiB
Rust

#![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};
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<String>,
#[serde_as(as = "DisplayFromStr")]
first_file_start: Time,
#[serde_as(as = "DisplayFromStr")]
last_file_end: Time,
metadata: Option<ProjectSourceMetadata>
}
#[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 = "DisplayFromStr")]
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<Resolution>
}
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();
}