diff --git a/CHANGELOG.md b/CHANGELOG.md index ac815b7..24d3b6f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Unreleased (v0.9.1) * Fix xpsnr inf score parsing. +* Add `--xpsnr-fps`: Frame rate override used to analyse both reference & distorted videos. Default 60. # v0.9.0 * Add XPSNR support as a VMAF alternative. diff --git a/src/command/args.rs b/src/command/args.rs index d2b1a96..6676cd7 100644 --- a/src/command/args.rs +++ b/src/command/args.rs @@ -114,3 +114,26 @@ pub struct ScoreArgs { #[arg(long)] pub reference_vfilter: Option>, } + +/// Common xpsnr options. +#[derive(Debug, Parser, Clone, Copy)] +pub struct Xpsnr { + /// Frame rate override used to analyse both reference & distorted videos. + /// Maps to ffmpeg `-r` input arg. + /// + /// Setting to 0 disables use. + #[arg(long, default_value_t = 60.0)] + pub xpsnr_fps: f32, +} + +impl Xpsnr { + pub fn fps(&self) -> Option { + Some(self.xpsnr_fps).filter(|r| *r > 0.0) + } +} + +impl std::hash::Hash for Xpsnr { + fn hash(&self, state: &mut H) { + self.xpsnr_fps.to_ne_bytes().hash(state); + } +} diff --git a/src/command/crf_search.rs b/src/command/crf_search.rs index 25d082a..db2dc65 100644 --- a/src/command/crf_search.rs +++ b/src/command/crf_search.rs @@ -104,6 +104,9 @@ pub struct Args { #[clap(flatten)] pub score: args::ScoreArgs, + #[clap(flatten)] + pub xpsnr: args::Xpsnr, + #[command(flatten)] pub verbose: clap_verbosity_flag::Verbosity, } @@ -212,6 +215,7 @@ pub fn run( cache, vmaf, score, + xpsnr, verbose: _, }: Args, input_probe: Arc, @@ -252,6 +256,7 @@ pub fn run( vmaf: vmaf.clone(), score: score.clone(), xpsnr: min_xpsnr.is_some(), + xpsnr_opts: xpsnr, }; let mut crf_attempts = Vec::new(); diff --git a/src/command/sample_encode.rs b/src/command/sample_encode.rs index 509857c..91a37dc 100644 --- a/src/command/sample_encode.rs +++ b/src/command/sample_encode.rs @@ -72,6 +72,9 @@ pub struct Args { #[clap(flatten)] pub score: args::ScoreArgs, + #[clap(flatten)] + pub xpsnr_opts: args::Xpsnr, + /// Calculate a XPSNR score instead of VMAF. #[arg(long)] pub xpsnr: bool, @@ -147,6 +150,7 @@ pub fn run( vmaf, score, xpsnr, + xpsnr_opts, }: Args, input_probe: Arc, ) -> impl Stream> { @@ -162,7 +166,7 @@ pub fn run( let keep = sample_args.keep; let temp_dir = sample_args.temp_dir; let scoring = match xpsnr { - true => ScoringInfo::Xpsnr(&score), + true => ScoringInfo::Xpsnr(&xpsnr_opts, &score), _ => ScoringInfo::Vmaf(&vmaf, &score), }; @@ -358,7 +362,7 @@ pub fn run( let lavfi = super::xpsnr::lavfi( score.reference_vfilter.as_deref().or(args.vfilter.as_deref()) ); - let xpsnr_out = xpsnr::run(&sample, &encoded_sample, &lavfi)?; + let xpsnr_out = xpsnr::run(&sample, &encoded_sample, &lavfi, xpsnr_opts.fps())?; let mut xpsnr_out = pin!(xpsnr_out); let mut logger = ProgressLogger::new("ab_av1::xpsnr", Instant::now()); let mut score = None; diff --git a/src/command/sample_encode/cache.rs b/src/command/sample_encode/cache.rs index d9e32b8..fd4cbfd 100644 --- a/src/command/sample_encode/cache.rs +++ b/src/command/sample_encode/cache.rs @@ -1,6 +1,6 @@ //! _sample-encode_ file system caching logic. use crate::{ - command::args::{ScoreArgs, Vmaf}, + command::args::{ScoreArgs, Vmaf, Xpsnr}, ffmpeg::FfmpegEncodeArgs, }; use anyhow::Context; @@ -71,7 +71,7 @@ pub async fn cached_encode( #[derive(Debug, Hash, Clone, Copy)] pub enum ScoringInfo<'a> { Vmaf(&'a Vmaf, &'a ScoreArgs), - Xpsnr(&'a ScoreArgs), + Xpsnr(&'a Xpsnr, &'a ScoreArgs), } pub async fn cache_result(key: Key, result: &super::EncodeResult) -> anyhow::Result<()> { diff --git a/src/command/xpsnr.rs b/src/command/xpsnr.rs index 37f1a10..33f04d5 100644 --- a/src/command/xpsnr.rs +++ b/src/command/xpsnr.rs @@ -33,6 +33,9 @@ pub struct Args { #[clap(flatten)] pub score: args::ScoreArgs, + + #[clap(flatten)] + pub xpsnr: args::Xpsnr, } pub async fn xpsnr( @@ -40,6 +43,7 @@ pub async fn xpsnr( reference, distorted, score, + xpsnr, }: Args, ) -> anyhow::Result<()> { let bar = ProgressBar::new(1).with_style( @@ -65,6 +69,7 @@ pub async fn xpsnr( &reference, &distorted, &lavfi(score.reference_vfilter.as_deref()), + xpsnr.fps(), )?); let mut logger = ProgressLogger::new(module_path!(), Instant::now()); let mut score = None; diff --git a/src/xpsnr.rs b/src/xpsnr.rs index f561b48..4a8ad01 100644 --- a/src/xpsnr.rs +++ b/src/xpsnr.rs @@ -12,6 +12,7 @@ pub fn run( reference: &Path, distorted: &Path, filter_complex: &str, + fps: Option, ) -> anyhow::Result> { info!( "xpsnr {} vs reference {}", @@ -20,7 +21,9 @@ pub fn run( ); let mut cmd = Command::new("ffmpeg"); - cmd.arg2("-i", reference) + cmd.arg2_opt("-r", fps) + .arg2("-i", reference) + .arg2_opt("-r", fps) .arg2("-i", distorted) .arg2("-filter_complex", filter_complex) .arg2("-f", "null")