mod filter;
pub mod frames;
pub mod params;
use anyhow::Error;
use tracing_subscriber::EnvFilter;
use vapoursynth::core::CoreRef;
use vapoursynth::plugins::{Filter, FilterArgument, Metadata};
use vapoursynth::prelude::{API, Node};
use vapoursynth::{export_vapoursynth_plugin, make_filter_function};
use crate::filter::Denoise;
use crate::params::{AlgorithmKind, RawParams};
fn init_logging() {
let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
let _ = tracing_subscriber::fmt()
.with_env_filter(filter)
.with_writer(std::io::stderr)
.try_init();
}
fn opt_string(bytes: Option<&[u8]>, field: &str) -> Result<Option<String>, Error> {
bytes
.map(|b| String::from_utf8(b.to_vec()).map_err(|_| anyhow::anyhow!("{field} must be valid UTF-8")))
.transpose()
}
fn opt_accelerators(bytes: Option<&[u8]>) -> Result<Option<Vec<String>>, Error> {
let Some(joined) = opt_string(bytes, "accelerators")? else {
return Ok(None);
};
let names: Vec<String> = joined
.split(',')
.map(str::trim)
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect();
if names.is_empty() {
anyhow::bail!("accelerators must name at least one accelerator when set");
}
Ok(Some(names))
}
fn opt_bool(value: Option<i64>) -> Option<bool> {
value.map(|v| v != 0)
}
#[expect(clippy::too_many_arguments)]
fn raw_params(
strength: Option<f64>,
variant: Option<&[u8]>,
preset: Option<&[u8]>,
prefilter: Option<&[u8]>,
channel_mode: Option<&[u8]>,
luma_strength: Option<f64>,
chroma_strength: Option<f64>,
luma_lambda_ht: Option<f64>,
chroma_lambda_ht: Option<f64>,
luma_mismatch_scale: Option<f64>,
chroma_mismatch_scale: Option<f64>,
device: Option<&[u8]>,
accelerators: Option<&[u8]>,
search_radius: Option<i64>,
patch_radius: Option<i64>,
temporal_radius: Option<i64>,
sigma: Option<f64>,
sigma_scale: Option<f64>,
motion_compensation: Option<i64>,
lambda_ht: Option<f64>,
lambda_ht_scale: Option<f64>,
spatial_radius: Option<i64>,
refine: Option<i64>,
) -> Result<RawParams, Error> {
Ok(RawParams {
strength,
variant: opt_string(variant, "variant")?,
preset: opt_string(preset, "preset")?,
prefilter: opt_string(prefilter, "prefilter")?,
channel_mode: opt_string(channel_mode, "channel_mode")?,
luma_strength,
chroma_strength,
luma_lambda_ht,
chroma_lambda_ht,
luma_mismatch_scale,
chroma_mismatch_scale,
device: opt_string(device, "device")?,
accelerators: opt_accelerators(accelerators)?,
search_radius,
patch_radius,
temporal_radius,
sigma,
sigma_scale,
motion_compensation: opt_bool(motion_compensation),
lambda_ht,
lambda_ht_scale,
spatial_radius,
refine,
})
}
make_filter_function! {
NlmeansFunction, "NLMeans"
#[expect(clippy::too_many_arguments)]
fn create_nlmeans<'core>(
api: API,
core: CoreRef<'core>,
clip: Node<'core>,
strength: Option<f64>,
variant: Option<&[u8]>,
preset: Option<&[u8]>,
prefilter: Option<&[u8]>,
channel_mode: Option<&[u8]>,
luma_strength: Option<f64>,
chroma_strength: Option<f64>,
device: Option<&[u8]>,
accelerators: Option<&[u8]>,
search_radius: Option<i64>,
patch_radius: Option<i64>,
temporal_radius: Option<i64>,
sigma: Option<f64>,
sigma_scale: Option<f64>,
motion_compensation: Option<i64>,
) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
let raw = raw_params(
strength,
variant,
preset,
prefilter,
channel_mode,
luma_strength,
chroma_strength,
None,
None,
None,
None,
device,
accelerators,
search_radius,
patch_radius,
temporal_radius,
sigma,
sigma_scale,
motion_compensation,
None,
None,
None,
None,
)?;
let filter = Denoise::create(api, core, clip, AlgorithmKind::Nlmeans, &raw)?;
Ok(Some(Box::new(filter)))
}
}
make_filter_function! {
Nl4dFunction, "NL4D"
#[expect(clippy::too_many_arguments)]
fn create_nl4d<'core>(
api: API,
core: CoreRef<'core>,
clip: Node<'core>,
preset: Option<&[u8]>,
channel_mode: Option<&[u8]>,
luma_strength: Option<f64>,
chroma_strength: Option<f64>,
luma_lambda_ht: Option<f64>,
chroma_lambda_ht: Option<f64>,
luma_mismatch_scale: Option<f64>,
chroma_mismatch_scale: Option<f64>,
device: Option<&[u8]>,
accelerators: Option<&[u8]>,
temporal_radius: Option<i64>,
sigma: Option<f64>,
sigma_scale: Option<f64>,
lambda_ht: Option<f64>,
lambda_ht_scale: Option<f64>,
spatial_radius: Option<i64>,
refine: Option<i64>,
) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
let raw = raw_params(
None,
None,
preset,
None,
channel_mode,
luma_strength,
chroma_strength,
luma_lambda_ht,
chroma_lambda_ht,
luma_mismatch_scale,
chroma_mismatch_scale,
device,
accelerators,
None,
None,
temporal_radius,
sigma,
sigma_scale,
None,
lambda_ht,
lambda_ht_scale,
spatial_radius,
refine,
)?;
let filter = Denoise::create(api, core, clip, AlgorithmKind::Nl4d, &raw)?;
Ok(Some(Box::new(filter)))
}
}
export_vapoursynth_plugin! {
Metadata {
identifier: "com.chillfish8.avdenoise",
namespace: "avd",
name: "av-denoise",
read_only: true,
},
[NlmeansFunction::new(), Nl4dFunction::new()]
}