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}