1use crate::error::{CcapError, Result};
2use crate::frame::VideoFrame;
3use crate::sys;
4use crate::types::PixelFormat;
5use std::ffi::CString;
6use std::path::Path;
7
8pub struct Utils;
10
11impl Utils {
12 pub fn pixel_format_to_string(format: PixelFormat) -> Result<String> {
14 let mut buffer = [0i8; 64];
15 let result = unsafe {
16 sys::ccap_pixel_format_to_string(format.to_c_enum(), buffer.as_mut_ptr(), buffer.len())
17 };
18
19 if result < 0 {
20 return Err(CcapError::StringConversionError(
21 "Unknown pixel format".to_string(),
22 ));
23 }
24
25 let c_str = unsafe { std::ffi::CStr::from_ptr(buffer.as_ptr()) };
26 c_str.to_str().map(|s| s.to_string()).map_err(|_| {
27 CcapError::StringConversionError("Invalid pixel format string".to_string())
28 })
29 }
30
31 pub fn string_to_pixel_format(format_str: &str) -> Result<PixelFormat> {
33 match format_str.to_lowercase().as_str() {
35 "unknown" => Ok(PixelFormat::Unknown),
36 "nv12" => Ok(PixelFormat::Nv12),
37 "nv12f" => Ok(PixelFormat::Nv12F),
38 "i420" => Ok(PixelFormat::I420),
39 "i420f" => Ok(PixelFormat::I420F),
40 "yuyv" => Ok(PixelFormat::Yuyv),
41 "yuyvf" => Ok(PixelFormat::YuyvF),
42 "uyvy" => Ok(PixelFormat::Uyvy),
43 "uyvyf" => Ok(PixelFormat::UyvyF),
44 "rgb24" => Ok(PixelFormat::Rgb24),
45 "bgr24" => Ok(PixelFormat::Bgr24),
46 "rgba32" => Ok(PixelFormat::Rgba32),
47 "bgra32" => Ok(PixelFormat::Bgra32),
48 _ => Err(CcapError::StringConversionError(
49 "Unknown pixel format string".to_string(),
50 )),
51 }
52 }
53
54 pub fn save_frame_as_bmp<P: AsRef<Path>>(frame: &VideoFrame, file_path: P) -> Result<()> {
56 Self::dump_frame_to_file(frame, file_path)?;
58 Ok(())
59 }
60
61 fn path_to_cstring<P: AsRef<Path>>(path: P) -> Result<CString> {
63 #[cfg(windows)]
64 {
65 let path_str = path.as_ref().to_string_lossy();
67 CString::new(path_str.as_bytes())
68 .map_err(|_| CcapError::StringConversionError("Invalid file path".to_string()))
69 }
70
71 #[cfg(not(windows))]
72 {
73 let path_str = path
75 .as_ref()
76 .to_str()
77 .ok_or_else(|| CcapError::StringConversionError("Invalid file path".to_string()))?;
78 CString::new(path_str)
79 .map_err(|_| CcapError::StringConversionError("Invalid file path".to_string()))
80 }
81 }
82
83 pub fn dump_frame_to_file<P: AsRef<Path>>(
85 frame: &VideoFrame,
86 filename_no_suffix: P,
87 ) -> Result<String> {
88 let c_path = Self::path_to_cstring(filename_no_suffix)?;
89
90 let buffer_size = unsafe {
92 sys::ccap_dump_frame_to_file(frame.as_c_ptr(), c_path.as_ptr(), std::ptr::null_mut(), 0)
93 };
94
95 if buffer_size <= 0 {
96 return Err(CcapError::FileOperationFailed(
97 "Failed to dump frame to file".to_string(),
98 ));
99 }
100
101 let mut buffer = vec![0u8; buffer_size as usize];
103 let result_len = unsafe {
104 sys::ccap_dump_frame_to_file(
105 frame.as_c_ptr(),
106 c_path.as_ptr(),
107 buffer.as_mut_ptr() as *mut i8,
108 buffer.len(),
109 )
110 };
111
112 if result_len <= 0 {
113 return Err(CcapError::FileOperationFailed(
114 "Failed to dump frame to file".to_string(),
115 ));
116 }
117
118 buffer.truncate(result_len as usize);
120 String::from_utf8(buffer)
121 .map_err(|_| CcapError::StringConversionError("Invalid output path string".to_string()))
122 }
123
124 pub fn dump_frame_to_directory<P: AsRef<Path>>(
126 frame: &VideoFrame,
127 directory: P,
128 ) -> Result<String> {
129 let c_dir = Self::path_to_cstring(directory)?;
130
131 let buffer_size = unsafe {
133 sys::ccap_dump_frame_to_directory(
134 frame.as_c_ptr(),
135 c_dir.as_ptr(),
136 std::ptr::null_mut(),
137 0,
138 )
139 };
140
141 if buffer_size <= 0 {
142 return Err(CcapError::FileOperationFailed(
143 "Failed to dump frame to directory".to_string(),
144 ));
145 }
146
147 let mut buffer = vec![0u8; buffer_size as usize];
149 let result_len = unsafe {
150 sys::ccap_dump_frame_to_directory(
151 frame.as_c_ptr(),
152 c_dir.as_ptr(),
153 buffer.as_mut_ptr() as *mut i8,
154 buffer.len(),
155 )
156 };
157
158 if result_len <= 0 {
159 return Err(CcapError::FileOperationFailed(
160 "Failed to dump frame to directory".to_string(),
161 ));
162 }
163
164 buffer.truncate(result_len as usize);
166 String::from_utf8(buffer)
167 .map_err(|_| CcapError::StringConversionError("Invalid output path string".to_string()))
168 }
169
170 #[allow(clippy::too_many_arguments)]
172 pub fn save_rgb_data_as_bmp<P: AsRef<Path>>(
173 filename: P,
174 data: &[u8],
175 width: u32,
176 stride: u32,
177 height: u32,
178 is_bgr: bool,
179 has_alpha: bool,
180 is_top_to_bottom: bool,
181 ) -> Result<()> {
182 let c_path = Self::path_to_cstring(filename)?;
183
184 let success = unsafe {
185 sys::ccap_save_rgb_data_as_bmp(
186 c_path.as_ptr(),
187 data.as_ptr(),
188 width,
189 stride,
190 height,
191 is_bgr,
192 has_alpha,
193 is_top_to_bottom,
194 )
195 };
196
197 if success {
198 Ok(())
199 } else {
200 Err(CcapError::FileOperationFailed(
201 "Failed to save RGB data as BMP".to_string(),
202 ))
203 }
204 }
205
206 pub fn select_camera(devices: &[String]) -> Result<usize> {
208 if devices.is_empty() {
209 return Err(CcapError::DeviceNotFound);
210 }
211
212 if devices.len() == 1 {
213 println!("Using the only available device: {}", devices[0]);
214 return Ok(0);
215 }
216
217 println!("Multiple devices found, please select one:");
218 for (i, device) in devices.iter().enumerate() {
219 println!(" {}: {}", i, device);
220 }
221
222 print!("Enter the index of the device you want to use: ");
223 use std::io::{self, Write};
224 io::stdout().flush().unwrap();
225
226 let mut input = String::new();
227 io::stdin()
228 .read_line(&mut input)
229 .map_err(|e| CcapError::InvalidParameter(format!("Failed to read input: {}", e)))?;
230
231 let selected_index = input.trim().parse::<usize>().unwrap_or(0);
232
233 if selected_index >= devices.len() {
234 println!("Invalid index, using the first device: {}", devices[0]);
235 Ok(0)
236 } else {
237 println!("Using device: {}", devices[selected_index]);
238 Ok(selected_index)
239 }
240 }
241
242 pub fn set_log_level(level: LogLevel) {
244 unsafe {
245 sys::ccap_set_log_level(level.to_c_enum());
246 }
247 }
248}
249
250#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum LogLevel {
253 None,
255 Error,
257 Warning,
259 Info,
261 Verbose,
263}
264
265impl LogLevel {
266 pub fn to_c_enum(self) -> sys::CcapLogLevel {
268 match self {
269 LogLevel::None => sys::CcapLogLevel_CCAP_LOG_LEVEL_NONE,
270 LogLevel::Error => sys::CcapLogLevel_CCAP_LOG_LEVEL_ERROR,
271 LogLevel::Warning => sys::CcapLogLevel_CCAP_LOG_LEVEL_WARNING,
272 LogLevel::Info => sys::CcapLogLevel_CCAP_LOG_LEVEL_INFO,
273 LogLevel::Verbose => sys::CcapLogLevel_CCAP_LOG_LEVEL_VERBOSE,
274 }
275 }
276}