gstreamer 0.19.7

Rust bindings for GStreamer
Documentation
// Take a look at the license at the top of the repository in the LICENSE file.

use std::ffi::CStr;
use std::fmt;
use std::marker::PhantomData;
use std::mem;

use once_cell::sync::Lazy;

use glib::translate::{
    from_glib, from_glib_full, FromGlibPtrFull, IntoGlib, ToGlibPtr, ToGlibPtrMut,
};
use glib::value::{FromValue, SendValue, ToSendValue, Value};
use glib::StaticType;

use crate::Sample;
use crate::TagError;
use crate::TagMergeMode;
use crate::TagScope;

pub trait Tag<'a> {
    type TagType: StaticType + FromValue<'a> + ToSendValue + Send + Sync;
    fn tag_name<'b>() -> &'b str;
}

macro_rules! impl_tag(
    ($name:ident, $t:ty, $rust_tag:ident, $gst_tag:ident) => {
        pub enum $name {}
        impl<'a> Tag<'a> for $name {
            type TagType = $t;

            fn tag_name<'b>() -> &'b str {
                *$rust_tag
            }
        }

        pub(crate) static $rust_tag: Lazy<&'static str> = Lazy::new(||
            unsafe { CStr::from_ptr(ffi::$gst_tag).to_str().unwrap() });
    };
);

