ccap/
utils.rs

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
8/// Utility functions
9pub struct Utils;
10
11impl Utils {
12    /// Convert pixel format enum to string
13    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    /// Convert string to pixel format enum
32    pub fn string_to_pixel_format(format_str: &str) -> Result<PixelFormat> {
33        // This function doesn't exist in C API, we'll implement a simple mapping
34        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    /// Save frame as BMP file
55    pub fn save_frame_as_bmp<P: AsRef<Path>>(frame: &VideoFrame, file_path: P) -> Result<()> {
56        // This function doesn't exist in C API, we'll use the dump_frame_to_file instead
57        Self::dump_frame_to_file(frame, file_path)?;
58        Ok(())
59    }
60
61    /// Convert path to C string safely, handling Windows-specific path issues
62    fn path_to_cstring<P: AsRef<Path>>(path: P) -> Result<CString> {
63        #[cfg(windows)]
64        {
65            // On Windows, handle potential UTF-16 to UTF-8 conversion issues
66            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            // On Unix-like systems, standard conversion should work
74            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    /// Save a video frame to a file with automatic format detection
84    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        // First call to get required buffer size
91        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        // Second call to get actual result
102        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        // Convert to string
119        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    /// Save a video frame to directory with auto-generated filename
125    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        // First call to get required buffer size
132        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        // Second call to get actual result
148        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        // Convert to string
165        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    /// Save RGB data as BMP file (generic version)
171    #[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    /// Interactive camera selection helper
207    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    /// Set log level
243    pub fn set_log_level(level: LogLevel) {
244        unsafe {
245            sys::ccap_set_log_level(level.to_c_enum());
246        }
247    }
248}
249
250/// Log level enumeration
251#[derive(Debug, Clone, Copy, PartialEq, Eq)]
252pub enum LogLevel {
253    /// No log output
254    None,
255    /// Error log level
256    Error,
257    /// Warning log level
258    Warning,
259    /// Info log level
260    Info,
261    /// Verbose log level
262    Verbose,
263}
264
265impl LogLevel {
266    /// Convert log level to C enum
267    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}