Skip to main content

vapoursynth4_rs/
core.rs

1/*
2 This Source Code Form is subject to the terms of the Mozilla Public
3 License, v. 2.0. If a copy of the MPL was not distributed with this
4 file, You can obtain one at http://mozilla.org/MPL/2.0/.
5*/
6
7use std::{
8    ffi::CStr,
9    marker::PhantomData,
10    mem::MaybeUninit,
11    ops::{Deref, DerefMut},
12    ptr::{NonNull, null_mut},
13};
14
15use bon::bon;
16use core_builder::State;
17
18use crate::{
19    AudioInfo, ColorFamily, SampleType, VideoInfo,
20    api::Api,
21    ffi,
22    frame::{
23        AudioFormat, AudioFrame, FormatName, Frame, VideoFormat, VideoFrame, internal::FrameFromPtr,
24    },
25    function::Function,
26    map::{Map, MapRef},
27    node::{Dependencies, Filter, internal::FilterExtern},
28    plugin::{Plugin, Plugins},
29};
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
32pub struct CoreRef<'c> {
33    handle: *const ffi::VSCore,
34    api: Api,
35    marker: PhantomData<&'c ()>,
36}
37
38impl CoreRef<'_> {
39    #[must_use]
40    pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSCore, api: Api) -> Self {
41        Self {
42            handle: ptr.cast_mut(),
43            api,
44            marker: PhantomData,
45        }
46    }
47}
48
49impl AsRef<Core> for CoreRef<'_> {
50    fn as_ref(&self) -> &Core {
51        unsafe { &*std::ptr::from_ref(self).cast() }
52    }
53}
54
55impl Deref for CoreRef<'_> {
56    type Target = Core;
57
58    fn deref(&self) -> &Self::Target {
59        unsafe { &*std::ptr::from_ref(self).cast() }
60    }
61}
62
63impl DerefMut for CoreRef<'_> {
64    fn deref_mut(&mut self) -> &mut Self::Target {
65        unsafe { &mut *std::ptr::from_mut(self).cast() }
66    }
67}
68
69#[derive(Debug, PartialEq, Eq, Hash)]
70pub struct Core {
71    handle: *const ffi::VSCore,
72    api: Api,
73}
74
75impl Core {
76    #[must_use]
77    pub fn as_ptr(&self) -> *mut ffi::VSCore {
78        self.handle.cast_mut()
79    }
80
81    pub fn set_max_cache_size(&mut self, size: i64) {
82        unsafe {
83            (self.api.setMaxCacheSize)(size, self.as_ptr());
84        }
85    }
86
87    pub fn set_thread_count(&mut self, count: i32) {
88        unsafe {
89            (self.api.setThreadCount)(count, self.as_ptr());
90        }
91    }
92
93    #[must_use]
94    pub fn get_info(&self) -> ffi::VSCoreInfo {
95        unsafe {
96            let mut info = MaybeUninit::uninit();
97            (self.api.getCoreInfo)(self.as_ptr(), info.as_mut_ptr());
98            info.assume_init()
99        }
100    }
101
102    /// Same as [`get_info()`](Self::get_info), but also reports the
103    /// [`ffi::VSCoreCreationFlags`] the core was created with.
104    #[cfg(feature = "vs-42")]
105    #[must_use]
106    pub fn get_info2(&self) -> ffi::VSCoreInfo2 {
107        unsafe {
108            let mut info = MaybeUninit::uninit();
109            (self.api.getCoreInfo2)(self.as_ptr(), info.as_mut_ptr());
110            info.assume_init()
111        }
112    }
113
114    /// # Panics
115    ///
116    /// Panic if the `dependencies` has more item than [`i32::MAX`]
117    pub fn create_video_filter<F: Filter>(
118        &mut self,
119        out: MapRef,
120        name: &CStr,
121        info: &VideoInfo,
122        filter: Box<F>,
123        dependencies: &Dependencies,
124    ) {
125        debug_assert!(!out.as_ptr().is_null());
126        unsafe {
127            (self.api.createVideoFilter)(
128                out.as_ptr(),
129                name.as_ptr(),
130                info,
131                F::filter_get_frame,
132                Some(F::filter_free),
133                F::FILTER_MODE,
134                dependencies.as_ptr(),
135                dependencies.len().try_into().unwrap(),
136                Box::into_raw(filter).cast(),
137                self.as_ptr(),
138            );
139        }
140    }
141
142    /// # Panics
143    ///
144    /// Panic if the `dependencies` has more item than [`i32::MAX`]
145    pub fn create_audio_filter<F: Filter>(
146        &mut self,
147        out: &mut MapRef,
148        name: &CStr,
149        info: &AudioInfo,
150        filter: F,
151        dependencies: &Dependencies,
152    ) {
153        let filter = Box::new(filter);
154        unsafe {
155            (self.api.createAudioFilter)(
156                out.as_ptr(),
157                name.as_ptr(),
158                info,
159                F::filter_get_frame,
160                Some(F::filter_free),
161                F::FILTER_MODE,
162                dependencies.as_ptr(),
163                dependencies.len().try_into().unwrap(),
164                Box::into_raw(filter).cast(),
165                self.as_ptr(),
166            );
167        }
168    }
169
170    #[must_use]
171    pub fn new_video_frame(
172        &self,
173        format: &VideoFormat,
174        width: i32,
175        height: i32,
176        prop_src: Option<&VideoFrame>,
177    ) -> VideoFrame {
178        unsafe {
179            let ptr = (self.api.newVideoFrame)(
180                format,
181                width,
182                height,
183                prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
184                self.as_ptr(),
185            );
186            VideoFrame::from_ptr(ptr, self.api)
187        }
188    }
189
190    #[must_use]
191    pub fn new_video_frame2(
192        &self,
193        format: &VideoFormat,
194        width: i32,
195        height: i32,
196        plane_src: &[*const ffi::VSFrame],
197        planes: &[i32],
198        prop_src: Option<&VideoFrame>,
199    ) -> VideoFrame {
200        unsafe {
201            let ptr = (self.api.newVideoFrame2)(
202                format,
203                width,
204                height,
205                plane_src.as_ptr(),
206                planes.as_ptr(),
207                prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
208                self.as_ptr(),
209            );
210            VideoFrame::from_ptr(ptr, self.api)
211        }
212    }
213
214    #[must_use]
215    pub fn new_audio_frame(
216        &self,
217        format: &AudioFormat,
218        num_samples: i32,
219        prop_src: Option<&AudioFrame>,
220    ) -> AudioFrame {
221        unsafe {
222            let ptr = (self.api.newAudioFrame)(
223                format,
224                num_samples,
225                prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
226                self.as_ptr(),
227            );
228            AudioFrame::from_ptr(ptr, self.api)
229        }
230    }
231
232    #[must_use]
233    pub fn new_audio_frame2(
234        &self,
235        format: &AudioFormat,
236        num_samples: i32,
237        channel_src: &[*const ffi::VSFrame],
238        channels: &[i32],
239        prop_src: Option<&AudioFrame>,
240    ) -> AudioFrame {
241        unsafe {
242            let ptr = (self.api.newAudioFrame2)(
243                format,
244                num_samples,
245                channel_src.as_ptr(),
246                channels.as_ptr(),
247                prop_src.map_or(null_mut(), |f| f.as_ptr().cast()),
248                self.as_ptr(),
249            );
250            AudioFrame::from_ptr(ptr, self.api)
251        }
252    }
253
254    #[must_use]
255    pub fn copy_frame<F: Frame>(&self, frame: &F) -> F {
256        unsafe {
257            F::from_ptr(
258                (self.api.copyFrame)(frame.as_ptr(), self.as_ptr()),
259                self.api,
260            )
261        }
262    }
263
264    #[must_use]
265    pub fn query_video_format(
266        &self,
267        color_family: ColorFamily,
268        sample_type: SampleType,
269        bits_per_sample: i32,
270        subsampling_w: i32,
271        subsampling_h: i32,
272    ) -> VideoFormat {
273        unsafe {
274            let mut format = MaybeUninit::uninit();
275            (self.api.queryVideoFormat)(
276                format.as_mut_ptr(),
277                color_family,
278                sample_type,
279                bits_per_sample,
280                subsampling_w,
281                subsampling_h,
282                self.as_ptr(),
283            );
284            format.assume_init()
285        }
286    }
287
288    #[must_use]
289    pub fn get_video_format_name(&self, format: &VideoFormat) -> Option<String> {
290        let mut buffer = FormatName::new();
291        if 0 == unsafe { (self.api.getVideoFormatName)(format, buffer.as_mut_ptr().cast()) } {
292            None
293        } else {
294            Some(buffer.to_string())
295        }
296    }
297
298    #[must_use]
299    pub fn query_audio_format(
300        &self,
301        sample_type: SampleType,
302        bits_per_sample: i32,
303        channel_layout: u64,
304    ) -> AudioFormat {
305        unsafe {
306            let mut format = MaybeUninit::uninit();
307            (self.api.queryAudioFormat)(
308                format.as_mut_ptr(),
309                sample_type,
310                bits_per_sample,
311                channel_layout,
312                self.as_ptr(),
313            );
314            format.assume_init()
315        }
316    }
317
318    #[must_use]
319    pub fn get_audio_format_name(&self, format: &AudioFormat) -> Option<String> {
320        let mut buffer = FormatName::new();
321        if 0 == unsafe { (self.api.getAudioFormatName)(format, buffer.as_mut_ptr().cast()) } {
322            None
323        } else {
324            Some(buffer.to_string())
325        }
326    }
327
328    #[must_use]
329    pub fn query_video_format_id(
330        &self,
331        color_family: ColorFamily,
332        sample_type: SampleType,
333        bits_per_sample: i32,
334        subsampling_w: i32,
335        subsampling_h: i32,
336    ) -> u32 {
337        unsafe {
338            (self.api.queryVideoFormatID)(
339                color_family,
340                sample_type,
341                bits_per_sample,
342                subsampling_w,
343                subsampling_h,
344                self.as_ptr(),
345            )
346        }
347    }
348
349    #[must_use]
350    pub fn get_video_format_by_id(&self, id: u32) -> VideoFormat {
351        unsafe {
352            let mut format = MaybeUninit::uninit();
353            (self.api.getVideoFormatByID)(format.as_mut_ptr(), id, self.as_ptr());
354            format.assume_init()
355        }
356    }
357
358    pub fn create_function<T>(
359        &mut self,
360        func: ffi::VSPublicFunction,
361        data: Box<T>,
362        free: ffi::VSFreeFunctionData,
363    ) -> Function {
364        unsafe {
365            Function::from_ptr(
366                (self.api.createFunction)(func, Box::into_raw(data).cast(), free, self.as_ptr()),
367                self.api,
368            )
369        }
370    }
371
372    #[must_use]
373    pub fn get_plugin_by_id(&self, id: &CStr) -> Option<Plugin> {
374        unsafe {
375            NonNull::new((self.api.getPluginByID)(id.as_ptr(), self.as_ptr()))
376                .map(|p| Plugin::new(p, self.api))
377        }
378    }
379
380    #[must_use]
381    pub fn get_plugin_by_namespace(&self, ns: &CStr) -> Option<Plugin> {
382        unsafe {
383            NonNull::new((self.api.getPluginByNamespace)(ns.as_ptr(), self.as_ptr()))
384                .map(|p| Plugin::new(p, self.api))
385        }
386    }
387
388    #[must_use]
389    pub fn plugins(&self) -> Plugins<'_> {
390        Plugins::new(self)
391    }
392
393    pub fn log(&mut self, level: ffi::VSMessageType, msg: &CStr) {
394        unsafe {
395            (self.api.logMessage)(level, msg.as_ptr(), self.as_ptr());
396        }
397    }
398}
399
400impl Drop for Core {
401    fn drop(&mut self) {
402        unsafe {
403            (self.api.freeCore)(self.handle.cast_mut());
404        }
405    }
406}
407
408// MARK: Helper
409
410impl Core {
411    unsafe fn new_with(flags: i32, api: Api) -> Self {
412        let core = unsafe { (api.createCore)(flags) };
413        Self { handle: core, api }
414    }
415
416    #[must_use]
417    pub fn api(&self) -> Api {
418        self.api
419    }
420
421    #[must_use]
422    pub fn create_map(&self) -> Map {
423        unsafe {
424            let ptr = (self.api.createMap)();
425            Map::from_ptr(ptr, self.api)
426        }
427    }
428}
429
430// MARK: Builder
431
432#[bon]
433impl Core {
434    #[builder]
435    pub fn new(
436        #[builder(field)] flags: i32,
437        max_cache_size: Option<i64>,
438        thread_count: Option<i32>,
439        #[cfg(feature = "link-vs")]
440        #[builder(default)]
441        api: Api,
442        #[cfg(not(feature = "link-vs"))] api: Api,
443    ) -> Self {
444        let mut core = unsafe { Core::new_with(flags, api) };
445        if let Some(size) = max_cache_size {
446            core.set_max_cache_size(size);
447        }
448        if let Some(count) = thread_count {
449            core.set_thread_count(count);
450        }
451
452        core
453    }
454}
455
456impl<S: State> CoreBuilder<S> {
457    pub fn enable_graph_inspection(mut self) -> Self {
458        self.flags |= ffi::VSCoreCreationFlags::EnableGraphInspection as i32;
459        self
460    }
461
462    pub fn disable_auto_loading(mut self) -> Self {
463        self.flags |= ffi::VSCoreCreationFlags::DisableAutoLoading as i32;
464        self
465    }
466
467    pub fn disable_library_unloading(mut self) -> Self {
468        self.flags |= ffi::VSCoreCreationFlags::DisableLibraryUnloading as i32;
469        self
470    }
471
472    /// Log a list of all allocated frames after every completed external frame request.
473    #[cfg(feature = "vs-42")]
474    pub fn enable_frame_ref_debug(mut self) -> Self {
475        self.flags |= ffi::VSCoreCreationFlags::EnableFrameRefDebug as i32;
476        self
477    }
478}
479
480#[cfg(test)]
481#[cfg(feature = "link-vs")]
482mod tests {
483    use super::*;
484
485    #[test]
486    fn builder() {
487        let api = Api::default();
488        let core = Core::builder()
489            .api(api)
490            .enable_graph_inspection()
491            .disable_auto_loading()
492            .disable_library_unloading()
493            .max_cache_size(1024)
494            .thread_count(4)
495            .build();
496        assert_eq!(core.get_info().max_framebuffer_size, 1024);
497        assert_eq!(core.get_info().num_threads, 4);
498    }
499
500    #[cfg(feature = "vs-42")]
501    #[test]
502    fn info2_reports_creation_flags() {
503        let core = Core::builder()
504            .api(Api::default())
505            .enable_graph_inspection()
506            .disable_auto_loading()
507            .build();
508
509        let info = core.get_info2();
510        let expected = ffi::VSCoreCreationFlags::EnableGraphInspection
511            | ffi::VSCoreCreationFlags::DisableAutoLoading;
512        assert_eq!(info.creation_flags, expected);
513        assert_eq!(info.core, core.get_info().core);
514    }
515}