vapoursynth/vsscript/
environment.rs

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