use crate::error::{CcapError, Result};
use crate::frame::VideoFrame;
use crate::sys;
use crate::types::PixelFormat;
use std::ffi::CString;
use std::path::Path;
pub struct Utils;
impl Utils {
pub fn pixel_format_to_string(format: PixelFormat) -> Result<String> {
let mut buffer = [0i8; 64];
let result = unsafe {
sys::ccap_pixel_format_to_string(format.to_c_enum(), buffer.as_mut_ptr(), buffer.len())
};
if result < 0 {
return Err(CcapError::StringConversionError(
"Unknown pixel format".to_string(),
));
}
let c_str = unsafe { std::ffi::CStr::from_ptr(buffer.as_ptr()) };
c_str.to_str().map(|s| s.to_string()).map_err(|_| {
CcapError::StringConversionError("Invalid pixel format string".to_string())
})
}
pub fn string_to_pixel_format(format_str: &str) -> Result<PixelFormat> {
match format_str.to_lowercase().as_str() {
"unknown" => Ok(PixelFormat::Unknown),
"nv12" => Ok(PixelFormat::Nv12),
"nv12f" => Ok(PixelFormat::Nv12F),
"i420" => Ok(PixelFormat::I420),
"i420f" => Ok(PixelFormat::I420F),
"yuyv" => Ok(PixelFormat::Yuyv),
"yuyvf" => Ok(PixelFormat::YuyvF),
"uyvy" => Ok(PixelFormat::Uyvy),
"uyvyf" => Ok(PixelFormat::UyvyF),
"rgb24" => Ok(PixelFormat::Rgb24),
"bgr24" => Ok(PixelFormat::Bgr24),
"rgba32" => Ok(PixelFormat::Rgba32),
"bgra32" => Ok(PixelFormat::Bgra32),
_ => Err(CcapError::StringConversionError(
"Unknown pixel format string".to_string(),
)),
}
}
pub fn save_frame_as_bmp<P: AsRef<Path>>(frame: &VideoFrame, file_path: P) -> Result<()> {
Self::dump_frame_to_file(frame, file_path)?;
Ok(())
}
fn path_to_cstring<P: AsRef<Path>>(path: P) -> Result<CString> {
#[cfg(windows)]
{
let path_str = path.as_ref().to_string_lossy();
CString::new(path_str.as_bytes())
.map_err(|_| CcapError::StringConversionError("Invalid file path".to_string()))
}
#[cfg(not(windows))]
{
let path_str = path
.as_ref()
.to_str()
.ok_or_else(|| CcapError::StringConversionError("Invalid file path".to_string()))?;
CString::new(path_str)
.map_err(|_| CcapError::StringConversionError("Invalid file path".to_string()))
}
}
pub fn dump_frame_to_file<P: AsRef<Path>>(
frame: &VideoFrame,
filename_no_suffix: P,
) -> Result<String> {
let c_path = Self::path_to_cstring(filename_no_suffix)?;
let buffer_size = unsafe {
sys::ccap_dump_frame_to_file(frame.as_c_ptr(), c_path.as_ptr(), std::ptr::null_mut(), 0)
};
if buffer_size <= 0 {
return Err(CcapError::FileOperationFailed(
"Failed to dump frame to file".to_string(),
));
}
let mut buffer = vec![0u8; buffer_size as usize];
let result_len = unsafe {
sys::ccap_dump_frame_to_file(
frame.as_c_ptr(),
c_path.as_ptr(),
buffer.as_mut_ptr() as *mut i8,
buffer.len(),
)
};
if result_len <= 0 {
return Err(CcapError::FileOperationFailed(
"Failed to dump frame to file".to_string(),
));
}
buffer.truncate(result_len as usize);
String::from_utf8(buffer)
.map_err(|_| CcapError::StringConversionError("Invalid output path string".to_string()))
}
pub fn dump_frame_to_directory<P: AsRef<Path>>(
frame: &VideoFrame,
directory: P,
) -> Result<String> {
let c_dir = Self::path_to_cstring(directory)?;
let buffer_size = unsafe {
sys::ccap_dump_frame_to_directory(
frame.as_c_ptr(),
c_dir.as_ptr(),
std::ptr::null_mut(),
0,
)
};
if buffer_size <= 0 {
return Err(CcapError::FileOperationFailed(
"Failed to dump frame to directory".to_string(),
));
}
let mut buffer = vec![0u8; buffer_size as usize];
let result_len = unsafe {
sys::ccap_dump_frame_to_directory(
frame.as_c_ptr(),
c_dir.as_ptr(),
buffer.as_mut_ptr() as *mut i8,
buffer.len(),
)
};
if result_len <= 0 {
return Err(CcapError::FileOperationFailed(
"Failed to dump frame to directory".to_string(),
));
}
buffer.truncate(result_len as usize);
String::from_utf8(buffer)
.map_err(|_| CcapError::StringConversionError("Invalid output path string".to_string()))
}
#[allow(clippy::too_many_arguments)]
pub fn save_rgb_data_as_bmp<P: AsRef<Path>>(
filename: P,
data: &[u8],
width: u32,
stride: u32,
height: u32,
is_bgr: bool,
has_alpha: bool,
is_top_to_bottom: bool,
) -> Result<()> {
let c_path = Self::path_to_cstring(filename)?;
let success = unsafe {
sys::ccap_save_rgb_data_as_bmp(
c_path.as_ptr(),
data.as_ptr(),
width,
stride,
height,
is_bgr,
has_alpha,
is_top_to_bottom,
)
};
if success {
Ok(())
} else {
Err(CcapError::FileOperationFailed(
"Failed to save RGB data as BMP".to_string(),
))
}
}
pub fn select_camera(devices: &[String]) -> Result<usize> {
if devices.is_empty() {
return Err(CcapError::DeviceNotFound);
}
if devices.len() == 1 {
println!("Using the only available device: {}", devices[0]);
return Ok(0);
}
println!("Multiple devices found, please select one:");
for (i, device) in devices.iter().enumerate() {
println!(" {}: {}", i, device);
}
print!("Enter the index of the device you want to use: ");
use std::io::{self, Write};
io::stdout().flush().unwrap();
let mut input = String::new();
io::stdin()
.read_line(&mut input)
.map_err(|e| CcapError::InvalidParameter(format!("Failed to read input: {}", e)))?;
let selected_index = input.trim().parse::<usize>().unwrap_or(0);
if selected_index >= devices.len() {
println!("Invalid index, using the first device: {}", devices[0]);
Ok(0)
} else {
println!("Using device: {}", devices[selected_index]);
Ok(selected_index)
}
}
pub fn set_log_level(level: LogLevel) {
unsafe {
sys::ccap_set_log_level(level.to_c_enum());
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum LogLevel {
None,
Error,
Warning,
Info,
Verbose,
}
impl LogLevel {
pub fn to_c_enum(self) -> sys::CcapLogLevel {
match self {
LogLevel::None => sys::CcapLogLevel_CCAP_LOG_LEVEL_NONE,
LogLevel::Error => sys::CcapLogLevel_CCAP_LOG_LEVEL_ERROR,
LogLevel::Warning => sys::CcapLogLevel_CCAP_LOG_LEVEL_WARNING,
LogLevel::Info => sys::CcapLogLevel_CCAP_LOG_LEVEL_INFO,
LogLevel::Verbose => sys::CcapLogLevel_CCAP_LOG_LEVEL_VERBOSE,
}
}
}