1mod filter;
4pub mod frames;
5pub mod params;
6
7use anyhow::Error;
8use vapoursynth::core::CoreRef;
9use vapoursynth::plugins::{Filter, FilterArgument, Metadata};
10use vapoursynth::prelude::{API, Node};
11use vapoursynth::{export_vapoursynth_plugin, make_filter_function};
12
13use crate::filter::Denoise;
14use crate::params::{AlgorithmKind, RawParams};
15
16fn opt_string(bytes: Option<&[u8]>, field: &str) -> Result<Option<String>, Error> {
19 bytes
20 .map(|b| String::from_utf8(b.to_vec()).map_err(|_| anyhow::anyhow!("{field} must be valid UTF-8")))
21 .transpose()
22}
23
24fn opt_accelerators(bytes: Option<&[u8]>) -> Result<Option<Vec<String>>, Error> {
33 let Some(joined) = opt_string(bytes, "accelerators")? else {
34 return Ok(None);
35 };
36
37 let names: Vec<String> = joined
38 .split(',')
39 .map(str::trim)
40 .filter(|s| !s.is_empty())
41 .map(str::to_string)
42 .collect();
43
44 if names.is_empty() {
45 anyhow::bail!("accelerators must name at least one accelerator when set");
46 }
47
48 Ok(Some(names))
49}
50
51fn opt_bool(value: Option<i64>) -> Option<bool> {
58 value.map(|v| v != 0)
59}
60
61#[expect(clippy::too_many_arguments)]
63fn raw_params(
64 strength: Option<f64>,
65 variant: Option<&[u8]>,
66 preset: Option<&[u8]>,
67 prefilter: Option<&[u8]>,
68 channel_mode: Option<&[u8]>,
69 luma_strength: Option<f64>,
70 chroma_strength: Option<f64>,
71 luma_lambda_ht: Option<f64>,
72 chroma_lambda_ht: Option<f64>,
73 luma_mismatch_scale: Option<f64>,
74 chroma_mismatch_scale: Option<f64>,
75 device: Option<&[u8]>,
76 accelerators: Option<&[u8]>,
77 search_radius: Option<i64>,
78 patch_radius: Option<i64>,
79 temporal_radius: Option<i64>,
80 sigma: Option<f64>,
81 sigma_scale: Option<f64>,
82 motion_compensation: Option<i64>,
83 lambda_ht: Option<f64>,
84 lambda_ht_scale: Option<f64>,
85 spatial_radius: Option<i64>,
86 refine: Option<i64>,
87) -> Result<RawParams, Error> {
88 Ok(RawParams {
89 strength,
90 variant: opt_string(variant, "variant")?,
91 preset: opt_string(preset, "preset")?,
92 prefilter: opt_string(prefilter, "prefilter")?,
93 channel_mode: opt_string(channel_mode, "channel_mode")?,
94 luma_strength,
95 chroma_strength,
96 luma_lambda_ht,
97 chroma_lambda_ht,
98 luma_mismatch_scale,
99 chroma_mismatch_scale,
100 device: opt_string(device, "device")?,
101 accelerators: opt_accelerators(accelerators)?,
102 search_radius,
103 patch_radius,
104 temporal_radius,
105 sigma,
106 sigma_scale,
107 motion_compensation: opt_bool(motion_compensation),
108 lambda_ht,
109 lambda_ht_scale,
110 spatial_radius,
111 refine,
112 })
113}
114
115make_filter_function! {
116 NlmeansFunction, "NLMeans"
117
118 #[expect(clippy::too_many_arguments)]
119 fn create_nlmeans<'core>(
120 api: API,
121 core: CoreRef<'core>,
122 clip: Node<'core>,
123 strength: Option<f64>,
124 variant: Option<&[u8]>,
125 preset: Option<&[u8]>,
126 prefilter: Option<&[u8]>,
127 channel_mode: Option<&[u8]>,
128 luma_strength: Option<f64>,
129 chroma_strength: Option<f64>,
130 device: Option<&[u8]>,
131 accelerators: Option<&[u8]>,
132 search_radius: Option<i64>,
133 patch_radius: Option<i64>,
134 temporal_radius: Option<i64>,
135 sigma: Option<f64>,
136 sigma_scale: Option<f64>,
137 motion_compensation: Option<i64>,
138 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
139 let raw = raw_params(
140 strength,
141 variant,
142 preset,
143 prefilter,
144 channel_mode,
145 luma_strength,
146 chroma_strength,
147 None,
148 None,
149 None,
150 None,
151 device,
152 accelerators,
153 search_radius,
154 patch_radius,
155 temporal_radius,
156 sigma,
157 sigma_scale,
158 motion_compensation,
159 None,
160 None,
161 None,
162 None,
163 )?;
164 let filter = Denoise::create(api, core, clip, AlgorithmKind::Nlmeans, &raw)?;
165 Ok(Some(Box::new(filter)))
166 }
167}
168
169make_filter_function! {
170 Nl4dFunction, "NL4D"
171
172 #[expect(clippy::too_many_arguments)]
186 fn create_nl4d<'core>(
187 api: API,
188 core: CoreRef<'core>,
189 clip: Node<'core>,
190 preset: Option<&[u8]>,
191 channel_mode: Option<&[u8]>,
192 luma_strength: Option<f64>,
193 chroma_strength: Option<f64>,
194 luma_lambda_ht: Option<f64>,
195 chroma_lambda_ht: Option<f64>,
196 luma_mismatch_scale: Option<f64>,
197 chroma_mismatch_scale: Option<f64>,
198 device: Option<&[u8]>,
199 accelerators: Option<&[u8]>,
200 temporal_radius: Option<i64>,
201 sigma: Option<f64>,
202 sigma_scale: Option<f64>,
203 lambda_ht: Option<f64>,
204 lambda_ht_scale: Option<f64>,
205 spatial_radius: Option<i64>,
206 refine: Option<i64>,
207 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
208 let raw = raw_params(
209 None,
210 None,
211 preset,
212 None,
213 channel_mode,
214 luma_strength,
215 chroma_strength,
216 luma_lambda_ht,
217 chroma_lambda_ht,
218 luma_mismatch_scale,
219 chroma_mismatch_scale,
220 device,
221 accelerators,
222 None,
223 None,
224 temporal_radius,
225 sigma,
226 sigma_scale,
227 None,
228 lambda_ht,
229 lambda_ht_scale,
230 spatial_radius,
231 refine,
232 )?;
233 let filter = Denoise::create(api, core, clip, AlgorithmKind::Nl4d, &raw)?;
234 Ok(Some(Box::new(filter)))
235 }
236}
237
238export_vapoursynth_plugin! {
239 Metadata {
240 identifier: "com.chillfish8.avdenoise",
241 namespace: "avd",
242 name: "av-denoise",
243 read_only: true,
244 },
245 [NlmeansFunction::new(), Nl4dFunction::new()]
246}