vapoursynth/vsscript/
environment.rs

1use std::ffi::{CStr, CString};
2use std::ops::{Deref, DerefMut};
3use std::path::Path;
4use std::ptr;
5use std::ptr::NonNull;
6use vapoursynth_sys as ffi;
7
8use crate::api::API;
9use crate::core::CoreRef;
10use crate::map::Map;
11use crate::node::Node;
12use crate::vsscript::errors::Result;
13use crate::vsscript::*;
14
15use crate::vsscript::VSScriptError;
16
17/// VSScript file evaluation flags.
18#[derive(Debug, Clone, Copy, Eq, PartialEq)]
19pub enum EvalFlags {
20    Nothing,
21    /// The working directory will be changed to the script's directory for the evaluation.
22    SetWorkingDir,
23}
24
25/// Contains two possible variants of arguments to `Environment::evaluate_script()`.
26#[derive(Clone, Copy)]
27enum EvaluateScriptArgs<'a> {
28    /// Evaluate a script contained in the string.
29    Script(&'a str),
30    /// Evaluate a script contained in the file.
31    File(&'a Path, EvalFlags),
32}
33
34/// A wrapper for the VSScript environment.
35#[derive(Debug)]
36pub struct Environment {
37    handle: NonNull<ffi::VSScript>,
38}
39
40unsafe impl Send for Environment {}
41unsafe impl Sync for Environment {}
42
43impl Drop for Environment {
44    #[inline]
45    fn drop(&mut self) {
46        let api = VSScriptAPI::get().expect("VSScript API not available");
47        unsafe {
48            (api.handle().freeScript.unwrap())(self.handle.as_ptr());
49        }
50    }
51}
52
53impl Environment {
54    /// Retrieves the VSScript error message.
55    ///
56    /// # Safety
57    /// This function must only be called if an error is present.
58    #[inline]
59    unsafe fn error(&self) -> CString {
60        let api = VSScriptAPI::get().expect("VSScript API not available");
61        let message = (api.handle().getError.unwrap())(self.handle.as_ptr());
62        CStr::from_ptr(message).to_owned()
63    }
64
65    /// Creates an empty script environment.
66    ///
67    /// Useful if it is necessary to set some variable in the script environment before evaluating
68    /// any scripts.
69    pub fn new() -> Result<Self> {
70        let api = VSScriptAPI::get().expect("VSScript API not available");
71        let handle = unsafe { (api.handle().createScript.unwrap())(ptr::null_mut()) };
72
73        if handle.is_null() {
74            Err(Error::ScriptCreationFailed)
75        } else {
76            Ok(Self {
77                handle: unsafe { NonNull::new_unchecked(handle) },
78            })
79        }
80    }
81
82    /// Evaluates a script using the VSScript API.
83    ///
84    /// `self` is taken by a mutable reference mainly to ensure the atomicity of a call to
85    /// `evaluateBuffer/evaluateFile` (a function that could produce an error) and the following call
86    /// to `getError()`. If atomicity is not enforced, another thread could perform some
87    /// operation between these two and clear or change the error message.
88    fn evaluate_script(&mut self, args: EvaluateScriptArgs) -> Result<()> {
89        let api = VSScriptAPI::get().expect("VSScript API not available");
90
91        let rv = match args {
92            EvaluateScriptArgs::Script(script) => {
93                let script = CString::new(script)?;
94                let filename = CString::new("<string>").unwrap();
95                unsafe {
96                    (api.handle().evaluateBuffer.unwrap())(
97                        self.handle.as_ptr(),
98                        script.as_ptr(),
99                        filename.as_ptr(),
100                    )
101                }
102            }
103            EvaluateScriptArgs::File(path, flags) => {
104                // Set working directory flag if requested
105                if flags == EvalFlags::SetWorkingDir {
106                    unsafe {
107                        (api.handle().evalSetWorkingDir.unwrap())(self.handle.as_ptr(), 1);
108                    }
109                }
110
111                // vsscript throws an error if the path is not valid UTF-8 anyway.
112                let path_str = path.to_str().ok_or(Error::PathInvalidUnicode)?;
113                let path_cstr = CString::new(path_str)?;
114
115                let rv = unsafe {
116                    (api.handle().evaluateFile.unwrap())(self.handle.as_ptr(), path_cstr.as_ptr())
117                };
118
119                // Reset working directory flag if it was set
120                if flags == EvalFlags::SetWorkingDir {
121                    unsafe {
122                        (api.handle().evalSetWorkingDir.unwrap())(self.handle.as_ptr(), 0);
123                    }
124                }
125
126                rv
127            }
128        };
129
130        if rv != 0 {
131            Err(VSScriptError::new(unsafe { self.error() }).into())
132        } else {
133            Ok(())
134        }
135    }
136
137    /// Creates a script environment and evaluates a script contained in a string.
138    #[inline]
139    pub fn from_script(script: &str) -> Result<Self> {
140        let mut environment = Self::new()?;
141        environment.evaluate_script(EvaluateScriptArgs::Script(script))?;
142        Ok(environment)
143    }
144
145    /// Creates a script environment and evaluates a script contained in a file.
146    #[inline]
147    pub fn from_file<P: AsRef<Path>>(path: P, flags: EvalFlags) -> Result<Self> {
148        let mut environment = Self::new()?;
149        environment.evaluate_script(EvaluateScriptArgs::File(path.as_ref(), flags))?;
150        Ok(environment)
151    }
152
153    /// Evaluates a script contained in a string.
154    #[inline]
155    pub fn eval_script(&mut self, script: &str) -> Result<()> {
156        self.evaluate_script(EvaluateScriptArgs::Script(script))
157    }
158
159    /// Evaluates a script contained in a file.
160    #[inline]
161    pub fn eval_file<P: AsRef<Path>>(&mut self, path: P, flags: EvalFlags) -> Result<()> {
162        self.evaluate_script(EvaluateScriptArgs::File(path.as_ref(), flags))
163    }
164
165    /// Clears the script environment.
166    ///
167    /// Note: In VapourSynth v4, this is a no-op. To clear the environment,
168    /// drop the Environment and create a new one.
169    #[inline]
170    pub fn clear(&self) {
171        // The clearEnvironment function was removed in VapourSynth v4.
172        // Users should drop and recreate the Environment instead.
173    }
174
175    /// Retrieves a node from the script environment. A node in the script must have been marked
176    /// for output with the requested index. The second node, if any, contains the alpha clip.
177    #[inline]
178    pub fn get_output(&self, index: i32) -> Result<(Node<'_>, Option<Node<'_>>)> {
179        // Node needs the API.
180        API::get().ok_or(Error::NoAPI)?;
181
182        let vsscript_api = VSScriptAPI::get().expect("VSScript API not available");
183        let node_handle =
184            unsafe { (vsscript_api.handle().getOutputNode.unwrap())(self.handle.as_ptr(), index) };
185
186        if node_handle.is_null() {
187            return Err(Error::NoOutput);
188        }
189
190        let node = unsafe { Node::from_ptr(node_handle) };
191
192        // Get the alpha node separately
193        let alpha_handle = unsafe {
194            (vsscript_api.handle().getOutputAlphaNode.unwrap())(self.handle.as_ptr(), index)
195        };
196        let alpha_node = if alpha_handle.is_null() {
197            None
198        } else {
199            Some(unsafe { Node::from_ptr(alpha_handle) })
200        };
201
202        Ok((node, alpha_node))
203    }
204
205    /// Cancels a node set for output. The node will no longer be available to `get_output()`.
206    ///
207    /// Note: In VapourSynth v4, this function has been removed. This is now a no-op that always
208    /// returns Ok. To clear outputs, drop the Environment and create a new one.
209    #[inline]
210    pub fn clear_output(&self, _index: i32) -> Result<()> {
211        // The clearOutput function was removed in VapourSynth v4.
212        Ok(())
213    }
214
215    /// Retrieves the VapourSynth core that was created in the script environment. If a VapourSynth
216    /// core has not been created yet, it will be created now, with the default options.
217    pub fn get_core(&self) -> Result<CoreRef<'_>> {
218        // CoreRef needs the API.
219        API::get().ok_or(Error::NoAPI)?;
220
221        let vsscript_api = VSScriptAPI::get().expect("VSScript API not available");
222        let ptr = unsafe { (vsscript_api.handle().getCore.unwrap())(self.handle.as_ptr()) };
223        if ptr.is_null() {
224            Err(Error::NoCore)
225        } else {
226            Ok(unsafe { CoreRef::from_ptr(ptr) })
227        }
228    }
229
230    /// Retrieves a variable from the script environment.
231    pub fn get_variable(&self, name: &str, map: &mut Map) -> Result<()> {
232        let vsscript_api = VSScriptAPI::get().expect("VSScript API not available");
233        let name = CString::new(name)?;
234        let rv = unsafe {
235            (vsscript_api.handle().getVariable.unwrap())(
236                self.handle.as_ptr(),
237                name.as_ptr(),
238                map.deref_mut(),
239            )
240        };
241        if rv != 0 {
242            Err(Error::NoSuchVariable)
243        } else {
244            Ok(())
245        }
246    }
247
248    /// Sets variables in the script environment.
249    pub fn set_variables(&self, variables: &Map) -> Result<()> {
250        let vsscript_api = VSScriptAPI::get().expect("VSScript API not available");
251        let rv = unsafe {
252            (vsscript_api.handle().setVariables.unwrap())(self.handle.as_ptr(), variables.deref())
253        };
254        if rv != 0 {
255            Err(Error::NoSuchVariable)
256        } else {
257            Ok(())
258        }
259    }
260
261    /// Deletes a variable from the script environment.
262    ///
263    /// Note: In VapourSynth v4, the clearVariable function has been removed.
264    /// This is now a no-op that always returns Ok.
265    #[inline]
266    pub fn clear_variable(&self, _name: &str) -> Result<()> {
267        // The clearVariable function was removed in VapourSynth v4.
268        Ok(())
269    }
270}