Skip to content

Commit e10880d

Browse files
woot000shssoichiro
andauthored
Add additional flags, options (#671)
* Add never overwrite -n flag Acts as the opposite of the overwrite -y flag * Add --scaler flag This allows the user to control which scaling algorithm is used during scene detection downscaling and VMAF calculations, and also allows the width of the lanczos scaler to be selected from 1 to 9 * Add inputres option for --vmaf-res This allows the user to have VMAF calculations use the input resolution automatically, without having to type it in manually per file * Change progress bar characters for Windows This looks smoother on the default Windows command prompt * Add --extra-splits-sec option Allows the user to specify extra splits in seconds. If both frames and seconds are specified, frames will take priority * Add --ignore-frame-mismatch flag Allows the user to ignore any reported frame mismatches between the encoder and chunk frame counts, which is useful if an ffmpeg filter changes the frame count, or the input video is encoded badly (ex. "Missing key frame while searching for timestamp" ffmpeg warning) * formatting * Fix compilation oops --------- Co-authored-by: Josh Holmer <[email protected]>
1 parent ac687ab commit e10880d

File tree

10 files changed

+162
-33
lines changed

10 files changed

+162
-33
lines changed

av1an-core/src/broker.rs

+20-6
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,12 @@ impl Display for EncoderCrash {
9696

9797
impl<'a> Broker<'a> {
9898
/// Main encoding loop. set_thread_affinity may be ignored if the value is invalid.
99-
pub fn encoding_loop(self, tx: Sender<()>, mut set_thread_affinity: Option<usize>) {
99+
pub fn encoding_loop(
100+
self,
101+
tx: Sender<()>,
102+
mut set_thread_affinity: Option<usize>,
103+
ignore_frame_mismatch: bool,
104+
) {
100105
assert!(!self.chunk_queue.is_empty());
101106

102107
if !self.chunk_queue.is_empty() {
@@ -149,7 +154,7 @@ impl<'a> Broker<'a> {
149154
}
150155

151156
while let Ok(mut chunk) = rx.recv() {
152-
if let Err(e) = queue.encode_chunk(&mut chunk, worker_id) {
157+
if let Err(e) = queue.encode_chunk(&mut chunk, worker_id, ignore_frame_mismatch) {
153158
error!("[chunk {}] {}", chunk.index, e);
154159

155160
tx.send(()).unwrap();
@@ -170,7 +175,12 @@ impl<'a> Broker<'a> {
170175
}
171176
}
172177

173-
fn encode_chunk(&self, chunk: &mut Chunk, worker_id: usize) -> Result<(), Box<EncoderCrash>> {
178+
fn encode_chunk(
179+
&self,
180+
chunk: &mut Chunk,
181+
worker_id: usize,
182+
ignore_frame_mismatch: bool,
183+
) -> Result<(), Box<EncoderCrash>> {
174184
let st_time = Instant::now();
175185

176186
if let Some(ref tq) = self.project.args.target_quality {
@@ -190,9 +200,13 @@ impl<'a> Broker<'a> {
190200
let passes = chunk.passes;
191201
for current_pass in 1..=passes {
192202
for r#try in 1..=self.project.args.max_tries {
193-
let res = self
194-
.project
195-
.create_pipes(chunk, current_pass, worker_id, padding);
203+
let res = self.project.create_pipes(
204+
chunk,
205+
current_pass,
206+
worker_id,
207+
padding,
208+
ignore_frame_mismatch,
209+
);
196210
if let Err((e, frames)) = res {
197211
dec_bar(frames);
198212

av1an-core/src/context.rs

+29-7
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,11 @@ impl Av1anContext {
308308

309309
let (tx, rx) = mpsc::channel();
310310
let handle = s.spawn(|_| {
311-
broker.encoding_loop(tx, self.args.set_thread_affinity);
311+
broker.encoding_loop(
312+
tx,
313+
self.args.set_thread_affinity,
314+
self.args.ignore_frame_mismatch,
315+
);
312316
});
313317

314318
// Queue::encoding_loop only sends a message if there was an error (meaning a chunk crashed)
@@ -348,11 +352,25 @@ impl Av1anContext {
348352
}
349353

350354
if let Some(ref tq) = self.args.target_quality {
355+
let mut temp_res = tq.vmaf_res.to_string();
356+
if tq.vmaf_res == "inputres" {
357+
let inputres = self.args.input.resolution()?;
358+
temp_res.push_str(&format!(
359+
"{}x{}",
360+
&inputres.0.to_string(),
361+
&inputres.1.to_string()
362+
));
363+
temp_res.to_string();
364+
} else {
365+
temp_res = tq.vmaf_res.to_string();
366+
}
367+
351368
if let Err(e) = vmaf::plot(
352369
self.args.output_file.as_ref(),
353370
&self.args.input,
354371
tq.model.as_deref(),
355-
tq.vmaf_res.as_str(),
372+
temp_res.as_str(),
373+
tq.vmaf_scaler.as_str(),
356374
1,
357375
tq.vmaf_filter.as_deref(),
358376
tq.vmaf_threads,
@@ -400,6 +418,7 @@ impl Av1anContext {
400418
current_pass: u8,
401419
worker_id: usize,
402420
padding: usize,
421+
ignore_frame_mismatch: bool,
403422
) -> Result<(), (Box<EncoderCrash>, u64)> {
404423
update_mp_chunk(worker_id, chunk.index, padding);
405424

@@ -618,11 +637,13 @@ impl Av1anContext {
618637
let encoded_frames = num_frames(chunk.output().as_ref());
619638

620639
let err_str = match encoded_frames {
621-
Ok(encoded_frames) if encoded_frames != chunk.frames() => Some(format!(
622-
"FRAME MISMATCH: chunk {}: {encoded_frames}/{} (actual/expected frames)",
623-
chunk.index,
624-
chunk.frames()
625-
)),
640+
Ok(encoded_frames) if !ignore_frame_mismatch && encoded_frames != chunk.frames() => {
641+
Some(format!(
642+
"FRAME MISMATCH: chunk {}: {encoded_frames}/{} (actual/expected frames)",
643+
chunk.index,
644+
chunk.frames()
645+
))
646+
}
626647
Err(error) => Some(format!(
627648
"FAILED TO COUNT FRAMES: chunk {}: {error}",
628649
chunk.index
@@ -689,6 +710,7 @@ impl Av1anContext {
689710
self.frames,
690711
self.args.min_scene_len,
691712
self.args.verbosity,
713+
self.args.scaler.as_str(),
692714
self.args.sc_pix_format,
693715
self.args.sc_method,
694716
self.args.sc_downscale_height,

av1an-core/src/progress_bar.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,16 @@ use once_cell::sync::OnceCell;
1010
use crate::util::printable_base10_digits;
1111
use crate::{get_done, Verbosity};
1212

13-
const PROGRESS_CHARS: &str = "█▉▊▋▌▍▎▏ ";
13+
const PROGRESS_CHARS: &str = if cfg!(windows) {
14+
"█▓▒░ "
15+
} else {
16+
"█▉▊▋▌▍▎▏ "
17+
};
1418

1519
const INDICATIF_PROGRESS_TEMPLATE: &str = if cfg!(windows) {
1620
// Do not use a spinner on Windows since the default console cannot display
1721
// the characters used for the spinner
18-
"{elapsed_precise:.bold} {wide_bar:.blue/white.dim} {percent:.bold} {pos} ({fps:.bold}, eta {fixed_eta}{msg})"
22+
"{elapsed_precise:.bold} {wide_bar:.blue/white.dim} {percent:.bold} {pos} ({fps:.bold}, eta {fixed_eta}{msg})"
1923
} else {
2024
"{spinner:.green.bold} {elapsed_precise:.bold} ▕{wide_bar:.blue/white.dim}▏ {percent:.bold} {pos} ({fps:.bold}, eta {fixed_eta}{msg})"
2125
};

av1an-core/src/scene_detect.rs

+17-4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ pub fn av_scenechange_detect(
1818
total_frames: usize,
1919
min_scene_len: usize,
2020
verbosity: Verbosity,
21+
sc_scaler: &str,
2122
sc_pix_format: Option<Pixel>,
2223
sc_method: ScenecutMethod,
2324
sc_downscale_height: Option<usize>,
@@ -54,6 +55,7 @@ pub fn av_scenechange_detect(
5455
})
5556
},
5657
min_scene_len,
58+
sc_scaler,
5759
sc_pix_format,
5860
sc_method,
5961
sc_downscale_height,
@@ -75,12 +77,19 @@ pub fn scene_detect(
7577
total_frames: usize,
7678
callback: Option<&dyn Fn(usize)>,
7779
min_scene_len: usize,
80+
sc_scaler: &str,
7881
sc_pix_format: Option<Pixel>,
7982
sc_method: ScenecutMethod,
8083
sc_downscale_height: Option<usize>,
8184
zones: &[Scene],
8285
) -> anyhow::Result<Vec<Scene>> {
83-
let (mut decoder, bit_depth) = build_decoder(input, encoder, sc_pix_format, sc_downscale_height)?;
86+
let (mut decoder, bit_depth) = build_decoder(
87+
input,
88+
encoder,
89+
sc_scaler,
90+
sc_pix_format,
91+
sc_downscale_height,
92+
)?;
8493

8594
let mut scenes = Vec::new();
8695
let mut cur_zone = zones.first().filter(|frame| frame.start_frame == 0);
@@ -194,6 +203,7 @@ pub fn scene_detect(
194203
fn build_decoder(
195204
input: &Input,
196205
encoder: Encoder,
206+
sc_scaler: &str,
197207
sc_pix_format: Option<Pixel>,
198208
sc_downscale_height: Option<usize>,
199209
) -> anyhow::Result<(y4m::Decoder<impl Read>, usize)> {
@@ -202,12 +212,15 @@ fn build_decoder(
202212
(Some(sdh), Some(spf)) => into_smallvec![
203213
"-vf",
204214
format!(
205-
"format={},scale=-2:'min({},ih)'",
215+
"format={},scale=-2:'min({},ih)':flags={}",
206216
spf.descriptor().unwrap().name(),
207-
sdh
217+
sdh,
218+
sc_scaler
208219
)
209220
],
210-
(Some(sdh), None) => into_smallvec!["-vf", format!("scale=-2:'min({sdh},ih)'")],
221+
(Some(sdh), None) => {
222+
into_smallvec!["-vf", format!("scale=-2:'min({sdh},ih)':flags={sc_scaler}")]
223+
}
211224
(None, Some(spf)) => into_smallvec!["-pix_fmt", spf.descriptor().unwrap().name()],
212225
(None, None) => smallvec![],
213226
};

av1an-core/src/scenes.rs

+2
Original file line numberDiff line numberDiff line change
@@ -295,6 +295,8 @@ fn get_test_args() -> Av1anContext {
295295
workers: 1,
296296
set_thread_affinity: None,
297297
zones: None,
298+
scaler: String::new(),
299+
ignore_frame_mismatch: false,
298300
};
299301
Av1anContext {
300302
vs_script: None,

av1an-core/src/settings.rs

+6
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ pub struct EncodeArgs {
3737

3838
pub chunk_method: ChunkMethod,
3939
pub chunk_order: ChunkOrdering,
40+
pub scaler: String,
4041
pub scenes: Option<PathBuf>,
4142
pub split_method: SplitMethod,
4243
pub sc_pix_format: Option<Pixel>,
@@ -46,6 +47,7 @@ pub struct EncodeArgs {
4647
pub extra_splits_len: Option<usize>,
4748
pub min_scene_len: usize,
4849
pub force_keyframes: Vec<usize>,
50+
pub ignore_frame_mismatch: bool,
4951

5052
pub max_tries: usize,
5153

@@ -127,6 +129,10 @@ properly into a mkv file. Specify mkvmerge as the concatenation method by settin
127129
warn!("It is not recommended to use the \"select\" chunk method, as it is very slow");
128130
}
129131

132+
if self.ignore_frame_mismatch {
133+
warn!("The output video's frame count may differ, and VMAF calculations may be incorrect");
134+
}
135+
130136
if let Some(vmaf_path) = &self
131137
.target_quality
132138
.as_ref()

av1an-core/src/target_quality.rs

+2
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const VMAF_PERCENTILE: f64 = 0.01;
2020
#[derive(Debug, Clone, Serialize, Deserialize)]
2121
pub struct TargetQuality {
2222
pub vmaf_res: String,
23+
pub vmaf_scaler: String,
2324
pub vmaf_filter: Option<String>,
2425
pub vmaf_threads: usize,
2526
pub model: Option<PathBuf>,
@@ -255,6 +256,7 @@ impl TargetQuality {
255256
&fl_path,
256257
self.model.as_ref(),
257258
&self.vmaf_res,
259+
&self.vmaf_scaler,
258260
self.probing_rate,
259261
self.vmaf_filter.as_deref(),
260262
self.vmaf_threads,

av1an-core/src/vmaf.rs

+6-3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ pub fn plot(
120120
reference: &Input,
121121
model: Option<impl AsRef<Path>>,
122122
res: &str,
123+
scaler: &str,
123124
sample_rate: usize,
124125
filter: Option<&str>,
125126
threads: usize,
@@ -153,6 +154,7 @@ pub fn plot(
153154
&json_file,
154155
model,
155156
res,
157+
scaler,
156158
sample_rate,
157159
filter,
158160
threads,
@@ -168,6 +170,7 @@ pub fn run_vmaf(
168170
stat_file: impl AsRef<Path>,
169171
model: Option<impl AsRef<Path>>,
170172
res: &str,
173+
scaler: &str,
171174
sample_rate: usize,
172175
vmaf_filter: Option<&str>,
173176
threads: usize,
@@ -229,10 +232,10 @@ pub fn run_vmaf(
229232
cmd.arg(encoded);
230233
cmd.args(["-r", "60", "-i", "-", "-filter_complex"]);
231234

232-
let distorted = format!("[0:v]scale={}:flags=bicubic:force_original_aspect_ratio=decrease,setpts=PTS-STARTPTS[distorted];", &res);
235+
let distorted = format!("[0:v]scale={}:flags={}:force_original_aspect_ratio=decrease,setpts=PTS-STARTPTS,setsar=1[distorted];", &res, &scaler);
233236
let reference = format!(
234-
"[1:v]{}scale={}:flags=bicubic:force_original_aspect_ratio=decrease,setpts=PTS-STARTPTS[ref];",
235-
filter, &res
237+
"[1:v]{}scale={}:flags={}:force_original_aspect_ratio=decrease,setpts=PTS-STARTPTS,setsar=1[ref];",
238+
filter, &res, &scaler
236239
);
237240

238241
cmd.arg(format!("{distorted}{reference}{vmaf}"));

0 commit comments

Comments
 (0)