vapoursynth/
format.rs

1//! VapourSynth frame formats.
2
3use std::ffi::{CStr, c_char};
4use std::fmt::{self, Display};
5use std::ops::Deref;
6use std::ptr;
7use vapoursynth_sys as ffi;
8
9/// Contains information about a video format.
10#[derive(Debug, Clone, Copy)]
11pub struct Format<'core> {
12    handle: &'core ffi::VSVideoFormat,
13}
14
15/// Preset VapourSynth formats.
16///
17/// The presets suffixed with H and S have floating point sample type. The H and S suffixes stand
18/// for half precision and single precision, respectively.
19///
20/// Format IDs in VapourSynth v4 are computed using the formula:
21/// `(colorFamily << 28) | (sampleType << 24) | (bitsPerSample << 16) | (subSamplingW << 8) | (subSamplingH << 0)`
22#[repr(i32)]
23#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
24pub enum PresetFormat {
25    None = 0,
26
27    Gray8 = make_video_id(ColorFamily::Gray, SampleType::Integer, 8, 0, 0),
28    Gray9 = make_video_id(ColorFamily::Gray, SampleType::Integer, 9, 0, 0),
29    Gray10 = make_video_id(ColorFamily::Gray, SampleType::Integer, 10, 0, 0),
30    Gray12 = make_video_id(ColorFamily::Gray, SampleType::Integer, 12, 0, 0),
31    Gray14 = make_video_id(ColorFamily::Gray, SampleType::Integer, 14, 0, 0),
32    Gray16 = make_video_id(ColorFamily::Gray, SampleType::Integer, 16, 0, 0),
33    Gray32 = make_video_id(ColorFamily::Gray, SampleType::Integer, 32, 0, 0),
34
35    GrayH = make_video_id(ColorFamily::Gray, SampleType::Float, 16, 0, 0),
36    GrayS = make_video_id(ColorFamily::Gray, SampleType::Float, 32, 0, 0),
37
38    YUV410P8 = make_video_id(ColorFamily::YUV, SampleType::Integer, 8, 2, 2),
39    YUV411P8 = make_video_id(ColorFamily::YUV, SampleType::Integer, 8, 2, 0),
40    YUV440P8 = make_video_id(ColorFamily::YUV, SampleType::Integer, 8, 0, 1),
41
42    YUV420P8 = make_video_id(ColorFamily::YUV, SampleType::Integer, 8, 1, 1),
43    YUV422P8 = make_video_id(ColorFamily::YUV, SampleType::Integer, 8, 1, 0),
44    YUV444P8 = make_video_id(ColorFamily::YUV, SampleType::Integer, 8, 0, 0),
45
46    YUV420P9 = make_video_id(ColorFamily::YUV, SampleType::Integer, 9, 1, 1),
47    YUV422P9 = make_video_id(ColorFamily::YUV, SampleType::Integer, 9, 1, 0),
48    YUV444P9 = make_video_id(ColorFamily::YUV, SampleType::Integer, 9, 0, 0),
49
50    YUV420P10 = make_video_id(ColorFamily::YUV, SampleType::Integer, 10, 1, 1),
51    YUV422P10 = make_video_id(ColorFamily::YUV, SampleType::Integer, 10, 1, 0),
52    YUV444P10 = make_video_id(ColorFamily::YUV, SampleType::Integer, 10, 0, 0),
53
54    YUV420P12 = make_video_id(ColorFamily::YUV, SampleType::Integer, 12, 1, 1),
55    YUV422P12 = make_video_id(ColorFamily::YUV, SampleType::Integer, 12, 1, 0),
56    YUV444P12 = make_video_id(ColorFamily::YUV, SampleType::Integer, 12, 0, 0),
57
58    YUV420P14 = make_video_id(ColorFamily::YUV, SampleType::Integer, 14, 1, 1),
59    YUV422P14 = make_video_id(ColorFamily::YUV, SampleType::Integer, 14, 1, 0),
60    YUV444P14 = make_video_id(ColorFamily::YUV, SampleType::Integer, 14, 0, 0),
61
62    YUV420P16 = make_video_id(ColorFamily::YUV, SampleType::Integer, 16, 1, 1),
63    YUV422P16 = make_video_id(ColorFamily::YUV, SampleType::Integer, 16, 1, 0),
64    YUV444P16 = make_video_id(ColorFamily::YUV, SampleType::Integer, 16, 0, 0),
65
66    YUV420PH = make_video_id(ColorFamily::YUV, SampleType::Float, 16, 1, 1),
67    YUV420PS = make_video_id(ColorFamily::YUV, SampleType::Float, 32, 1, 1),
68    YUV422PH = make_video_id(ColorFamily::YUV, SampleType::Float, 16, 1, 0),
69    YUV422PS = make_video_id(ColorFamily::YUV, SampleType::Float, 32, 1, 0),
70    YUV444PH = make_video_id(ColorFamily::YUV, SampleType::Float, 16, 0, 0),
71    YUV444PS = make_video_id(ColorFamily::YUV, SampleType::Float, 32, 0, 0),
72
73    RGB24 = make_video_id(ColorFamily::RGB, SampleType::Integer, 8, 0, 0),
74    RGB27 = make_video_id(ColorFamily::RGB, SampleType::Integer, 9, 0, 0),
75    RGB30 = make_video_id(ColorFamily::RGB, SampleType::Integer, 10, 0, 0),
76    RGB36 = make_video_id(ColorFamily::RGB, SampleType::Integer, 12, 0, 0),
77    RGB42 = make_video_id(ColorFamily::RGB, SampleType::Integer, 14, 0, 0),
78    RGB48 = make_video_id(ColorFamily::RGB, SampleType::Integer, 16, 0, 0),
79
80    RGBH = make_video_id(ColorFamily::RGB, SampleType::Float, 16, 0, 0),
81    RGBS = make_video_id(ColorFamily::RGB, SampleType::Float, 32, 0, 0),
82}
83
84/// Format color families.
85#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
86pub enum ColorFamily {
87    Undefined = 0,
88    Gray = 1,
89    RGB = 2,
90    YUV = 3,
91}
92
93/// Format sample types.
94#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
95pub enum SampleType {
96    Integer = 0,
97    Float = 1,
98}
99
100/// Computes a VapourSynth video format ID from its components.
101///
102/// This is equivalent to the C macro:
103/// `VS_MAKE_VIDEO_ID(colorFamily, sampleType, bitsPerSample, subSamplingW, subSamplingH)`
104const fn make_video_id(
105    color_family: ColorFamily,
106    sample_type: SampleType,
107    bits_per_sample: i32,
108    sub_sampling_w: i32,
109    sub_sampling_h: i32,
110) -> i32 {
111    ((color_family as i32) << 28)
112        | ((sample_type as i32) << 24)
113        | (bits_per_sample << 16)
114        | (sub_sampling_w << 8)
115        | sub_sampling_h
116}
117
118/// A unique format identifier.
119#[derive(Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash)]
120pub struct FormatID(pub(crate) i32);
121
122impl<'core> PartialEq for Format<'core> {
123    #[inline]
124    fn eq(&self, other: &Format<'core>) -> bool {
125        self.id() == other.id()
126    }
127}
128
129impl<'core> Eq for Format<'core> {}
130
131#[doc(hidden)]
132impl<'core> Deref for Format<'core> {
133    type Target = ffi::VSVideoFormat;
134
135    // Technically this should return `&'core`.
136    #[inline]
137    fn deref(&self) -> &Self::Target {
138        self.handle
139    }
140}
141
142impl<'core> Format<'core> {
143    /// Wraps a raw pointer in a `Format`.
144    ///
145    /// # Safety
146    /// The caller must ensure `ptr` and the lifetime is valid.
147    #[inline]
148    pub(crate) unsafe fn from_ptr(ptr: *const ffi::VSVideoFormat) -> Self {
149        Self { handle: &*ptr }
150    }
151
152    /// Gets the unique identifier of this format.
153    ///
154    /// In VapourSynth v4, format IDs are computed from format properties.
155    #[inline]
156    pub fn id(self) -> FormatID {
157        use crate::api::API;
158
159        // In v4, we compute the format ID from the properties
160        unsafe {
161            let api = API::get_cached();
162            let id = api.query_video_format_id(
163                self.handle.colorFamily,
164                self.handle.sampleType,
165                self.handle.bitsPerSample,
166                self.handle.subSamplingW,
167                self.handle.subSamplingH,
168                ptr::null_mut(), // core parameter not needed for ID query
169            );
170            FormatID(id as i32)
171        }
172    }
173
174    /// Gets the printable name of this format.
175    ///
176    /// In VapourSynth v4, format names are generated on-demand.
177    #[inline]
178    pub fn name(self) -> &'core str {
179        use crate::api::API;
180
181        // V4 requires a buffer to write the name into
182        // Format names are typically short (e.g., "YUV420P8")
183        const NAME_BUF_SIZE: usize = 64;
184        let mut buf = [0 as c_char; NAME_BUF_SIZE];
185
186        unsafe {
187            let api = API::get_cached();
188            api.get_video_format_name(self.handle as *const _, buf.as_mut_ptr());
189
190            // Convert to Rust string
191            // Note: This creates a temporary string. In v3, this returned a reference
192            // to a static string. In v4, we need to handle this differently.
193            // For now, we leak the string to maintain the 'core lifetime.
194            let cstr = CStr::from_ptr(buf.as_ptr());
195            let string = cstr.to_str().unwrap().to_owned();
196            Box::leak(string.into_boxed_str())
197        }
198    }
199
200    /// Gets the number of planes of this format.
201    #[inline]
202    pub fn plane_count(self) -> usize {
203        let plane_count = self.handle.numPlanes;
204        debug_assert!(plane_count >= 0);
205        plane_count as usize
206    }
207
208    /// Gets the color family of this format.
209    #[inline]
210    pub fn color_family(self) -> ColorFamily {
211        match self.handle.colorFamily {
212            x if x == ffi::VSColorFamily_cfGray as i32 => ColorFamily::Gray,
213            x if x == ffi::VSColorFamily_cfRGB as i32 => ColorFamily::RGB,
214            x if x == ffi::VSColorFamily_cfYUV as i32 => ColorFamily::YUV,
215            _ => unreachable!(),
216        }
217    }
218
219    /// Gets the sample type of this format.
220    #[inline]
221    pub fn sample_type(self) -> SampleType {
222        match self.handle.sampleType {
223            x if x == ffi::VSSampleType_stInteger as i32 => SampleType::Integer,
224            x if x == ffi::VSSampleType_stFloat as i32 => SampleType::Float,
225            _ => unreachable!(),
226        }
227    }
228
229    /// Gets the number of significant bits per sample.
230    #[inline]
231    pub fn bits_per_sample(self) -> u8 {
232        let rv = self.handle.bitsPerSample;
233        debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX));
234        rv as u8
235    }
236
237    /// Gets the number of bytes needed for a sample. This is always a power of 2 and the smallest
238    /// possible that can fit the number of bits used per sample.
239    #[inline]
240    pub fn bytes_per_sample(self) -> u8 {
241        let rv = self.handle.bytesPerSample;
242        debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX));
243        rv as u8
244    }
245
246    /// log2 subsampling factor, applied to second and third plane.
247    #[inline]
248    pub fn sub_sampling_w(self) -> u8 {
249        let rv = self.handle.subSamplingW;
250        debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX));
251        rv as u8
252    }
253
254    /// log2 subsampling factor, applied to second and third plane.
255    #[inline]
256    pub fn sub_sampling_h(self) -> u8 {
257        let rv = self.handle.subSamplingH;
258        debug_assert!(rv >= 0 && rv <= i32::from(u8::MAX));
259        rv as u8
260    }
261}
262
263impl From<PresetFormat> for FormatID {
264    fn from(x: PresetFormat) -> Self {
265        FormatID(x as i32)
266    }
267}
268
269#[doc(hidden)]
270impl From<ColorFamily> for ffi::VSColorFamily {
271    #[inline]
272    fn from(x: ColorFamily) -> Self {
273        match x {
274            ColorFamily::Gray => ffi::VSColorFamily_cfGray,
275            ColorFamily::RGB => ffi::VSColorFamily_cfRGB,
276            ColorFamily::YUV => ffi::VSColorFamily_cfYUV,
277            ColorFamily::Undefined => ffi::VSColorFamily_cfUndefined,
278        }
279    }
280}
281
282#[doc(hidden)]
283impl From<SampleType> for ffi::VSSampleType {
284    #[inline]
285    fn from(x: SampleType) -> Self {
286        match x {
287            SampleType::Integer => ffi::VSSampleType_stInteger,
288            SampleType::Float => ffi::VSSampleType_stFloat,
289        }
290    }
291}
292
293impl Display for ColorFamily {
294    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
295        write!(
296            f,
297            "{}",
298            match *self {
299                ColorFamily::Gray => "Gray",
300                ColorFamily::RGB => "RGB",
301                ColorFamily::YUV => "YUV",
302                ColorFamily::Undefined => "Undefined",
303            }
304        )
305    }
306}
307
308impl Display for SampleType {
309    fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
310        write!(
311            f,
312            "{}",
313            match *self {
314                SampleType::Integer => "Integer",
315                SampleType::Float => "Float",
316            }
317        )
318    }
319}
320
321impl From<i32> for FormatID {
322    fn from(x: i32) -> Self {
323        FormatID(x)
324    }
325}
326
327impl From<FormatID> for i32 {
328    fn from(x: FormatID) -> Self {
329        x.0
330    }
331}