diff --git a/assets/fastforward.svg b/assets/fastforward.svg new file mode 100644 index 0000000000000000000000000000000000000000..52439673412bb731dbb2b61f1542a7d303d447d4 --- /dev/null +++ b/assets/fastforward.svg @@ -0,0 +1,8 @@ +<svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> + <style type="text/css" id="current-color-scheme"> + .ColorScheme-Text { + color:#eff0f1; + } + </style> + <path d="m8 2v12l7-6zm-7 0v12l7-6z" class="ColorScheme-Text" fill="currentColor"/> +</svg> diff --git a/assets/logo.svg b/assets/logo.svg new file mode 100644 index 0000000000000000000000000000000000000000..b14083d82504258fccf9f66766fff63694b10e53 --- /dev/null +++ b/assets/logo.svg @@ -0,0 +1,199 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:xlink="http://www.w3.org/1999/xlink" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="299.99982" + height="300.00003" + id="svg2" + sodipodi:version="0.32" + inkscape:version="0.46" + sodipodi:docbase="C:\Eigene Dateien\Video AG" + sodipodi:docname="logo.svg" + inkscape:output_extension="org.inkscape.output.svg.inkscape" + version="1.0" + inkscape:export-filename="Q:\video AG\fs-pub-video\folien\logo-1024.png" + inkscape:export-xdpi="307.20001" + inkscape:export-ydpi="307.20001"> + <defs + id="defs4"> + <inkscape:perspective + sodipodi:type="inkscape:persp3d" + inkscape:vp_x="0 : 526.18109 : 1" + inkscape:vp_y="0 : 1000 : 0" + inkscape:vp_z="744.09448 : 526.18109 : 1" + inkscape:persp3d-origin="372.04724 : 350.78739 : 1" + id="perspective21" /> + <linearGradient + id="linearGradient2041"> + <stop + style="stop-color:#ffffff;stop-opacity:1.0000000;" + offset="0.00000000" + id="stop2043" /> + <stop + style="stop-color:#7d7d7d;stop-opacity:1.0000000;" + offset="1.0000000" + id="stop2045" /> + </linearGradient> + <linearGradient + inkscape:collect="always" + xlink:href="#linearGradient2041" + id="linearGradient2047" + x1="194.17578" + y1="139.59265" + x2="250.03906" + y2="139.59265" + gradientUnits="userSpaceOnUse" + gradientTransform="translate(317,-57)" /> + </defs> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0" + inkscape:pageshadow="2" + inkscape:zoom="1.4889823" + inkscape:cx="71.681092" + inkscape:cy="217.8489" + inkscape:document-units="px" + inkscape:current-layer="layer1" + inkscape:window-width="1400" + inkscape:window-height="988" + inkscape:window-x="-8" + inkscape:window-y="-8" + showgrid="false" + inkscape:snap-bbox="false" + inkscape:snap-nodes="true" + inkscape:object-paths="false" + inkscape:object-nodes="true" + objecttolerance="10" + gridtolerance="10000" + guidetolerance="10000" + showborder="true" + inkscape:showpageshadow="true" + borderlayer="false" /> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Ebene 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-438.99979,0.6379836)"> + <path + sodipodi:type="arc" + style="opacity:1;fill:#ffffff;fill-opacity:1;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" + id="path2393" + sodipodi:cx="454" + sodipodi:cy="112.36218" + sodipodi:rx="87" + sodipodi:ry="56" + d="M 541,112.36218 A 87,56 0 1 1 367,112.36218 A 87,56 0 1 1 541,112.36218 z" + transform="matrix(-1.977323e-3,-1.724137,2.678569,-3.071905e-3,288.9275,932.4654)" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" /> + <path + sodipodi:type="arc" + style="opacity:1;fill:#000000;fill-opacity:1;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" + id="path1306" + sodipodi:cx="281.42856" + sodipodi:cy="270.93362" + sodipodi:rx="38.57143" + sodipodi:ry="44.285713" + d="M 319.99999,270.93362 A 38.57143,44.285713 0 1 1 242.85713,270.93362 A 38.57143,44.285713 0 1 1 319.99999,270.93362 z" + transform="matrix(0.972222,0,0,0.846774,264.7456,-151.7001)" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" /> + <text + xml:space="preserve" + style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:url(#linearGradient2047);fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial Black" + x="511" + y="108.36218" + id="text1308" + sodipodi:linespacing="125%" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"><tspan + sodipodi:role="line" + id="tspan1312" + x="511" + y="108.36218" + style="fill:url(#linearGradient2047);fill-opacity:1">V</tspan></text> + <path + sodipodi:type="arc" + style="opacity:1;fill:#000000;fill-opacity:1;stroke-width:1;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0" + id="path3517" + sodipodi:cx="454" + sodipodi:cy="112.36218" + sodipodi:rx="87" + sodipodi:ry="56" + d="M 541,112.36218 A 87,56 0 1 1 454,56.362183 L 454,112.36218 z" + transform="matrix(-1.977323e-3,-1.724137,2.678569,-3.071905e-3,288.9275,932.4654)" + sodipodi:start="0" + sodipodi:end="4.712389" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000" /> + <text + xml:space="preserve" + style="font-size:28px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial Black" + x="598" + y="105.36218" + id="text2055" + sodipodi:linespacing="125%" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"><tspan + sodipodi:role="line" + id="tspan2785" + x="598" + y="105.36218" + style="font-size:36px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;font-family:Arial Black">ideo</tspan></text> + <text + xml:space="preserve" + style="font-size:100px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial Black" + x="511" + y="243.36218" + id="text2051" + sodipodi:linespacing="125%" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"><tspan + sodipodi:role="line" + id="tspan2053" + x="511" + y="243.36218" + style="font-size:100px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#ffffff;fill-opacity:1;font-family:Arial Black">AG</tspan></text> + <text + xml:space="preserve" + style="font-size:72px;font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;text-align:start;line-height:125%;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:0.25098039;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1;font-family:Arial Black" + x="471" + y="108.36218" + id="text4979" + sodipodi:linespacing="125%" + inkscape:export-filename="C:\Eigene Dateien\Video AG\logo.png" + inkscape:export-xdpi="90.000000" + inkscape:export-ydpi="90.000000"><tspan + sodipodi:role="line" + id="tspan4981" + x="471" + y="108.36218">V</tspan></text> + </g> +</svg> diff --git a/src/render/ffmpeg.rs b/src/render/ffmpeg.rs index 1c94319c22c9cda4ced32af48574cebb9bd5d9a4..70e5dbaf58c5d5d82cae77c209d7d781e32c5e5e 100644 --- a/src/render/ffmpeg.rs +++ b/src/render/ffmpeg.rs @@ -2,9 +2,10 @@ use super::cmd; use crate::time::{format_time, Time}; use camino::{Utf8Path as Path, Utf8PathBuf as PathBuf}; use rational::Rational; -use std::process::Command; +use std::{borrow::Cow, process::Command}; pub(crate) struct FfmpegInput { + pub(crate) concat: bool, pub(crate) loop_input: bool, pub(crate) fps: Option<Rational>, pub(crate) start: Option<Time>, @@ -15,6 +16,7 @@ pub(crate) struct FfmpegInput { impl FfmpegInput { pub(crate) fn new(path: PathBuf) -> Self { Self { + concat: false, loop_input: false, fps: None, start: None, @@ -24,6 +26,9 @@ impl FfmpegInput { } fn append_to_cmd(self, cmd: &mut Command) { + if self.concat { + cmd.arg("-f").arg("concat").arg("-safe").arg("0"); + } if self.loop_input { cmd.arg("-loop").arg("1"); } @@ -40,8 +45,65 @@ impl FfmpegInput { } } +pub(crate) enum Filter { + Concat { + inputs: Vec<Cow<'static, str>>, + n: usize, + output: Cow<'static, str> + }, + + FadeIn { + input: Cow<'static, str>, + start: Time, + duration: Time, + output: Cow<'static, str> + }, + + FadeOut { + input: Cow<'static, str>, + start: Time, + duration: Time, + output: Cow<'static, str> + }, + + Overlay { + video_input: Cow<'static, str>, + overlay_input: Cow<'static, str>, + x: Cow<'static, str>, + y: Cow<'static, str>, + output: Cow<'static, str> + }, + + GenerateSilence { + output: Cow<'static, str> + } +} + +impl Filter { + fn is_video_filter(&self) -> bool { + matches!( + self, + Self::Concat { .. } + | Self::FadeIn { .. } + | Self::FadeOut { .. } + | Self::Overlay { .. } + ) + } + + fn is_audio_filter(&self) -> bool { + matches!( + self, + Self::Concat { .. } + | Self::FadeIn { .. } + | Self::FadeOut { .. } + | Self::GenerateSilence { .. } + ) + } +} + pub(crate) struct Ffmpeg { inputs: Vec<FfmpegInput>, + filters: Vec<Filter>, output: PathBuf } @@ -49,6 +111,7 @@ impl Ffmpeg { pub fn new(output: PathBuf) -> Self { Self { inputs: Vec::new(), + filters: Vec::new(), output } } @@ -57,9 +120,13 @@ impl Ffmpeg { let mut cmd = cmd(); cmd.arg("ffmpeg").arg("-hide_banner"); + // determine whether the video need to be re-encoded + let venc = self.filters.iter().any(|f| f.is_video_filter()); + let aenc = self.filters.iter().any(|f| f.is_audio_filter()); + // initialise a vaapi device if one exists let vaapi_device: PathBuf = "/dev/dri/renderD128".into(); - let vaapi = vaapi_device.exists(); + let vaapi = venc && vaapi_device.exists(); if vaapi { cmd.arg("-vaapi_device").arg(&vaapi_device); } @@ -72,6 +139,20 @@ impl Ffmpeg { // always try to synchronise audio cmd.arg("-async").arg("1"); + // TODO apply filters + + // append encoding options + if vaapi { + cmd.arg("-c:v").arg("h264_vaapi"); + cmd.arg("-rc_mode").arg("CQP"); + cmd.arg("-global_quality").arg("22"); + } else if venc { + cmd.arg("-c:v").arg("libx264"); + cmd.arg("-crf").arg("22"); + } else { + cmd.arg("-c:v").arg("copy"); + } + unimplemented!() } } diff --git a/src/render/mod.rs b/src/render/mod.rs index 28126a54962a99df7ee48b454034adc42bad0e63..843d910207d51eb77cc118e09e0247ae538b4f90 100644 --- a/src/render/mod.rs +++ b/src/render/mod.rs @@ -4,6 +4,7 @@ pub mod ffmpeg; use crate::{ iotro::intro, + render::ffmpeg::Ffmpeg, time::{format_date, Time}, Project, ProjectSourceMetadata, Resolution }; @@ -133,6 +134,20 @@ impl<'a> Renderer<'a> { pub(crate) fn preprocess(&self, project: &mut Project) -> anyhow::Result<()> { assert!(!project.progress.preprocessed); + let logo = self.target.join("logo.svg"); + fs::write( + &logo, + include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/assets/logo.svg")) + )?; + let fastforward = self.target.join("fastforward.svg"); + fs::write( + &fastforward, + include_bytes!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/assets/fastforward.svg" + )) + )?; + let recording_txt = self.target.join("recording.txt"); let mut file = File::create(recording_txt)?; for filename in &project.source.files { @@ -141,7 +156,8 @@ impl<'a> Renderer<'a> { drop(file); println!("\x1B[1m ==> Concatenating Video and Normalising Audio ..."); - let mut ffmpeg = Ffmpeg::new(); + let recording_mp4 = self.target.join("recording.mp4"); + let mut ffmpeg = Ffmpeg::new(recording_mp4); // project.source.metadata = Some(ProjectSourceMetadata { // source_duration: ffprobe_video("format=duration", input)?.parse()?