| 1 | // Take a look at the license at the top of the repository in the LICENSE file. |
| 2 | |
| 3 | use std::{ptr, slice}; |
| 4 | |
| 5 | use glib::translate::*; |
| 6 | |
| 7 | use crate::{ffi, Caps, Plugin, Rank, TypeFindFactory, TypeFindProbability}; |
| 8 | |
| 9 | #[repr (transparent)] |
| 10 | #[derive (Debug)] |
| 11 | #[doc (alias = "GstTypeFind" )] |
| 12 | pub struct TypeFind(ffi::GstTypeFind); |
| 13 | |
| 14 | pub trait TypeFindImpl { |
| 15 | fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]>; |
| 16 | fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps); |
| 17 | #[doc (alias = "get_length" )] |
| 18 | fn length(&self) -> Option<u64> { |
| 19 | None |
| 20 | } |
| 21 | } |
| 22 | |
| 23 | impl TypeFind { |
| 24 | #[doc (alias = "gst_type_find_register" )] |
| 25 | pub fn register<F>( |
| 26 | plugin: Option<&Plugin>, |
| 27 | name: &str, |
| 28 | rank: Rank, |
| 29 | extensions: Option<&str>, |
| 30 | possible_caps: Option<&Caps>, |
| 31 | func: F, |
| 32 | ) -> Result<(), glib::error::BoolError> |
| 33 | where |
| 34 | F: Fn(&mut TypeFind) + Send + Sync + 'static, |
| 35 | { |
| 36 | skip_assert_initialized!(); |
| 37 | unsafe { |
| 38 | let func: Box<F> = Box::new(func); |
| 39 | let func = Box::into_raw(func); |
| 40 | |
| 41 | let res = ffi::gst_type_find_register( |
| 42 | plugin.to_glib_none().0, |
| 43 | name.to_glib_none().0, |
| 44 | rank.into_glib() as u32, |
| 45 | Some(type_find_trampoline::<F>), |
| 46 | extensions.to_glib_none().0, |
| 47 | possible_caps.to_glib_none().0, |
| 48 | func as *mut _, |
| 49 | Some(type_find_closure_drop::<F>), |
| 50 | ); |
| 51 | |
| 52 | glib::result_from_gboolean!(res, "Failed to register typefind factory" ) |
| 53 | } |
| 54 | } |
| 55 | |
| 56 | #[doc (alias = "gst_type_find_peek" )] |
| 57 | pub fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> { |
| 58 | unsafe { |
| 59 | let data = ffi::gst_type_find_peek(&mut self.0, offset, size); |
| 60 | if data.is_null() { |
| 61 | None |
| 62 | } else if size == 0 { |
| 63 | Some(&[]) |
| 64 | } else { |
| 65 | Some(slice::from_raw_parts(data, size as usize)) |
| 66 | } |
| 67 | } |
| 68 | } |
| 69 | |
| 70 | #[doc (alias = "gst_type_find_suggest" )] |
| 71 | pub fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) { |
| 72 | unsafe { |
| 73 | ffi::gst_type_find_suggest( |
| 74 | &mut self.0, |
| 75 | probability.into_glib() as u32, |
| 76 | caps.to_glib_none().0, |
| 77 | ); |
| 78 | } |
| 79 | } |
| 80 | |
| 81 | #[doc (alias = "get_length" )] |
| 82 | #[doc (alias = "gst_type_find_get_length" )] |
| 83 | pub fn length(&mut self) -> Option<u64> { |
| 84 | unsafe { |
| 85 | let len = ffi::gst_type_find_get_length(&mut self.0); |
| 86 | if len == 0 { |
| 87 | None |
| 88 | } else { |
| 89 | Some(len) |
| 90 | } |
| 91 | } |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | impl TypeFindFactory { |
| 96 | #[doc (alias = "gst_type_find_factory_call_function" )] |
| 97 | pub fn call_function<T: TypeFindImpl + ?Sized>(&self, mut find: &mut T) { |
| 98 | unsafe { |
| 99 | let find_ptr: *mut c_void = &mut find as *mut &mut T as glib::ffi::gpointer; |
| 100 | let mut find: GstTypeFind = ffi::GstTypeFind { |
| 101 | peek: Some(type_find_peek::<T>), |
| 102 | suggest: Some(type_find_suggest::<T>), |
| 103 | data: find_ptr, |
| 104 | get_length: Some(type_find_get_length::<T>), |
| 105 | _gst_reserved: [ptr::null_mut(); 4], |
| 106 | }; |
| 107 | |
| 108 | ffi::gst_type_find_factory_call_function(self.to_glib_none().0, &mut find) |
| 109 | } |
| 110 | } |
| 111 | } |
| 112 | |
| 113 | unsafe extern "C" fn type_find_trampoline<F: Fn(&mut TypeFind) + Send + Sync + 'static>( |
| 114 | find: *mut ffi::GstTypeFind, |
| 115 | user_data: glib::ffi::gpointer, |
| 116 | ) { |
| 117 | let func: &F = &*(user_data as *const F); |
| 118 | func(&mut *(find as *mut TypeFind)); |
| 119 | } |
| 120 | |
| 121 | unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>( |
| 122 | data: glib::ffi::gpointer, |
| 123 | ) { |
| 124 | let _ = Box::<F>::from_raw(data as *mut _); |
| 125 | } |
| 126 | |
| 127 | unsafe extern "C" fn type_find_peek<T: TypeFindImpl + ?Sized>( |
| 128 | data: glib::ffi::gpointer, |
| 129 | offset: i64, |
| 130 | size: u32, |
| 131 | ) -> *const u8 { |
| 132 | let find: &mut &mut T = &mut *(data as *mut &mut T); |
| 133 | match find.peek(offset, size) { |
| 134 | None => ptr::null(), |
| 135 | Some(data: &[u8]) => data.as_ptr(), |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | unsafe extern "C" fn type_find_suggest<T: TypeFindImpl + ?Sized>( |
| 140 | data: glib::ffi::gpointer, |
| 141 | probability: u32, |
| 142 | caps: *mut ffi::GstCaps, |
| 143 | ) { |
| 144 | let find: &mut &mut T = &mut *(data as *mut &mut T); |
| 145 | find.suggest(probability:from_glib(probability as i32), &from_glib_borrow(ptr:caps)); |
| 146 | } |
| 147 | |
| 148 | unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>( |
| 149 | data: glib::ffi::gpointer, |
| 150 | ) -> u64 { |
| 151 | let find: &&mut T = &*(data as *mut &mut T); |
| 152 | find.length().unwrap_or(default:u64::MAX) |
| 153 | } |
| 154 | |
| 155 | #[derive (Debug)] |
| 156 | pub struct SliceTypeFind<T: AsRef<[u8]>> { |
| 157 | pub probability: Option<TypeFindProbability>, |
| 158 | pub caps: Option<Caps>, |
| 159 | data: T, |
| 160 | } |
| 161 | |
| 162 | impl<T: AsRef<[u8]>> SliceTypeFind<T> { |
| 163 | pub fn new(data: T) -> SliceTypeFind<T> { |
| 164 | assert_initialized_main_thread!(); |
| 165 | SliceTypeFind { |
| 166 | probability: None, |
| 167 | caps: None, |
| 168 | data, |
| 169 | } |
| 170 | } |
| 171 | |
| 172 | pub fn run(&mut self) { |
| 173 | let factories = TypeFindFactory::factories(); |
| 174 | |
| 175 | for factory in factories { |
| 176 | factory.call_function(self); |
| 177 | if let Some(prob) = self.probability { |
| 178 | if prob >= TypeFindProbability::Maximum { |
| 179 | break; |
| 180 | } |
| 181 | } |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) { |
| 186 | assert_initialized_main_thread!(); |
| 187 | let mut t = SliceTypeFind { |
| 188 | probability: None, |
| 189 | caps: None, |
| 190 | data, |
| 191 | }; |
| 192 | |
| 193 | t.run(); |
| 194 | |
| 195 | (t.probability.unwrap_or(TypeFindProbability::None), t.caps) |
| 196 | } |
| 197 | } |
| 198 | |
| 199 | impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> { |
| 200 | fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> { |
| 201 | let data = self.data.as_ref(); |
| 202 | let len = data.len(); |
| 203 | |
| 204 | let offset = if offset >= 0 { |
| 205 | usize::try_from(offset).ok()? |
| 206 | } else { |
| 207 | let offset = usize::try_from(offset.unsigned_abs()).ok()?; |
| 208 | if len < offset { |
| 209 | return None; |
| 210 | } |
| 211 | |
| 212 | len - offset |
| 213 | }; |
| 214 | |
| 215 | let size = usize::try_from(size).ok()?; |
| 216 | let end_offset = offset.checked_add(size)?; |
| 217 | if end_offset <= len { |
| 218 | Some(&data[offset..end_offset]) |
| 219 | } else { |
| 220 | None |
| 221 | } |
| 222 | } |
| 223 | |
| 224 | fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) { |
| 225 | match self.probability { |
| 226 | None => { |
| 227 | self.probability = Some(probability); |
| 228 | self.caps = Some(caps.clone()); |
| 229 | } |
| 230 | Some(old_probability) if old_probability < probability => { |
| 231 | self.probability = Some(probability); |
| 232 | self.caps = Some(caps.clone()); |
| 233 | } |
| 234 | _ => (), |
| 235 | } |
| 236 | } |
| 237 | fn length(&self) -> Option<u64> { |
| 238 | Some(self.data.as_ref().len() as u64) |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | #[cfg (test)] |
| 243 | mod tests { |
| 244 | use super::*; |
| 245 | |
| 246 | #[test ] |
| 247 | fn test_typefind_call_function() { |
| 248 | crate::init().unwrap(); |
| 249 | |
| 250 | let xml_factory = TypeFindFactory::factories() |
| 251 | .into_iter() |
| 252 | .find(|f| { |
| 253 | f.caps() |
| 254 | .map(|c| { |
| 255 | c.structure(0) |
| 256 | .map(|s| s.name() == "application/xml" ) |
| 257 | .unwrap_or(false) |
| 258 | }) |
| 259 | .unwrap_or(false) |
| 260 | }) |
| 261 | .unwrap(); |
| 262 | |
| 263 | let data = b"<?xml version= \"1.0 \"?><test>test</test>" ; |
| 264 | let data = &data[..]; |
| 265 | let mut typefind = SliceTypeFind::new(&data); |
| 266 | xml_factory.call_function(&mut typefind); |
| 267 | |
| 268 | assert_eq!( |
| 269 | typefind.caps, |
| 270 | Some(Caps::builder("application/xml" ).build()) |
| 271 | ); |
| 272 | assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum)); |
| 273 | } |
| 274 | |
| 275 | #[test ] |
| 276 | fn test_typefind_register() { |
| 277 | crate::init().unwrap(); |
| 278 | |
| 279 | TypeFind::register( |
| 280 | None, |
| 281 | "test_typefind" , |
| 282 | crate::Rank::PRIMARY, |
| 283 | None, |
| 284 | Some(&Caps::builder("test/test" ).build()), |
| 285 | |typefind| { |
| 286 | assert_eq!(typefind.length(), Some(8)); |
| 287 | let mut found = false; |
| 288 | if let Some(data) = typefind.peek(0, 8) { |
| 289 | if data == b"abcdefgh" { |
| 290 | found = true; |
| 291 | } |
| 292 | } |
| 293 | |
| 294 | if found { |
| 295 | typefind.suggest( |
| 296 | TypeFindProbability::Likely, |
| 297 | &Caps::builder("test/test" ).build(), |
| 298 | ); |
| 299 | } |
| 300 | }, |
| 301 | ) |
| 302 | .unwrap(); |
| 303 | |
| 304 | let data = b"abcdefgh" ; |
| 305 | let data = &data[..]; |
| 306 | let (probability, caps) = SliceTypeFind::type_find(data); |
| 307 | |
| 308 | assert_eq!(caps, Some(Caps::builder("test/test" ).build())); |
| 309 | assert_eq!(probability, TypeFindProbability::Likely); |
| 310 | } |
| 311 | } |
| 312 | |