vapoursynth/plugins/mod.rs
1//! Things related to making VapourSynth plugins.
2
3use anyhow::Error;
4
5use crate::api::API;
6use crate::core::CoreRef;
7use crate::frame::FrameRef;
8use crate::function::Function;
9use crate::map::{self, Map, Value, ValueIter};
10use crate::node::Node;
11use crate::video_info::VideoInfo;
12
13mod frame_context;
14pub use self::frame_context::FrameContext;
15
16pub mod ffi;
17
18/// Plugin metadata.
19#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
20pub struct Metadata {
21 /// A "reverse" URL, unique among all plugins.
22 ///
23 /// For example, `com.example.invert`.
24 pub identifier: &'static str,
25
26 /// Namespace where the plugin's filters will go, unique among all plugins.
27 ///
28 /// Only lowercase letters and the underscore should be used, and it shouldn't be too long.
29 /// Additionally, words that are special to Python, e.g. `del`, should be avoided.
30 ///
31 /// For example, `invert`.
32 pub namespace: &'static str,
33
34 /// Plugin name in readable form.
35 ///
36 /// For example, `Invert Example Plugin`.
37 pub name: &'static str,
38
39 /// Whether new filters can be registered at runtime.
40 ///
41 /// This should generally be set to `false`. It's used for the built-in AviSynth compat plugin.
42 pub read_only: bool,
43}
44
45/// A filter function interface.
46///
47/// See the `make_filter_function!` macro that generates types implementing this automatically.
48pub trait FilterFunction: Send + Sync {
49 /// Returns the name of the function.
50 ///
51 /// The characters allowed are letters, numbers, and the underscore. The first character must
52 /// be a letter. In other words: `^[a-zA-Z][a-zA-Z0-9_]*$`.
53 ///
54 /// For example, `Invert`.
55 fn name(&self) -> &str;
56
57 /// Returns the argument string.
58 ///
59 /// Arguments are separated by a semicolon. Each argument is made of several fields separated
60 /// by a colon. Don’t insert additional whitespace characters, or VapourSynth will die.
61 ///
62 /// Fields:
63 /// - The argument name. The same characters are allowed as for the filter's name. Argument
64 /// names should be all lowercase and use only letters and the underscore.
65 ///
66 /// - The type. One of `int`, `float`, `data`, `clip`, `frame`, `func`. They correspond to the
67 /// `Map::get_*()` functions (`clip` is `get_node()`). It's possible to declare an array by
68 /// appending `[]` to the type.
69 ///
70 /// - `opt` if the parameter is optional.
71 ///
72 /// - `empty` if the array is allowed to be empty.
73 ///
74 /// The following example declares the arguments "blah", "moo", and "asdf":
75 /// `blah:clip;moo:int[]:opt;asdf:float:opt;`
76 fn args(&self) -> &str;
77
78 /// The callback for this filter function.
79 ///
80 /// In most cases this is where you should create a new instance of the filter and return it.
81 /// However, a filter function like AviSynth compat's `LoadPlugin()` which isn't actually a
82 /// filter, can return `None`.
83 ///
84 /// `args` contains the filter arguments, as specified by the argument string from
85 /// `FilterFunction::args()`. Their presence and types are validated by VapourSynth so it's
86 /// safe to `unwrap()`.
87 ///
88 /// In this function you should take all input nodes for your filter and store them somewhere
89 /// so that you can request their frames in `get_frame_initial()`.
90 // TODO: with generic associated types it'll be possible to make Filter<'core> an associated
91 // type of this trait and get rid of this Box.
92 fn create<'core>(
93 &self,
94 api: API,
95 core: CoreRef<'core>,
96 args: &Map<'core>,
97 ) -> Result<Option<Box<dyn Filter<'core> + 'core>>, Error>;
98}
99
100/// A filter interface.
101// TODO: perhaps it's possible to figure something out about Send + Sync with specialization? Since
102// there are Node flags which say that the filter will be called strictly by one thread, in which
103// case Sync shouldn't be required.
104pub trait Filter<'core>: Send + Sync {
105 /// Returns the parameters of this filter's output node.
106 ///
107 /// The returned vector should contain one entry for each node output index.
108 fn video_info(&self, api: API, core: CoreRef<'core>) -> Vec<VideoInfo<'core>>;
109
110 /// Requests the necessary frames from downstream nodes.
111 ///
112 /// This is always the first function to get called for a given frame `n`.
113 ///
114 /// In this function you should call `request_frame_filter()` on any input nodes that you need
115 /// and return `None`. If you do not need any input frames, you should generate the output
116 /// frame and return it here.
117 ///
118 /// Do not call `Node::get_frame()` from within this function.
119 fn get_frame_initial(
120 &self,
121 api: API,
122 core: CoreRef<'core>,
123 context: FrameContext,
124 n: usize,
125 ) -> Result<Option<FrameRef<'core>>, Error>;
126
127 /// Returns the requested frame.
128 ///
129 /// This is always the second function to get called for a given frame `n`. If the frame was
130 /// retrned from `get_frame_initial()`, this function is not called.
131 ///
132 /// In this function you should call `get_frame_filter()` on the input nodes to retrieve the
133 /// frames you requested in `get_frame_initial()`.
134 ///
135 /// Do not call `Node::get_frame()` from within this function.
136 fn get_frame(
137 &self,
138 api: API,
139 core: CoreRef<'core>,
140 context: FrameContext,
141 n: usize,
142 ) -> Result<FrameRef<'core>, Error>;
143}
144
145/// An internal trait representing a filter argument type.
146pub trait FilterArgument<'map, 'elem: 'map>: Value<'map, 'elem> + private::Sealed {
147 /// Returns the VapourSynth type name for this argument type.
148 fn type_name() -> &'static str;
149}
150
151/// An internal trait representing a filter parameter type (argument type + whether it's an array
152/// or optional).
153pub trait FilterParameter<'map, 'elem: 'map>: private::Sealed {
154 /// The underlying argument type for this parameter type.
155 type Argument: FilterArgument<'map, 'elem>;
156
157 /// Returns whether this parameter is an array.
158 fn is_array() -> bool;
159
160 /// Returns whether this parameter is optional.
161 fn is_optional() -> bool;
162
163 /// Retrieves this parameter from the given map.
164 fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self;
165}
166
167impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for i64 {
168 #[inline]
169 fn type_name() -> &'static str {
170 "int"
171 }
172}
173
174impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for f64 {
175 #[inline]
176 fn type_name() -> &'static str {
177 "float"
178 }
179}
180
181impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for &'map [u8] {
182 #[inline]
183 fn type_name() -> &'static str {
184 "data"
185 }
186}
187
188impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for Node<'elem> {
189 #[inline]
190 fn type_name() -> &'static str {
191 "clip"
192 }
193}
194
195impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for FrameRef<'elem> {
196 #[inline]
197 fn type_name() -> &'static str {
198 "frame"
199 }
200}
201
202impl<'map, 'elem: 'map> FilterArgument<'map, 'elem> for Function<'elem> {
203 #[inline]
204 fn type_name() -> &'static str {
205 "func"
206 }
207}
208
209impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for T
210where
211 T: FilterArgument<'map, 'elem>,
212{
213 type Argument = Self;
214
215 #[inline]
216 fn is_array() -> bool {
217 false
218 }
219
220 #[inline]
221 fn is_optional() -> bool {
222 false
223 }
224
225 #[inline]
226 fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
227 Self::get_from_map(map, key).unwrap()
228 }
229}
230
231impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for Option<T>
232where
233 T: FilterArgument<'map, 'elem>,
234{
235 type Argument = T;
236
237 #[inline]
238 fn is_array() -> bool {
239 false
240 }
241
242 #[inline]
243 fn is_optional() -> bool {
244 true
245 }
246
247 #[inline]
248 fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
249 match <Self::Argument as Value>::get_from_map(map, key) {
250 Ok(x) => Some(x),
251 Err(map::Error::KeyNotFound) => None,
252 _ => unreachable!(),
253 }
254 }
255}
256
257impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for ValueIter<'map, 'elem, T>
258where
259 T: FilterArgument<'map, 'elem>,
260{
261 type Argument = T;
262
263 #[inline]
264 fn is_array() -> bool {
265 true
266 }
267
268 #[inline]
269 fn is_optional() -> bool {
270 false
271 }
272
273 #[inline]
274 fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
275 <Self::Argument>::get_iter_from_map(map, key).unwrap()
276 }
277}
278
279impl<'map, 'elem: 'map, T> FilterParameter<'map, 'elem> for Option<ValueIter<'map, 'elem, T>>
280where
281 T: FilterArgument<'map, 'elem>,
282{
283 type Argument = T;
284
285 #[inline]
286 fn is_array() -> bool {
287 true
288 }
289
290 #[inline]
291 fn is_optional() -> bool {
292 true
293 }
294
295 #[inline]
296 fn get_from_map(map: &'map Map<'elem>, key: &str) -> Self {
297 match <Self::Argument as Value>::get_iter_from_map(map, key) {
298 Ok(x) => Some(x),
299 Err(map::Error::KeyNotFound) => None,
300 _ => unreachable!(),
301 }
302 }
303}
304
305mod private {
306 use super::{FilterArgument, FrameRef, Function, Node, ValueIter};
307
308 pub trait Sealed {}
309
310 impl Sealed for i64 {}
311 impl Sealed for f64 {}
312 impl<'map> Sealed for &'map [u8] {}
313 impl<'elem> Sealed for Node<'elem> {}
314 impl<'elem> Sealed for FrameRef<'elem> {}
315 impl<'elem> Sealed for Function<'elem> {}
316
317 impl<'map, 'elem: 'map, T> Sealed for Option<T> where T: FilterArgument<'map, 'elem> {}
318
319 impl<'map, 'elem: 'map, T> Sealed for ValueIter<'map, 'elem, T> where T: FilterArgument<'map, 'elem> {}
320
321 impl<'map, 'elem: 'map, T> Sealed for Option<ValueIter<'map, 'elem, T>> where
322 T: FilterArgument<'map, 'elem>
323 {
324 }
325}
326
327/// Make a filter function easily and avoid boilerplate.
328///
329/// This macro accepts the name of the filter function type, the name of the filter and the create
330/// function.
331///
332/// The macro generates a type implementing `FilterFunction` with the correct `args()` string
333/// derived from the function parameters of the specified create function. The generated
334/// `FilterFunction::create()` extracts all parameters from the argument map received from
335/// VapourSynth and passes them into the specified create function.
336///
337/// The create function should look like:
338///
339/// ```ignore
340/// fn create<'core>(
341/// api: API,
342/// core: CoreRef<'core>,
343/// /* filter arguments */
344/// ) -> Result<Option<Box<Filter<'core> + 'core>>, Error> {
345/// /* ... */
346/// }
347/// ```
348///
349/// All VapourSynth-supported types can be used, as well as `Option<T>` for optional parameters and
350/// `ValueIter<T>` for array parameters. Array parameters can be empty.
351///
352/// Caveat: the macro doesn't currently allow specifying mutable parameters, so to do that they
353/// have to be reassigned to a mutable variable in the function body. This is mainly a problem for
354/// array parameters. See how the example below handles it.
355///
356/// Another caveat: underscore lifetimes are required for receiving `ValueIter<T>`.
357///
358/// # Example
359/// ```ignore
360/// make_filter_function! {
361/// MyFilterFunction, "MyFilter"
362///
363/// fn create_my_filter<'core>(
364/// _api: API,
365/// _core: CoreRef<'core>,
366/// int_parameter: i64,
367/// some_data: &[u8],
368/// optional_parameter: Option<f64>,
369/// array_parameter: ValueIter<'_, 'core, Node<'core>>,
370/// optional_array_parameter: Option<ValueIter<'_, 'core, FrameRef<'core>>>,
371/// ) -> Result<Option<Box<Filter<'core> + 'core>>, Error> {
372/// let mut array_parameter = array_parameter;
373/// Ok(Some(Box::new(MyFilter::new(/* ... */))));
374/// }
375/// }
376/// ```
377#[macro_export]
378macro_rules! make_filter_function {
379 (
380 $struct_name:ident, $function_name:tt
381
382 $(#[$attr:meta])*
383 fn $create_fn_name:ident<$lifetime:tt>(
384 $api_arg_name:ident : $api_arg_type:ty,
385 $core_arg_name:ident : $core_arg_type:ty,
386 $($arg_name:ident : $arg_type:ty),* $(,)*
387 ) -> $return_type:ty {
388 $($body:tt)*
389 }
390 ) => (
391 struct $struct_name {
392 args: String,
393 }
394
395 impl $struct_name {
396 fn new<'core>() -> Self {
397 let mut args = String::new();
398
399 $(
400 // Don't use format!() for better constant propagation.
401 args += stringify!($arg_name); // TODO: allow using a different name.
402 args += ":";
403 args
404 += <<$arg_type as $crate::plugins::FilterParameter>::Argument>::type_name();
405
406 if <$arg_type as $crate::plugins::FilterParameter>::is_array() {
407 args += "[]";
408 }
409
410 if <$arg_type as $crate::plugins::FilterParameter>::is_optional() {
411 args += ":opt";
412 }
413
414 // TODO: allow specifying this.
415 if <$arg_type as $crate::plugins::FilterParameter>::is_array() {
416 args += ":empty";
417 }
418
419 args += ";";
420 )*
421
422 Self { args }
423 }
424 }
425
426 impl $crate::plugins::FilterFunction for $struct_name {
427 #[inline]
428 fn name(&self) -> &str {
429 $function_name
430 }
431
432 #[inline]
433 fn args(&self) -> &str {
434 &self.args
435 }
436
437 #[inline]
438 fn create<'core>(
439 &self,
440 api: API,
441 core: CoreRef<'core>,
442 args: &Map<'core>,
443 ) -> Result<Option<Box<dyn $crate::plugins::Filter<'core> + 'core>>, Error> {
444 $create_fn_name(
445 api,
446 core,
447 $(
448 <$arg_type as $crate::plugins::FilterParameter>::get_from_map(
449 args,
450 stringify!($arg_name),
451 )
452 ),*
453 )
454 }
455 }
456
457 $(#[$attr])*
458 fn $create_fn_name<$lifetime>(
459 $api_arg_name : $api_arg_type,
460 $core_arg_name : $core_arg_type,
461 $($arg_name : $arg_type),*
462 ) -> $return_type {
463 $($body)*
464 }
465 )
466}