vapoursynth/
plugin.rs

1//! VapourSynth plugins.
2
3use std::ffi::{CStr, CString, NulError};
4use std::marker::PhantomData;
5use std::ops::Deref;
6use std::ptr::NonNull;
7use vapoursynth_sys as ffi;
8
9use crate::api::API;
10use crate::map::{Map, OwnedMap};
11use crate::plugins::{self, FilterFunction};
12
13/// A VapourSynth plugin.
14#[derive(Debug, Clone, Copy)]
15pub struct Plugin<'core> {
16    handle: NonNull<ffi::VSPlugin>,
17    _owner: PhantomData<&'core ()>,
18}
19
20unsafe impl<'core> Send for Plugin<'core> {}
21unsafe impl<'core> Sync for Plugin<'core> {}
22
23impl<'core> Plugin<'core> {
24    /// Wraps `handle` in a `Plugin`.
25    ///
26    /// # Safety
27    /// The caller must ensure `handle` is valid and API is cached.
28    #[inline]
29    pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSPlugin) -> Self {
30        Self {
31            handle: unsafe { NonNull::new_unchecked(handle) },
32            _owner: PhantomData,
33        }
34    }
35
36    /// Returns the absolute path to the plugin, including the plugin's file name. This is the real
37    /// location of the plugin, i.e. there are no symbolic links in the path.
38    ///
39    /// Path elements are always delimited with forward slashes.
40    #[inline]
41    pub fn path(&self) -> Option<&'core CStr> {
42        let ptr = unsafe { API::get_cached().get_plugin_path(self.handle.as_ptr()) };
43        if ptr.is_null() {
44            None
45        } else {
46            Some(unsafe { CStr::from_ptr(ptr) })
47        }
48    }
49
50    /// Invokes a filter.
51    ///
52    /// `invoke()` makes sure the filter has no compat input nodes, checks that the args passed to
53    /// the filter are consistent with the argument list registered by the plugin that contains the
54    /// filter, creates the filter, and checks that the filter doesn't return any compat nodes. If
55    /// everything goes smoothly, the filter will be ready to generate frames after `invoke()`
56    /// returns.
57    ///
58    /// Returns a map containing the filter's return value(s). Use `Map::error()` to check if the
59    /// filter was invoked successfully.
60    ///
61    /// Most filters will either add an error to the map, or one or more clips with the key `clip`.
62    /// The exception to this are functions, for example `LoadPlugin`, which doesn't return any
63    /// clips for obvious reasons.
64    #[inline]
65    pub fn invoke(&self, name: &str, args: &Map<'core>) -> Result<OwnedMap<'core>, NulError> {
66        let name = CString::new(name)?;
67        Ok(unsafe {
68            OwnedMap::from_ptr(API::get_cached().invoke(
69                self.handle.as_ptr(),
70                name.as_ptr(),
71                args.deref(),
72            ))
73        })
74    }
75
76    /// Registers a filter function to be exported by a non-readonly plugin.
77    #[inline]
78    pub fn register_function<F: FilterFunction>(&self, filter_function: F) -> Result<(), NulError> {
79        // TODO: this is almost the same code as plugins::ffi::call_register_function().
80        let name_cstring = CString::new(filter_function.name())?;
81        let args_cstring = CString::new(filter_function.args())?;
82        let return_type_cstring = CString::new("clip:vnode;")?;
83
84        let data = Box::new(plugins::ffi::FilterFunctionData::<F> {
85            filter_function,
86            name: name_cstring,
87        });
88
89        unsafe {
90            API::get_cached().register_function(
91                data.name.as_ptr(),
92                args_cstring.as_ptr(),
93                return_type_cstring.as_ptr(),
94                Some(plugins::ffi::create::<F>),
95                Box::into_raw(data) as _,
96                self.handle.as_ptr(),
97            );
98        }
99
100        Ok(())
101    }
102
103    /// Returns a plugin function by name.
104    ///
105    /// This function retrieves a specific filter function exported by the plugin. In VapourSynth v4,
106    /// this is the recommended way to query plugin functions, as the `functions()` method has been
107    /// removed.
108    ///
109    /// Returns `None` if no function with the given name exists.
110    #[inline]
111    pub fn get_plugin_function_by_name(
112        &self,
113        name: &str,
114    ) -> Result<Option<PluginFunction<'core>>, NulError> {
115        let name = CString::new(name)?;
116        let ptr = unsafe {
117            API::get_cached().get_plugin_function_by_name(name.as_ptr(), self.handle.as_ptr())
118        };
119        if ptr.is_null() {
120            Ok(None)
121        } else {
122            Ok(Some(unsafe { PluginFunction::from_ptr(ptr) }))
123        }
124    }
125}
126
127/// A VapourSynth plugin function.
128///
129/// This represents a specific filter function exported by a plugin. In VapourSynth v4, plugin
130/// functions must be queried individually by name using `Plugin::get_plugin_function_by_name()`.
131#[derive(Debug, Clone, Copy)]
132pub struct PluginFunction<'core> {
133    handle: NonNull<ffi::VSPluginFunction>,
134    _owner: PhantomData<&'core ()>,
135}
136
137unsafe impl<'core> Send for PluginFunction<'core> {}
138unsafe impl<'core> Sync for PluginFunction<'core> {}
139
140impl<'core> PluginFunction<'core> {
141    /// Wraps `handle` in a `PluginFunction`.
142    ///
143    /// # Safety
144    /// The caller must ensure `handle` is valid and API is cached.
145    #[inline]
146    pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSPluginFunction) -> Self {
147        Self {
148            handle: unsafe { NonNull::new_unchecked(handle) },
149            _owner: PhantomData,
150        }
151    }
152
153    /// Returns the name of this plugin function.
154    #[inline]
155    pub fn name(&self) -> &'core CStr {
156        let ptr = unsafe { API::get_cached().get_plugin_function_name(self.handle.as_ptr()) };
157        unsafe { CStr::from_ptr(ptr) }
158    }
159
160    /// Returns the argument specification string for this plugin function.
161    ///
162    /// The argument string describes the parameters the function accepts using VapourSynth's
163    /// argument specification format (e.g., "clip:vnode;width:int:opt;height:int:opt;").
164    #[inline]
165    pub fn arguments(&self) -> &'core CStr {
166        let ptr = unsafe { API::get_cached().get_plugin_function_arguments(self.handle.as_ptr()) };
167        unsafe { CStr::from_ptr(ptr) }
168    }
169
170    /// Returns the return type specification string for this plugin function.
171    ///
172    /// The return type string describes what the function returns using VapourSynth's
173    /// type specification format (typically "vnode" for filters that return video nodes).
174    #[inline]
175    pub fn return_type(&self) -> &'core CStr {
176        let ptr =
177            unsafe { API::get_cached().get_plugin_function_return_type(self.handle.as_ptr()) };
178        unsafe { CStr::from_ptr(ptr) }
179    }
180}