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 pin_plugin_library() {
55 static PIN: std::sync::Once = std::sync::Once::new();
56 PIN.call_once(|| {
57 #[cfg(windows)]
58 pin_plugin_library_windows();
59 });
60}
61
62#[cfg(windows)]
63fn pin_plugin_library_windows() {
64 use std::ffi::c_void;
65
66 const GET_MODULE_HANDLE_EX_FLAG_PIN: u32 = 0x0000_0001;
67 const GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS: u32 = 0x0000_0004;
68
69 #[link(name = "kernel32")]
70 unsafe extern "system" {
71 fn GetModuleHandleExW(flags: u32, module_name: *const u16, module: *mut *mut c_void) -> i32;
72 }
73
74 let address = pin_plugin_library_windows as *const () as *const u16;
75 let mut module: *mut c_void = std::ptr::null_mut();
76 let ok = unsafe {
79 GetModuleHandleExW(
80 GET_MODULE_HANDLE_EX_FLAG_PIN | GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS,
81 address,
82 &mut module,
83 )
84 };
85 if ok == 0 {
86 tracing::warn!("could not pin the plugin library, the process may crash at exit");
87 }
88}
89
90fn opt_string(bytes: Option<&[u8]>, field: &str) -> Result<Option<String>, Error> {
93 bytes
94 .map(|b| String::from_utf8(b.to_vec()).map_err(|_| anyhow::anyhow!("{field} must be valid UTF-8")))
95 .transpose()
96}
97
98fn opt_accelerators(bytes: Option<&[u8]>) -> Result<Option<Vec<String>>, Error> {
107 let Some(joined) = opt_string(bytes, "accelerators")? else {
108 return Ok(None);
109 };
110
111 let names: Vec<String> = joined
112 .split(',')
113 .map(str::trim)
114 .filter(|s| !s.is_empty())
115 .map(str::to_string)
116 .collect();
117
118 if names.is_empty() {
119 anyhow::bail!("accelerators must name at least one accelerator when set");
120 }
121
122 Ok(Some(names))
123}
124
125fn opt_bool(value: Option<i64>) -> Option<bool> {
132 value.map(|v| v != 0)
133}
134
135#[expect(clippy::too_many_arguments)]
137fn raw_params(
138 strength: Option<f64>,
139 variant: Option<&[u8]>,
140 preset: Option<&[u8]>,
141 prefilter: Option<&[u8]>,
142 channel_mode: Option<&[u8]>,
143 luma_strength: Option<f64>,
144 chroma_strength: Option<f64>,
145 luma_lambda_ht: Option<f64>,
146 chroma_lambda_ht: Option<f64>,
147 luma_mismatch_scale: Option<f64>,
148 chroma_mismatch_scale: Option<f64>,
149 device: Option<&[u8]>,
150 accelerators: Option<&[u8]>,
151 search_radius: Option<i64>,
152 patch_radius: Option<i64>,
153 temporal_radius: Option<i64>,
154 sigma: Option<f64>,
155 sigma_scale: Option<f64>,
156 motion_compensation: Option<i64>,
157 lambda_ht: Option<f64>,
158 lambda_ht_scale: Option<f64>,
159 spatial_radius: Option<i64>,
160 refine: Option<i64>,
161) -> Result<RawParams, Error> {
162 Ok(RawParams {
163 strength,
164 variant: opt_string(variant, "variant")?,
165 preset: opt_string(preset, "preset")?,
166 prefilter: opt_string(prefilter, "prefilter")?,
167 channel_mode: opt_string(channel_mode, "channel_mode")?,
168 luma_strength,
169 chroma_strength,
170 luma_lambda_ht,
171 chroma_lambda_ht,
172 luma_mismatch_scale,
173 chroma_mismatch_scale,
174 device: opt_string(device, "device")?,
175 accelerators: opt_accelerators(accelerators)?,
176 search_radius,
177 patch_radius,
178 temporal_radius,
179 sigma,
180 sigma_scale,
181 motion_compensation: opt_bool(motion_compensation),
182 lambda_ht,
183 lambda_ht_scale,
184 spatial_radius,
185 refine,
186 })
187}
188
189make_filter_function! {
190 NlmeansFunction, "NLMeans"
191
192 #[expect(clippy::too_many_arguments)]
193 fn create_nlmeans<'core>(
194 api: API,
195 core: CoreRef<'core>,
196 clip: Node<'core>,
197 strength: Option<f64>,
198 variant: Option<&[u8]>,
199 preset: Option<&[u8]>,
200 prefilter: Option<&[u8]>,
201 channel_mode: Option<&[u8]>,
202 luma_strength: Option<f64>,
203 chroma_strength: Option<f64>,
204 device: Option<&[u8]>,
205 accelerators: Option<&[u8]>,
206 search_radius: Option<i64>,
207 patch_radius: Option<i64>,
208 temporal_radius: Option<i64>,
209 sigma: Option<f64>,
210 sigma_scale: Option<f64>,
211 motion_compensation: Option<i64>,
212 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
213 let raw = raw_params(
214 strength,
215 variant,
216 preset,
217 prefilter,
218 channel_mode,
219 luma_strength,
220 chroma_strength,
221 None,
222 None,
223 None,
224 None,
225 device,
226 accelerators,
227 search_radius,
228 patch_radius,
229 temporal_radius,
230 sigma,
231 sigma_scale,
232 motion_compensation,
233 None,
234 None,
235 None,
236 None,
237 )?;
238 let filter = Denoise::create(api, core, clip, AlgorithmKind::Nlmeans, &raw)?;
239 Ok(Some(Box::new(filter)))
240 }
241}
242
243make_filter_function! {
244 Nl4dFunction, "NL4D"
245
246 #[expect(clippy::too_many_arguments)]
260 fn create_nl4d<'core>(
261 api: API,
262 core: CoreRef<'core>,
263 clip: Node<'core>,
264 preset: Option<&[u8]>,
265 channel_mode: Option<&[u8]>,
266 luma_strength: Option<f64>,
267 chroma_strength: Option<f64>,
268 luma_lambda_ht: Option<f64>,
269 chroma_lambda_ht: Option<f64>,
270 luma_mismatch_scale: Option<f64>,
271 chroma_mismatch_scale: Option<f64>,
272 device: Option<&[u8]>,
273 accelerators: Option<&[u8]>,
274 temporal_radius: Option<i64>,
275 sigma: Option<f64>,
276 sigma_scale: Option<f64>,
277 lambda_ht: Option<f64>,
278 lambda_ht_scale: Option<f64>,
279 spatial_radius: Option<i64>,
280 refine: Option<i64>,
281 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error> {
282 let raw = raw_params(
283 None,
284 None,
285 preset,
286 None,
287 channel_mode,
288 luma_strength,
289 chroma_strength,
290 luma_lambda_ht,
291 chroma_lambda_ht,
292 luma_mismatch_scale,
293 chroma_mismatch_scale,
294 device,
295 accelerators,
296 None,
297 None,
298 temporal_radius,
299 sigma,
300 sigma_scale,
301 None,
302 lambda_ht,
303 lambda_ht_scale,
304 spatial_radius,
305 refine,
306 )?;
307 let filter = Denoise::create(api, core, clip, AlgorithmKind::Nl4d, &raw)?;
308 Ok(Some(Box::new(filter)))
309 }
310}
311
312export_vapoursynth_plugin! {
313 Metadata {
314 identifier: "com.chillfish8.avdenoise",
315 namespace: "avd",
316 name: "av-denoise",
317 read_only: true,
318 },
319 [NlmeansFunction::new(), Nl4dFunction::new()]
320}