Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
D
dominic_render_video
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Iterations
Wiki
Requirements
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Snippets
Locked files
Build
Pipelines
Jobs
Pipeline schedules
Test cases
Artifacts
Deploy
Releases
Package registry
Container registry
Model registry
Operate
Environments
Terraform modules
Monitor
Incidents
Analyze
Value stream analytics
Contributor analytics
CI/CD analytics
Repository analytics
Code review analytics
Issue analytics
Insights
Model experiments
Help
Help
Support
GitLab documentation
Compare GitLab plans
GitLab community forum
Contribute to GitLab
Provide feedback
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
videoag
dominic_render_video
Commits
f4adda91
Unverified
Commit
f4adda91
authored
Dec 19, 2023
by
Dominic Meiser
Browse files
Options
Downloads
Patches
Plain Diff
use AV1 for 1440p and higher
parent
c288f55e
No related branches found
No related tags found
No related merge requests found
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
src/main.rs
+16
-10
16 additions, 10 deletions
src/main.rs
src/render/ffmpeg.rs
+58
-39
58 additions, 39 deletions
src/render/ffmpeg.rs
src/render/mod.rs
+57
-35
57 additions, 35 deletions
src/render/mod.rs
with
131 additions
and
84 deletions
src/main.rs
+
16
−
10
View file @
f4adda91
...
...
@@ -7,7 +7,7 @@ mod render;
mod
time
;
use
crate
::{
render
::
Renderer
,
render
::
{
ffmpeg
::
FfmpegOutputFormat
,
Renderer
}
,
time
::{
parse_date
,
parse_time
,
Date
,
Time
}
};
use
camino
::
Utf8PathBuf
as
PathBuf
;
...
...
@@ -46,7 +46,7 @@ struct Args {
}
macro_rules!
resolutions
{
(
$
(
$res:ident
:
$width:literal
x
$height:literal
at
$bitrate:literal
),
+
)
=>
{
(
$
(
$res:ident
:
$width:literal
x
$height:literal
at
$bitrate:literal
in
$format:ident
),
+
)
=>
{
#[allow(non_camel_case_types,
clippy::upper_case_acronyms)]
#[derive(Clone,
Copy,
Debug,
Deserialize,
Eq,
Ord,
PartialEq,
PartialOrd,
Serialize)]
enum
Resolution
{
...
...
@@ -84,6 +84,12 @@ macro_rules! resolutions {
$
(
Self
::
$res
=>
$bitrate
),
+
}
}
fn
format
(
self
)
->
FfmpegOutputFormat
{
match
self
{
$
(
Self
::
$res
=>
FfmpegOutputFormat
::
$format
),
+
}
}
}
impl
FromStr
for
Resolution
{
...
...
@@ -100,12 +106,12 @@ macro_rules! resolutions {
}
resolutions!
{
nHD
:
640
x
360
at
500_000
,
HD
:
1280
x
720
at
1_000_000
,
FullHD
:
1920
x
1080
at
2_000_000
,
WQHD
:
2560
x
1440
at
3_000_000
,
nHD
:
640
x
360
at
500_000
in
AvcAac
,
HD
:
1280
x
720
at
1_000_000
in
AvcAac
,
FullHD
:
1920
x
1080
at
2_000_000
in
AvcAac
,
WQHD
:
2560
x
1440
at
3_000_000
in
Av1Opus
,
// TODO qsx muss mal sagen wieviel bitrate für 4k
UHD
:
3840
x
2160
at
4_000_000
UHD
:
3840
x
2160
at
4_000_000
in
Av1Opus
}
#[derive(Deserialize,
Serialize)]
...
...
@@ -234,7 +240,7 @@ fn main() {
println!
(
"{}"
,
toml
::
to_string
(
&
project
)
.unwrap
());
let
renderer
=
Renderer
::
new
(
&
directory
,
&
project
)
.unwrap
();
let
recording
=
renderer
.recording_m
p4
();
let
recording
=
renderer
.recording_m
kv
();
// preprocess the video
if
!
project
.progress.preprocessed
{
...
...
@@ -282,7 +288,7 @@ fn main() {
// render the video
let
mut
videos
=
Vec
::
new
();
videos
.push
(
if
project
.progress.rendered
{
renderer
.video_
mp4
(
&
project
)
renderer
.video_
file_output
(
)
}
else
{
let
video
=
renderer
.render
(
&
mut
project
)
.unwrap
();
project
.progress.rendered
=
true
;
...
...
@@ -302,7 +308,7 @@ fn main() {
continue
;
}
if
!
project
.progress.transcoded
.contains
(
&
res
)
{
videos
.push
(
renderer
.rescale
(
res
,
&
project
)
.unwrap
());
videos
.push
(
renderer
.rescale
(
res
)
.unwrap
());
project
.progress.transcoded
.insert
(
res
);
println!
(
"{}"
,
toml
::
to_string
(
&
project
)
.unwrap
());
...
...
This diff is collapsed.
Click to expand it.
src/render/ffmpeg.rs
+
58
−
39
View file @
f4adda91
...
...
@@ -41,7 +41,9 @@ impl FfmpegInput {
cmd
.arg
(
"-r"
)
.arg
(
fps
.to_string
());
}
if
let
Some
(
start
)
=
self
.start
{
cmd
.arg
(
"-seek_streams_individually"
)
.arg
(
"false"
);
if
self
.path
.ends_with
(
".mp4"
)
{
cmd
.arg
(
"-seek_streams_individualy"
)
.arg
(
"false"
);
}
cmd
.arg
(
"-ss"
)
.arg
(
format_time
(
start
));
}
if
let
Some
(
duration
)
=
self
.duration
{
...
...
@@ -51,18 +53,35 @@ impl FfmpegInput {
}
}
pub
(
crate
)
enum
FfmpegOutputFormat
{
/// AV1 / FLAC
Av1Flac
,
/// AV1 / OPUS
Av1Opus
,
/// AVC (H.264) / AAC
AvcAac
}
pub
(
crate
)
struct
FfmpegOutput
{
pub
(
crate
)
format
:
FfmpegOutputFormat
,
pub
(
crate
)
audio_bitrate
:
Option
<
u64
>
,
pub
(
crate
)
video_bitrate
:
Option
<
u64
>
,
pub
(
crate
)
fps
:
Option
<
Rational
>
,
pub
(
crate
)
duration
:
Option
<
Time
>
,
pub
(
crate
)
time_base
:
Option
<
Rational
>
,
pub
(
crate
)
fps_mode_vfr
:
bool
,
pub
(
crate
)
faststart
:
bool
,
pub
(
crate
)
path
:
PathBuf
}
impl
FfmpegOutput
{
pub
(
crate
)
fn
new
(
path
:
PathBuf
)
->
Self
{
pub
(
crate
)
fn
new
(
format
:
FfmpegOutputFormat
,
path
:
PathBuf
)
->
Self
{
Self
{
format
,
audio_bitrate
:
None
,
video_bitrate
:
None
,
fps
:
None
,
duration
:
None
,
time_base
:
None
,
...
...
@@ -77,7 +96,42 @@ impl FfmpegOutput {
self
}
fn
append_to_cmd
(
self
,
cmd
:
&
mut
Command
)
{
fn
append_to_cmd
(
self
,
cmd
:
&
mut
Command
,
venc
:
bool
,
_aenc
:
bool
,
vaapi
:
bool
)
{
// select codec and bitrate
const
QUALITY
:
&
str
=
"22"
;
if
venc
{
let
mut
vcodec
:
String
=
match
self
.format
{
FfmpegOutputFormat
::
Av1Flac
|
FfmpegOutputFormat
::
Av1Opus
=>
"av1"
.into
(),
FfmpegOutputFormat
::
AvcAac
=>
"h264"
.into
()
};
if
vaapi
{
vcodec
=
format!
(
"{vcodec}_vaapi"
);
}
cmd
.arg
(
"-c:v"
)
.arg
(
vcodec
);
if
let
Some
(
bv
)
=
self
.video_bitrate
{
cmd
.arg
(
"-b:v"
)
.arg
(
bv
.to_string
());
}
else
if
vaapi
{
cmd
.arg
(
"-rc_mode"
)
.arg
(
"CQP"
);
cmd
.arg
(
"-global_quality"
)
.arg
(
QUALITY
);
}
else
{
cmd
.arg
(
"-crf"
)
.arg
(
QUALITY
);
}
}
else
{
cmd
.arg
(
"-c:v"
)
.arg
(
"copy"
);
}
cmd
.arg
(
"-c:a"
)
.arg
(
match
self
.format
{
FfmpegOutputFormat
::
Av1Flac
=>
"flac"
,
FfmpegOutputFormat
::
Av1Opus
=>
"libopus"
,
FfmpegOutputFormat
::
AvcAac
=>
"aac"
});
if
let
Some
(
ba
)
=
self
.audio_bitrate
{
cmd
.arg
(
"-b:a"
)
.arg
(
ba
.to_string
());
}
else
{
cmd
.arg
(
"-b:a"
)
.arg
(
"128k"
);
}
// other output options
if
let
Some
(
fps
)
=
self
.fps
{
cmd
.arg
(
"-r"
)
.arg
(
fps
.to_string
());
}
...
...
@@ -110,7 +164,6 @@ enum FfmpegFilter {
pub
(
crate
)
struct
Ffmpeg
{
inputs
:
Vec
<
FfmpegInput
>
,
filter
:
FfmpegFilter
,
video_bitrate
:
Option
<
u64
>
,
output
:
FfmpegOutput
,
filter_idx
:
usize
...
...
@@ -121,7 +174,6 @@ impl Ffmpeg {
Self
{
inputs
:
Vec
::
new
(),
filter
:
FfmpegFilter
::
None
,
video_bitrate
:
None
,
output
,
filter_idx
:
0
...
...
@@ -182,11 +234,6 @@ impl Ffmpeg {
self
}
pub
fn
set_video_bitrate
(
&
mut
self
,
bitrate
:
u64
)
->
&
mut
Self
{
self
.video_bitrate
=
Some
(
bitrate
);
self
}
pub
fn
run
(
mut
self
)
->
anyhow
::
Result
<
()
>
{
let
mut
cmd
=
cmd
();
cmd
.arg
(
"ffmpeg"
)
.arg
(
"-hide_banner"
)
.arg
(
"-y"
);
...
...
@@ -256,35 +303,7 @@ impl Ffmpeg {
}
}
// append encoding options
const
QUALITY
:
&
str
=
"22"
;
if
venc
{
if
vaapi
{
cmd
.arg
(
"-c:v"
)
.arg
(
"h264_vaapi"
);
if
self
.video_bitrate
.is_none
()
{
cmd
.arg
(
"-rc_mode"
)
.arg
(
"CQP"
);
cmd
.arg
(
"-global_quality"
)
.arg
(
QUALITY
);
}
}
else
{
cmd
.arg
(
"-c:v"
)
.arg
(
"libx264"
);
if
self
.video_bitrate
.is_none
()
{
cmd
.arg
(
"-crf"
)
.arg
(
QUALITY
);
}
}
if
self
.video_bitrate
.is_some
()
{
cmd
.arg
(
"-b:v"
)
.arg
(
self
.video_bitrate
.unwrap
()
.to_string
());
}
}
else
{
cmd
.arg
(
"-c:v"
)
.arg
(
"copy"
);
}
if
aenc
{
cmd
.arg
(
"-c:a"
)
.arg
(
"aac"
);
cmd
.arg
(
"-b:a"
)
.arg
(
"128000"
);
}
else
{
cmd
.arg
(
"-c:a"
)
.arg
(
"copy"
);
}
self
.output
.append_to_cmd
(
&
mut
cmd
);
self
.output
.append_to_cmd
(
&
mut
cmd
,
venc
,
aenc
,
vaapi
);
let
status
=
cmd
.status
()
?
;
if
status
.success
()
{
...
...
This diff is collapsed.
Click to expand it.
src/render/mod.rs
+
57
−
35
View file @
f4adda91
pub
mod
ffmpeg
;
mod
filter
;
use
self
::{
ffmpeg
::
FfmpegOutput
,
filter
::
Filter
};
use
self
::{
ffmpeg
::{
FfmpegOutput
,
FfmpegOutputFormat
},
filter
::
Filter
};
use
crate
::{
iotro
::{
intro
,
outro
},
render
::
ffmpeg
::{
Ffmpeg
,
FfmpegInput
},
...
...
@@ -114,19 +117,17 @@ pub(crate) struct Renderer<'a> {
target
:
PathBuf
}
fn
svg2m
p4
(
fn
svg2m
kv
(
meta
:
&
ProjectSourceMetadata
,
svg
:
PathBuf
,
m
p4
:
PathBuf
,
m
kv
:
PathBuf
,
duration
:
Time
)
->
anyhow
::
Result
<
()
>
{
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
{
fps
:
None
,
duration
:
Some
(
duration
),
time_base
:
Some
(
meta
.source_tbn
),
fps_mode_vfr
:
true
,
faststart
:
false
,
path
:
mp4
..
FfmpegOutput
::
new
(
FfmpegOutputFormat
::
Av1Flac
,
mkv
)
});
ffmpeg
.add_input
(
FfmpegInput
{
loop_input
:
true
,
...
...
@@ -176,8 +177,16 @@ impl<'a> Renderer<'a> {
})
}
pub
(
crate
)
fn
recording_mp4
(
&
self
)
->
PathBuf
{
self
.target
.join
(
"recording.mp4"
)
pub
(
crate
)
fn
recording_mkv
(
&
self
)
->
PathBuf
{
self
.target
.join
(
"recording.mkv"
)
}
fn
intro_mkv
(
&
self
)
->
PathBuf
{
self
.target
.join
(
"intro.mkv"
)
}
fn
outro_mkv
(
&
self
)
->
PathBuf
{
self
.target
.join
(
"outro.mkv"
)
}
pub
(
crate
)
fn
preprocess
(
&
self
,
project
:
&
mut
Project
)
->
anyhow
::
Result
<
()
>
{
...
...
@@ -193,8 +202,11 @@ impl<'a> Renderer<'a> {
println!
(
"
\x1B
[1m ==> Concatenating Video and Normalising Audio ...
\x1B
[0m"
);
let
source_sample_rate
=
ffprobe_audio
(
"stream=sample_rate"
,
&
recording_txt
)
?
.parse
()
?
;
let
recording_mp4
=
self
.recording_mp4
();
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
::
new
(
recording_mp4
.clone
()));
let
recording_mkv
=
self
.recording_mkv
();
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
::
new
(
FfmpegOutputFormat
::
Av1Flac
,
recording_mkv
.clone
()
));
ffmpeg
.add_input
(
FfmpegInput
{
concat
:
true
,
..
FfmpegInput
::
new
(
recording_txt
)
...
...
@@ -202,8 +214,8 @@ impl<'a> Renderer<'a> {
ffmpeg
.enable_loudnorm
();
ffmpeg
.run
()
?
;
let
width
=
ffprobe_video
(
"stream=width"
,
&
recording_m
p4
)
?
.parse
()
?
;
let
height
=
ffprobe_video
(
"stream=height"
,
&
recording_m
p4
)
?
.parse
()
?
;
let
width
=
ffprobe_video
(
"stream=width"
,
&
recording_m
kv
)
?
.parse
()
?
;
let
height
=
ffprobe_video
(
"stream=height"
,
&
recording_m
kv
)
?
.parse
()
?
;
let
source_res
=
match
(
width
,
height
)
{
(
3840
,
2160
)
=>
Resolution
::
UHD
,
(
2560
,
1440
)
=>
Resolution
::
WQHD
,
...
...
@@ -213,9 +225,9 @@ impl<'a> Renderer<'a> {
(
width
,
height
)
=>
bail!
(
"Unknown resolution: {width}x{height}"
)
};
project
.source.metadata
=
Some
(
ProjectSourceMetadata
{
source_duration
:
ffprobe_video
(
"format=duration"
,
&
recording_m
p4
)
?
.parse
()
?
,
source_fps
:
ffprobe_video
(
"stream=r_frame_rate"
,
&
recording_m
p4
)
?
.parse
()
?
,
source_tbn
:
ffprobe_video
(
"stream=time_base"
,
&
recording_m
p4
)
?
.parse
()
?
,
source_duration
:
ffprobe_video
(
"format=duration"
,
&
recording_m
kv
)
?
.parse
()
?
,
source_fps
:
ffprobe_video
(
"stream=r_frame_rate"
,
&
recording_m
kv
)
?
.parse
()
?
,
source_tbn
:
ffprobe_video
(
"stream=time_base"
,
&
recording_m
kv
)
?
.parse
()
?
,
source_res
,
source_sample_rate
});
...
...
@@ -231,8 +243,8 @@ impl<'a> Renderer<'a> {
.to_string_pretty
()
.into_bytes
()
)
?
;
let
intro_m
p4
=
self
.
target
.join
(
"intro.mp4"
);
svg2m
p4
(
metadata
,
intro_svg
,
intro_m
p4
,
INTRO_LEN
)
?
;
let
intro_m
kv
=
self
.
intro_mkv
(
);
svg2m
kv
(
metadata
,
intro_svg
,
intro_m
kv
,
INTRO_LEN
)
?
;
// render outro to svg then mp4
let
outro_svg
=
self
.target
.join
(
"outro.svg"
);
...
...
@@ -240,8 +252,8 @@ impl<'a> Renderer<'a> {
&
outro_svg
,
outro
(
source_res
)
.to_string_pretty
()
.into_bytes
()
)
?
;
let
outro_m
p4
=
self
.
target
.join
(
"outro.mp4"
);
svg2m
p4
(
metadata
,
outro_svg
,
outro_m
p4
,
OUTRO_LEN
)
?
;
let
outro_m
kv
=
self
.
outro_mkv
(
);
svg2m
kv
(
metadata
,
outro_svg
,
outro_m
kv
,
OUTRO_LEN
)
?
;
// copy logo then render to png
let
logo_svg
=
self
.target
.join
(
"logo.svg"
);
...
...
@@ -271,25 +283,35 @@ impl<'a> Renderer<'a> {
Ok
(())
}
fn
video_mp4_res
(
&
self
,
res
:
Resolution
)
->
PathBuf
{
/// Get the video file for a specific resolution, completely finished.
fn
video_file_res
(
&
self
,
res
:
Resolution
)
->
PathBuf
{
let
extension
=
match
res
.format
()
{
FfmpegOutputFormat
::
Av1Flac
=>
"mkv"
,
FfmpegOutputFormat
::
Av1Opus
=>
"webm"
,
FfmpegOutputFormat
::
AvcAac
=>
"mp4"
};
self
.target
.join
(
format!
(
"{}-{}p.
mp4
"
,
self
.slug
,
res
.height
()))
.join
(
format!
(
"{}-{}p.
{extension}
"
,
self
.slug
,
res
.height
()))
}
pub
(
crate
)
fn
video_mp4
(
&
self
,
project
:
&
Project
)
->
PathBuf
{
self
.video_mp4_res
(
project
.source.metadata
.as_ref
()
.unwrap
()
.source_res
)
/// Get the video file directly outputed to further transcode.
pub
(
crate
)
fn
video_file_output
(
&
self
)
->
PathBuf
{
self
.target
.join
(
format!
(
"{}.mkv"
,
self
.slug
))
}
pub
(
crate
)
fn
render
(
&
self
,
project
:
&
mut
Project
)
->
anyhow
::
Result
<
PathBuf
>
{
let
source_res
=
project
.source.metadata
.as_ref
()
.unwrap
()
.source_res
;
let
output
=
self
.video_mp4
(
project
);
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
::
new
(
output
.clone
()));
let
output
=
self
.video_file_output
();
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
{
video_bitrate
:
Some
(
source_res
.bitrate
()
*
3
),
..
FfmpegOutput
::
new
(
FfmpegOutputFormat
::
Av1Flac
,
output
.clone
())
});
// add all of our inputs
let
intro
=
ffmpeg
.add_input
(
FfmpegInput
::
new
(
self
.
target
.join
(
"intro.mp4"
)));
let
rec_file
=
self
.
target
.join
(
"
recording
.mp4"
);
let
outro
=
ffmpeg
.add_input
(
FfmpegInput
::
new
(
self
.
target
.join
(
"outro.mp4"
)));
let
intro
=
ffmpeg
.add_input
(
FfmpegInput
::
new
(
self
.
intro_mkv
(
)));
let
rec_file
=
self
.recording
_mkv
(
);
let
outro
=
ffmpeg
.add_input
(
FfmpegInput
::
new
(
self
.
outro_mkv
(
)));
let
logo
=
ffmpeg
.add_input
(
FfmpegInput
::
new
(
self
.target
.join
(
"logo.png"
)));
let
ff
=
ffmpeg
.add_input
(
FfmpegInput
::
new
(
self
.target
.join
(
"fastforward.png"
)));
...
...
@@ -444,22 +466,22 @@ impl<'a> Renderer<'a> {
// we're done :)
ffmpeg
.set_filter_output
(
overlay
);
ffmpeg
.set_video_bitrate
(
source_res
.bitrate
()
*
3
);
ffmpeg
.run
()
?
;
Ok
(
output
)
}
pub
fn
rescale
(
&
self
,
res
:
Resolution
,
project
:
&
Project
)
->
anyhow
::
Result
<
PathBuf
>
{
let
input
=
self
.video_
mp4
(
project
);
let
output
=
self
.video_
mp4
_res
(
res
);
pub
fn
rescale
(
&
self
,
res
:
Resolution
)
->
anyhow
::
Result
<
PathBuf
>
{
let
input
=
self
.video_
file_output
(
);
let
output
=
self
.video_
file
_res
(
res
);
println!
(
"
\x1B
[1m ==> Rescaling to {}p
\x1B
[0m"
,
res
.height
());
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
::
new
(
output
.clone
())
.enable_faststart
());
let
mut
ffmpeg
=
Ffmpeg
::
new
(
FfmpegOutput
{
video_bitrate
:
Some
(
res
.bitrate
()),
..
FfmpegOutput
::
new
(
res
.format
(),
output
.clone
())
.enable_faststart
()
});
ffmpeg
.add_input
(
FfmpegInput
::
new
(
input
));
ffmpeg
.rescale_video
(
res
);
ffmpeg
.set_video_bitrate
(
res
.bitrate
());
ffmpeg
.run
()
?
;
Ok
(
output
)
}
...
...
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment