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::{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 | use std::u64; |
152 | |
153 | let find: &&mut T = &*(data as *mut &mut T); |
154 | find.length().unwrap_or(default:u64::MAX) |
155 | } |
156 | |
157 | #[derive (Debug)] |
158 | pub struct SliceTypeFind<T: AsRef<[u8]>> { |
159 | pub probability: Option<TypeFindProbability>, |
160 | pub caps: Option<Caps>, |
161 | data: T, |
162 | } |
163 | |
164 | impl<T: AsRef<[u8]>> SliceTypeFind<T> { |
165 | pub fn new(data: T) -> SliceTypeFind<T> { |
166 | assert_initialized_main_thread!(); |
167 | SliceTypeFind { |
168 | probability: None, |
169 | caps: None, |
170 | data, |
171 | } |
172 | } |
173 | |
174 | pub fn run(&mut self) { |
175 | let factories = TypeFindFactory::factories(); |
176 | |
177 | for factory in factories { |
178 | factory.call_function(self); |
179 | if let Some(prob) = self.probability { |
180 | if prob >= TypeFindProbability::Maximum { |
181 | break; |
182 | } |
183 | } |
184 | } |
185 | } |
186 | |
187 | pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) { |
188 | assert_initialized_main_thread!(); |
189 | let mut t = SliceTypeFind { |
190 | probability: None, |
191 | caps: None, |
192 | data, |
193 | }; |
194 | |
195 | t.run(); |
196 | |
197 | (t.probability.unwrap_or(TypeFindProbability::None), t.caps) |
198 | } |
199 | } |
200 | |
201 | impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> { |
202 | fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> { |
203 | let data = self.data.as_ref(); |
204 | let len = data.len(); |
205 | |
206 | let offset = if offset >= 0 { |
207 | usize::try_from(offset).ok()? |
208 | } else { |
209 | let offset = usize::try_from(offset.unsigned_abs()).ok()?; |
210 | if len < offset { |
211 | return None; |
212 | } |
213 | |
214 | len - offset |
215 | }; |
216 | |
217 | let size = usize::try_from(size).ok()?; |
218 | let end_offset = offset.checked_add(size)?; |
219 | if end_offset <= len { |
220 | Some(&data[offset..end_offset]) |
221 | } else { |
222 | None |
223 | } |
224 | } |
225 | |
226 | fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) { |
227 | match self.probability { |
228 | None => { |
229 | self.probability = Some(probability); |
230 | self.caps = Some(caps.clone()); |
231 | } |
232 | Some(old_probability) if old_probability < probability => { |
233 | self.probability = Some(probability); |
234 | self.caps = Some(caps.clone()); |
235 | } |
236 | _ => (), |
237 | } |
238 | } |
239 | fn length(&self) -> Option<u64> { |
240 | Some(self.data.as_ref().len() as u64) |
241 | } |
242 | } |
243 | |
244 | #[cfg (test)] |
245 | mod tests { |
246 | use super::*; |
247 | |
248 | #[test ] |
249 | fn test_typefind_call_function() { |
250 | crate::init().unwrap(); |
251 | |
252 | let xml_factory = TypeFindFactory::factories() |
253 | .into_iter() |
254 | .find(|f| { |
255 | f.caps() |
256 | .map(|c| { |
257 | c.structure(0) |
258 | .map(|s| s.name() == "application/xml" ) |
259 | .unwrap_or(false) |
260 | }) |
261 | .unwrap_or(false) |
262 | }) |
263 | .unwrap(); |
264 | |
265 | let data = b"<?xml version= \"1.0 \"?><test>test</test>" ; |
266 | let data = &data[..]; |
267 | let mut typefind = SliceTypeFind::new(&data); |
268 | xml_factory.call_function(&mut typefind); |
269 | |
270 | assert_eq!( |
271 | typefind.caps, |
272 | Some(Caps::builder("application/xml" ).build()) |
273 | ); |
274 | assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum)); |
275 | } |
276 | |
277 | #[test ] |
278 | fn test_typefind_register() { |
279 | crate::init().unwrap(); |
280 | |
281 | TypeFind::register( |
282 | None, |
283 | "test_typefind" , |
284 | crate::Rank::Primary, |
285 | None, |
286 | Some(&Caps::builder("test/test" ).build()), |
287 | |typefind| { |
288 | assert_eq!(typefind.length(), Some(8)); |
289 | let mut found = false; |
290 | if let Some(data) = typefind.peek(0, 8) { |
291 | if data == b"abcdefgh" { |
292 | found = true; |
293 | } |
294 | } |
295 | |
296 | if found { |
297 | typefind.suggest( |
298 | TypeFindProbability::Likely, |
299 | &Caps::builder("test/test" ).build(), |
300 | ); |
301 | } |
302 | }, |
303 | ) |
304 | .unwrap(); |
305 | |
306 | let data = b"abcdefgh" ; |
307 | let data = &data[..]; |
308 | let (probability, caps) = SliceTypeFind::type_find(data); |
309 | |
310 | assert_eq!(caps, Some(Caps::builder("test/test" ).build())); |
311 | assert_eq!(probability, TypeFindProbability::Likely); |
312 | } |
313 | } |
314 | |