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