Refactor the code into a binary and library #1

Merged
msrd0 merged 2 commits from refactor into main 2024-06-23 15:53:45 +00:00
9 changed files with 142 additions and 129 deletions
Showing only changes of commit ba9c6ede3e - Show all commits

54
src/cli.rs Normal file
View file

@ -0,0 +1,54 @@
//! This module contains helper functions for implementing CLI/TUI.
use crate::time::{parse_time, Time};
use console::style;
use std::{
fmt::Display,
io::{self, BufRead as _, Write as _}
};
pub fn ask(question: impl Display) -> String {
let mut stdout = io::stdout().lock();
let mut stdin = io::stdin().lock();
write!(
stdout,
"{} {} ",
style(question).bold().magenta(),
style(">").cyan()
)
.unwrap();
stdout.flush().unwrap();
let mut line = String::new();
stdin.read_line(&mut line).unwrap();
line.trim().to_owned()
}
pub fn ask_time(question: impl Display + Copy) -> Time {
let mut stdout = io::stdout().lock();
let mut stdin = io::stdin().lock();
let mut line = String::new();
loop {
line.clear();
write!(
stdout,
"{} {} ",
style(question).bold().magenta(),
style(">").cyan()
)
.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,
"{} {line:?}: {err}",
style("Invalid Input").bold().red()
)
.unwrap()
}
}
}

View file