impl_tag!(Title, &'a str, TAG_TITLE, GST_TAG_TITLE);
impl_tag!(
    TitleSortname,
    &'a str,
    TAG_TITLE_SORTNAME,
    GST_TAG_TITLE_SORTNAME
);
impl_tag!(Artist, &'a str, TAG_ARTIST, GST_TAG_ARTIST);
impl_tag!(
    ArtistSortname,
    &'a str,
    TAG_ARTIST_SORTNAME,
    GST_TAG_ARTIST_SORTNAME
);
impl_tag!(Album, &'a str, TAG_ALBUM, GST_TAG_ALBUM);
impl_tag!(
    AlbumSortname,
    &'a str,
    TAG_ALBUM_SORTNAME,
    GST_TAG_ALBUM_SORTNAME
);
impl_tag!(AlbumArtist, &'a str, TAG_ALBUM_ARTIST, GST_TAG_ALBUM_ARTIST);
impl_tag!(
    AlbumArtistSortname,
    &'a str,
    TAG_ALBUM_ARTIST_SORTNAME,
    GST_TAG_ALBUM_ARTIST_SORTNAME
);
impl_tag!(Date, glib::Date, TAG_DATE, GST_TAG_DATE);
impl_tag!(
    DateTime,
    crate::auto::DateTime,
    TAG_DATE_TIME,
    GST_TAG_DATE_TIME
);
impl_tag!(Genre, &'a str, TAG_GENRE, GST_TAG_GENRE);
impl_tag!(Comment, &'a str, TAG_COMMENT, GST_TAG_COMMENT);
impl_tag!(
    ExtendedComment,
    &'a str,
    TAG_EXTENDED_COMMENT,
    GST_TAG_EXTENDED_COMMENT
);
impl_tag!(TrackNumber, u32, TAG_TRACK_NUMBER, GST_TAG_TRACK_NUMBER);
impl_tag!(TrackCount, u32, TAG_TRACK_COUNT, GST_TAG_TRACK_COUNT);
impl_tag!(
    AlbumVolumeNumber,
    u32,
    TAG_ALBUM_VOLUME_NUMBER,
    GST_TAG_ALBUM_VOLUME_NUMBER
);
impl_tag!(
    AlbumVolumeCount,
    u32,
    TAG_ALBUM_VOLUME_COUNT,
    GST_TAG_ALBUM_VOLUME_COUNT
);
impl_tag!(Location, &'a str, TAG_LOCATION, GST_TAG_LOCATION);
impl_tag!(Homepage, &'a str, TAG_HOMEPAGE, GST_TAG_HOMEPAGE);
impl_tag!(Description, &'a str, TAG_DESCRIPTION, GST_TAG_DESCRIPTION);
impl_tag!(Version, &'a str, TAG_VERSION, GST_TAG_VERSION);
impl_tag!(ISRC, &'a str, TAG_ISRC, GST_TAG_ISRC);
impl_tag!(
    Organization,
    &'a str,
    TAG_ORGANIZATION,
    GST_TAG_ORGANIZATION
);
impl_tag!(Copyright, &'a str, TAG_COPYRIGHT, GST_TAG_COPYRIGHT);
impl_tag!(
    CopyrightUri,
    &'a str,
    TAG_COPYRIGHT_URI,
    GST_TAG_COPYRIGHT_URI
);
impl_tag!(EncodedBy, &'a str, TAG_ENCODED_BY, GST_TAG_ENCODED_BY);
impl_tag!(Composer, &'a str, TAG_COMPOSER, GST_TAG_COMPOSER);
impl_tag!(Conductor, &'a str, TAG_CONDUCTOR, GST_TAG_CONDUCTOR);
impl_tag!(Contact, &'a str, TAG_CONTACT, GST_TAG_CONTACT);
impl_tag!(License, &'a str, TAG_LICENSE, GST_TAG_LICENSE);
impl_tag!(LicenseUri, &'a str, TAG_LICENSE_URI, GST_TAG_LICENSE_URI);
impl_tag!(Performer, &'a str, TAG_PERFORMER, GST_TAG_PERFORMER);
impl_tag!(Duration, crate::ClockTime, TAG_DURATION, GST_TAG_DURATION);
impl_tag!(Codec, &'a str, TAG_CODEC, GST_TAG_CODEC);
impl_tag!(VideoCodec, &'a str, TAG_VIDEO_CODEC, GST_TAG_VIDEO_CODEC);
impl_tag!(AudioCodec, &'a str, TAG_AUDIO_CODEC, GST_TAG_AUDIO_CODEC);
impl_tag!(
    SubtitleCodec,
    &'a str,
    TAG_SUBTITLE_CODEC,
    GST_TAG_SUBTITLE_CODEC
);
impl_tag!(
    ContainerFormat,
    &'a str,
    TAG_CONTAINER_FORMAT,
    GST_TAG_CONTAINER_FORMAT
);
impl_tag!(Bitrate, u32, TAG_BITRATE, GST_TAG_BITRATE);
impl_tag!(
    NominalBitrate,
    u32,
    TAG_NOMINAL_BITRATE,
    GST_TAG_NOMINAL_BITRATE
);
impl_tag!(
    MinimumBitrate,
    u32,
    TAG_MINIMUM_BITRATE,
    GST_TAG_MINIMUM_BITRATE
);
impl_tag!(
    MaximumBitrate,
    u32,
    TAG_MAXIMUM_BITRATE,
    GST_TAG_MAXIMUM_BITRATE
);
impl_tag!(Serial, u32, TAG_SERIAL, GST_TAG_SERIAL);
impl_tag!(Encoder, &'a str, TAG_ENCODER, GST_TAG_ENCODER);
impl_tag!(
    EncoderVersion,
    u32,
    TAG_ENCODER_VERSION,
    GST_TAG_ENCODER_VERSION
);
impl_tag!(TrackGain, f64, TAG_TRACK_GAIN, GST_TAG_TRACK_GAIN);
impl_tag!(TrackPeak, f64, TAG_TRACK_PEAK, GST_TAG_TRACK_PEAK);
impl_tag!(AlbumGain, f64, TAG_ALBUM_GAIN, GST_TAG_ALBUM_GAIN);
impl_tag!(AlbumPeak, f64, TAG_ALBUM_PEAK, GST_TAG_ALBUM_PEAK);
impl_tag!(
    ReferenceLevel,
    f64,
    TAG_REFERENCE_LEVEL,
    GST_TAG_REFERENCE_LEVEL
);
// TODO: Should ideally enforce this to be ISO-639
impl_tag!(
    LanguageCode,
    &'a str,
    TAG_LANGUAGE_CODE,
    GST_TAG_LANGUAGE_CODE
);
impl_tag!(
    LanguageName,
    &'a str,
    TAG_LANGUAGE_NAME,
    GST_TAG_LANGUAGE_NAME
);
impl_tag!(Image, Sample, TAG_IMAGE, GST_TAG_IMAGE);
impl_tag!(
    PreviewImage,
    Sample,
    TAG_PREVIEW_IMAGE,
    GST_TAG_PREVIEW_IMAGE
);
impl_tag!(Attachment, Sample, TAG_ATTACHMENT, GST_TAG_ATTACHMENT);
impl_tag!(
    BeatsPerMinute,
    f64,
    TAG_BEATS_PER_MINUTE,
    GST_TAG_BEATS_PER_MINUTE
);
impl_tag!(Keywords, &'a str, TAG_KEYWORDS, GST_TAG_KEYWORDS);
impl_tag!(
    GeoLocationName,
    &'a str,
    TAG_GEO_LOCATION_NAME,
    GST_TAG_GEO_LOCATION_NAME
);
impl_tag!(
    GeoLocationLatitude,
    f64,
    TAG_GEO_LOCATION_LATITUDE,
    GST_TAG_GEO_LOCATION_LATITUDE
);
impl_tag!(
    GeoLocationLongitute,
    f64,
    TAG_GEO_LOCATION_LONGITUDE,
    GST_TAG_GEO_LOCATION_LONGITUDE
);
impl_tag!(
    GeoLocationElevation,
    f64,
    TAG_GEO_LOCATION_ELEVATION,
    GST_TAG_GEO_LOCATION_ELEVATION
);
impl_tag!(
    GeoLocationCity,
    &'a str,
    TAG_GEO_LOCATION_CITY,
    GST_TAG_GEO_LOCATION_CITY
);
impl_tag!(
    GeoLocationCountry,
    &'a str,
    TAG_GEO_LOCATION_COUNTRY,
    GST_TAG_GEO_LOCATION_COUNTRY
);
impl_tag!(
    GeoLocationSublocation,
    &'a str,
    TAG_GEO_LOCATION_SUBLOCATION,
    GST_TAG_GEO_LOCATION_SUBLOCATION
);
impl_tag!(
    GeoLocationHorizontalError,
    f64,
    TAG_GEO_LOCATION_HORIZONTAL_ERROR,
    GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR
);
impl_tag!(
    GeoLocationMovementDirection,
    f64,
    TAG_GEO_LOCATION_MOVEMENT_DIRECTION,
    GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION
);
impl_tag!(
    GeoLocationMovementSpeed,
    f64,
    TAG_GEO_LOCATION_MOVEMENT_SPEED,
    GST_TAG_GEO_LOCATION_MOVEMENT_SPEED
);
impl_tag!(
    GeoLocationCaptureDirection,
    f64,
    TAG_GEO_LOCATION_CAPTURE_DIRECTION,
    GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION
);
impl_tag!(ShowName, &'a str, TAG_SHOW_NAME, GST_TAG_SHOW_NAME);
impl_tag!(
    ShowSortname,
    &'a str,
    TAG_SHOW_SORTNAME,
    GST_TAG_SHOW_SORTNAME
);
impl_tag!(
    ShowEpisodeNumber,
    u32,
    TAG_SHOW_EPISODE_NUMBER,
    GST_TAG_SHOW_EPISODE_NUMBER
);
impl_tag!(
    ShowSeasonNumber,
    u32,
    TAG_SHOW_SEASON_NUMBER,
    GST_TAG_SHOW_SEASON_NUMBER
);
impl_tag!(Lyrics, &'a str, TAG_LYRICS, GST_TAG_LYRICS);
impl_tag!(
    ComposerSortname,
    &'a str,
    TAG_COMPOSER_SORTNAME,
    GST_TAG_COMPOSER_SORTNAME
);
impl_tag!(Grouping, &'a str, TAG_GROUPING, GST_TAG_GROUPING);
impl_tag!(UserRating, u32, TAG_USER_RATING, GST_TAG_USER_RATING);
impl_tag!(
    DeviceManufacturer,
    &'a str,
    TAG_DEVICE_MANUFACTURER,
    GST_TAG_DEVICE_MANUFACTURER
);
impl_tag!(DeviceModel, &'a str, TAG_DEVICE_MODEL, GST_TAG_DEVICE_MODEL);
impl_tag!(
    ApplicationName,
    &'a str,
    TAG_APPLICATION_NAME,
    GST_TAG_APPLICATION_NAME
);
impl_tag!(
    ApplicationData,
    Sample,
    TAG_APPLICATION_DATA,
    GST_TAG_APPLICATION_DATA
);
impl_tag!(
    ImageOrientation,
    &'a str,
    TAG_IMAGE_ORIENTATION,
    GST_TAG_IMAGE_ORIENTATION
);
impl_tag!(Publisher, &'a str, TAG_PUBLISHER, GST_TAG_PUBLISHER);
impl_tag!(
    InterpretedBy,
    &'a str,
    TAG_INTERPRETED_BY,
    GST_TAG_INTERPRETED_BY
);
impl_tag!(
    MidiBaseNote,
    &'a str,
    TAG_MIDI_BASE_NOTE,
    GST_TAG_MIDI_BASE_NOTE
);
impl_tag!(PrivateData, Sample, TAG_PRIVATE_DATA, GST_TAG_PRIVATE_DATA);

mini_object_wrapper!(TagList, TagListRef, ffi::GstTagList, || {
    ffi::gst_tag_list_get_type()
});

impl TagList {
    #[doc(alias = "gst_tag_list_new_empty")]
    pub fn new() -> Self {
        assert_initialized_main_thread!();
        unsafe { from_glib_full(ffi::gst_tag_list_new_empty()) }
    }
}

impl Default for TagList {
    fn default() -> Self {
        Self::new()
    }
}

#[derive(Debug, Clone)]
#[repr(transparent)]
pub struct TagValue<T>(SendValue, PhantomData<T>);

impl<T> TagValue<T> {
    pub fn get<'a>(&'a self) -> T
    where
        T: StaticType + FromValue<'a>,
    {
        self.0.get().expect("Invalid tag type")
    }
}

impl TagListRef {
    #[doc(alias = "gst_tag_list_add")]
    pub fn add<'a, T: Tag<'a>>(&mut self, value: &T::TagType, mode: TagMergeMode) {
        // result can be safely ignored here as `value`'s type is tied to `T::tag_name()`
        let v = <T::TagType as ToSendValue>::to_send_value(value);
        let _res = self.add_value(T::tag_name(), &v, mode);
    }

    #[doc(alias = "gst_tag_list_add")]
    pub fn add_generic<T: ToSendValue + Sync>(
        &mut self,
        tag_name: &str,
        value: T,
        mode: TagMergeMode,
    ) -> Result<(), TagError> {
        let v = value.to_send_value();
        self.add_value(tag_name, &v, mode)
    }

    #[doc(alias = "gst_tag_list_add_value")]
    pub fn add_value(
        &mut self,
        tag_name: &str,
        value: &glib::SendValue,
        mode: TagMergeMode,
    ) -> Result<(), TagError> {
        unsafe {
            let tag_name = tag_name.to_glib_none();

            let tag_type: glib::Type = from_glib(ffi::gst_tag_get_type(tag_name.0));
            if tag_type != value.type_() {
                return Err(TagError::TypeMismatch);
            }

            ffi::gst_tag_list_add_value(
                self.as_mut_ptr(),
                mode.into_glib(),
                tag_name.0,
                value.to_glib_none().0,
            );
        }

        Ok(())
    }

    #[doc(alias = "gst_tag_list_remove_tag")]
    pub fn remove<'a, T: Tag<'a>>(&mut self) {
        self.remove_generic(T::tag_name());
    }

    #[doc(alias = "gst_tag_list_remove_tag")]
    pub fn remove_generic(&mut self, tag_name: &str) {
        unsafe {
            let tag_name = tag_name.to_glib_none();

            ffi::gst_tag_list_remove_tag(self.as_mut_ptr(), tag_name.0);
        }
    }

    #[doc(alias = "gst_tag_list_get")]
    pub fn get<'a, T: Tag<'a>>(&self) -> Option<TagValue<T::TagType>> {
        self.generic(T::tag_name()).map(|value| {
            if !value.is::<T::TagType>() {
                panic!(
                    "TagListRef::get type mismatch for tag {}: {}",
                    T::tag_name(),
                    value.type_()
                );
            }
            TagValue(value, PhantomData)
        })
    }

    #[doc(alias = "gst_tag_list_get")]
    #[doc(alias = "get_generic")]
    pub fn generic(&self, tag_name: &str) -> Option<SendValue> {
        unsafe {
            let mut value: mem::MaybeUninit<SendValue> = mem::MaybeUninit::zeroed();

            let found: bool = from_glib(ffi::gst_tag_list_copy_value(
                (*value.as_mut_ptr()).to_glib_none_mut().0,
                self.as_ptr(),
                tag_name.to_glib_none().0,
            ));

            if !found {
                return None;
            }

            Some(value.assume_init())
        }
    }

    #[doc(alias = "gst_tag_list_n_tags")]
    pub fn n_tags(&self) -> u32 {
        unsafe { ffi::gst_tag_list_n_tags(self.as_ptr()) as u32 }
    }

    #[doc(alias = "gst_tag_list_nth_tag_name")]
    pub fn nth_tag_name(&self, idx: u32) -> Option<&str> {
        if idx >= self.n_tags() {
            return None;
        }

        unsafe {
            let name = ffi::gst_tag_list_nth_tag_name(self.as_ptr(), idx);
            assert!(!name.is_null());
            Some(CStr::from_ptr(name).to_str().unwrap())
        }
    }

    #[doc(alias = "get_index")]
    #[doc(alias = "gst_tag_list_get_index")]
    pub fn index<'a, T: Tag<'a>>(&self, idx: u32) -> Option<&'a TagValue<T::TagType>> {
        self.index_generic(T::tag_name(), idx).map(|value| {
            if !value.is::<T::TagType>() {
                panic!(
                    "TagListRef::get_index type mismatch for tag {}: {}",
                    T::tag_name(),
                    value.type_()
                );
            }
            unsafe { &*(value as *const SendValue as *const TagValue<T::TagType>) }
        })
    }

    #[doc(alias = "get_index_generic")]
    #[doc(alias = "gst_tag_list_get_index")]
    pub fn index_generic<'a>(&'a self, tag_name: &str, idx: u32) -> Option<&'a SendValue> {
        unsafe {
            let value =
                ffi::gst_tag_list_get_value_index(self.as_ptr(), tag_name.to_glib_none().0, idx);

            if value.is_null() {
                return None;
            }

            Some(&*(value as *const SendValue))
        }
    }

    #[doc(alias = "get_size")]
    #[doc(alias = "gst_tag_list_get_tag_size")]
    pub fn size<'a, T: Tag<'a>>(&self) -> u32 {
        self.size_by_name(T::tag_name())
    }

    #[doc(alias = "get_size_by_name")]
    #[doc(alias = "gst_tag_list_get_tag_size")]
    pub fn size_by_name(&self, tag_name: &str) -> u32 {
        unsafe { ffi::gst_tag_list_get_tag_size(self.as_ptr(), tag_name.to_glib_none().0) }
    }

    pub fn iter_tag<'a, T: Tag<'a>>(&'a self) -> TagIter<'a, T> {
        TagIter::new(self)
    }

    pub fn iter_tag_generic<'a>(&'a self, tag_name: &'a str) -> GenericTagIter<'a> {
        GenericTagIter::new(self, tag_name)
    }

    pub fn iter_generic(&self) -> GenericIter {
        GenericIter::new(self)
    }

    pub fn iter(&self) -> Iter {
        Iter::new(self)
    }

    #[doc(alias = "gst_tag_list_insert")]
    pub fn insert(&mut self, other: &TagListRef, mode: TagMergeMode) {
        unsafe { ffi::gst_tag_list_insert(self.as_mut_ptr(), other.as_ptr(), mode.into_glib()) }
    }

    #[doc(alias = "gst_tag_list_merge")]
    pub fn merge(&self, other: &TagListRef, mode: TagMergeMode) -> TagList {
        unsafe {
            from_glib_full(ffi::gst_tag_list_merge(
                self.as_ptr(),
                other.as_ptr(),
                mode.into_glib(),
            ))
        }
    }

    #[doc(alias = "get_scope")]
    #[doc(alias = "gst_tag_list_get_scope")]
    pub fn scope(&self) -> TagScope {
        unsafe { from_glib(ffi::gst_tag_list_get_scope(self.as_ptr())) }
    }

    #[doc(alias = "gst_tag_list_set_scope")]
    pub fn set_scope(&mut self, scope: TagScope) {
        unsafe { ffi::gst_tag_list_set_scope(self.as_mut_ptr(), scope.into_glib()) }
    }
}

impl fmt::Debug for TagList {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        <TagListRef as fmt::Debug>::fmt(self, f)
    }
}

impl fmt::Display for TagList {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        <TagListRef as fmt::Display>::fmt(self, f)
    }
}

impl PartialEq for TagList {
    fn eq(&self, other: &TagList) -> bool {
        TagListRef::eq(self, other)
    }
}

impl Eq for TagList {}

impl PartialEq<TagListRef> for TagList {
    fn eq(&self, other: &TagListRef) -> bool {
        TagListRef::eq(self, other)
    }
}

impl PartialEq<TagList> for TagListRef {
    fn eq(&self, other: &TagList) -> bool {
        TagListRef::eq(other, self)
    }
}

impl fmt::Debug for TagListRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let mut debug = f.debug_struct("TagList");

        for (key, value) in self.iter() {
            debug.field(key, &value);
        }

        debug.finish()
    }
}

impl fmt::Display for TagListRef {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        let s =
            unsafe { glib::GString::from_glib_full(ffi::gst_tag_list_to_string(self.as_ptr())) };
        f.write_str(&s)
    }
}

impl PartialEq for TagListRef {
    #[doc(alias = "gst_tag_list_is_equal")]
    fn eq(&self, other: &TagListRef) -> bool {
        unsafe { from_glib(ffi::gst_tag_list_is_equal(self.as_ptr(), other.as_ptr())) }
    }
}

impl Eq for TagListRef {}

#[derive(Debug)]
pub struct TagIter<'a, T: Tag<'a>> {
    taglist: &'a TagListRef,
    idx: usize,
    size: usize,
    phantom: PhantomData<T>,
}

impl<'a, T: Tag<'a>> TagIter<'a, T> {
    fn new(taglist: &'a TagListRef) -> TagIter<'a, T> {
        skip_assert_initialized!();
        TagIter {
            taglist,
            idx: 0,
            size: taglist.size::<T>() as usize,
            phantom: PhantomData,
        }
    }
}

impl<'a, T: Tag<'a>> Iterator for TagIter<'a, T>
where
    <T as Tag<'a>>::TagType: 'a,
    T: 'a,
{
    type Item = &'a TagValue<T::TagType>;

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx >= self.size {
            return None;
        }

        let item = self.taglist.index::<T>(self.idx as u32).unwrap();
        self.idx += 1;

        Some(item)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.size - self.idx;

        (remaining, Some(remaining))
    }

    fn count(self) -> usize {
        self.size - self.idx
    }

    fn nth(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.idx.overflowing_add(n);
        if end >= self.size || overflow {
            self.idx = self.size;
            None
        } else {
            self.idx = end + 1;
            Some(self.taglist.index::<T>(end as u32).unwrap())
        }
    }

    fn last(self) -> Option<Self::Item> {
        if self.idx == self.size {
            None
        } else {
            Some(self.taglist.index::<T>(self.size as u32 - 1).unwrap())
        }
    }
}

impl<'a, T: Tag<'a>> DoubleEndedIterator for TagIter<'a, T>
where
    <T as Tag<'a>>::TagType: 'a,
    T: 'a,
{
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.idx == self.size {
            return None;
        }

        self.size -= 1;
        Some(self.taglist.index::<T>(self.size as u32).unwrap())
    }

    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.size.overflowing_sub(n);
        if end <= self.idx || overflow {
            self.idx = self.size;
            None
        } else {
            self.size = end - 1;
            Some(self.taglist.index::<T>(self.size as u32).unwrap())
        }
    }
}

impl<'a, T: Tag<'a>> ExactSizeIterator for TagIter<'a, T>
where
    <T as Tag<'a>>::TagType: 'a,
    T: 'a,
{
}

impl<'a, T: Tag<'a>> std::iter::FusedIterator for TagIter<'a, T>
where
    <T as Tag<'a>>::TagType: 'a,
    T: 'a,
{
}

#[derive(Debug)]
pub struct GenericTagIter<'a> {
    taglist: &'a TagListRef,
    name: &'a str,
    idx: usize,
    size: usize,
}

impl<'a> GenericTagIter<'a> {
    fn new(taglist: &'a TagListRef, name: &'a str) -> GenericTagIter<'a> {
        skip_assert_initialized!();
        GenericTagIter {
            taglist,
            name,
            idx: 0,
            size: taglist.size_by_name(name) as usize,
        }
    }
}

impl<'a> Iterator for GenericTagIter<'a> {
    type Item = &'a SendValue;

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx >= self.size {
            return None;
        }

        let item = self
            .taglist
            .index_generic(self.name, self.idx as u32)
            .unwrap();
        self.idx += 1;

        Some(item)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.size - self.idx;

        (remaining, Some(remaining))
    }

    fn count(self) -> usize {
        self.size - self.idx
    }

    fn nth(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.idx.overflowing_add(n);
        if end >= self.size || overflow {
            self.idx = self.size;
            None
        } else {
            self.idx = end + 1;
            Some(self.taglist.index_generic(self.name, end as u32).unwrap())
        }
    }

    fn last(self) -> Option<Self::Item> {
        if self.idx == self.size {
            None
        } else {
            Some(
                self.taglist
                    .index_generic(self.name, self.size as u32 - 1)
                    .unwrap(),
            )
        }
    }
}

impl<'a> DoubleEndedIterator for GenericTagIter<'a> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.idx == self.size {
            return None;
        }

        self.size -= 1;
        Some(
            self.taglist
                .index_generic(self.name, self.size as u32)
                .unwrap(),
        )
    }

    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.size.overflowing_sub(n);
        if end <= self.idx || overflow {
            self.idx = self.size;
            None
        } else {
            self.size = end - 1;
            Some(
                self.taglist
                    .index_generic(self.name, self.size as u32)
                    .unwrap(),
            )
        }
    }
}

impl<'a> ExactSizeIterator for GenericTagIter<'a> {}

impl<'a> std::iter::FusedIterator for GenericTagIter<'a> {}

#[derive(Debug)]
pub struct GenericIter<'a> {
    taglist: &'a TagListRef,
    idx: usize,
    size: usize,
}

impl<'a> GenericIter<'a> {
    fn new(taglist: &'a TagListRef) -> GenericIter<'a> {
        skip_assert_initialized!();
        let size = taglist.n_tags();
        GenericIter {
            taglist,
            idx: 0,
            size: if size > 0 { size as usize } else { 0 },
        }
    }
}

impl<'a> Iterator for GenericIter<'a> {
    type Item = (&'a str, GenericTagIter<'a>);

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx >= self.size {
            return None;
        }

        let name = self.taglist.nth_tag_name(self.idx as u32).unwrap();
        let item = (name, self.taglist.iter_tag_generic(name));
        self.idx += 1;

        Some(item)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.size - self.idx;

        (remaining, Some(remaining))
    }

    fn count(self) -> usize {
        self.size - self.idx
    }

    fn nth(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.idx.overflowing_add(n);
        if end >= self.size || overflow {
            self.idx = self.size;
            None
        } else {
            self.idx = end + 1;
            let name = self.taglist.nth_tag_name(end as u32).unwrap();
            Some((name, self.taglist.iter_tag_generic(name)))
        }
    }

    fn last(self) -> Option<Self::Item> {
        if self.idx == self.size {
            None
        } else {
            let name = self.taglist.nth_tag_name(self.size as u32 - 1).unwrap();
            Some((name, self.taglist.iter_tag_generic(name)))
        }
    }
}

impl<'a> DoubleEndedIterator for GenericIter<'a> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.idx == self.size {
            return None;
        }

        self.size -= 1;
        let name = self.taglist.nth_tag_name(self.idx as u32).unwrap();
        Some((name, self.taglist.iter_tag_generic(name)))
    }

    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.size.overflowing_sub(n);
        if end <= self.idx || overflow {
            self.idx = self.size;
            None
        } else {
            self.size = end - 1;
            let name = self.taglist.nth_tag_name(self.size as u32).unwrap();
            Some((name, self.taglist.iter_tag_generic(name)))
        }
    }
}

impl<'a> ExactSizeIterator for GenericIter<'a> {}

impl<'a> std::iter::FusedIterator for GenericIter<'a> {}

#[derive(Debug)]
pub struct Iter<'a> {
    taglist: &'a TagListRef,
    idx: usize,
    size: usize,
}

impl<'a> Iter<'a> {
    fn new(taglist: &'a TagListRef) -> Iter<'a> {
        skip_assert_initialized!();
        let size = taglist.n_tags();
        Iter {
            taglist,
            idx: 0,
            size: if size > 0 { size as usize } else { 0 },
        }
    }
}

impl<'a> Iterator for Iter<'a> {
    type Item = (&'a str, glib::SendValue);

    fn next(&mut self) -> Option<Self::Item> {
        if self.idx >= self.size {
            return None;
        }

        let name = self.taglist.nth_tag_name(self.idx as u32).unwrap();
        let item = (name, self.taglist.generic(name).unwrap());
        self.idx += 1;

        Some(item)
    }

    fn size_hint(&self) -> (usize, Option<usize>) {
        let remaining = self.size - self.idx;

        (remaining, Some(remaining))
    }

    fn count(self) -> usize {
        self.size - self.idx
    }

    fn nth(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.idx.overflowing_add(n);
        if end >= self.size || overflow {
            self.idx = self.size;
            None
        } else {
            self.idx = end + 1;
            let name = self.taglist.nth_tag_name(end as u32).unwrap();
            Some((name, self.taglist.generic(name).unwrap()))
        }
    }

    fn last(self) -> Option<Self::Item> {
        if self.idx == self.size {
            None
        } else {
            let name = self.taglist.nth_tag_name(self.size as u32 - 1).unwrap();
            Some((name, self.taglist.generic(name).unwrap()))
        }
    }
}

impl<'a> DoubleEndedIterator for Iter<'a> {
    fn next_back(&mut self) -> Option<Self::Item> {
        if self.idx == self.size {
            return None;
        }

        self.size -= 1;
        let name = self.taglist.nth_tag_name(self.idx as u32).unwrap();
        Some((name, self.taglist.generic(name).unwrap()))
    }

    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
        let (end, overflow) = self.size.overflowing_sub(n);
        if end <= self.idx || overflow {
            self.idx = self.size;
            None
        } else {
            self.size = end - 1;
            let name = self.taglist.nth_tag_name(self.size as u32).unwrap();
            Some((name, self.taglist.generic(name).unwrap()))
        }
    }
}

impl<'a> ExactSizeIterator for Iter<'a> {}

impl<'a> std::iter::FusedIterator for Iter<'a> {}

#[doc(alias = "gst_tag_exists")]
pub fn tag_exists(name: &str) -> bool {
    skip_assert_initialized!();
    unsafe { from_glib(ffi::gst_tag_exists(name.to_glib_none().0)) }
}

#[doc(alias = "gst_tag_get_type")]
pub fn tag_get_type(name: &str) -> glib::Type {
    skip_assert_initialized!();
    unsafe { from_glib(ffi::gst_tag_get_type(name.to_glib_none().0)) }
}

#[doc(alias = "gst_tag_get_nick")]
pub fn tag_get_nick(name: &str) -> &str {
    skip_assert_initialized!();
    unsafe {
        let ptr = ffi::gst_tag_get_nick(name.to_glib_none().0);
        CStr::from_ptr(ptr).to_str().unwrap()
    }
}

#[doc(alias = "gst_tag_get_description")]
pub fn tag_get_description<'b>(name: &str) -> Option<&'b str> {
    skip_assert_initialized!();
    unsafe {
        let ptr = ffi::gst_tag_get_description(name.to_glib_none().0);

        if ptr.is_null() {
            None
        } else {
            Some(CStr::from_ptr(ptr).to_str().unwrap())
        }
    }
}

#[doc(alias = "gst_tag_get_flag")]
pub fn tag_get_flag(name: &str) -> crate::TagFlag {
    skip_assert_initialized!();
    unsafe { from_glib(ffi::gst_tag_get_flag(name.to_glib_none().0)) }
}

pub trait CustomTag<'a>: Tag<'a> {
    const FLAG: crate::TagFlag;
    const NICK: &'static str;
    const DESCRIPTION: &'static str;

    fn merge_func(src: &Value) -> Value {
        skip_assert_initialized!();
        merge_use_first(src)
    }
}

#[doc(alias = "gst_tag_register")]
pub fn register<T: for<'a> CustomTag<'a>>() {
    assert!(!tag_exists(T::tag_name()));

    unsafe extern "C" fn merge_func_trampoline<T: for<'a> CustomTag<'a>>(
        dest: *mut glib::gobject_ffi::GValue,
        src: *const glib::gobject_ffi::GValue,
    ) {
        *dest = T::merge_func(&*(src as *const Value)).into_raw();
    }

    unsafe {
        ffi::gst_tag_register(
            T::tag_name().to_glib_none().0,
            T::FLAG.into_glib(),
            T::TagType::static_type().into_glib(),
            T::NICK.to_glib_none().0,
            T::DESCRIPTION.to_glib_none().0,
            Some(merge_func_trampoline::<T>),
        )
    }
}

#[doc(alias = "gst_tag_merge_use_first")]
pub fn merge_use_first(src: &Value) -> Value {
    skip_assert_initialized!();
    assert_eq!(src.type_(), crate::List::static_type());

    unsafe {
        use glib::translate::Uninitialized;

        let mut res = Value::uninitialized();
        ffi::gst_tag_merge_use_first(res.to_glib_none_mut().0, src.to_glib_none().0);
        res
    }
}

#[doc(alias = "gst_tag_merge_strings_with_comma")]
pub fn merge_strings_with_comma(src: &Value) -> Value {
    skip_assert_initialized!();
    assert_eq!(src.type_(), crate::List::static_type());

    unsafe {
        use glib::translate::Uninitialized;

        let mut res = Value::uninitialized();
        ffi::gst_tag_merge_strings_with_comma(res.to_glib_none_mut().0, src.to_glib_none().0);
        res
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::ClockTime;

    #[test]
    fn test_add() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        assert_eq!(tags.to_string(), "taglist;");
        {
            let tags = tags.get_mut().unwrap();
            tags.add::<Title>(&"some title", TagMergeMode::Append);
            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append);
        }
        assert_eq!(
            tags.to_string(),
            "taglist, title=(string)\"some\\ title\", duration=(guint64)120000000000;"
        );
    }

    #[test]
    fn test_get() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        assert_eq!(tags.to_string(), "taglist;");
        {
            let tags = tags.get_mut().unwrap();
            tags.add::<Title>(&"some title", TagMergeMode::Append);
            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append);
        }

        assert_eq!(tags.get::<Title>().unwrap().get(), "some title");
        assert_eq!(
            tags.get::<Duration>().unwrap().get(),
            ClockTime::SECOND * 120,
        );
        assert_eq!(tags.index::<Title>(0).unwrap().get(), "some title");
        assert_eq!(tags.index::<Title>(0).unwrap().get(), "some title");
        assert_eq!(
            tags.index::<Duration>(0).unwrap().get(),
            ClockTime::SECOND * 120,
        );
    }

    #[test]
    fn test_scope() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        assert_eq!(tags.scope(), TagScope::Stream);
        {
            let tags = tags.get_mut().unwrap();
            tags.set_scope(TagScope::Global);
        }
        assert_eq!(tags.scope(), TagScope::Global);
    }

    #[test]
    #[allow(clippy::cognitive_complexity)]
    fn test_generic() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        {
            let tags = tags.get_mut().unwrap();
            assert!(tags
                .add_generic(&TAG_TITLE, "some title", TagMergeMode::Append)
                .is_ok());
            assert!(tags
                .add_generic(&TAG_TITLE, "second title", TagMergeMode::Append)
                .is_ok());
            assert!(tags
                .add_generic(&TAG_DURATION, ClockTime::SECOND * 120, TagMergeMode::Append)
                .is_ok());
            assert!(tags
                .add_generic(&TAG_TITLE, "third title", TagMergeMode::Append)
                .is_ok());

            assert_eq!(
                tags.add_generic(
                    &TAG_IMAGE,
                    "`&[str] instead of `Sample`",
                    TagMergeMode::Append
                ),
                Err(TagError::TypeMismatch),
            );
        }

        assert_eq!(
            tags.index_generic(&TAG_TITLE, 0).unwrap().get(),
            Ok(Some("some title"))
        );
        assert_eq!(
            tags.index_generic(&TAG_TITLE, 1).unwrap().get(),
            Ok(Some("second title"))
        );
        assert_eq!(
            tags.index_generic(&TAG_DURATION, 0).unwrap().get(),
            Ok(Some(ClockTime::SECOND * 120))
        );
        assert_eq!(
            tags.index_generic(&TAG_TITLE, 2).unwrap().get(),
            Ok(Some("third title"))
        );

        assert_eq!(
            tags.generic(&TAG_TITLE).unwrap().get(),
            Ok(Some("some title, second title, third title"))
        );

        assert_eq!(tags.n_tags(), 2);
        assert_eq!(tags.nth_tag_name(0), Some(*TAG_TITLE));
        assert_eq!(tags.size_by_name(&TAG_TITLE), 3);
        assert_eq!(tags.nth_tag_name(1), Some(*TAG_DURATION));
        assert_eq!(tags.size_by_name(&TAG_DURATION), 1);

        // GenericTagIter
        let mut title_iter = tags.iter_tag_generic(&TAG_TITLE);
        assert_eq!(title_iter.size_hint(), (3, Some(3)));
        let first_title = title_iter.next().unwrap();
        assert_eq!(first_title.get(), Ok(Some("some title")));
        let second_title = title_iter.next().unwrap();
        assert_eq!(second_title.get(), Ok(Some("second title")));
        let third_title = title_iter.next().unwrap();
        assert_eq!(third_title.get(), Ok(Some("third title")));
        assert!(title_iter.next().is_none());

        // GenericIter
        let mut tag_list_iter = tags.iter_generic();
        assert_eq!(tag_list_iter.size_hint(), (2, Some(2)));

        let (tag_name, mut tag_iter) = tag_list_iter.next().unwrap();
        assert_eq!(tag_name, *TAG_TITLE);
        let first_title = tag_iter.next().unwrap();
        assert_eq!(first_title.get(), Ok(Some("some title")));
        let second_title = tag_iter.next().unwrap();
        assert_eq!(second_title.get(), Ok(Some("second title")));
        let third_title = tag_iter.next().unwrap();
        assert_eq!(third_title.get(), Ok(Some("third title")));
        assert!(tag_iter.next().is_none());

        let (tag_name, mut tag_iter) = tag_list_iter.next().unwrap();
        assert_eq!(tag_name, *TAG_DURATION);
        let first_duration = tag_iter.next().unwrap();
        assert_eq!(first_duration.get(), Ok(Some(ClockTime::SECOND * 120)));
        assert!(tag_iter.next().is_none());

        // Iter
        let mut tag_list_iter = tags.iter();
        assert_eq!(tag_list_iter.size_hint(), (2, Some(2)));

        let (tag_name, tag_value) = tag_list_iter.next().unwrap();
        assert_eq!(tag_name, *TAG_TITLE);
        assert_eq!(
            tag_value.get(),
            Ok(Some("some title, second title, third title"))
        );

        let (tag_name, tag_value) = tag_list_iter.next().unwrap();
        assert_eq!(tag_name, *TAG_DURATION);
        assert_eq!(tag_value.get(), Ok(Some(ClockTime::SECOND * 120)));
        assert!(tag_iter.next().is_none());
    }

    #[test]
    fn test_custom_tags() {
        crate::init().unwrap();

        enum MyCustomTag {}

        impl<'a> Tag<'a> for MyCustomTag {
            type TagType = &'a str;
            fn tag_name<'b>() -> &'b str {
                "my-custom-tag"
            }
        }

        impl<'a> CustomTag<'a> for MyCustomTag {
            const FLAG: crate::TagFlag = crate::TagFlag::Meta;
            const NICK: &'static str = "my custom tag";
            const DESCRIPTION: &'static str = "My own custom tag type for testing";

            fn merge_func(src: &Value) -> Value {
                skip_assert_initialized!();
                merge_strings_with_comma(src)
            }
        }

        register::<MyCustomTag>();

        assert!(tag_exists(MyCustomTag::tag_name()));
        assert_eq!(
            tag_get_type(MyCustomTag::tag_name()),
            <MyCustomTag as Tag>::TagType::static_type()
        );
        assert_eq!(tag_get_nick(MyCustomTag::tag_name()), MyCustomTag::NICK);
        assert_eq!(
            tag_get_description(MyCustomTag::tag_name()),
            Some(MyCustomTag::DESCRIPTION)
        );
        assert_eq!(tag_get_flag(MyCustomTag::tag_name()), MyCustomTag::FLAG);

        let mut tags = TagList::new();
        {
            let tags = tags.get_mut().unwrap();
            tags.add::<MyCustomTag>(&"first one", TagMergeMode::Append);
        }

        assert_eq!(tags.get::<MyCustomTag>().unwrap().get(), "first one");

        {
            let tags = tags.get_mut().unwrap();
            tags.add::<MyCustomTag>(&"second one", TagMergeMode::Append);
        }

        assert_eq!(
            tags.get::<MyCustomTag>().unwrap().get(),
            "first one, second one"
        );
    }

    #[test]
    fn test_display() {
        crate::init().unwrap();

        format!("{}", TagList::new());
    }

    #[test]
    fn test_debug() {
        crate::init().unwrap();

        let mut tags = TagList::new();
        assert_eq!(format!("{:?}", tags), "TagList");
        {
            let tags = tags.get_mut().unwrap();
            tags.add::<Title>(&"some title", TagMergeMode::Append);
            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append);
        }
        assert_eq!(
            format!("{:?}", tags),
            "TagList { title: (gchararray) \"some title\", duration: (guint64) 120000000000 }"
        );
    }
}