diff --git a/src/command/auto_encode.rs b/src/command/auto_encode.rs index f8f520f..077e7fd 100644 --- a/src/command/auto_encode.rs +++ b/src/command/auto_encode.rs @@ -2,6 +2,7 @@ use crate::{ command::{ args, crf_search, encode::{self, default_output_name}, + sample_encode::{self, Work}, PROGRESS_CHARS, }, console_ext::style, @@ -9,10 +10,14 @@ use crate::{ float::TerseF32, temporary, }; +use anyhow::Context; use clap::Parser; use console::style; +use futures_util::StreamExt; use indicatif::{ProgressBar, ProgressStyle}; -use std::{sync::Arc, time::Duration}; +use std::{pin::pin, sync::Arc, time::Duration}; + +const BAR_LEN: u64 = 1024 * 1024 * 1024; /// Automatically determine the best crf to deliver the min-vmaf and use it to encode a video or image. /// @@ -32,9 +37,9 @@ pub struct Args { pub async fn auto_encode(Args { mut search, encode }: Args) -> anyhow::Result<()> { const SPINNER_RUNNING: &str = - "{spinner:.cyan.bold} {prefix} {elapsed_precise:.bold} {wide_bar:.cyan/blue} ({msg}eta {eta})"; + "{spinner:.cyan.bold} {elapsed_precise:.bold} {prefix} {wide_bar:.cyan/blue} ({msg}eta {eta})"; const SPINNER_FINISHED: &str = - "{spinner:.cyan.bold} {prefix} {elapsed_precise:.bold} {wide_bar:.cyan/blue} ({msg})"; + "{spinner:.cyan.bold} {elapsed_precise:.bold} {prefix} {wide_bar:.cyan/blue} ({msg})"; search.quiet = true; let defaulting_output = encode.output.is_none(); @@ -49,53 +54,86 @@ pub async fn auto_encode(Args { mut search, encode }: Args) -> anyhow::Result<() }); search.sample.set_extension_from_output(&output); - let bar = ProgressBar::new(12).with_style( + let bar = ProgressBar::new(BAR_LEN).with_style( ProgressStyle::default_bar() .template(SPINNER_RUNNING)? .progress_chars(PROGRESS_CHARS), ); - bar.set_prefix("Searching"); if defaulting_output { let out = shell_escape::escape(output.display().to_string().into()); bar.println(style!("Encoding {out}").dim().to_string()); } - let best = match crf_search::run(&search, input_probe.clone(), bar.clone()).await { - Ok(best) => best, - Err(err) => { - if let crf_search::Error::NoGoodCrf { last } = &err { - // show last sample attempt in progress bar - bar.set_style( - ProgressStyle::default_bar() - .template(SPINNER_FINISHED)? - .progress_chars(PROGRESS_CHARS), - ); - let mut vmaf = style(last.enc.vmaf); - if last.enc.vmaf < search.min_vmaf { - vmaf = vmaf.red(); + let min_vmaf = search.min_vmaf; + let max_encoded_percent = search.max_encoded_percent; + let enc_args = search.args.clone(); + let thorough = search.thorough; + + let mut crf_search = pin!(crf_search::run(search, input_probe.clone())); + let mut best = None; + while let Some(update) = crf_search.next().await { + match update { + Err(err) => { + if let crf_search::Error::NoGoodCrf { last } = &err { + // show last sample attempt in progress bar + bar.set_style( + ProgressStyle::default_bar() + .template(SPINNER_FINISHED)? + .progress_chars(PROGRESS_CHARS), + ); + let mut vmaf = style(last.enc.vmaf); + if last.enc.vmaf < min_vmaf { + vmaf = vmaf.red(); + } + let mut percent = style!("{:.0}%", last.enc.encode_percent); + if last.enc.encode_percent > max_encoded_percent as _ { + percent = percent.red(); + } + bar.finish_with_message(format!("VMAF {vmaf:.2}, size {percent}")); + } + bar.finish(); + return Err(err.into()); + } + Ok(crf_search::Update::Status { + crf_run, + crf, + sample: + sample_encode::Status { + work, + fps, + progress, + sample, + samples, + full_pass, + }, + }) => { + bar.set_position(crf_search::guess_progress(crf_run, progress, thorough) as _); + let crf = TerseF32(crf); + match full_pass { + true => bar.set_prefix(format!("crf {crf} full pass")), + false => bar.set_prefix(format!("crf {crf} {sample}/{samples}")), } - let mut percent = style!("{:.0}%", last.enc.encode_percent); - if last.enc.encode_percent > search.max_encoded_percent as _ { - percent = percent.red(); + match work { + Work::Encode if fps <= 0.0 => bar.set_message("encoding, "), + Work::Encode => bar.set_message(format!("enc {fps} fps, ")), + Work::Vmaf if fps <= 0.0 => bar.set_message("vmaf, "), + Work::Vmaf => bar.set_message(format!("vmaf {fps} fps, ")), } - bar.finish_with_message(format!( - "crf {}, VMAF {vmaf:.2}, size {percent}", - style(TerseF32(last.crf())).red(), - )); } - bar.finish(); - return Err(err.into()); + Ok(crf_search::Update::RunResult(..)) => {} + Ok(crf_search::Update::Done(result)) => best = Some(result), } - }; + } + let best = best.context("no crf-search best?")?; + bar.set_style( ProgressStyle::default_bar() .template(SPINNER_FINISHED)? .progress_chars(PROGRESS_CHARS), ); bar.finish_with_message(format!( - "crf {}, VMAF {:.2}, size {}", - style(TerseF32(best.crf())).green(), + "VMAF {:.2}, size {}", style(best.enc.vmaf).green(), style(format!("{:.0}%", best.enc.encode_percent)).green(), )); @@ -103,15 +141,15 @@ pub async fn auto_encode(Args { mut search, encode }: Args) -> anyhow::Result<() let bar = ProgressBar::new(12).with_style( ProgressStyle::default_bar() - .template("{spinner:.cyan.bold} {prefix} {elapsed_precise:.bold} {wide_bar:.cyan/blue} ({msg}eta {eta})")? - .progress_chars(PROGRESS_CHARS) + .template(SPINNER_RUNNING)? + .progress_chars(PROGRESS_CHARS), ); - bar.set_prefix("Encoding "); + bar.set_prefix("Encoding"); bar.enable_steady_tick(Duration::from_millis(100)); encode::run( encode::Args { - args: search.args, + args: enc_args, crf: best.crf(), encode: args::EncodeToOutput { output: Some(output), diff --git a/src/command/crf_search.rs b/src/command/crf_search.rs index e6439fb..47fc3bc 100644 --- a/src/command/crf_search.rs +++ b/src/command/crf_search.rs @@ -5,7 +5,6 @@ pub use err::Error; use crate::{ command::{ args, - crf_search::err::ensure_or_no_good_crf, sample_encode::{self, Work}, PROGRESS_CHARS, }, @@ -16,8 +15,7 @@ use crate::{ use anyhow::Context; use clap::{ArgAction, Parser}; use console::style; -use err::ensure_other; -use futures_util::StreamExt; +use futures_util::{Stream, StreamExt}; use indicatif::{HumanBytes, HumanDuration, ProgressBar, ProgressStyle}; use log::info; use std::{ @@ -96,7 +94,7 @@ pub struct Args { } pub async fn crf_search(mut args: Args) -> anyhow::Result<()> { - let bar = ProgressBar::new(12).with_style( + let bar = ProgressBar::new(BAR_LEN).with_style( ProgressStyle::default_bar() .template("{spinner:.cyan.bold} {elapsed_precise:.bold} {prefix} {wide_bar:.cyan/blue} ({msg}eta {eta})")? .progress_chars(PROGRESS_CHARS) @@ -108,35 +106,67 @@ pub async fn crf_search(mut args: Args) -> anyhow::Result<()> { args.sample .set_extension_from_input(&args.args.input, &args.args.encoder, &probe); - let best = run(&args, probe.into(), bar.clone()).await; - bar.finish(); - let best = best?; - - if std::io::stderr().is_terminal() { - // encode how-to hint - eprintln!( - "\n{} {}\n", - style("Encode with:").dim(), - style(args.args.encode_hint(best.crf())).dim().italic(), - ); - } - - StdoutFormat::Human.print_result(&best, input_is_image); - - Ok(()) -} + let min_vmaf = args.min_vmaf; + let max_encoded_percent = args.max_encoded_percent; + let thorough = args.thorough; + let enc_args = args.args.clone(); -pub async fn run( - args: &Args, - input_probe: Arc, - bar: ProgressBar, -) -> Result { - _run(args, input_probe, bar) - .await - .inspect(|s| info!("crf {} successful", s.crf())) + let mut run = pin!(run(args, probe.into())); + while let Some(update) = run.next().await { + let update = update.inspect_err(|e| { + if let Error::NoGoodCrf { last } = e { + last.print_attempt(&bar, min_vmaf, max_encoded_percent, false); + } + })?; + match update { + Update::Status { + crf_run, + crf, + sample: + sample_encode::Status { + work, + fps, + progress, + sample, + samples, + full_pass, + }, + } => { + bar.set_position(guess_progress(crf_run, progress, thorough) as _); + let crf = TerseF32(crf); + match full_pass { + true => bar.set_prefix(format!("crf {crf} full pass")), + false => bar.set_prefix(format!("crf {crf} {sample}/{samples}")), + } + match work { + Work::Encode if fps <= 0.0 => bar.set_message("encoding, "), + Work::Encode => bar.set_message(format!("enc {fps} fps, ")), + Work::Vmaf if fps <= 0.0 => bar.set_message("vmaf, "), + Work::Vmaf => bar.set_message(format!("vmaf {fps} fps, ")), + } + } + Update::RunResult(result) => { + result.print_attempt(&bar, min_vmaf, max_encoded_percent, false) + } + Update::Done(best) => { + info!("crf {} successful", best.crf()); + bar.finish_with_message(""); + if std::io::stderr().is_terminal() { + eprintln!( + "\n{} {}\n", + style("Encode with:").dim(), + style(enc_args.encode_hint(best.crf())).dim().italic(), + ); + } + StdoutFormat::Human.print_result(&best, input_is_image); + return Ok(()); + } + } + } + unreachable!() } -async fn _run( +pub fn run( Args { args, min_vmaf, @@ -146,154 +176,138 @@ async fn _run( crf_increment, thorough, sample, - quiet, + quiet: _, cache, vmaf, - }: &Args, + }: Args, input_probe: Arc, - bar: ProgressBar, -) -> Result { - let default_max_crf = args.encoder.default_max_crf(); - let max_crf = max_crf.unwrap_or(default_max_crf); - ensure_other!(*min_crf < max_crf, "Invalid --min-crf & --max-crf"); - - // Whether to make the 2nd iteration on the ~20%/~80% crf point instead of the min/max to - // improve interpolation by narrowing the crf range a 20% (or 30%) subrange. - // - // 20/80% is preferred to 25/75% to account for searches in the "middle" benefitting from - // having both bounds computed after the 2nd iteration, whereas the two edges must compute - // the min/max crf on the 3rd iter. - // - // If a custom crf range is being used under half the default, this 2nd cut is not needed. - let cut_on_iter2 = (max_crf - *min_crf) > (default_max_crf - DEFAULT_MIN_CRF) * 0.5; - - let crf_increment = crf_increment - .unwrap_or_else(|| args.encoder.default_crf_increment()) - .max(0.001); - - let min_q = q_from_crf(*min_crf, crf_increment); - let max_q = q_from_crf(max_crf, crf_increment); - let mut q: u64 = (min_q + max_q) / 2; - - let mut args = sample_encode::Args { - args: args.clone(), - crf: 0.0, - sample: sample.clone(), - cache: *cache, - stdout_format: sample_encode::StdoutFormat::Json, - vmaf: vmaf.clone(), - }; +) -> impl Stream> { + async_stream::try_stream! { + let default_max_crf = args.encoder.default_max_crf(); + let max_crf = max_crf.unwrap_or(default_max_crf); + Error::ensure_other(min_crf < max_crf, "Invalid --min-crf & --max-crf")?; + + // Whether to make the 2nd iteration on the ~20%/~80% crf point instead of the min/max to + // improve interpolation by narrowing the crf range a 20% (or 30%) subrange. + // + // 20/80% is preferred to 25/75% to account for searches in the "middle" benefitting from + // having both bounds computed after the 2nd iteration, whereas the two edges must compute + // the min/max crf on the 3rd iter. + // + // If a custom crf range is being used under half the default, this 2nd cut is not needed. + let cut_on_iter2 = (max_crf - min_crf) > (default_max_crf - DEFAULT_MIN_CRF) * 0.5; + + let crf_increment = crf_increment + .unwrap_or_else(|| args.encoder.default_crf_increment()) + .max(0.001); + + let min_q = q_from_crf(min_crf, crf_increment); + let max_q = q_from_crf(max_crf, crf_increment); + let mut q: u64 = (min_q + max_q) / 2; + + let mut args = sample_encode::Args { + args: args.clone(), + crf: 0.0, + sample: sample.clone(), + cache, + stdout_format: sample_encode::StdoutFormat::Json, + vmaf: vmaf.clone(), + }; - bar.set_length(BAR_LEN); - let mut crf_attempts = Vec::new(); + let mut crf_attempts = Vec::new(); - for run in 1.. { - // how much we're prepared to go higher than the min-vmaf - let higher_tolerance = match thorough { - true => 0.05, - // increment 1.0 => +0.1, +0.2, +0.4, +0.8 .. - // increment 0.1 => +0.1, +0.1, +0.1, +0.16 .. - _ => (crf_increment * 2_f32.powi(run as i32 - 1) * 0.1).max(0.1), - }; - args.crf = q.to_crf(crf_increment); - let terse_crf = TerseF32(args.crf); - - let mut sample_enc = pin!(sample_encode::run(args.clone(), input_probe.clone())); - let mut sample_enc_output = None; - while let Some(update) = sample_enc.next().await { - match update? { - sample_encode::Update::Status { - work, - fps, - progress, - sample, - samples, - full_pass, - } => { - bar.set_position(guess_progress(run, progress, *thorough) as _); - match full_pass { - true => bar.set_prefix(format!("crf {terse_crf} full pass")), - false => bar.set_prefix(format!("crf {terse_crf} {sample}/{samples}")), - } - match work { - Work::Encode if fps <= 0.0 => bar.set_message("encoding, "), - Work::Encode => bar.set_message(format!("enc {fps} fps, ")), - Work::Vmaf if fps <= 0.0 => bar.set_message("vmaf, "), - Work::Vmaf => bar.set_message(format!("vmaf {fps} fps, ")), + for run in 1.. { + // how much we're prepared to go higher than the min-vmaf + let higher_tolerance = match thorough { + true => 0.05, + // increment 1.0 => +0.1, +0.2, +0.4, +0.8 .. + // increment 0.1 => +0.1, +0.1, +0.1, +0.16 .. + _ => (crf_increment * 2_f32.powi(run as i32 - 1) * 0.1).max(0.1), + }; + args.crf = q.to_crf(crf_increment); + + let mut sample_enc = pin!(sample_encode::run(args.clone(), input_probe.clone())); + let mut sample_enc_output = None; + while let Some(update) = sample_enc.next().await { + match update? { + sample_encode::Update::Status(status) => { + yield Update::Status { crf_run: run, crf: args.crf, sample: status }; } + sample_encode::Update::SampleResult { .. } => {} + sample_encode::Update::Done(output) => sample_enc_output = Some(output), } - sample_encode::Update::SampleResult { .. } => {} - sample_encode::Update::Done(output) => sample_enc_output = Some(output), } - } - let sample = Sample { - crf_increment, - q, - enc: sample_enc_output.context("no sample output?")?, - }; - let from_cache = sample.enc.from_cache; - crf_attempts.push(sample.clone()); - let sample_small_enough = sample.enc.encode_percent <= *max_encoded_percent as _; - - if sample.enc.vmaf > *min_vmaf { - // good - if sample_small_enough && sample.enc.vmaf < min_vmaf + higher_tolerance { - return Ok(sample); - } - let u_bound = crf_attempts - .iter() - .filter(|s| s.q > sample.q) - .min_by_key(|s| s.q); - - match u_bound { - Some(upper) if upper.q == sample.q + 1 => { - ensure_or_no_good_crf!(sample_small_enough, sample); - return Ok(sample); - } - Some(upper) => { - q = vmaf_lerp_q(*min_vmaf, upper, &sample); - } - None if sample.q == max_q => { - ensure_or_no_good_crf!(sample_small_enough, sample); - return Ok(sample); - } - None if cut_on_iter2 && run == 1 && sample.q + 1 < max_q => { - q = (sample.q as f32 * 0.4 + max_q as f32 * 0.6).round() as _; - } - None => q = max_q, + let sample = Sample { + crf_increment, + q, + enc: sample_enc_output.context("no sample output?")?, }; - } else { - // not good enough - if !sample_small_enough || sample.q == min_q { - sample.print_attempt(&bar, *min_vmaf, *max_encoded_percent, *quiet, from_cache); - ensure_or_no_good_crf!(false, sample); - } - let l_bound = crf_attempts - .iter() - .filter(|s| s.q < sample.q) - .max_by_key(|s| s.q); - - match l_bound { - Some(lower) if lower.q + 1 == sample.q => { - sample.print_attempt(&bar, *min_vmaf, *max_encoded_percent, *quiet, from_cache); - let lower_small_enough = lower.enc.encode_percent <= *max_encoded_percent as _; - ensure_or_no_good_crf!(lower_small_enough, sample); - return Ok(lower.clone()); - } - Some(lower) => { - q = vmaf_lerp_q(*min_vmaf, &sample, lower); + crf_attempts.push(sample.clone()); + let sample_small_enough = sample.enc.encode_percent <= max_encoded_percent as _; + + if sample.enc.vmaf > min_vmaf { + // good + if sample_small_enough && sample.enc.vmaf < min_vmaf + higher_tolerance { + yield Update::Done(sample); + return; } - None if cut_on_iter2 && run == 1 && sample.q > min_q + 1 => { - q = (sample.q as f32 * 0.4 + min_q as f32 * 0.6).round() as _; + let u_bound = crf_attempts + .iter() + .filter(|s| s.q > sample.q) + .min_by_key(|s| s.q); + + match u_bound { + Some(upper) if upper.q == sample.q + 1 => { + Error::ensure_or_no_good_crf(sample_small_enough, &sample)?; + yield Update::Done(sample); + return; + } + Some(upper) => { + q = vmaf_lerp_q(min_vmaf, upper, &sample); + } + None if sample.q == max_q => { + Error::ensure_or_no_good_crf(sample_small_enough, &sample)?; + yield Update::Done(sample); + return; + } + None if cut_on_iter2 && run == 1 && sample.q + 1 < max_q => { + q = (sample.q as f32 * 0.4 + max_q as f32 * 0.6).round() as _; + } + None => q = max_q, + }; + } else { + // not good enough + if !sample_small_enough || sample.q == min_q { + Err(Error::NoGoodCrf { last: sample.clone() })?; } - None => q = min_q, - }; + + let l_bound = crf_attempts + .iter() + .filter(|s| s.q < sample.q) + .max_by_key(|s| s.q); + + match l_bound { + Some(lower) if lower.q + 1 == sample.q => { + Error::ensure_or_no_good_crf(lower.enc.encode_percent <= max_encoded_percent as _, &sample)?; + yield Update::RunResult(sample.clone()); + yield Update::Done(lower.clone()); + return; + } + Some(lower) => { + q = vmaf_lerp_q(min_vmaf, &sample, lower); + } + None if cut_on_iter2 && run == 1 && sample.q > min_q + 1 => { + q = (sample.q as f32 * 0.4 + min_q as f32 * 0.6).round() as _; + } + None => q = min_q, + }; + } + yield Update::RunResult(sample.clone()); } - sample.print_attempt(&bar, *min_vmaf, *max_encoded_percent, *quiet, from_cache); + unreachable!(); } - unreachable!(); } #[derive(Debug, Clone)] @@ -314,7 +328,6 @@ impl Sample { min_vmaf: f32, max_encoded_percent: f32, quiet: bool, - from_cache: bool, ) { if quiet { return; @@ -326,7 +339,7 @@ impl Sample { let mut percent = style!("{:.0}%", self.enc.encode_percent); let open = style("(").dim(); let close = style(")").dim(); - let cache_msg = match from_cache { + let cache_msg = match self.enc.from_cache { true => style(" (cache)").dim(), false => style(""), }; @@ -405,7 +418,7 @@ fn vmaf_lerp_q(min_vmaf: f32, worse_q: &Sample, better_q: &Sample) -> u64 { } /// sample_progress: [0, 1] -fn guess_progress(run: usize, sample_progress: f32, thorough: bool) -> f64 { +pub fn guess_progress(run: usize, sample_progress: f32, thorough: bool) -> f64 { let total_runs_guess = match () { // Guess 6 iterations for a "thorough" search _ if thorough && run < 7 => 6.0, @@ -441,3 +454,17 @@ fn q_crf_conversions() { assert_eq!(q_from_crf(33.5, 0.1), 335); assert_eq!(q_from_crf(27.0, 1.0), 27); } + +#[derive(Debug)] +pub enum Update { + Status { + /// run number starting from `1`. + crf_run: usize, + /// crf of this run + crf: f32, + sample: sample_encode::Status, + }, + /// Run result (excludes successful final runs) + RunResult(Sample), + Done(Sample), +} diff --git a/src/command/crf_search/err.rs b/src/command/crf_search/err.rs index e2b61ec..a43013c 100644 --- a/src/command/crf_search/err.rs +++ b/src/command/crf_search/err.rs @@ -7,6 +7,22 @@ pub enum Error { Other(anyhow::Error), } +impl Error { + pub fn ensure_other(condition: bool, reason: &'static str) -> Result<(), Self> { + if !condition { + return Err(Self::Other(anyhow::anyhow!(reason))); + } + Ok(()) + } + + pub fn ensure_or_no_good_crf(condition: bool, last: &Sample) -> Result<(), Self> { + if !condition { + return Err(Self::NoGoodCrf { last: last.clone() }); + } + Ok(()) + } +} + impl From for Error { fn from(err: anyhow::Error) -> Self { Self::Other(err) @@ -29,24 +45,3 @@ impl fmt::Display for Error { } impl std::error::Error for Error {} - -macro_rules! ensure_other { - ($condition:expr, $reason:expr) => { - #[allow(clippy::neg_cmp_op_on_partial_ord)] - if !$condition { - return Err($crate::command::crf_search::err::Error::Other( - anyhow::anyhow!($reason), - )); - } - }; -} -pub(crate) use ensure_other; - -macro_rules! ensure_or_no_good_crf { - ($condition:expr, $last_sample:expr) => { - if !$condition { - return Err($crate::command::crf_search::err::Error::NoGoodCrf { last: $last_sample }); - } - }; -} -pub(crate) use ensure_or_no_good_crf; diff --git a/src/command/sample_encode.rs b/src/command/sample_encode.rs index c906a20..4d6634f 100644 --- a/src/command/sample_encode.rs +++ b/src/command/sample_encode.rs @@ -90,14 +90,14 @@ pub async fn sample_encode(mut args: Args) -> anyhow::Result<()> { let mut run = pin!(run(args, probe.into())); while let Some(update) = run.next().await { match update? { - Update::Status { + Update::Status(Status { work, fps, progress, sample, samples, full_pass, - } => { + }) => { match full_pass { true => bar.set_prefix("Full pass"), false => bar.set_prefix(format!("Sample {sample}/{samples}")), @@ -134,14 +134,12 @@ pub async fn sample_encode(mut args: Args) -> anyhow::Result<()> { Update::Done(output) => { bar.finish(); if io::stderr().is_terminal() { - // encode how-to hint eprintln!( "\n{} {}\n", style("Encode with:").dim(), style(enc_args.encode_hint(crf)).dim().italic(), ); } - // stdout result stdout_fmt.print_result( output.vmaf, output.predicted_encode_size, @@ -236,14 +234,14 @@ pub fn run( let (sample, sample_size) = sample?; info!("encoding sample {sample_n}/{samples} crf {crf}"); - yield Update::Status { + yield Update::Status(Status { work: Work::Encode, fps: 0.0, progress: sample_idx as f32 / samples as f32, full_pass, sample: sample_n, samples, - }; + }); // encode sample let result = match cache::cached_encode( @@ -281,7 +279,7 @@ pub fn run( )?; while let Some(enc_progress) = output.next().await { if let FfmpegOut::Progress { time, fps, .. } = enc_progress? { - yield Update::Status { + yield Update::Status(Status { work: Work::Encode, fps, progress: (time.as_micros_u64() + sample_idx * sample_duration_us * 2) as f32 @@ -289,7 +287,7 @@ pub fn run( full_pass, sample: sample_n, samples, - }; + }); logger.update(sample_duration, time, fps); } } @@ -298,14 +296,14 @@ pub fn run( let encoded_probe = ffprobe::probe(&encoded_sample); // calculate vmaf - yield Update::Status { + yield Update::Status(Status { work: Work::Vmaf, fps: 0.0, progress: (sample_idx as f32 + 0.5) / samples as f32, full_pass, sample: sample_n, samples, - }; + }); let vmaf = vmaf::run( &sample, &encoded_sample, @@ -327,7 +325,7 @@ pub fn run( break; } VmafOut::Progress(FfmpegOut::Progress { time, fps, .. }) => { - yield Update::Status { + yield Update::Status(Status { work: Work::Vmaf, fps, progress: (sample_duration_us + @@ -337,7 +335,7 @@ pub fn run( full_pass, sample: sample_n, samples, - }; + }); logger.update(sample_duration, time, fps); } VmafOut::Progress(_) => {} @@ -617,22 +615,25 @@ pub enum Work { Vmaf, } +#[derive(Debug)] +pub struct Status { + /// Kind of work being performed + pub work: Work, + /// fps, `0.0` may be interpreted as "unknown" + pub fps: f32, + /// sample progress `[0, 1]` + pub progress: f32, + /// Sample number `1,....,n` + pub sample: u64, + /// Total samples + pub samples: u64, + /// Encoding the entire input video + pub full_pass: bool, +} + #[derive(Debug)] pub enum Update { - Status { - /// Kind of work being performed - work: Work, - /// fps, `0.0` may be interpreted as "unknown" - fps: f32, - /// sample progress `[0, 1]` - progress: f32, - /// Sample number `1,....,n` - sample: u64, - /// Total samples - samples: u64, - /// Encoding the entire input video - full_pass: bool, - }, + Status(Status), SampleResult { /// Sample number `1,....,n` sample: u64,