@ -1,6 +1,9 @@
//! A module for writing intros and outros //! A module for writing intros and outros
use crate::{time::Date, ProjectLecture, Resolution}; use crate::{
project::{ProjectLecture, Resolution},
time::Date
};
use anyhow::anyhow; use anyhow::anyhow;
use std::{ use std::{
fmt::{self, Debug, Display, Formatter}, fmt::{self, Debug, Display, Formatter},

17
src/lib.rs Normal file
View file

@ -0,0 +1,17 @@
#![allow(clippy::manual_range_contains)]
#![warn(clippy::unreadable_literal, rust_2018_idioms)]
#![forbid(elided_lifetimes_in_paths, unsafe_code)]
pub mod cli;
pub mod iotro;
pub mod preset;
pub mod project;
pub mod question;
pub mod render;
pub mod time;
#[cfg(feature = "mem_limit")]
use std::sync::RwLock;
#[cfg(feature = "mem_limit")]
pub static MEM_LIMIT: RwLock<String> = RwLock::new(String::new());

View file

@ -2,32 +2,17 @@
#![warn(clippy::unreadable_literal, rust_2018_idioms)] #![warn(clippy::unreadable_literal, rust_2018_idioms)]
#![forbid(elided_lifetimes_in_paths, unsafe_code)] #![forbid(elided_lifetimes_in_paths, unsafe_code)]
mod iotro;
mod preset;
mod project;
mod question;
mod render;
mod time;
use self::{
project::{Project, ProjectLecture, ProjectSource, Resolution},
render::Renderer,
time::{parse_date, parse_time, Time}
};
use crate::preset::Preset;
use camino::Utf8PathBuf as PathBuf; use camino::Utf8PathBuf as PathBuf;
use clap::Parser; use clap::Parser;
use console::style; use console::style;
#[cfg(feature = "mem_limit")] use render_video::{
use std::sync::RwLock; cli::{ask, ask_time},
use std::{ preset::Preset,
fmt::Display, project::{Project, ProjectLecture, ProjectSource, Resolution},
fs, render::Renderer,
io::{self, BufRead as _, Write} time::parse_date
}; };
use std::fs;
#[cfg(feature = "mem_limit")]
static MEM_LIMIT: RwLock<String> = RwLock::new(String::new());
#[derive(Debug, Parser)] #[derive(Debug, Parser)]
struct Args { struct Args {
@ -60,58 +45,12 @@ struct Args {
stereo: bool stereo: bool
} }
fn ask(question: impl Display) -> String {
let mut stdout = io::stdout().lock();
let mut stdin = io::stdin().lock();
write!(
stdout,
"{} {} ",
style(question).bold().magenta(),
style(">").cyan()
)
.unwrap();
stdout.flush().unwrap();
let mut line = String::new();
stdin.read_line(&mut line).unwrap();
line.trim().to_owned()
}
fn ask_time(question: impl Display + Copy) -> Time {
let mut stdout = io::stdout().lock();
let mut stdin = io::stdin().lock();
let mut line = String::new();
loop {
line.clear();
write!(
stdout,
"{} {} ",
style(question).bold().magenta(),
style(">").cyan()
)
.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,
"{} {line:?}: {err}",
style("Invalid Input").bold().red()
)
.unwrap()
}
}
}
fn main() { fn main() {
let args = Args::parse(); let args = Args::parse();
#[cfg(feature = "mem_limit")] #[cfg(feature = "mem_limit")]
{ {
*(MEM_LIMIT.write().unwrap()) = args.mem_limit; *(render_video::MEM_LIMIT.write().unwrap()) = args.mem_limit;
} }
// process arguments // process arguments

View file

@ -11,23 +11,23 @@ use std::{fs, io};
#[serde_as] #[serde_as]
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct Preset { pub struct Preset {
// options for the intro slide // options for the intro slide
pub(crate) course: String, pub course: String,
pub(crate) label: String, pub label: String,
pub(crate) docent: String, pub docent: String,
/// Course language /// Course language
#[serde(default = "Default::default")] #[serde(default = "Default::default")]
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub(crate) lang: Language<'static>, pub lang: Language<'static>,
// coding options // coding options
pub(crate) transcode_start: Resolution, pub transcode_start: Resolution,
pub(crate) transcode: Option<Resolution> pub transcode: Option<Resolution>
} }
fn preset_23ws_malo2() -> Preset { pub fn preset_23ws_malo2() -> Preset {
Preset { Preset {
course: "23ws-malo2".into(), course: "23ws-malo2".into(),
label: "Mathematische Logik II".into(), label: "Mathematische Logik II".into(),
@ -38,7 +38,7 @@ fn preset_23ws_malo2() -> Preset {
} }
} }
fn preset_24ss_algomod() -> Preset { pub fn preset_24ss_algomod() -> Preset {
Preset { Preset {
course: "24ss-algomod".into(), course: "24ss-algomod".into(),
label: "Algorithmische Modelltheorie".into(), label: "Algorithmische Modelltheorie".into(),
@ -49,7 +49,7 @@ fn preset_24ss_algomod() -> Preset {
} }
} }
fn preset_24ss_qc() -> Preset { pub fn preset_24ss_qc() -> Preset {
Preset { Preset {
course: "24ss-qc".into(), course: "24ss-qc".into(),
label: "Introduction to Quantum Computing".into(), label: "Introduction to Quantum Computing".into(),
@ -61,7 +61,7 @@ fn preset_24ss_qc() -> Preset {
} }
impl Preset { impl Preset {
pub(crate) fn find(name: &str) -> anyhow::Result<Self> { pub fn find(name: &str) -> anyhow::Result<Self> {
match fs::read(name) { match fs::read(name) {
Ok(buf) => return Ok(toml::from_slice(&buf)?), Ok(buf) => return Ok(toml::from_slice(&buf)?),
Err(err) if err.kind() == io::ErrorKind::NotFound => {}, Err(err) if err.kind() == io::ErrorKind::NotFound => {},

View file

@ -14,7 +14,7 @@ macro_rules! resolutions {
($($res:ident: $width:literal x $height:literal at $bitrate:literal in $format:ident),+) => { ($($res:ident: $width:literal x $height:literal at $bitrate:literal in $format:ident),+) => {
#[allow(non_camel_case_types, clippy::upper_case_acronyms)] #[allow(non_camel_case_types, clippy::upper_case_acronyms)]
#[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)] #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialEq, PartialOrd, Serialize)]
pub(crate) enum Resolution { pub enum Resolution {
$( $(
#[doc = concat!(stringify!($width), "x", stringify!($height))] #[doc = concat!(stringify!($width), "x", stringify!($height))]
$res $res
@ -28,29 +28,29 @@ macro_rules! resolutions {
}; };
impl Resolution { impl Resolution {
pub(crate) fn values() -> [Self; NUM_RESOLUTIONS] { pub fn values() -> [Self; NUM_RESOLUTIONS] {
[$(Self::$res),+] [$(Self::$res),+]
} }
pub(crate) fn width(self) -> usize { pub fn width(self) -> usize {
match self { match self {
$(Self::$res => $width),+ $(Self::$res => $width),+
} }
} }
pub(crate) fn height(self) -> usize { pub fn height(self) -> usize {
match self { match self {
$(Self::$res => $height),+ $(Self::$res => $height),+
} }
} }
pub(crate) fn bitrate(self) -> u64 { pub fn bitrate(self) -> u64 {
match self { match self {
$(Self::$res => $bitrate),+ $(Self::$res => $bitrate),+
} }
} }
pub(crate) fn format(self) -> FfmpegOutputFormat { pub fn format(self) -> FfmpegOutputFormat {
match self { match self {
$(Self::$res => FfmpegOutputFormat::$format),+ $(Self::$res => FfmpegOutputFormat::$format),+
} }
@ -80,85 +80,85 @@ resolutions! {
} }
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct Project { pub struct Project {
pub(crate) lecture: ProjectLecture, pub lecture: ProjectLecture,
pub(crate) source: ProjectSource, pub source: ProjectSource,
pub(crate) progress: ProjectProgress pub progress: ProjectProgress
} }
#[serde_as] #[serde_as]
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct ProjectLecture { pub struct ProjectLecture {
pub(crate) course: String, pub course: String,
pub(crate) label: String, pub label: String,
pub(crate) docent: String, pub docent: String,
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub(crate) date: Date, pub date: Date,
#[serde(default = "Default::default")] #[serde(default = "Default::default")]
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub(crate) lang: Language<'static> pub lang: Language<'static>
} }
#[serde_as] #[serde_as]
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct ProjectSource { pub struct ProjectSource {
pub(crate) files: Vec<String>, pub files: Vec<String>,
pub(crate) stereo: bool, pub stereo: bool,
#[serde_as(as = "Option<DisplayFromStr>")] #[serde_as(as = "Option<DisplayFromStr>")]
pub(crate) start: Option<Time>, pub start: Option<Time>,
#[serde_as(as = "Option<DisplayFromStr>")] #[serde_as(as = "Option<DisplayFromStr>")]
pub(crate) end: Option<Time>, pub end: Option<Time>,
#[serde(default)] #[serde(default)]
#[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr)>")] #[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr)>")]
pub(crate) fast: Vec<(Time, Time)>, pub fast: Vec<(Time, Time)>,
#[serde(default)] #[serde(default)]
#[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr, _)>")] #[serde_as(as = "Vec<(DisplayFromStr, DisplayFromStr, _)>")]
pub(crate) questions: Vec<(Time, Time, String)>, pub questions: Vec<(Time, Time, String)>,
pub(crate) metadata: Option<ProjectSourceMetadata> pub metadata: Option<ProjectSourceMetadata>
} }
#[serde_as] #[serde_as]
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub(crate) struct ProjectSourceMetadata { pub struct ProjectSourceMetadata {
/// The duration of the source video. /// The duration of the source video.
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub(crate) source_duration: Time, pub source_duration: Time,
/// The FPS of the source video. /// The FPS of the source video.
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub(crate) source_fps: Rational, pub source_fps: Rational,
/// The time base of the source video. /// The time base of the source video.
#[serde_as(as = "DisplayFromStr")] #[serde_as(as = "DisplayFromStr")]
pub(crate) source_tbn: Rational, pub source_tbn: Rational,
/// The resolution of the source video. /// The resolution of the source video.
pub(crate) source_res: Resolution, pub source_res: Resolution,
/// The sample rate of the source audio. /// The sample rate of the source audio.
pub(crate) source_sample_rate: u32 pub source_sample_rate: u32
} }
#[derive(Default, Deserialize, Serialize)] #[derive(Default, Deserialize, Serialize)]
pub(crate) struct ProjectProgress { pub struct ProjectProgress {
#[serde(default)] #[serde(default)]
pub(crate) preprocessed: bool, pub preprocessed: bool,
#[serde(default)] #[serde(default)]
pub(crate) asked_start_end: bool, pub asked_start_end: bool,
#[serde(default)] #[serde(default)]
pub(crate) asked_fast: bool, pub asked_fast: bool,
#[serde(default)] #[serde(default)]
pub(crate) asked_questions: bool, pub asked_questions: bool,
#[serde(default)] #[serde(default)]
pub(crate) rendered_assets: bool, pub rendered_assets: bool,
#[serde(default)] #[serde(default)]
pub(crate) rendered: bool, pub rendered: bool,
#[serde(default)] #[serde(default)]
pub(crate) transcoded: BTreeSet<Resolution> pub transcoded: BTreeSet<Resolution>
} }

View file

@ -1,4 +1,4 @@
use crate::{iotro::Language, Resolution}; use crate::{iotro::Language, project::Resolution};
use fontconfig::Fontconfig; use fontconfig::Fontconfig;
use harfbuzz_rs::{Face, Font, Owned, UnicodeBuffer}; use harfbuzz_rs::{Face, Font, Owned, UnicodeBuffer};
use std::sync::OnceLock; use std::sync::OnceLock;

View file

@ -1,8 +1,8 @@
use super::{cmd, filter::Filter}; use super::{cmd, filter::Filter};
use crate::{ use crate::{
project::Resolution,
render::filter::channel, render::filter::channel,
time::{format_time, Time}, time::{format_time, Time}
Resolution
}; };
use anyhow::bail; use anyhow::bail;
use camino::Utf8PathBuf as PathBuf; use camino::Utf8PathBuf as PathBuf;

View file

@ -119,7 +119,7 @@ fn ffprobe_audio(query: &str, concat_input: &Path) -> anyhow::Result<String> {
) )
} }
pub(crate) struct Renderer<'a> { pub struct Renderer<'a> {
/// The directory with all the sources. /// The directory with all the sources.
directory: &'a Path, directory: &'a Path,
@ -175,7 +175,7 @@ fn svg2png(svg: &Path, png: &Path, width: usize, height: usize) -> anyhow::Resul
} }
impl<'a> Renderer<'a> { impl<'a> Renderer<'a> {
pub(crate) fn new(directory: &'a Path, project: &Project) -> anyhow::Result<Self> { pub fn new(directory: &'a Path, project: &Project) -> anyhow::Result<Self> {
let slug = format!( let slug = format!(
"{}-{}", "{}-{}",
project.lecture.course, project.lecture.course,
@ -210,7 +210,7 @@ impl<'a> Renderer<'a> {
}) })
} }
pub(crate) fn recording_mkv(&self) -> PathBuf { pub fn recording_mkv(&self) -> PathBuf {
self.target.join("recording.mkv") self.target.join("recording.mkv")
} }
@ -230,7 +230,7 @@ impl<'a> Renderer<'a> {
self.target.join(format!("question{q_idx}.png")) self.target.join(format!("question{q_idx}.png"))
} }
pub(crate) fn preprocess(&self, project: &mut Project) -> anyhow::Result<()> { pub fn preprocess(&self, project: &mut Project) -> anyhow::Result<()> {
assert!(!project.progress.preprocessed); assert!(!project.progress.preprocessed);
let recording_txt = self.target.join("recording.txt"); let recording_txt = self.target.join("recording.txt");
@ -283,7 +283,7 @@ impl<'a> Renderer<'a> {
} }
/// Prepare assets like intro, outro and questions. /// Prepare assets like intro, outro and questions.
pub(crate) fn render_assets(&self, project: &Project) -> anyhow::Result<()> { pub fn render_assets(&self, project: &Project) -> anyhow::Result<()> {
let metadata = project.source.metadata.as_ref().unwrap(); let metadata = project.source.metadata.as_ref().unwrap();
println!(); println!();
@ -375,11 +375,11 @@ impl<'a> Renderer<'a> {
} }
/// Get the video file directly outputed to further transcode. /// Get the video file directly outputed to further transcode.
pub(crate) fn video_file_output(&self) -> PathBuf { pub fn video_file_output(&self) -> PathBuf {
self.target.join(format!("{}.mkv", self.slug)) self.target.join(format!("{}.mkv", self.slug))
} }
pub(crate) fn render(&self, project: &mut Project) -> anyhow::Result<PathBuf> { pub fn render(&self, project: &mut Project) -> anyhow::Result<PathBuf> {
let source_res = project.source.metadata.as_ref().unwrap().source_res; let source_res = project.source.metadata.as_ref().unwrap().source_res;
let output = self.video_file_output(); let output = self.video_file_output();