Skip to main content

vapoursynth4_rs/
sciprt.rs

1use std::{
2    ffi::{CStr, c_int},
3    ptr::{NonNull, null_mut},
4};
5
6use thiserror::Error;
7use vapoursynth4_sys::VAPOURSYNTH_API_VERSION;
8
9use crate::{
10    api::{Api, VssApi},
11    core::{Core, CoreRef},
12    node::{AudioNode, VideoNode},
13};
14
15use super::ffi;
16
17#[derive(Debug, Clone, PartialEq, Eq, Hash)]
18pub struct Script {
19    handle: NonNull<ffi::VSScript>,
20    vssapi: VssApi,
21    api: Api,
22}
23
24#[derive(Debug, Clone, PartialEq, Eq, Hash)]
25pub enum OutputNode {
26    Audio(AudioNode),
27    Video(VideoNode),
28}
29
30impl OutputNode {
31    #[must_use]
32    pub fn as_audio(&self) -> Option<&AudioNode> {
33        if let OutputNode::Audio(node) = self {
34            Some(node)
35        } else {
36            None
37        }
38    }
39
40    #[must_use]
41    pub fn as_video(&self) -> Option<&VideoNode> {
42        if let OutputNode::Video(node) = self {
43            Some(node)
44        } else {
45            None
46        }
47    }
48}
49
50impl Script {
51    /// Creates a new script instance.
52    ///
53    /// # Panics
54    ///
55    /// Panics if the script creation fails.
56    pub fn new(core: Option<&Core>, vssapi: VssApi) -> Self {
57        unsafe {
58            let handle = NonNull::new((vssapi.createScript)(core.map_or(null_mut(), Core::as_ptr)))
59                .expect("Failed to create script");
60            let api_ptr = (vssapi.getVSAPI)(VAPOURSYNTH_API_VERSION);
61            assert!(!api_ptr.is_null());
62            let api = Api::from_ptr(api_ptr);
63            Self {
64                handle,
65                vssapi,
66                api,
67            }
68        }
69    }
70
71    #[must_use]
72    pub fn get_api(&self) -> Api {
73        self.api
74    }
75
76    /// Returns a reference to the core associated with this script.
77    ///
78    /// # Errors
79    ///
80    /// Returns an error message if the core could not be retrieved.
81    pub fn core(&self) -> Result<CoreRef<'_>, ScriptError> {
82        unsafe {
83            let core = (self.vssapi.getCore)(self.handle.as_ptr());
84            self.get_ptr_error(core)
85                .map(|core| CoreRef::from_ptr(core, self.api))
86        }
87    }
88
89    /// Evaluates a script buffer with the given filename.
90    ///
91    /// # Errors
92    ///
93    /// Returns an error message if the script evaluation fails.
94    pub fn evaluate(&self, buffer: &CStr, filename: &CStr) -> Result<(), ScriptError> {
95        unsafe {
96            let result = (self.vssapi.evaluateBuffer)(
97                self.handle.as_ptr(),
98                buffer.as_ptr(),
99                filename.as_ptr(),
100            );
101            self.get_error(result)
102        }
103    }
104
105    /// Evaluates a script from a file.
106    ///
107    /// # Errors
108    ///
109    /// Returns an error message if the script evaluation fails.
110    pub fn evaluate_file(&self, filename: &CStr) -> Result<(), ScriptError> {
111        unsafe {
112            let result = (self.vssapi.evaluateFile)(self.handle.as_ptr(), filename.as_ptr());
113            self.get_error(result)
114        }
115    }
116
117    /// Gets the output node at the specified index.
118    ///
119    /// # Errors
120    ///
121    /// Returns a `ScriptError` if the output node could not be retrieved.
122    pub fn get_output(&self, index: c_int) -> Result<OutputNode, ScriptError> {
123        unsafe {
124            let ptr =
125                self.get_ptr_error((self.vssapi.getOutputNode)(self.handle.as_ptr(), index))?;
126            match (self.api.getNodeType)(ptr) {
127                ffi::VSMediaType::Audio => {
128                    Ok(OutputNode::Audio(AudioNode::from_ptr(ptr, self.api)))
129                }
130                ffi::VSMediaType::Video => {
131                    Ok(OutputNode::Video(VideoNode::from_ptr(ptr, self.api)))
132                }
133            }
134        }
135    }
136}
137
138// MARK: Helper
139impl Script {
140    fn get_error(&self, ret: c_int) -> Result<(), ScriptError> {
141        if ret == 0 {
142            Ok(())
143        } else {
144            Err(unsafe { ScriptError::from_vss(self) })
145        }
146    }
147
148    fn get_ptr_error<T>(&self, ptr: *mut T) -> Result<*mut T, ScriptError> {
149        if ptr.is_null() {
150            Err(unsafe { ScriptError::from_vss(self) })
151        } else {
152            Ok(ptr)
153        }
154    }
155}
156
157impl Drop for Script {
158    fn drop(&mut self) {
159        unsafe { (self.vssapi.freeScript)(self.handle.as_ptr()) };
160    }
161}
162
163#[cfg(feature = "link-vsscript")]
164impl Default for Script {
165    fn default() -> Self {
166        Self::new(None, VssApi::default())
167    }
168}
169
170// MARK: ScriptError
171
172#[derive(Debug, Clone, PartialEq, Eq, Hash, Error)]
173#[error("VSScript error: {0}")]
174pub struct ScriptError(String);
175
176impl ScriptError {
177    unsafe fn from_vss(vss: &Script) -> Self {
178        unsafe {
179            Self(
180                CStr::from_ptr((vss.vssapi.getError)(vss.handle.as_ptr()))
181                    .to_string_lossy()
182                    .into_owned(),
183            )
184        }
185    }
186}