Skip to main content

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::{Caps, Plugin, Rank, TypeFindFactory, TypeFindProbability, ffi};
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    ///
90    /// # Returns
91    ///
92    /// the
93    ///  requested data, or [`None`] if that data is not available.
94    #[doc(alias = "gst_type_find_peek")]
95    pub fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
96        unsafe {
97            let data = ffi::gst_type_find_peek(&mut self.0, offset, size);
98            if data.is_null() {
99                None
100            } else if size == 0 {
101                Some(&[])
102            } else {
103                Some(slice::from_raw_parts(data, size as usize))
104            }
105        }
106    }
107
108    /// If a `GstTypeFindFunction` calls this function it suggests the caps with the
109    /// given probability. A `GstTypeFindFunction` may supply different suggestions
110    /// in one call.
111    /// It is up to the caller of the `GstTypeFindFunction` to interpret these values.
112    /// ## `probability`
113    /// The probability in percent that the suggestion is right
114    /// ## `caps`
115    /// The fixed [`Caps`][crate::Caps] to suggest
116    #[doc(alias = "gst_type_find_suggest")]
117    pub fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
118        unsafe {
119            ffi::gst_type_find_suggest(
120                &mut self.0,
121                probability.into_glib() as u32,
122                caps.to_glib_none().0,
123            );
124        }
125    }
126
127    /// Get the length of the data stream.
128    ///
129    /// # Returns
130    ///
131    /// The length of the data stream, or 0 if it is not available.
132    #[doc(alias = "get_length")]
133    #[doc(alias = "gst_type_find_get_length")]
134    pub fn length(&mut self) -> Option<u64> {
135        unsafe {
136            let len = ffi::gst_type_find_get_length(&mut self.0);
137            if len == 0 { None } else { Some(len) }
138        }
139    }
140}
141
142impl TypeFindFactory {
143    /// Calls the `GstTypeFindFunction` associated with this factory.
144    /// ## `find`
145    /// a properly setup [`TypeFind`][crate::TypeFind] entry. The get_data
146    ///  and suggest_type members must be set.
147    #[doc(alias = "gst_type_find_factory_call_function")]
148    pub fn call_function<T: TypeFindImpl + ?Sized>(&self, mut find: &mut T) {
149        unsafe {
150            let find_ptr = &mut find as *mut &mut T as glib::ffi::gpointer;
151            let mut find = ffi::GstTypeFind {
152                peek: Some(type_find_peek::<T>),
153                suggest: Some(type_find_suggest::<T>),
154                data: find_ptr,
155                get_length: Some(type_find_get_length::<T>),
156                _gst_reserved: [ptr::null_mut(); 4],
157            };
158
159            ffi::gst_type_find_factory_call_function(self.to_glib_none().0, &mut find)
160        }
161    }
162}
163
164unsafe extern "C" fn type_find_trampoline<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
165    find: *mut ffi::GstTypeFind,
166    user_data: glib::ffi::gpointer,
167) {
168    unsafe {
169        let func: &F = &*(user_data as *const F);
170
171        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
172            func(&mut *(find as *mut TypeFind));
173        }));
174
175        if let Err(err) = panic_result {
176            let cause = err
177                .downcast_ref::<&str>()
178                .copied()
179                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
180            if let Some(cause) = cause {
181                crate::error!(
182                    crate::CAT_RUST,
183                    "Failed to call typefind function due to panic: {}",
184                    cause
185                );
186            } else {
187                crate::error!(
188                    crate::CAT_RUST,
189                    "Failed to call typefind function due to panic"
190                );
191            }
192        }
193    }
194}
195
196unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
197    data: glib::ffi::gpointer,
198) {
199    unsafe {
200        let _ = Box::<F>::from_raw(data as *mut _);
201    }
202}
203
204unsafe extern "C" fn type_find_peek<T: TypeFindImpl + ?Sized>(
205    data: glib::ffi::gpointer,
206    offset: i64,
207    size: u32,
208) -> *const u8 {
209    unsafe {
210        let find = &mut *(data as *mut &mut T);
211
212        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
213            match find.peek(offset, size) {
214                None => ptr::null(),
215                Some(data) => data.as_ptr(),
216            }
217        }));
218
219        match panic_result {
220            Ok(res) => res,
221            Err(err) => {
222                let cause = err
223                    .downcast_ref::<&str>()
224                    .copied()
225                    .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
226                if let Some(cause) = cause {
227                    crate::error!(
228                        crate::CAT_RUST,
229                        "Failed to call typefind peek function due to panic: {}",
230                        cause
231                    );
232                } else {
233                    crate::error!(
234                        crate::CAT_RUST,
235                        "Failed to call typefind peek function due to panic"
236                    );
237                }
238
239                ptr::null()
240            }
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    unsafe {
251        let find = &mut *(data as *mut &mut T);
252
253        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
254            find.suggest(from_glib(probability as i32), &from_glib_borrow(caps));
255        }));
256
257        if let Err(err) = panic_result {
258            let cause = err
259                .downcast_ref::<&str>()
260                .copied()
261                .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
262            if let Some(cause) = cause {
263                crate::error!(
264                    crate::CAT_RUST,
265                    "Failed to call typefind suggest function due to panic: {}",
266                    cause
267                );
268            } else {
269                crate::error!(
270                    crate::CAT_RUST,
271                    "Failed to call typefind suggest function due to panic"
272                );
273            }
274        }
275    }
276}
277
278unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>(
279    data: glib::ffi::gpointer,
280) -> u64 {
281    unsafe {
282        let find = &*(data as *mut &mut T);
283
284        let panic_result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
285            find.length().unwrap_or(u64::MAX)
286        }));
287
288        match panic_result {
289            Ok(res) => res,
290            Err(err) => {
291                let cause = err
292                    .downcast_ref::<&str>()
293                    .copied()
294                    .or_else(|| err.downcast_ref::<String>().map(|s| s.as_str()));
295                if let Some(cause) = cause {
296                    crate::error!(
297                        crate::CAT_RUST,
298                        "Failed to call typefind length function due to panic: {}",
299                        cause
300                    );
301                } else {
302                    crate::error!(
303                        crate::CAT_RUST,
304                        "Failed to call typefind length function due to panic"
305                    );
306                }
307
308                u64::MAX
309            }
310        }
311    }
312}
313
314#[derive(Debug)]
315pub struct SliceTypeFind<T: AsRef<[u8]>> {
316    pub probability: Option<TypeFindProbability>,
317    pub caps: Option<Caps>,
318    data: T,
319}
320
321impl<T: AsRef<[u8]>> SliceTypeFind<T> {
322    pub fn new(data: T) -> SliceTypeFind<T> {
323        assert_initialized_main_thread!();
324        SliceTypeFind {
325            probability: None,
326            caps: None,
327            data,
328        }
329    }
330
331    pub fn run(&mut self) {
332        let factories = TypeFindFactory::factories();
333
334        for factory in factories {
335            factory.call_function(self);
336            if let Some(prob) = self.probability
337                && prob >= TypeFindProbability::Maximum
338            {
339                break;
340            }
341        }
342    }
343
344    pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) {
345        assert_initialized_main_thread!();
346        let mut t = SliceTypeFind {
347            probability: None,
348            caps: None,
349            data,
350        };
351
352        t.run();
353
354        (t.probability.unwrap_or(TypeFindProbability::None), t.caps)
355    }
356}
357
358impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> {
359    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
360        let data = self.data.as_ref();
361        let len = data.len();
362
363        let offset = if offset >= 0 {
364            usize::try_from(offset).ok()?
365        } else {
366            let offset = usize::try_from(offset.unsigned_abs()).ok()?;
367            if len < offset {
368                return None;
369            }
370
371            len - offset
372        };
373
374        let size = usize::try_from(size).ok()?;
375        let end_offset = offset.checked_add(size)?;
376        if end_offset <= len {
377            Some(&data[offset..end_offset])
378        } else {
379            None
380        }
381    }
382
383    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
384        match self.probability {
385            None => {
386                self.probability = Some(probability);
387                self.caps = Some(caps.clone());
388            }
389            Some(old_probability) if old_probability < probability => {
390                self.probability = Some(probability);
391                self.caps = Some(caps.clone());
392            }
393            _ => (),
394        }
395    }
396    fn length(&self) -> Option<u64> {
397        Some(self.data.as_ref().len() as u64)
398    }
399}
400
401#[cfg(test)]
402mod tests {
403    use super::*;
404
405    #[test]
406    fn test_typefind_call_function() {
407        crate::init().unwrap();
408
409        let xml_factory = TypeFindFactory::factories()
410            .into_iter()
411            .find(|f| {
412                f.caps()
413                    .map(|c| {
414                        c.structure(0)
415                            .map(|s| s.name() == "application/xml")
416                            .unwrap_or(false)
417                    })
418                    .unwrap_or(false)
419            })
420            .unwrap();
421
422        let data = b"<?xml version=\"1.0\"?><test>test</test>";
423        let data = &data[..];
424        let mut typefind = SliceTypeFind::new(&data);
425        xml_factory.call_function(&mut typefind);
426
427        assert_eq!(
428            typefind.caps,
429            Some(Caps::builder("application/xml").build())
430        );
431        assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum));
432    }
433
434    #[test]
435    fn test_typefind_register() {
436        crate::init().unwrap();
437
438        TypeFind::register(
439            None,
440            "test_typefind",
441            crate::Rank::PRIMARY,
442            None,
443            Some(&Caps::builder("test/test").build()),
444            |typefind| {
445                assert_eq!(typefind.length(), Some(8));
446                let mut found = false;
447                if let Some(data) = typefind.peek(0, 8)
448                    && data == b"abcdefgh"
449                {
450                    found = true;
451                }
452
453                if found {
454                    typefind.suggest(
455                        TypeFindProbability::Likely,
456                        &Caps::builder("test/test").build(),
457                    );
458                }
459            },
460        )
461        .unwrap();
462
463        let data = b"abcdefgh";
464        let data = &data[..];
465        let (probability, caps) = SliceTypeFind::type_find(data);
466
467        assert_eq!(caps, Some(Caps::builder("test/test").build()));
468        assert_eq!(probability, TypeFindProbability::Likely);
469    }
470}