gstreamer/
typefind.rs

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/// The following functions allow you to detect the media type of an unknown
10/// stream.
11#[repr(transparent)]
12#[derive(Debug)]
13#[doc(alias = "GstTypeFind")]
14pub struct TypeFind(ffi::GstTypeFind);
15
16pub trait TypeFindImpl {
17    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]>;
18    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps);
19    #[doc(alias = "get_length")]
20    fn length(&self) -> Option<u64> {
21        None
22    }
23}
24
25impl TypeFind {
26    /// Registers a new typefind function to be used for typefinding. After
27    /// registering this function will be available for typefinding.
28    /// This function is typically called during an element's plugin initialization.
29    /// ## `plugin`
30    /// A [`Plugin`][crate::Plugin], or [`None`] for a static typefind function
31    /// ## `name`
32    /// The name for registering
33    /// ## `rank`
34    /// The rank (or importance) of this typefind function
35    /// ## `func`
36    /// The `GstTypeFindFunction` to use
37    /// ## `extensions`
38    /// Optional comma-separated list of extensions
39    ///  that could belong to this type
40    /// ## `possible_caps`
41    /// Optionally the caps that could be returned when typefinding
42    ///  succeeds
43    /// ## `data_notify`
44    /// a `GDestroyNotify` that will be called on `data` when the plugin
45    ///  is unloaded.
46    ///
47    /// # Returns
48    ///
49    /// [`true`] on success, [`false`] otherwise
50    #[doc(alias = "gst_type_find_register")]
51    pub fn register<F>(
52        plugin: Option<&Plugin>,
53        name: &str,
54        rank: Rank,
55        extensions: Option<&str>,
56        possible_caps: Option<&Caps>,
57        func: F,
58    ) -> Result<(), glib::error::BoolError>
59    where
60        F: Fn(&mut TypeFind) + Send + Sync + 'static,
61    {
62        skip_assert_initialized!();
63        unsafe {
64            let func: Box<F> = Box::new(func);
65            let func = Box::into_raw(func);
66
67            let res = ffi::gst_type_find_register(
68                plugin.to_glib_none().0,
69                name.to_glib_none().0,
70                rank.into_glib() as u32,
71                Some(type_find_trampoline::<F>),
72                extensions.to_glib_none().0,
73                possible_caps.to_glib_none().0,
74                func as *mut _,
75                Some(type_find_closure_drop::<F>),
76            );
77
78            glib::result_from_gboolean!(res, "Failed to register typefind factory")
79        }
80    }
81
82    /// Returns the `size` bytes of the stream to identify beginning at offset. If
83    /// offset is a positive number, the offset is relative to the beginning of the
84    /// stream, if offset is a negative number the offset is relative to the end of
85    /// the stream. The returned memory is valid until the typefinding function
86    /// returns and must not be freed.
87    /// ## `offset`
88    /// The offset
89    /// ## `size`
90    /// The number of bytes to return
91    ///
92    /// # Returns
93    ///
94    /// the
95    ///  requested data, or [`None`] if that data is not available.
96    #[doc(alias = "gst_type_find_peek")]
97    pub fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
98        unsafe {
99            let data = ffi::gst_type_find_peek(&mut self.0, offset, size);
100            if data.is_null() {
101                None
102            } else if size == 0 {
103                Some(&[])
104            } else {
105                Some(slice::from_raw_parts(data, size as usize))
106            }
107        }
108    }
109
110    /// If a `GstTypeFindFunction` calls this function it suggests the caps with the
111    /// given probability. A `GstTypeFindFunction` may supply different suggestions
112    /// in one call.
113    /// It is up to the caller of the `GstTypeFindFunction` to interpret these values.
114    /// ## `probability`
115    /// The probability in percent that the suggestion is right
116    /// ## `caps`
117    /// The fixed [`Caps`][crate::Caps] to suggest
118    #[doc(alias = "gst_type_find_suggest")]
119    pub fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
120        unsafe {
121            ffi::gst_type_find_suggest(
122                &mut self.0,
123                probability.into_glib() as u32,
124                caps.to_glib_none().0,
125            );
126        }
127    }
128
129    /// Get the length of the data stream.
130    ///
131    /// # Returns
132    ///
133    /// The length of the data stream, or 0 if it is not available.
134    #[doc(alias = "get_length")]
135    #[doc(alias = "gst_type_find_get_length")]
136    pub fn length(&mut self) -> Option<u64> {
137        unsafe {
138            let len = ffi::gst_type_find_get_length(&mut self.0);
139            if len == 0 {
140                None
141            } else {
142                Some(len)
143            }
144        }
145    }
146}
147
148impl TypeFindFactory {
149    /// Calls the `GstTypeFindFunction` associated with this factory.
150    /// ## `find`
151    /// a properly setup [`TypeFind`][crate::TypeFind] entry. The get_data
152    ///  and suggest_type members must be set.
153    #[doc(alias = "gst_type_find_factory_call_function")]
154    pub fn call_function<T: TypeFindImpl + ?Sized>(&self, mut find: &mut T) {
155        unsafe {
156            let find_ptr = &mut find as *mut &mut T as glib::ffi::gpointer;
157            let mut find = ffi::GstTypeFind {
158                peek: Some(type_find_peek::<T>),
159                suggest: Some(type_find_suggest::<T>),
160                data: find_ptr,
161                get_length: Some(type_find_get_length::<T>),
162                _gst_reserved: [ptr::null_mut(); 4],
163            };
164
165            ffi::gst_type_find_factory_call_function(self.to_glib_none().0, &mut find)
166        }
167    }
168}
169
170unsafe extern "C" fn type_find_trampoline<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
171    find: *mut ffi::GstTypeFind,
172    user_data: glib::ffi::gpointer,
173) {
174    let func: &F = &*(user_data as *const F);
175
176    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
177        func(&mut *(find as *mut TypeFind));
178    }));
179
180    if let Err(err) = panic_result {
181        let cause = err
182            .downcast_ref::<&str>()
183            .copied()
184            .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
185        if let Some(cause) = cause {
186            crate::error!(
187                crate::CAT_RUST,
188                "Failed to call typefind function due to panic: {}",
189                cause
190            );
191        } else {
192            crate::error!(
193                crate::CAT_RUST,
194                "Failed to call typefind function due to panic"
195            );
196        }
197    }
198}
199
200unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
201    data: glib::ffi::gpointer,
202) {
203    let _ = Box::<F>::from_raw(data as *mut _);
204}
205
206unsafe extern "C" fn type_find_peek<T: TypeFindImpl + ?Sized>(
207    data: glib::ffi::gpointer,
208    offset: i64,
209    size: u32,
210) -> *const u8 {
211    let find = &mut *(data as *mut &mut T);
212
213    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
214        match find.peek(offset, size) {
215            None => ptr::null(),
216            Some(data) => data.as_ptr(),
217        }
218    }));
219
220    match panic_result {
221        Ok(res) => res,
222        Err(err) => {
223            let cause = err
224                .downcast_ref::<&str>()
225                .copied()
226                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
227            if let Some(cause) = cause {
228                crate::error!(
229                    crate::CAT_RUST,
230                    "Failed to call typefind peek function due to panic: {}",
231                    cause
232                );
233            } else {
234                crate::error!(
235                    crate::CAT_RUST,
236                    "Failed to call typefind peek function due to panic"
237                );
238            }
239
240            ptr::null()
241        }
242    }
243}
244
245unsafe extern "C" fn type_find_suggest<T: TypeFindImpl + ?Sized>(
246    data: glib::ffi::gpointer,
247    probability: u32,
248    caps: *mut ffi::GstCaps,
249) {
250    let find = &mut *(data as *mut &mut T);
251
252    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
253        find.suggest(from_glib(probability as i32), &from_glib_borrow(caps));
254    }));
255
256    if let Err(err) = panic_result {
257        let cause = err
258            .downcast_ref::<&str>()
259            .copied()
260            .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
261        if let Some(cause) = cause {
262            crate::error!(
263                crate::CAT_RUST,
264                "Failed to call typefind suggest function due to panic: {}",
265                cause
266            );
267        } else {
268            crate::error!(
269                crate::CAT_RUST,
270                "Failed to call typefind suggest function due to panic"
271            );
272        }
273    }
274}
275
276unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>(
277    data: glib::ffi::gpointer,
278) -> u64 {
279    let find = &*(data as *mut &mut T);
280
281    let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
282        find.length().unwrap_or(u64::MAX)
283    }));
284
285    match panic_result {
286        Ok(res) => res,
287        Err(err) => {
288            let cause = err
289                .downcast_ref::<&str>()
290                .copied()
291                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
292            if let Some(cause) = cause {
293                crate::error!(
294                    crate::CAT_RUST,
295                    "Failed to call typefind length function due to panic: {}",
296                    cause
297                );
298            } else {
299                crate::error!(
300                    crate::CAT_RUST,
301                    "Failed to call typefind length function due to panic"
302                );
303            }
304
305            u64::MAX
306        }
307    }
308}
309
310#[derive(Debug)]
311pub struct SliceTypeFind<T: AsRef<[u8]>> {
312    pub probability: Option<TypeFindProbability>,
313    pub caps: Option<Caps>,
314    data: T,
315}
316
317impl<T: AsRef<[u8]>> SliceTypeFind<T> {
318    pub fn new(data: T) -> SliceTypeFind<T> {
319        assert_initialized_main_thread!();
320        SliceTypeFind {
321            probability: None,
322            caps: None,
323            data,
324        }
325    }
326
327    pub fn run(&mut self) {
328        let factories = TypeFindFactory::factories();
329
330        for factory in factories {
331            factory.call_function(self);
332            if let Some(prob) = self.probability {
333                if prob >= TypeFindProbability::Maximum {
334                    break;
335                }
336            }
337        }
338    }
339
340    pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) {
341        assert_initialized_main_thread!();
342        let mut t = SliceTypeFind {
343            probability: None,
344            caps: None,
345            data,
346        };
347
348        t.run();
349
350        (t.probability.unwrap_or(TypeFindProbability::None), t.caps)
351    }
352}
353
354impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> {
355    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
356        let data = self.data.as_ref();
357        let len = data.len();
358
359        let offset = if offset >= 0 {
360            usize::try_from(offset).ok()?
361        } else {
362            let offset = usize::try_from(offset.unsigned_abs()).ok()?;
363            if len < offset {
364                return None;
365            }
366
367            len - offset
368        };
369
370        let size = usize::try_from(size).ok()?;
371        let end_offset = offset.checked_add(size)?;
372        if end_offset <= len {
373            Some(&data[offset..end_offset])
374        } else {
375            None
376        }
377    }
378
379    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
380        match self.probability {
381            None => {
382                self.probability = Some(probability);
383                self.caps = Some(caps.clone());
384            }
385            Some(old_probability) if old_probability < probability => {
386                self.probability = Some(probability);
387                self.caps = Some(caps.clone());
388            }
389            _ => (),
390        }
391    }
392    fn length(&self) -> Option<u64> {
393        Some(self.data.as_ref().len() as u64)
394    }
395}
396
397#[cfg(test)]
398mod tests {
399    use super::*;
400
401    #[test]
402    fn test_typefind_call_function() {
403        crate::init().unwrap();
404
405        let xml_factory = TypeFindFactory::factories()
406            .into_iter()
407            .find(|f| {
408                f.caps()
409                    .map(|c| {
410                        c.structure(0)
411                            .map(|s| s.name() == "application/xml")
412                            .unwrap_or(false)
413                    })
414                    .unwrap_or(false)
415            })
416            .unwrap();
417
418        let data = b"<?xml version=\"1.0\"?><test>test</test>";
419        let data = &data[..];
420        let mut typefind = SliceTypeFind::new(&data);
421        xml_factory.call_function(&mut typefind);
422
423        assert_eq!(
424            typefind.caps,
425            Some(Caps::builder("application/xml").build())
426        );
427        assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum));
428    }
429
430    #[test]
431    fn test_typefind_register() {
432        crate::init().unwrap();
433
434        TypeFind::register(
435            None,
436            "test_typefind",
437            crate::Rank::PRIMARY,
438            None,
439            Some(&Caps::builder("test/test").build()),
440            |typefind| {
441                assert_eq!(typefind.length(), Some(8));
442                let mut found = false;
443                if let Some(data) = typefind.peek(0, 8) {
444                    if data == b"abcdefgh" {
445                        found = true;
446                    }
447                }
448
449                if found {
450                    typefind.suggest(
451                        TypeFindProbability::Likely,
452                        &Caps::builder("test/test").build(),
453                    );
454                }
455            },
456        )
457        .unwrap();
458
459        let data = b"abcdefgh";
460        let data = &data[..];
461        let (probability, caps) = SliceTypeFind::type_find(data);
462
463        assert_eq!(caps, Some(Caps::builder("test/test").build()));
464        assert_eq!(probability, TypeFindProbability::Likely);
465    }
466}