1mod filter;
4pub mod frames;
5pub mod params;
6
7use anyhow::Error;
8use tracing_subscriber::EnvFilter;
9use vapoursynth::core::CoreRef;
10use vapoursynth::plugins::{Filter, FilterArgument, Metadata};
11use vapoursynth::prelude::{API, Node};
12use vapoursynth::{export_vapoursynth_plugin, make_filter_function};
13
14use crate::filter::Denoise;
15use crate::params::{AlgorithmKind, RawParams};
16
17fn init_logging() {
22 let filter = EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("warn"));
23
24 let _ = tracing_subscriber::fmt()
25 .with_env_filter(filter)
26 .with_writer(std::io::stderr)
27 .try_init();
28}
29
30fn opt_string(bytes: Option<&[u8]>, field: &str) -> Result<Option<String>, Error> {
33 bytes
34 .map(|b| String::from_utf8(b.to_vec()).map_err(|_| anyhow::anyhow!("{field} must be valid UTF-8")))
35 .transpose()
36}
37
38fn opt_accelerators(bytes: Option<&[u8]>) -> Result<Option<Vec<String>>, Error> {
47 let Some(joined) = opt_string(bytes, "accelerators")? else {
48 return Ok(None);
49 };
50
51 let names: Vec<String> = joined
52 .split(',')
53 .map(str::trim)
54 .filter(|s| !s.is_empty())
55 .map(str::to_string)
56 .collect();
57
58 if names.is_empty() {
59 anyhow::bail!("accelerators must name at least one accelerator when set");
60 }
61
62 Ok(Some(names))
63}
64
65fn opt_bool(value: Option<i64>) -> Option<bool> {
72 value.map(|v| v != 0)
73}
74
75#[expect(clippy::too_many_arguments)]
77fn raw_params(
78 strength: Option<f64>,
79 variant: Option<&[u8]>,
80 preset: Option<&[u8]>,
81 prefilter: Option<&[u8]>,
82 channel_mode: Option<&[u8]>,
83 luma_strength: Option<f64>,
84 chroma_strength: Option<f64>,
85 luma_lambda_ht: Option<f64>,
86 chroma_lambda_ht: Option<f64>,
87 luma_mismatch_scale: Option<f64>,
88 chroma_mismatch_scale: Option<f64>,
89 device: Option<&[u8]>,
90 accelerators: Option<&[u8]>,
91 search_radius: Option<i64>,
92 patch_radius: Option<i64>,
93 temporal_radius: Option<i64>,
94 sigma: Option<f64>,
95 sigma_scale: Option<f64>,
96 motion_compensation: Option<i64>,
97 lambda_ht: Option<f64>,
98 lambda_ht_scale: Option<f64>,
99 spatial_radius: Option<i64>,
100 refine: Option<i64>,
101) -> Result<RawParams, Error> {
102 Ok(RawParams {
103 strength,
104 variant: opt_string(variant, "variant")?,
105 preset: opt_string(preset, "preset")?,
106 prefilter: opt_string(prefilter, "prefilter")?,
107 channel_mode: opt_string(channel_mode, "channel_mode")?,
108 luma_strength,
109 chroma_strength,
110 luma_lambda_ht,
111 chroma_lambda_ht,
112 luma_mismatch_scale,
113 chroma_mismatch_scale,
114 device: opt_string(device, "device")?,
115 accelerators: opt_accelerators(accelerators)?,
116 search_radius,
117 patch_radius,
118 temporal_radius,
119 sigma,
120 sigma_scale,
121 motion_compensation: opt_bool(motion_compensation),
122 lambda_ht,
123 lambda_ht_scale,
124 spatial_radius,
125 refine,
126 })
127}
128
129make_filter_function! {
130 NlmeansFunction, "NLMeans"
131
132 #[expect(clippy::too_many_arguments)]
133 fn create_nlmeans<'core>(
134 api: API,
135 core: CoreRef<'core>,
136 clip: Node<'core>,
137 strength: Option<f64>,
138 variant: Option<&[u8]>,
139 preset: Option<&[u8]>,
140 prefilter: Option<&[u8]>,
141 channel_mode: Option<&[u8]>,
142 luma_strength: Option<f64>,
143 chroma_strength: Option<f64>,
144 device: Option<&[u8]>,
145 accelerators: Option<&[u8]>,
146 search_radius: Option<i64>,
147 patch_radius: Option<i64>,
148 temporal_radius: Option<i64>,
149 sigma: Option<f64>,
150 sigma_scale: Option<f64>,
151 motion_compensation: Option<i64>,
152 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
153 let raw = raw_params(
154 strength,
155 variant,
156 preset,
157 prefilter,
158 channel_mode,
159 luma_strength,
160 chroma_strength,
161 None,
162 None,
163 None,
164 None,
165 device,
166 accelerators,
167 search_radius,
168 patch_radius,
169 temporal_radius,
170 sigma,
171 sigma_scale,
172 motion_compensation,
173 None,
174 None,
175 None,
176 None,
177 )?;
178 let filter = Denoise::create(api, core, clip, AlgorithmKind::Nlmeans, &raw)?;
179 Ok(Some(Box::new(filter)))
180 }
181}
182
183make_filter_function! {
184 Nl4dFunction, "NL4D"
185
186 #[expect(clippy::too_many_arguments)]
200 fn create_nl4d<'core>(
201 api: API,
202 core: CoreRef<'core>,
203 clip: Node<'core>,
204 preset: Option<&[u8]>,
205 channel_mode: Option<&[u8]>,
206 luma_strength: Option<f64>,
207 chroma_strength: Option<f64>,
208 luma_lambda_ht: Option<f64>,
209 chroma_lambda_ht: Option<f64>,
210 luma_mismatch_scale: Option<f64>,
211 chroma_mismatch_scale: Option<f64>,
212 device: Option<&[u8]>,
213 accelerators: Option<&[u8]>,
214 temporal_radius: Option<i64>,
215 sigma: Option<f64>,
216 sigma_scale: Option<f64>,
217 lambda_ht: Option<f64>,
218 lambda_ht_scale: Option<f64>,
219 spatial_radius: Option<i64>,
220 refine: Option<i64>,
221 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
222 let raw = raw_params(
223 None,
224 None,
225 preset,
226 None,
227 channel_mode,
228 luma_strength,
229 chroma_strength,
230 luma_lambda_ht,
231 chroma_lambda_ht,
232 luma_mismatch_scale,
233 chroma_mismatch_scale,
234 device,
235 accelerators,
236 None,
237 None,
238 temporal_radius,
239 sigma,
240 sigma_scale,
241 None,
242 lambda_ht,
243 lambda_ht_scale,
244 spatial_radius,
245 refine,
246 )?;
247 let filter = Denoise::create(api, core, clip, AlgorithmKind::Nl4d, &raw)?;
248 Ok(Some(Box::new(filter)))
249 }
250}
251
252export_vapoursynth_plugin! {
253 Metadata {
254 identifier: "com.chillfish8.avdenoise",
255 namespace: "avd",
256 name: "av-denoise",
257 read_only: true,
258 },
259 [NlmeansFunction::new(), Nl4dFunction::new()]
260}