1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! VapourSynth plugins.

use std::ffi::{CStr, CString, NulError};
use std::marker::PhantomData;
use std::ops::Deref;
use std::ptr::NonNull;
use vapoursynth_sys as ffi;

use crate::api::API;
use crate::map::{Map, OwnedMap};
use crate::plugins::{self, FilterFunction};

/// A VapourSynth plugin.
#[derive(Debug, Clone, Copy)]
pub struct Plugin<'core> {
    handle: NonNull<ffi::VSPlugin>,
    _owner: PhantomData<&'core ()>,
}

unsafe impl<'core> Send for Plugin<'core> {}
unsafe impl<'core> Sync for Plugin<'core> {}

impl<'core> Plugin<'core> {
    /// Wraps `handle` in a `Plugin`.
    ///
    /// # Safety
    /// The caller must ensure `handle` is valid and API is cached.
    #[inline]
    pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSPlugin) -> Self {
        Self {
            handle: NonNull::new_unchecked(handle),
            _owner: PhantomData,
        }
    }

    /// Returns a map containing a list of the filters exported by a plugin.
    ///
    /// Keys: the filter names;
    ///
    /// Values: the filter name followed by its argument string, separated by a semicolon.
    // TODO: parse the values on the crate side and return a nice struct.
    #[inline]
    pub fn functions(&self) -> OwnedMap<'core> {
        unsafe { OwnedMap::from_ptr(API::get_cached().get_functions(self.handle.as_ptr())) }
    }

    /// Returns the absolute path to the plugin, including the plugin's file name. This is the real
    /// location of the plugin, i.e. there are no symbolic links in the path.
    ///
    /// Path elements are always delimited with forward slashes.
    #[cfg(feature = "gte-vapoursynth-api-31")]
    #[inline]
    pub fn path(&self) -> Option<&'core CStr> {
        let ptr = unsafe { API::get_cached().get_plugin_path(self.handle.as_ptr()) };
        if ptr.is_null() {
            None
        } else {
            Some(unsafe { CStr::from_ptr(ptr) })
        }
    }

    /// Invokes a filter.
    ///
    /// `invoke()` makes sure the filter has no compat input nodes, checks that the args passed to
    /// the filter are consistent with the argument list registered by the plugin that contains the
    /// filter, creates the filter, and checks that the filter doesn't return any compat nodes. If
    /// everything goes smoothly, the filter will be ready to generate frames after `invoke()`
    /// returns.
    ///
    /// Returns a map containing the filter's return value(s). Use `Map::error()` to check if the
    /// filter was invoked successfully.
    ///
    /// Most filters will either add an error to the map, or one or more clips with the key `clip`.
    /// The exception to this are functions, for example `LoadPlugin`, which doesn't return any
    /// clips for obvious reasons.
    #[inline]
    pub fn invoke(&self, name: &str, args: &Map<'core>) -> Result<OwnedMap<'core>, NulError> {
        let name = CString::new(name)?;
        Ok(unsafe {
            OwnedMap::from_ptr(API::get_cached().invoke(
                self.handle.as_ptr(),
                name.as_ptr(),
                args.deref(),
            ))
        })
    }

    /// Registers a filter function to be exported by a non-readonly plugin.
    #[inline]
    pub fn register_function<F: FilterFunction>(&self, filter_function: F) -> Result<(), NulError> {
        // TODO: this is almost the same code as plugins::ffi::call_register_function().
        let name_cstring = CString::new(filter_function.name())?;
        let args_cstring = CString::new(filter_function.args())?;

        let data = Box::new(plugins::ffi::FilterFunctionData::<F> {
            filter_function,
            name: name_cstring,
        });

        unsafe {
            API::get_cached().register_function(
                data.name.as_ptr(),
                args_cstring.as_ptr(),
                plugins::ffi::create::<F>,
                Box::into_raw(data) as _,
                self.handle.as_ptr(),
            );
        }

        Ok(())
    }
}