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}