195 lines
4.5 KiB
Rust
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();
|
|
}
|