Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion av1an-core/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ use crate::{
settings::{EncodeArgs, InputPixelFormat},
split::segment,
vapoursynth::create_vs_file,
zones::parse_zones,
zones::{parse_zones, validate_zones},
ChunkMethod,
ChunkOrdering,
DashMap,
Expand Down Expand Up @@ -843,6 +843,7 @@ impl Av1anContext {
self.scene_factory = SceneFactory::from_scenes_file(&scene_file)?;
} else {
let zones = parse_zones(&self.args, self.frames)?;
validate_zones(&self.args, &zones)?;
self.scene_factory.compute_scenes(&self.args, &zones)?;
self.scene_factory.write_scenes_to_file(scene_file)?;
}
Expand Down
181 changes: 95 additions & 86 deletions av1an-core/src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,92 +174,15 @@ impl EncodeArgs {
);
}
}
match self.target_quality.metric {
TargetMetric::VMAF => validate_libvmaf()?,
TargetMetric::SSIMULACRA2 => {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vship)
|| self.vapoursynth_plugins.is_some_and(|p| p.vszip != VSZipVersion::None),
"SSIMULACRA2 metric requires either Vapoursynth-HIP or VapourSynth Zig Image \
Process to be installed"
);
ensure!(
matches!(
self.chunk_method,
ChunkMethod::LSMASH
| ChunkMethod::FFMS2
| ChunkMethod::BESTSOURCE
| ChunkMethod::DGDECNV
),
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for SSIMULACRA2"
);
},
TargetMetric::ButteraugliINF => {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vship)
|| self.vapoursynth_plugins.is_some_and(|p| p.julek),
"Butteraugli metric requires either Vapoursynth-HIP or \
vapoursynth-julek-plugin to be installed"
);
ensure!(
matches!(
self.chunk_method,
ChunkMethod::LSMASH
| ChunkMethod::FFMS2
| ChunkMethod::BESTSOURCE
| ChunkMethod::DGDECNV
),
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for Butteraugli"
);
},
TargetMetric::Butteraugli3 => {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vship),
"Butteraugli 3 Norm metric requires Vapoursynth-HIP plugin to be installed"
);
ensure!(
matches!(
self.chunk_method,
ChunkMethod::LSMASH
| ChunkMethod::FFMS2
| ChunkMethod::BESTSOURCE
| ChunkMethod::DGDECNV
),
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for Butteraugli 3 \
Norm"
);
},
TargetMetric::XPSNR | TargetMetric::XPSNRWeighted => {
let metric_name = if self.target_quality.metric == TargetMetric::XPSNRWeighted {
"Weighted "
} else {
""
};
if self.target_quality.probing_rate > 1 {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vszip == VSZipVersion::New),
format!(
"{metric_name}XPSNR metric with probing rate greater than 1 requires \
VapourSynth-Zig Image Process R7 or newer to be installed"
)
);
ensure!(
matches!(
self.chunk_method,
ChunkMethod::LSMASH
| ChunkMethod::FFMS2
| ChunkMethod::BESTSOURCE
| ChunkMethod::DGDECNV
),
format!(
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for \
{metric_name}XPSNR with probing rate greater than 1"
)
);
} else {
validate_libxpsnr()?;
}
},
if self.target_quality.target.is_some() {
match self.target_quality.metric {
TargetMetric::VMAF => validate_libvmaf()?,
TargetMetric::SSIMULACRA2 => self.validate_ssimulacra2()?,
TargetMetric::ButteraugliINF => self.validate_butteraugli_inf()?,
TargetMetric::Butteraugli3 => self.validate_butteraugli_3()?,
TargetMetric::XPSNR | TargetMetric::XPSNRWeighted => self
.validate_xpsnr(self.target_quality.metric, self.target_quality.probing_rate)?,
}
}

if which::which("ffmpeg").is_err() {
Expand Down Expand Up @@ -502,6 +425,92 @@ impl EncodeArgs {
.all(|&b| (b as char).is_ascii_digit())
}
}

#[inline]
pub fn validate_ssimulacra2(&self) -> anyhow::Result<()> {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vship)
|| self.vapoursynth_plugins.is_some_and(|p| p.vszip != VSZipVersion::None),
"SSIMULACRA2 metric requires either Vapoursynth-HIP or VapourSynth Zig Image Process \
to be installed"
);
self.ensure_chunk_method(
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for SSIMULACRA2"
.to_string(),
)?;

Ok(())
}

