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