1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{ptr, slice};
4
5use glib::translate::*;
6
7use crate::{ffi, Caps, Plugin, Rank, TypeFindFactory, TypeFindProbability};
8
9#[repr(transparent)]
10#[derive(Debug)]
11#[doc(alias = "GstTypeFind")]
12pub struct TypeFind(ffi::GstTypeFind);
13
14pub 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
23impl 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
95impl 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
113unsafe 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
121unsafe 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
127unsafe 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
139unsafe 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
148unsafe 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)]
156pub struct SliceTypeFind<T: AsRef<[u8]>> {
157 pub probability: Option<TypeFindProbability>,
158 pub caps: Option<Caps>,
159 data: T,
160}
161
162impl<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
199impl<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)]
243mod 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