vapoursynth/node/
mod.rs

1//! VapourSynth nodes.
2
3use std::borrow::Cow;
4use std::ffi::{CStr, CString};
5use std::marker::PhantomData;
6use std::os::raw::{c_char, c_void};
7use std::process;
8use std::ptr::NonNull;
9use std::{mem, panic};
10use vapoursynth_sys as ffi;
11
12use crate::api::API;
13use crate::frame::FrameRef;
14use crate::plugins::FrameContext;
15use crate::video_info::VideoInfo;
16
17mod errors;
18pub use self::errors::GetFrameError;
19
20/// A reference to a node in the constructed filter graph.
21#[derive(Debug)]
22pub struct Node<'core> {
23    handle: NonNull<ffi::VSNode>,
24    _owner: PhantomData<&'core ()>,
25}
26
27unsafe impl<'core> Send for Node<'core> {}
28unsafe impl<'core> Sync for Node<'core> {}
29
30impl<'core> Drop for Node<'core> {
31    #[inline]
32    fn drop(&mut self) {
33        unsafe {
34            API::get_cached().free_node(self.handle.as_ptr());
35        }
36    }
37}
38
39impl<'core> Clone for Node<'core> {
40    #[inline]
41    fn clone(&self) -> Self {
42        let handle = unsafe { API::get_cached().clone_node(self.handle.as_ptr()) };
43        Self {
44            handle: unsafe { NonNull::new_unchecked(handle) },
45            _owner: PhantomData,
46        }
47    }
48}
49
50impl<'core> Node<'core> {
51    /// Wraps `handle` in a `Node`.
52    ///
53    /// # Safety
54    /// The caller must ensure `handle` and the lifetime is valid and API is cached.
55    #[inline]
56    pub(crate) unsafe fn from_ptr(handle: *mut ffi::VSNode) -> Self {
57        Self {
58            handle: NonNull::new_unchecked(handle),
59            _owner: PhantomData,
60        }
61    }
62
63    /// Returns the underlying pointer.
64    #[inline]
65    pub(crate) fn ptr(&self) -> *mut ffi::VSNode {
66        self.handle.as_ptr()
67    }
68
69    /// Returns the video info associated with this `Node`.
70    // Since we don't store the pointer to the actual `ffi::VSVideoInfo` and the lifetime is that
71    // of the `ffi::VSFormat`, this returns `VideoInfo<'core>` rather than `VideoInfo<'a>`.
72    #[inline]
73    pub fn info(&self) -> VideoInfo<'core> {
74        unsafe {
75            let ptr = API::get_cached().get_video_info(self.handle.as_ptr());
76            VideoInfo::from_ptr(ptr)
77        }
78    }
79
80    /// Generates a frame directly.
81    ///
82    /// The `'error` lifetime is unbounded because this function always returns owned data.
83    ///
84    /// # Panics
85    /// Panics is `n` is greater than `i32::MAX`.
86    pub fn get_frame<'error>(&self, n: usize) -> Result<FrameRef<'core>, GetFrameError<'error>> {
87        assert!(n <= i32::MAX as usize);
88
89        let vi = &self.info();
90
91        if n >= vi.num_frames {
92            let err_cstring = CString::new("Requested frame number beyond the last one").unwrap();
93            return Err(GetFrameError::new(Cow::Owned(err_cstring)));
94        }
95
96        // Kinda arbitrary. Same value as used in vsvfw.
97        const ERROR_BUF_CAPACITY: usize = 32 * 1024;
98
99        let mut err_buf = vec![0; ERROR_BUF_CAPACITY].into_boxed_slice();
100
101        let handle =
102            unsafe { API::get_cached().get_frame(n as i32, self.handle.as_ptr(), &mut err_buf) };
103
104        if handle.is_null() {
105            // TODO: remove this extra allocation by reusing `Box<[c_char]>`.
106            let error = unsafe { CStr::from_ptr(err_buf.as_ptr()) }.to_owned();
107            Err(GetFrameError::new(Cow::Owned(error)))
108        } else {
109            Ok(unsafe { FrameRef::from_ptr(handle) })
110        }
111    }
112
113    /// Requests the generation of a frame. When the frame is ready, a user-provided function is
114    /// called.
115    ///
116    /// If multiple frames were requested, they can be returned in any order.
117    ///
118    /// The callback arguments are:
119    ///
120    /// - the generated frame or an error message if the generation failed,
121    /// - the frame number (equal to `n`),
122    /// - the node that generated the frame (the same as `self`).
123    ///
124    /// If the callback panics, the process is aborted.
125    ///
126    /// # Panics
127    /// Panics is `n` is greater than `i32::MAX`.
128    pub fn get_frame_async<F>(&self, n: usize, callback: F)
129    where
130        F: FnOnce(Result<FrameRef<'core>, GetFrameError>, usize, Node<'core>) + Send + 'core,
131    {
132        struct CallbackData<'core> {
133            callback: Box<dyn CallbackFn<'core> + 'core>,
134        }
135
136        // A little bit of magic for Box<FnOnce>.
137        trait CallbackFn<'core> {
138            fn call(
139                self: Box<Self>,
140                frame: Result<FrameRef<'core>, GetFrameError>,
141                n: usize,
142                node: Node<'core>,
143            );
144        }
145
146        impl<'core, F> CallbackFn<'core> for F
147        where
148            F: FnOnce(Result<FrameRef<'core>, GetFrameError>, usize, Node<'core>),
149        {
150            #[allow(clippy::boxed_local)]
151            fn call(
152                self: Box<Self>,
153                frame: Result<FrameRef<'core>, GetFrameError>,
154                n: usize,
155                node: Node<'core>,
156            ) {
157                (self)(frame, n, node)
158            }
159        }
160
161        unsafe extern "C" fn c_callback(
162            user_data: *mut c_void,
163            frame: *const ffi::VSFrame,
164            n: i32,
165            node: *mut ffi::VSNode,
166            error_msg: *const c_char,
167        ) {
168            // The actual lifetime isn't 'static, it's 'core, but we don't really have a way of
169            // retrieving it.
170            let user_data = Box::from_raw(user_data as *mut CallbackData<'static>);
171
172            let closure = panic::AssertUnwindSafe(move || {
173                let frame = if frame.is_null() {
174                    debug_assert!(!error_msg.is_null());
175                    let error_msg = Cow::Borrowed(CStr::from_ptr(error_msg));
176                    Err(GetFrameError::new(error_msg))
177                } else {
178                    debug_assert!(error_msg.is_null());
179                    Ok(FrameRef::from_ptr(frame))
180                };
181
182                let node = Node::from_ptr(node);
183
184                debug_assert!(n >= 0);
185                let n = n as usize;
186
187                user_data.callback.call(frame, n, node);
188            });
189
190            if panic::catch_unwind(closure).is_err() {
191                process::abort();
192            }
193        }
194
195        assert!(n <= i32::MAX as usize);
196        let n = n as i32;
197
198        let user_data = Box::new(CallbackData {
199            callback: Box::new(callback),
200        });
201
202        let new_node = self.clone();
203
204        unsafe {
205            API::get_cached().get_frame_async(
206                n,
207                new_node.handle.as_ptr(),
208                Some(c_callback),
209                Box::into_raw(user_data) as *mut c_void,
210            );
211        }
212
213        // It'll be dropped by the callback.
214        mem::forget(new_node);
215    }
216
217    /// Requests a frame from a node and returns immediately.
218    ///
219    /// This is only used in filters' "get frame" functions.
220    ///
221    /// A filter usually calls this function from `get_frame_initial()`. The requested frame can
222    /// then be retrieved using `get_frame_filter()` from within filter's `get_frame()` function.
223    ///
224    /// It is safe to request a frame more than once. An unimportant consequence of requesting a
225    /// frame more than once is that the filter's `get_frame()` function may be called more than
226    /// once for the same frame.
227    ///
228    /// It is best to request frames in ascending order, i.e. `n`, `n+1`, `n+2`, etc.
229    ///
230    /// # Panics
231    /// Panics is `n` is greater than `i32::MAX`.
232    pub fn request_frame_filter(&self, context: FrameContext, n: usize) {
233        assert!(n <= i32::MAX as usize);
234        let n = n as i32;
235
236        unsafe {
237            API::get_cached().request_frame_filter(n, self.ptr(), context.ptr());
238        }
239    }
240
241    /// Retrieves a frame that was previously requested with `request_frame_filter()`.
242    ///
243    /// A filter usually calls this function from `get_frame()`. It is safe to retrieve a frame
244    /// more than once.
245    ///
246    /// # Panics
247    /// Panics is `n` is greater than `i32::MAX`.
248    pub fn get_frame_filter(&self, context: FrameContext, n: usize) -> Option<FrameRef<'core>> {
249        assert!(n <= i32::MAX as usize);
250        let n = n as i32;
251
252        let ptr = unsafe { API::get_cached().get_frame_filter(n, self.ptr(), context.ptr()) };
253        if ptr.is_null() {
254            None
255        } else {
256            Some(unsafe { FrameRef::from_ptr(ptr) })
257        }
258    }
259}