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    func(&mut *(find as *mut TypeFind));
176}
177
178unsafe extern "C" fn type_find_closure_drop<F: Fn(&mut TypeFind) + Send + Sync + 'static>(
179    data: glib::ffi::gpointer,
180) {
181    let _ = Box::<F>::from_raw(data as *mut _);
182}
183
184unsafe extern "C" fn type_find_peek<T: TypeFindImpl + ?Sized>(
185    data: glib::ffi::gpointer,
186    offset: i64,
187    size: u32,
188) -> *const u8 {
189    let find = &mut *(data as *mut &mut T);
190    match find.peek(offset, size) {
191        None => ptr::null(),
192        Some(data) => data.as_ptr(),
193    }
194}
195
196unsafe extern "C" fn type_find_suggest<T: TypeFindImpl + ?Sized>(
197    data: glib::ffi::gpointer,
198    probability: u32,
199    caps: *mut ffi::GstCaps,
200) {
201    let find = &mut *(data as *mut &mut T);
202    find.suggest(from_glib(probability as i32), &from_glib_borrow(caps));
203}
204
205unsafe extern "C" fn type_find_get_length<T: TypeFindImpl + ?Sized>(
206    data: glib::ffi::gpointer,
207) -> u64 {
208    let find = &*(data as *mut &mut T);
209    find.length().unwrap_or(u64::MAX)
210}
211
212#[derive(Debug)]
213pub struct SliceTypeFind<T: AsRef<[u8]>> {
214    pub probability: Option<TypeFindProbability>,
215    pub caps: Option<Caps>,
216    data: T,
217}
218
219impl<T: AsRef<[u8]>> SliceTypeFind<T> {
220    pub fn new(data: T) -> SliceTypeFind<T> {
221        assert_initialized_main_thread!();
222        SliceTypeFind {
223            probability: None,
224            caps: None,
225            data,
226        }
227    }
228
229    pub fn run(&mut self) {
230        let factories = TypeFindFactory::factories();
231
232        for factory in factories {
233            factory.call_function(self);
234            if let Some(prob) = self.probability {
235                if prob >= TypeFindProbability::Maximum {
236                    break;
237                }
238            }
239        }
240    }
241
242    pub fn type_find(data: T) -> (TypeFindProbability, Option<Caps>) {
243        assert_initialized_main_thread!();
244        let mut t = SliceTypeFind {
245            probability: None,
246            caps: None,
247            data,
248        };
249
250        t.run();
251
252        (t.probability.unwrap_or(TypeFindProbability::None), t.caps)
253    }
254}
255
256impl<T: AsRef<[u8]>> TypeFindImpl for SliceTypeFind<T> {
257    fn peek(&mut self, offset: i64, size: u32) -> Option<&[u8]> {
258        let data = self.data.as_ref();
259        let len = data.len();
260
261        let offset = if offset >= 0 {
262            usize::try_from(offset).ok()?
263        } else {
264            let offset = usize::try_from(offset.unsigned_abs()).ok()?;
265            if len < offset {
266                return None;
267            }
268
269            len - offset
270        };
271
272        let size = usize::try_from(size).ok()?;
273        let end_offset = offset.checked_add(size)?;
274        if end_offset <= len {
275            Some(&data[offset..end_offset])
276        } else {
277            None
278        }
279    }
280
281    fn suggest(&mut self, probability: TypeFindProbability, caps: &Caps) {
282        match self.probability {
283            None => {
284                self.probability = Some(probability);
285                self.caps = Some(caps.clone());
286            }
287            Some(old_probability) if old_probability < probability => {
288                self.probability = Some(probability);
289                self.caps = Some(caps.clone());
290            }
291            _ => (),
292        }
293    }
294    fn length(&self) -> Option<u64> {
295        Some(self.data.as_ref().len() as u64)
296    }
297}
298
299#[cfg(test)]
300mod tests {
301    use super::*;
302
303    #[test]
304    fn test_typefind_call_function() {
305        crate::init().unwrap();
306
307        let xml_factory = TypeFindFactory::factories()
308            .into_iter()
309            .find(|f| {
310                f.caps()
311                    .map(|c| {
312                        c.structure(0)
313                            .map(|s| s.name() == "application/xml")
314                            .unwrap_or(false)
315                    })
316                    .unwrap_or(false)
317            })
318            .unwrap();
319
320        let data = b"<?xml version=\"1.0\"?><test>test</test>";
321        let data = &data[..];
322        let mut typefind = SliceTypeFind::new(&data);
323        xml_factory.call_function(&mut typefind);
324
325        assert_eq!(
326            typefind.caps,
327            Some(Caps::builder("application/xml").build())
328        );
329        assert_eq!(typefind.probability, Some(TypeFindProbability::Minimum));
330    }
331
332    #[test]
333    fn test_typefind_register() {
334        crate::init().unwrap();
335
336        TypeFind::register(
337            None,
338            "test_typefind",
339            crate::Rank::PRIMARY,
340            None,
341            Some(&Caps::builder("test/test").build()),
342            |typefind| {
343                assert_eq!(typefind.length(), Some(8));
344                let mut found = false;
345                if let Some(data) = typefind.peek(0, 8) {
346                    if data == b"abcdefgh" {
347                        found = true;
348                    }
349                }
350
351                if found {
352                    typefind.suggest(
353                        TypeFindProbability::Likely,
354                        &Caps::builder("test/test").build(),
355                    );
356                }
357            },
358        )
359        .unwrap();
360
361        let data = b"abcdefgh";
362        let data = &data[..];
363        let (probability, caps) = SliceTypeFind::type_find(data);
364
365        assert_eq!(caps, Some(Caps::builder("test/test").build()));
366        assert_eq!(probability, TypeFindProbability::Likely);
367    }
368}