vapoursynth/plugins/
ffi.rs

1//! Internal stuff for plugin FFI handling.
2use std::ffi::CString;
3use std::fmt::Write;
4use std::ops::{Deref, DerefMut};
5use std::os::raw::c_void;
6use std::ptr::{self};
7use std::{mem, panic, process};
8
9use vapoursynth_sys as ffi;
10
11use crate::api::API;
12use crate::core::CoreRef;
13use crate::map::{MapRef, MapRefMut};
14use crate::plugins::{Filter, FilterFunction, FrameContext, Metadata};
15use crate::video_info::VideoInfo;
16
17/// Container for the internal filter function data.
18pub(crate) struct FilterFunctionData<F: FilterFunction> {
19    pub filter_function: F,
20    // Store the name since it's supposed to be the same between two invocations (register and
21    // create_filter).
22    pub name: CString,
23}
24
25/// Drops the filter.
26unsafe extern "C" fn free(
27    instance_data: *mut c_void,
28    _core: *mut ffi::VSCore,
29    _vsapi: *const ffi::VSAPI,
30) {
31    let closure = move || {
32        // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
33        // retrieving it.
34        let filter = Box::from_raw(instance_data as *mut Box<dyn Filter<'static> + 'static>);
35        drop(filter);
36    };
37
38    if panic::catch_unwind(closure).is_err() {
39        process::abort();
40    }
41}
42
43/// Calls `Filter::get_frame_initial()` and `Filter::get_frame()`.
44unsafe extern "C" fn get_frame(
45    n: i32,
46    activation_reason: i32,
47    instance_data: *mut c_void,
48    _frame_data: *mut *mut c_void,
49    frame_ctx: *mut ffi::VSFrameContext,
50    core: *mut ffi::VSCore,
51    _vsapi: *const ffi::VSAPI,
52) -> *const ffi::VSFrame {
53    let closure = move || {
54        let api = API::get_cached();
55        let core = CoreRef::from_ptr(core);
56        let context = FrameContext::from_ptr(frame_ctx);
57
58        // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
59        // retrieving it.
60        let filter = Box::from_raw(instance_data as *mut Box<dyn Filter<'static> + 'static>);
61
62        debug_assert!(n >= 0);
63        let n = n as usize;
64
65        let rv = match activation_reason {
66            x if x == ffi::VSActivationReason_arInitial as _ => {
67                match filter.get_frame_initial(api, core, context, n) {
68                    Ok(Some(frame)) => {
69                        let ptr = frame.deref().deref() as *const _;
70                        // The ownership is transferred to the caller.
71                        mem::forget(frame);
72                        ptr
73                    }
74                    Ok(None) => ptr::null(),
75                    Err(err) => {
76                        let mut buf = String::with_capacity(64);
77
78                        write!(buf, "Error in Filter::get_frame_initial(): {}", err).unwrap();
79
80                        write!(buf, "{}", err).unwrap();
81
82                        let buf = CString::new(buf.replace('\0', "\\0")).unwrap();
83                        api.set_filter_error(buf.as_ptr(), frame_ctx);
84
85                        ptr::null()
86                    }
87                }
88            }
89            x if x == ffi::VSActivationReason_arAllFramesReady as _ => {
90                match filter.get_frame(api, core, context, n) {
91                    Ok(frame) => {
92                        let ptr = frame.deref().deref() as *const _;
93                        // The ownership is transferred to the caller.
94                        mem::forget(frame);
95                        ptr
96                    }
97                    Err(err) => {
98                        let buf = format!("{}", err);
99                        let buf = CString::new(buf.replace('\0', "\\0")).unwrap();
100                        api.set_filter_error(buf.as_ptr(), frame_ctx);
101
102                        ptr::null()
103                    }
104                }
105            }
106            _ => ptr::null(),
107        };
108
109        mem::forget(filter);
110
111        rv
112    };
113
114    match panic::catch_unwind(closure) {
115        Ok(frame) => frame,
116        Err(_) => process::abort(),
117    }
118}
119
120/// Creates a new instance of the filter.
121pub(crate) unsafe extern "C" fn create<F: FilterFunction>(
122    in_: *const ffi::VSMap,
123    out: *mut ffi::VSMap,
124    user_data: *mut c_void,
125    core: *mut ffi::VSCore,
126    api: *const ffi::VSAPI,
127) {
128    let closure = move || {
129        API::set(api);
130
131        let args = MapRef::from_ptr(in_);
132        let mut out = MapRefMut::from_ptr(out);
133        let core = CoreRef::from_ptr(core);
134        let data = Box::from_raw(user_data as *mut FilterFunctionData<F>);
135
136        let filter = match data.filter_function.create(API::get_cached(), core, &args) {
137            Ok(Some(filter)) => Some(Box::new(filter)),
138            Ok(None) => None,
139            Err(err) => {
140                let mut buf = String::with_capacity(64);
141
142                write!(
143                    buf,
144                    "Error in Filter::create() of {}: {}",
145                    data.name.to_str().unwrap(),
146                    err
147                )
148                .unwrap();
149
150                write!(buf, "{}", err).unwrap();
151
152                out.set_error(&buf.replace('\0', "\\0")).unwrap();
153                None
154            }
155        };
156
157        if let Some(filter) = filter {
158            // In v4, we need to get the video info before creating the filter
159            let vi = filter
160                .video_info(API::get_cached(), core)
161                .into_iter()
162                .map(VideoInfo::ffi_type)
163                .collect::<Vec<_>>();
164
165            // For now, assume single output (most common case)
166            // TODO: Handle multiple outputs if needed
167            let vi_ptr = if !vi.is_empty() {
168                vi.as_ptr()
169            } else {
170                ptr::null()
171            };
172
173            API::get_cached().create_video_filter(
174                out.deref_mut().deref_mut(),
175                data.name.as_ptr(),
176                vi_ptr,
177                Some(get_frame),
178                Some(free),
179                ffi::VSFilterMode_fmParallel as i32,
180                ptr::null(), // No dependencies for now
181                0,           // numDeps
182                Box::into_raw(filter) as *mut _,
183                core.ptr(),
184            );
185
186            // Keep vi alive until create_video_filter returns
187            mem::forget(vi);
188        }
189
190        mem::forget(data);
191    };
192
193    if panic::catch_unwind(closure).is_err() {
194        // The `FilterFunction` might have been left in an inconsistent state, so we have to abort.
195        process::abort();
196    }
197}
198
199/// Registers the plugin.
200///
201/// This function is for internal use only.
202///
203/// # Safety
204/// The caller must ensure the pointers are valid.
205#[inline]
206pub unsafe fn call_config_func(
207    vspapi: *const ffi::VSPLUGINAPI,
208    plugin: *mut ffi::VSPlugin,
209    metadata: Metadata,
210) {
211    let identifier_cstring = CString::new(metadata.identifier)
212        .expect("Couldn't convert the plugin identifier to a CString");
213    let namespace_cstring = CString::new(metadata.namespace)
214        .expect("Couldn't convert the plugin namespace to a CString");
215    let name_cstring =
216        CString::new(metadata.name).expect("Couldn't convert the plugin name to a CString");
217
218    let api_version = (ffi::VAPOURSYNTH_API_MAJOR << 16 | ffi::VAPOURSYNTH_API_MINOR) as i32;
219    let flags = if metadata.read_only {
220        0 // Read-only means NOT modifiable
221    } else {
222        ffi::VSPluginConfigFlags_pcModifiable as i32
223    };
224
225    ((*vspapi).configPlugin.unwrap())(
226        identifier_cstring.as_ptr(),
227        namespace_cstring.as_ptr(),
228        name_cstring.as_ptr(),
229        1, // Plugin version
230        api_version,
231        flags,
232        plugin,
233    );
234}
235
236/// Registers the filter `F`.
237///
238/// This function is for internal use only.
239///
240/// # Safety
241/// The caller must ensure the pointers are valid.
242#[inline]
243pub unsafe fn call_register_func<F: FilterFunction>(
244    vspapi: *const ffi::VSPLUGINAPI,
245    plugin: *mut ffi::VSPlugin,
246    filter_function: F,
247) {
248    let name_cstring = CString::new(filter_function.name())
249        .expect("Couldn't convert the filter name to a CString");
250    let args_cstring = CString::new(filter_function.args())
251        .expect("Couldn't convert the filter args to a CString");
252    let return_type_cstring =
253        CString::new("clip:vnode;").expect("Couldn't convert return type to a CString");
254
255    let data = Box::new(FilterFunctionData {
256        filter_function,
257        name: name_cstring,
258    });
259
260    ((*vspapi).registerFunction.unwrap())(
261        data.name.as_ptr(),
262        args_cstring.as_ptr(),
263        return_type_cstring.as_ptr(),
264        Some(create::<F>),
265        Box::into_raw(data) as _,
266        plugin,
267    );
268}
269
270/// Exports a VapourSynth plugin from this library.
271///
272/// This macro should be used only once at the top level of the library. The library should have a
273/// `cdylib` crate type.
274///
275/// The first parameter is a `Metadata` expression containing your plugin's metadata.
276///
277/// Following it is a list of values implementing `FilterFunction`, those are the filter functions
278/// the plugin will export.
279///
280/// # Example
281/// ```ignore
282/// export_vapoursynth_plugin! {
283///     Metadata {
284///         identifier: "com.example.invert",
285///         namespace: "invert",
286///         name: "Invert Example Plugin",
287///         read_only: true,
288///     },
289///     [SampleFilterFunction::new(), OtherFunction::new()]
290/// }
291/// ```
292#[macro_export]
293macro_rules! export_vapoursynth_plugin {
294    ($metadata:expr, [$($filter:expr),*$(,)*]) => (
295        #[allow(non_snake_case)]
296        #[unsafe(no_mangle)]
297        pub unsafe extern "C" fn VapourSynthPluginInit2(
298            plugin: *mut $crate::ffi::VSPlugin,
299            vspapi: *const $crate::ffi::VSPLUGINAPI,
300        ) {
301            use ::std::{panic, process};
302            use $crate::plugins::ffi::{call_config_func, call_register_func};
303
304            let closure = move || {
305                call_config_func(vspapi, plugin, $metadata);
306
307                $(
308                    call_register_func(vspapi, plugin, $filter);
309                )*
310            };
311
312            if panic::catch_unwind(closure).is_err() {
313                process::abort();
314            }
315        }
316    )
317}