From 823d8ce5dce7290ad03a6cea7334bbedd38c28e8 Mon Sep 17 00:00:00 2001 From: Dominic Date: Sat, 28 Oct 2023 23:38:17 +0200 Subject: [PATCH] prepare writing ffmpeg commands --- Cargo.toml | 5 +- src/iotro.rs | 115 +++++++++++++++++++++++++++++++ src/main.rs | 82 +++++++++++++++++++++-- src/render/ffmpeg.rs | 77 +++++++++++++++++++++ src/render/mod.rs | 156 +++++++++++++++++++++++++++++++++++++++++++ src/time.rs | 10 +-- 6 files changed, 434 insertions(+), 11 deletions(-) create mode 100644 src/iotro.rs create mode 100644 src/render/ffmpeg.rs create mode 100644 src/render/mod.rs diff --git a/Cargo.toml b/Cargo.toml index 4f6a213..a9832d4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,10 @@ edition = "2021" anyhow = "1.0" camino = "1.1" clap = { version = "4.4", features = ["derive"] } -ffmpeg = { package = "ffmpeg-next", version = "6.0" } +#ffmpeg = { package = "ffmpeg-next", version = "6.0" } +indexmap = "1.9" +rational = "1.4" serde = { version = "1.0.188", features = ["derive"] } serde_with = "3.4" +svgwriter = "0.1" toml = { package = "basic-toml", version = "0.1.4" } diff --git a/src/iotro.rs b/src/iotro.rs new file mode 100644 index 0000000..f430656 --- /dev/null +++ b/src/iotro.rs @@ -0,0 +1,115 @@ +//! A module for writing intros and outros + +use crate::{ + time::{format_date_long, Date}, + Resolution +}; +use svgwriter::{ + tags::{Group, Rect, TagWithPresentationAttributes, Text}, + Graphic +}; + +#[repr(u16)] +enum FontSize { + Huge = 72, + Large = 56, + Big = 44 +} + +#[repr(u16)] +enum FontWeight { + Normal = 400, + SemiBold = 500, + Bold = 700 +} + +struct Iotro { + res: Resolution, + g: Group +} + +impl Iotro { + fn new(res: Resolution) -> Self { + Self { + res, + g: Group::new() + .with_fill("white") + .with_text_anchor("middle") + .with_dominant_baseline("hanging") + .with_font_family("Noto Sans") + } + } + + fn add_text>( + &mut self, + font_size: FontSize, + font_weight: FontWeight, + y_top: usize, + content: T + ) { + let mut text = Text::new() + .with_x(960) + .with_y(y_top) + .with_font_size(font_size as u16) + .with_font_weight(font_weight as u16); + text.push(content.into()); + self.g.push(text); + } + + fn finish(self) -> Graphic { + let mut svg = Graphic::new(); + svg.set_width(self.res.width()); + svg.set_height(self.res.height()); + svg.set_view_box("0 0 1920 1080"); + svg.push( + Rect::new() + .with_fill("black") + .with_x(0) + .with_y(0) + .with_width(1920) + .with_height(1080) + ); + svg.push(self.g); + svg + } +} + +pub(crate) fn intro(res: Resolution, date: Date) -> Graphic { + use self::{FontSize::*, FontWeight::*}; + + let mut intro = Iotro::new(res); + intro.add_text(Huge, Bold, 110, "Mathematische Logik II"); + intro.add_text(Huge, SemiBold, 250, "Prof. E. Grädel"); + intro.add_text( + Huge, + SemiBold, + 460, + format!("Vorlesung vom {}", format_date_long(date)) + ); + intro.add_text( + Big, + Normal, + 870, + "Video erstellt von der Video AG, Fachschaft I/1" + ); + intro.add_text(Big, Normal, 930, "https://video.fsmpi.rwth-aachen.de"); + intro.add_text(Big, Normal, 990, "video@fsmpi.rwth-aachen.de"); + + intro.finish() +} + +pub(crate) fn outro(res: Resolution) -> Graphic { + use self::{FontSize::*, FontWeight::*}; + + let mut outro = Iotro::new(res); + outro.add_text(Large, SemiBold, 50, "Video erstellt von der"); + outro.add_text(Huge, Bold, 210, "Video AG, Fachschaft I/1"); + outro.add_text(Large, Normal, 360, "Website der Fachschaft:"); + outro.add_text(Large, Normal, 430, "https://www.fsmpi.rwth-aachen.de"); + outro.add_text(Large, Normal, 570, "Videos herunterladen:"); + outro.add_text(Large, Normal, 640, "https://video.fsmpi.rwth-aachen.de"); + outro.add_text(Large, Normal, 780, "Fragen, Vorschläge und Feedback:"); + outro.add_text(Large, Normal, 850, "video@fsmpi.rwth-aachen.de"); + + outro.finish() +} diff --git a/src/main.rs b/src/main.rs index d17dcbf..a9ea623 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,14 +1,18 @@ #![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 serde_with::{serde_as, DisplayFromStr, FromInto}; use std::{ + collections::BTreeSet, fmt::Display, fs, io::{self, BufRead as _, Write} @@ -23,10 +27,48 @@ struct Args { course: String } +#[allow(non_camel_case_types)] +#[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 + source: ProjectSource, + progress: ProjectProgress } #[serde_as] @@ -41,10 +83,37 @@ struct ProjectLecture { #[derive(Deserialize, Serialize)] struct ProjectSource { files: Vec, + #[serde_as(as = "DisplayFromStr")] first_file_start: Time, #[serde_as(as = "DisplayFromStr")] - last_file_end: Time + 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 { @@ -111,12 +180,15 @@ fn main() { source: ProjectSource { files, first_file_start, - last_file_end - } + 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(); } diff --git a/src/render/ffmpeg.rs b/src/render/ffmpeg.rs new file mode 100644 index 0000000..1c94319 --- /dev/null +++ b/src/render/ffmpeg.rs @@ -0,0 +1,77 @@ +use super::cmd; +use crate::time::{format_time, Time}; +use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; +use rational::Rational; +use std::process::Command; + +pub(crate) struct FfmpegInput { + pub(crate) loop_input: bool, + pub(crate) fps: Option, + pub(crate) start: Option