#[inline]
pub fn validate_butteraugli_inf(&self) -> anyhow::Result<()> {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vship)
|| self.vapoursynth_plugins.is_some_and(|p| p.julek),
"Butteraugli metric requires either Vapoursynth-HIP or vapoursynth-julek-plugin to be \
installed"
);
self.ensure_chunk_method(
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for butteraugli"
.to_string(),
)?;

Ok(())
}

#[inline]
pub fn validate_butteraugli_3(&self) -> anyhow::Result<()> {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vship),
"Butteraugli 3 Norm metric requires Vapoursynth-HIP plugin to be installed"
);
self.ensure_chunk_method(
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for butteraugli 3-Norm"
.to_string(),
)?;

Ok(())
}

#[inline]
pub fn validate_xpsnr(&self, metric: TargetMetric, probing_rate: usize) -> anyhow::Result<()> {
let metric_name = if metric == TargetMetric::XPSNRWeighted {
"Weighted XPSNR"
} else {
"XPSNR"
};
if probing_rate > 1 {
ensure!(
self.vapoursynth_plugins.is_some_and(|p| p.vszip == VSZipVersion::New),
format!(
"{metric_name} metric with probing rate greater than 1 requires \
VapourSynth-Zig Image Process R7 or newer to be installed"
)
);
self.ensure_chunk_method(format!(
"Chunk method must be lsmash, ffms2, bestsource, or dgdecnv for {metric_name} \
metric with probing rate greater than 1"
))?;
} else {
validate_libxpsnr()?;
}

Ok(())
}

fn ensure_chunk_method(&self, error_message: String) -> anyhow::Result<()> {
ensure!(
matches!(
self.chunk_method,
ChunkMethod::LSMASH
| ChunkMethod::FFMS2
| ChunkMethod::BESTSOURCE
| ChunkMethod::DGDECNV
),
error_message
);
Ok(())
}
}

#[must_use]
Expand Down
64 changes: 63 additions & 1 deletion av1an-core/src/zones.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ use std::fs;

use anyhow::bail;

use crate::{scenes::Scene, EncodeArgs};
use crate::{
metrics::vmaf::validate_libvmaf,
scenes::Scene,
EncodeArgs,
TargetMetric,
TargetQuality,
};

pub(crate) fn parse_zones(args: &EncodeArgs, frames: usize) -> anyhow::Result<Vec<Scene>> {
let mut zones = Vec::new();
Expand All @@ -22,3 +28,59 @@ pub(crate) fn parse_zones(args: &EncodeArgs, frames: usize) -> anyhow::Result<Ve
}
Ok(zones)
}

pub(crate) fn validate_zones(args: &EncodeArgs, zones: &[Scene]) -> anyhow::Result<()> {
if zones.is_empty() {
// No zones to validate
return Ok(());
}

let tq_used_and = |condition: &dyn Fn(&TargetQuality) -> bool| {
zones.iter().any(|zone| {
zone.zone_overrides
.as_ref()
.and_then(|ovr| ovr.target_quality.as_ref())
.and_then(|tq| tq.target.as_ref().filter(|_| condition(tq)))
.is_some()
})
};
let tq_used_and_metric_is = |metric: TargetMetric| tq_used_and(&|tq| tq.metric == metric);

if tq_used_and_metric_is(TargetMetric::VMAF) {
validate_libvmaf()?;
}

if tq_used_and_metric_is(TargetMetric::SSIMULACRA2) {
args.validate_ssimulacra2()?;
}

// Using butteraugli-INF, validate butteraugli-INF
if tq_used_and_metric_is(TargetMetric::ButteraugliINF) {
args.validate_butteraugli_inf()?;
}

// Using butteraugli-3, validate butteraugli-3
if tq_used_and_metric_is(TargetMetric::Butteraugli3) {
args.validate_butteraugli_3()?;
}

// Using XPSNR and a probing rate > 1, validate XPSNR
if tq_used_and(&|tq| {
matches!(tq.metric, TargetMetric::XPSNR | TargetMetric::XPSNRWeighted)
&& tq.probing_rate > 1
}) {
// Any value greater than 1, uses VapourSynth
args.validate_xpsnr(TargetMetric::XPSNR, 2)?;
}

// Using XPSNR and a probing rate of 1, validate XPSNR
if tq_used_and(&|tq| {
matches!(tq.metric, TargetMetric::XPSNR | TargetMetric::XPSNRWeighted)
&& tq.probing_rate == 1
}) {
// 1, uses FFmpeg
args.validate_xpsnr(TargetMetric::XPSNR, 1)?;
}

Ok(())
}
Loading