use std::fmt::{Debug, Display};
use num_enum::{IntoPrimitive, TryFromPrimitive};
use crate::{
bindings::{self, NDIlib_FourCC_audio_type_e, NDIlib_FourCC_video_type_e},
buffer_info::BufferInfo,
enums::NDIFieldedFrameMode,
resolution::Resolution,
subsampling::Subsampling,
};
#[cfg_attr(target_os = "windows", repr(i32))]
#[cfg_attr(not(target_os = "windows"), repr(u32))]
#[non_exhaustive]
#[allow(non_camel_case_types)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
pub enum FourCCVideo {
UYVY = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_UYVY,
UYVA = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_UYVA,
P216 = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_P216,
PA16 = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_PA16,
YV12 = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_YV12,
I420 = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_I420,
NV12 = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_NV12,
RGBA = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_RGBA,
RGBX = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_RGBX,
BGRA = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_BGRA,
BGRX = bindings::NDIlib_FourCC_video_type_e_NDIlib_FourCC_video_type_BGRX,
}
impl FourCCVideo {
pub(crate) fn to_ffi(self) -> NDIlib_FourCC_video_type_e {
self.into()
}
pub(crate) fn from_ffi(value: NDIlib_FourCC_video_type_e) -> Option<Self> {
Self::try_from_primitive(value).ok()
}
pub fn buffer_info(
self,
resolution: Resolution,
field_mode: NDIFieldedFrameMode,
) -> Result<BufferInfo, BufferInfoError> {
use FourCCVideo::*;
let pixel_stride;
let mut subsampling = Subsampling::none();
match self {
UYVY => {
pixel_stride = 2;
subsampling = Subsampling::new(4, 2, 2);
}
BGRA | BGRX | RGBA | RGBX => {
pixel_stride = 4;
}
cc => return Err(BufferInfoError::UnsupportedFourCC(cc)),
};
let size = resolution.pixels() * pixel_stride;
Ok(BufferInfo {
size: if field_mode.is_single_field() {
size / 2
} else {
size
},
line_stride: resolution.x * pixel_stride,
resolution,
field_mode,
subsampling,
})
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BufferInfoError {
UnsupportedFourCC(FourCCVideo),
UnspecifiedFourCC,
}
impl std::fmt::Display for BufferInfoError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::UnsupportedFourCC(fourcc) => write!(f, "No layout implementation for {fourcc:?}"),
Self::UnspecifiedFourCC => f.write_str("Video format was not specified"),
}
}
}
impl std::error::Error for BufferInfoError {}
#[cfg_attr(target_os = "windows", repr(i32))]
#[cfg_attr(not(target_os = "windows"), repr(u32))]
#[non_exhaustive]
#[allow(non_camel_case_types)]
#[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq, TryFromPrimitive, IntoPrimitive)]
pub enum FourCCAudio {
#[default]
FLTP = bindings::NDIlib_FourCC_audio_type_e_NDIlib_FourCC_audio_type_FLTP,
}
impl FourCCAudio {
pub fn to_ffi(self) -> NDIlib_FourCC_audio_type_e {
self.into()
}
pub fn from_ffi(value: NDIlib_FourCC_audio_type_e) -> Option<Self> {
Self::try_from_primitive(value).ok()
}
}
#[repr(transparent)]
#[derive(Clone, Copy, Hash, PartialEq, Eq)]
pub struct FourCC {
code: i32,
}
impl FourCC {
pub fn from_ffi(code: i32) -> Self {
FourCC { code }
}
pub fn to_ffi(self) -> i32 {
self.code
}
pub fn as_video(&self) -> Option<FourCCVideo> {
FourCCVideo::from_ffi(self.code as NDIlib_FourCC_video_type_e)
}
pub fn as_audio(&self) -> Option<FourCCAudio> {
FourCCAudio::from_ffi(self.code as NDIlib_FourCC_audio_type_e)
}
#[allow(clippy::inherent_to_string_shadow_display)] pub fn to_string(&self) -> String {
let bytes: [u8; 4] = self.code.to_le_bytes();
String::from_utf8_lossy(&bytes).to_string()
}
}
impl Display for FourCC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bytes: [u8; 4] = self.code.to_le_bytes();
let ascii = String::from_utf8_lossy(&bytes);
write!(f, "{}", ascii)
}
}
impl Debug for FourCC {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let bytes: [u8; 4] = self.code.to_le_bytes();
let ascii = String::from_utf8_lossy(&bytes);
write!(f, "FourCC({})", ascii,)
}
}
impl From<FourCCVideo> for FourCC {
fn from(value: FourCCVideo) -> Self {
FourCC {
code: value.to_ffi() as i32,
}
}
}
impl From<FourCCAudio> for FourCC {
fn from(value: FourCCAudio) -> Self {
FourCC {
code: value.to_ffi() as i32,
}
}
}
impl From<i32> for FourCC {
fn from(code: i32) -> Self {
FourCC { code }
}
}
impl From<FourCC> for i32 {
fn from(fourcc: FourCC) -> Self {
fourcc.code
}
}
impl TryFrom<FourCC> for FourCCVideo {
type Error = ();
fn try_from(value: FourCC) -> Result<Self, Self::Error> {
FourCCVideo::from_ffi(value.code as NDIlib_FourCC_video_type_e).ok_or(())
}
}
impl TryFrom<FourCC> for FourCCAudio {
type Error = ();
fn try_from(value: FourCC) -> Result<Self, Self::Error> {
FourCCAudio::from_ffi(value.code as NDIlib_FourCC_audio_type_e).ok_or(())
}
}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_fmt() {
let fourcc = FourCC::from(FourCCVideo::RGBA);
assert_eq!(fourcc.to_string(), "RGBA");
assert_eq!(format!("{:?}", fourcc), "FourCC(RGBA)");
}
}