gstreamer/
tags.rs

1// Take a look at the license at the top of the repository in the LICENSE file.
2
3use std::{fmt, marker::PhantomData, mem};
4
5use glib::{
6    prelude::*,
7    translate::*,
8    value::{FromValue, SendValue, ToSendValue, Value},
9};
10
11use crate::{ffi, Sample, TagError, TagMergeMode, TagScope};
12
13pub trait Tag<'a> {
14    type TagType: StaticType + FromValue<'a> + ToSendValue + Send + Sync;
15    const TAG_NAME: &'static glib::GStr;
16}
17
18macro_rules! impl_tag(
19    ($name:ident, $t:ty, $rust_tag:ident, $gst_tag:ident) => {
20        pub enum $name {}
21        impl<'a> Tag<'a> for $name {
22            type TagType = $t;
23            const TAG_NAME: &'static glib::GStr = unsafe { glib::GStr::from_utf8_with_nul_unchecked(ffi::$gst_tag) };
24        }
25    };
26);
27
28impl_tag!(Title, &'a str, TAG_TITLE, GST_TAG_TITLE);
29impl_tag!(
30    TitleSortname,
31    &'a str,
32    TAG_TITLE_SORTNAME,
33    GST_TAG_TITLE_SORTNAME
34);
35impl_tag!(Artist, &'a str, TAG_ARTIST, GST_TAG_ARTIST);
36impl_tag!(
37    ArtistSortname,
38    &'a str,
39    TAG_ARTIST_SORTNAME,
40    GST_TAG_ARTIST_SORTNAME
41);
42impl_tag!(Album, &'a str, TAG_ALBUM, GST_TAG_ALBUM);
43impl_tag!(
44    AlbumSortname,
45    &'a str,
46    TAG_ALBUM_SORTNAME,
47    GST_TAG_ALBUM_SORTNAME
48);
49impl_tag!(AlbumArtist, &'a str, TAG_ALBUM_ARTIST, GST_TAG_ALBUM_ARTIST);
50impl_tag!(
51    AlbumArtistSortname,
52    &'a str,
53    TAG_ALBUM_ARTIST_SORTNAME,
54    GST_TAG_ALBUM_ARTIST_SORTNAME
55);
56impl_tag!(Date, glib::Date, TAG_DATE, GST_TAG_DATE);
57impl_tag!(
58    DateTime,
59    crate::auto::DateTime,
60    TAG_DATE_TIME,
61    GST_TAG_DATE_TIME
62);
63impl_tag!(Genre, &'a str, TAG_GENRE, GST_TAG_GENRE);
64impl_tag!(Comment, &'a str, TAG_COMMENT, GST_TAG_COMMENT);
65impl_tag!(
66    ExtendedComment,
67    &'a str,
68    TAG_EXTENDED_COMMENT,
69    GST_TAG_EXTENDED_COMMENT
70);
71impl_tag!(TrackNumber, u32, TAG_TRACK_NUMBER, GST_TAG_TRACK_NUMBER);
72impl_tag!(TrackCount, u32, TAG_TRACK_COUNT, GST_TAG_TRACK_COUNT);
73impl_tag!(
74    AlbumVolumeNumber,
75    u32,
76    TAG_ALBUM_VOLUME_NUMBER,
77    GST_TAG_ALBUM_VOLUME_NUMBER
78);
79impl_tag!(
80    AlbumVolumeCount,
81    u32,
82    TAG_ALBUM_VOLUME_COUNT,
83    GST_TAG_ALBUM_VOLUME_COUNT
84);
85impl_tag!(Location, &'a str, TAG_LOCATION, GST_TAG_LOCATION);
86impl_tag!(Homepage, &'a str, TAG_HOMEPAGE, GST_TAG_HOMEPAGE);
87impl_tag!(Description, &'a str, TAG_DESCRIPTION, GST_TAG_DESCRIPTION);
88impl_tag!(Version, &'a str, TAG_VERSION, GST_TAG_VERSION);
89impl_tag!(ISRC, &'a str, TAG_ISRC, GST_TAG_ISRC);
90impl_tag!(
91    Organization,
92    &'a str,
93    TAG_ORGANIZATION,
94    GST_TAG_ORGANIZATION
95);
96impl_tag!(Copyright, &'a str, TAG_COPYRIGHT, GST_TAG_COPYRIGHT);
97impl_tag!(
98    CopyrightUri,
99    &'a str,
100    TAG_COPYRIGHT_URI,
101    GST_TAG_COPYRIGHT_URI
102);
103impl_tag!(EncodedBy, &'a str, TAG_ENCODED_BY, GST_TAG_ENCODED_BY);
104impl_tag!(Composer, &'a str, TAG_COMPOSER, GST_TAG_COMPOSER);
105impl_tag!(Conductor, &'a str, TAG_CONDUCTOR, GST_TAG_CONDUCTOR);
106impl_tag!(Contact, &'a str, TAG_CONTACT, GST_TAG_CONTACT);
107impl_tag!(License, &'a str, TAG_LICENSE, GST_TAG_LICENSE);
108impl_tag!(LicenseUri, &'a str, TAG_LICENSE_URI, GST_TAG_LICENSE_URI);
109impl_tag!(Performer, &'a str, TAG_PERFORMER, GST_TAG_PERFORMER);
110impl_tag!(Duration, crate::ClockTime, TAG_DURATION, GST_TAG_DURATION);
111impl_tag!(Codec, &'a str, TAG_CODEC, GST_TAG_CODEC);
112impl_tag!(VideoCodec, &'a str, TAG_VIDEO_CODEC, GST_TAG_VIDEO_CODEC);
113impl_tag!(AudioCodec, &'a str, TAG_AUDIO_CODEC, GST_TAG_AUDIO_CODEC);
114impl_tag!(
115    SubtitleCodec,
116    &'a str,
117    TAG_SUBTITLE_CODEC,
118    GST_TAG_SUBTITLE_CODEC
119);
120impl_tag!(
121    ContainerFormat,
122    &'a str,
123    TAG_CONTAINER_FORMAT,
124    GST_TAG_CONTAINER_FORMAT
125);
126impl_tag!(Bitrate, u32, TAG_BITRATE, GST_TAG_BITRATE);
127impl_tag!(
128    NominalBitrate,
129    u32,
130    TAG_NOMINAL_BITRATE,
131    GST_TAG_NOMINAL_BITRATE
132);
133impl_tag!(
134    MinimumBitrate,
135    u32,
136    TAG_MINIMUM_BITRATE,
137    GST_TAG_MINIMUM_BITRATE
138);
139impl_tag!(
140    MaximumBitrate,
141    u32,
142    TAG_MAXIMUM_BITRATE,
143    GST_TAG_MAXIMUM_BITRATE
144);
145impl_tag!(Serial, u32, TAG_SERIAL, GST_TAG_SERIAL);
146impl_tag!(Encoder, &'a str, TAG_ENCODER, GST_TAG_ENCODER);
147impl_tag!(
148    EncoderVersion,
149    u32,
150    TAG_ENCODER_VERSION,
151    GST_TAG_ENCODER_VERSION
152);
153impl_tag!(TrackGain, f64, TAG_TRACK_GAIN, GST_TAG_TRACK_GAIN);
154impl_tag!(TrackPeak, f64, TAG_TRACK_PEAK, GST_TAG_TRACK_PEAK);
155impl_tag!(AlbumGain, f64, TAG_ALBUM_GAIN, GST_TAG_ALBUM_GAIN);
156impl_tag!(AlbumPeak, f64, TAG_ALBUM_PEAK, GST_TAG_ALBUM_PEAK);
157impl_tag!(
158    ReferenceLevel,
159    f64,
160    TAG_REFERENCE_LEVEL,
161    GST_TAG_REFERENCE_LEVEL
162);
163// TODO: Should ideally enforce this to be ISO-639
164impl_tag!(
165    LanguageCode,
166    &'a str,
167    TAG_LANGUAGE_CODE,
168    GST_TAG_LANGUAGE_CODE
169);
170impl_tag!(
171    LanguageName,
172    &'a str,
173    TAG_LANGUAGE_NAME,
174    GST_TAG_LANGUAGE_NAME
175);
176impl_tag!(Image, Sample, TAG_IMAGE, GST_TAG_IMAGE);
177impl_tag!(
178    PreviewImage,
179    Sample,
180    TAG_PREVIEW_IMAGE,
181    GST_TAG_PREVIEW_IMAGE
182);
183impl_tag!(Attachment, Sample, TAG_ATTACHMENT, GST_TAG_ATTACHMENT);
184impl_tag!(
185    BeatsPerMinute,
186    f64,
187    TAG_BEATS_PER_MINUTE,
188    GST_TAG_BEATS_PER_MINUTE
189);
190impl_tag!(Keywords, &'a str, TAG_KEYWORDS, GST_TAG_KEYWORDS);
191impl_tag!(
192    GeoLocationName,
193    &'a str,
194    TAG_GEO_LOCATION_NAME,
195    GST_TAG_GEO_LOCATION_NAME
196);
197impl_tag!(
198    GeoLocationLatitude,
199    f64,
200    TAG_GEO_LOCATION_LATITUDE,
201    GST_TAG_GEO_LOCATION_LATITUDE
202);
203impl_tag!(
204    GeoLocationLongitude,
205    f64,
206    TAG_GEO_LOCATION_LONGITUDE,
207    GST_TAG_GEO_LOCATION_LONGITUDE
208);
209impl_tag!(
210    GeoLocationElevation,
211    f64,
212    TAG_GEO_LOCATION_ELEVATION,
213    GST_TAG_GEO_LOCATION_ELEVATION
214);
215impl_tag!(
216    GeoLocationCity,
217    &'a str,
218    TAG_GEO_LOCATION_CITY,
219    GST_TAG_GEO_LOCATION_CITY
220);
221impl_tag!(
222    GeoLocationCountry,
223    &'a str,
224    TAG_GEO_LOCATION_COUNTRY,
225    GST_TAG_GEO_LOCATION_COUNTRY
226);
227impl_tag!(
228    GeoLocationSublocation,
229    &'a str,
230    TAG_GEO_LOCATION_SUBLOCATION,
231    GST_TAG_GEO_LOCATION_SUBLOCATION
232);
233impl_tag!(
234    GeoLocationHorizontalError,
235    f64,
236    TAG_GEO_LOCATION_HORIZONTAL_ERROR,
237    GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR
238);
239impl_tag!(
240    GeoLocationMovementDirection,
241    f64,
242    TAG_GEO_LOCATION_MOVEMENT_DIRECTION,
243    GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION
244);
245impl_tag!(
246    GeoLocationMovementSpeed,
247    f64,
248    TAG_GEO_LOCATION_MOVEMENT_SPEED,
249    GST_TAG_GEO_LOCATION_MOVEMENT_SPEED
250);
251impl_tag!(
252    GeoLocationCaptureDirection,
253    f64,
254    TAG_GEO_LOCATION_CAPTURE_DIRECTION,
255    GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION
256);
257impl_tag!(ShowName, &'a str, TAG_SHOW_NAME, GST_TAG_SHOW_NAME);
258impl_tag!(
259    ShowSortname,
260    &'a str,
261    TAG_SHOW_SORTNAME,
262    GST_TAG_SHOW_SORTNAME
263);
264impl_tag!(
265    ShowEpisodeNumber,
266    u32,
267    TAG_SHOW_EPISODE_NUMBER,
268    GST_TAG_SHOW_EPISODE_NUMBER
269);
270impl_tag!(
271    ShowSeasonNumber,
272    u32,
273    TAG_SHOW_SEASON_NUMBER,
274    GST_TAG_SHOW_SEASON_NUMBER
275);
276impl_tag!(Lyrics, &'a str, TAG_LYRICS, GST_TAG_LYRICS);
277impl_tag!(
278    ComposerSortname,
279    &'a str,
280    TAG_COMPOSER_SORTNAME,
281    GST_TAG_COMPOSER_SORTNAME
282);
283impl_tag!(Grouping, &'a str, TAG_GROUPING, GST_TAG_GROUPING);
284impl_tag!(UserRating, u32, TAG_USER_RATING, GST_TAG_USER_RATING);
285impl_tag!(
286    DeviceManufacturer,
287    &'a str,
288    TAG_DEVICE_MANUFACTURER,
289    GST_TAG_DEVICE_MANUFACTURER
290);
291impl_tag!(DeviceModel, &'a str, TAG_DEVICE_MODEL, GST_TAG_DEVICE_MODEL);
292impl_tag!(
293    ApplicationName,
294    &'a str,
295    TAG_APPLICATION_NAME,
296    GST_TAG_APPLICATION_NAME
297);
298impl_tag!(
299    ApplicationData,
300    Sample,
301    TAG_APPLICATION_DATA,
302    GST_TAG_APPLICATION_DATA
303);
304impl_tag!(
305    ImageOrientation,
306    &'a str,
307    TAG_IMAGE_ORIENTATION,
308    GST_TAG_IMAGE_ORIENTATION
309);
310impl_tag!(Publisher, &'a str, TAG_PUBLISHER, GST_TAG_PUBLISHER);
311impl_tag!(
312    InterpretedBy,
313    &'a str,
314    TAG_INTERPRETED_BY,
315    GST_TAG_INTERPRETED_BY
316);
317impl_tag!(
318    MidiBaseNote,
319    &'a str,
320    TAG_MIDI_BASE_NOTE,
321    GST_TAG_MIDI_BASE_NOTE
322);
323impl_tag!(PrivateData, Sample, TAG_PRIVATE_DATA, GST_TAG_PRIVATE_DATA);
324
325#[cfg(feature = "v1_24")]
326#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
327mod v1_24 {
328    use super::*;
329
330    impl_tag!(
331        ContainerSpecificTrackId,
332        &'a str,
333        TAG_CONTAINER_SPECIFIC_TRACK_ID,
334        GST_TAG_CONTAINER_SPECIFIC_TRACK_ID
335    );
336}
337
338#[cfg(feature = "v1_24")]
339#[cfg_attr(docsrs, doc(cfg(feature = "v1_24")))]
340pub use v1_24::ContainerSpecificTrackId;
341
342mini_object_wrapper!(TagList, TagListRef, ffi::GstTagList, || {
343    ffi::gst_tag_list_get_type()
344});
345
346impl TagList {
347    #[doc(alias = "gst_tag_list_new_empty")]
348    pub fn new() -> Self {
349        assert_initialized_main_thread!();
350        unsafe { from_glib_full(ffi::gst_tag_list_new_empty()) }
351    }
352}
353
354impl Default for TagList {
355    fn default() -> Self {
356        Self::new()
357    }
358}
359
360#[derive(Debug, Clone)]
361#[repr(transparent)]
362pub struct TagValue<T>(SendValue, PhantomData<T>);
363
364impl<T> TagValue<T> {
365    pub fn get<'a>(&'a self) -> T
366    where
367        T: StaticType + FromValue<'a>,
368    {
369        self.0.get().expect("Invalid tag type")
370    }
371}
372
373impl TagListRef {
374    #[doc(alias = "gst_tag_list_add")]
375    pub fn add<'a, T: Tag<'a>>(&mut self, value: &T::TagType, mode: TagMergeMode) {
376        // result can be safely ignored here as `value`'s type is tied to `T::TAG_NAME`
377        let v = <T::TagType as ToSendValue>::to_send_value(value);
378        let _res = self.add_value(T::TAG_NAME, &v, mode);
379    }
380
381    #[doc(alias = "gst_tag_list_add")]
382    pub fn add_generic(
383        &mut self,
384        tag_name: impl IntoGStr,
385        value: impl ToSendValue,
386        mode: TagMergeMode,
387    ) -> Result<(), TagError> {
388        self.add_value(tag_name, &value.to_send_value(), mode)
389    }
390
391    #[doc(alias = "gst_tag_list_add_value")]
392    pub fn add_value(
393        &mut self,
394        tag_name: impl IntoGStr,
395        value: &glib::SendValue,
396        mode: TagMergeMode,
397    ) -> Result<(), TagError> {
398        unsafe {
399            tag_name.run_with_gstr(|tag_name| {
400                let tag_type: glib::Type = from_glib(ffi::gst_tag_get_type(tag_name.as_ptr()));
401                if tag_type != value.type_() {
402                    return Err(TagError::TypeMismatch);
403                }
404
405                ffi::gst_tag_list_add_value(
406                    self.as_mut_ptr(),
407                    mode.into_glib(),
408                    tag_name.as_ptr(),
409                    value.to_glib_none().0,
410                );
411                Ok(())
412            })
413        }
414    }
415
416    #[doc(alias = "gst_tag_list_remove_tag")]
417    pub fn remove<'a, T: Tag<'a>>(&mut self) {
418        self.remove_generic(T::TAG_NAME);
419    }
420
421    #[doc(alias = "gst_tag_list_remove_tag")]
422    pub fn remove_generic(&mut self, tag_name: impl IntoGStr) {
423        unsafe {
424            tag_name.run_with_gstr(|tag_name| {
425                ffi::gst_tag_list_remove_tag(self.as_mut_ptr(), tag_name.as_ptr());
426            })
427        }
428    }
429
430    #[doc(alias = "gst_tag_list_get")]
431    pub fn get<'a, T: Tag<'a>>(&self) -> Option<TagValue<T::TagType>> {
432        self.generic(T::TAG_NAME).map(|value| {
433            if !value.is::<T::TagType>() {
434                panic!(
435                    "TagListRef::get type mismatch for tag {}: {}",
436                    T::TAG_NAME,
437                    value.type_()
438                );
439            }
440            TagValue(value, PhantomData)
441        })
442    }
443
444    #[doc(alias = "gst_tag_list_get")]
445    #[doc(alias = "get_generic")]
446    pub fn generic(&self, tag_name: impl IntoGStr) -> Option<SendValue> {
447        unsafe {
448            let mut value: mem::MaybeUninit<SendValue> = mem::MaybeUninit::zeroed();
449
450            let found: bool = tag_name.run_with_gstr(|tag_name| {
451                from_glib(ffi::gst_tag_list_copy_value(
452                    (*value.as_mut_ptr()).to_glib_none_mut().0,
453                    self.as_ptr(),
454                    tag_name.as_ptr(),
455                ))
456            });
457
458            if !found {
459                None
460            } else {
461                Some(value.assume_init())
462            }
463        }
464    }
465
466    #[doc(alias = "gst_tag_list_n_tags")]
467    pub fn n_tags(&self) -> usize {
468        unsafe { ffi::gst_tag_list_n_tags(self.as_ptr()) as usize }
469    }
470
471    #[doc(alias = "gst_tag_list_nth_tag_name")]
472    pub fn nth_tag_name(&self, idx: usize) -> Option<&glib::GStr> {
473        if idx >= self.n_tags() {
474            return None;
475        }
476
477        unsafe {
478            let name = ffi::gst_tag_list_nth_tag_name(self.as_ptr(), idx as u32);
479            debug_assert!(!name.is_null());
480            Some(glib::GStr::from_ptr(name))
481        }
482    }
483
484    #[doc(alias = "get_index")]
485    #[doc(alias = "gst_tag_list_get_index")]
486    pub fn index<'a, T: Tag<'a>>(&'a self, idx: usize) -> Option<&'a TagValue<T::TagType>> {
487        self.index_generic(T::TAG_NAME, idx).map(|value| {
488            if !value.is::<T::TagType>() {
489                panic!(
490                    "TagListRef::get_index type mismatch for tag {}: {}",
491                    T::TAG_NAME,
492                    value.type_()
493                );
494            }
495            unsafe { &*(value as *const SendValue as *const TagValue<T::TagType>) }
496        })
497    }
498
499    #[doc(alias = "get_index_generic")]
500    #[doc(alias = "gst_tag_list_get_index")]
501    pub fn index_generic(&self, tag_name: impl IntoGStr, idx: usize) -> Option<&SendValue> {
502        unsafe {
503            let idx = u32::try_from(idx).ok()?;
504            let value = tag_name.run_with_gstr(|tag_name| {
505                ffi::gst_tag_list_get_value_index(self.as_ptr(), tag_name.as_ptr(), idx)
506            });
507
508            if value.is_null() {
509                None
510            } else {
511                Some(&*(value as *const SendValue))
512            }
513        }
514    }
515
516    #[doc(alias = "get_size")]
517    #[doc(alias = "gst_tag_list_get_tag_size")]
518    pub fn size<'a, T: Tag<'a>>(&self) -> usize {
519        self.size_by_name(T::TAG_NAME)
520    }
521
522    #[doc(alias = "get_size_by_name")]
523    #[doc(alias = "gst_tag_list_get_tag_size")]
524    pub fn size_by_name(&self, tag_name: impl IntoGStr) -> usize {
525        unsafe {
526            tag_name.run_with_gstr(|tag_name| {
527                ffi::gst_tag_list_get_tag_size(self.as_ptr(), tag_name.as_ptr()) as usize
528            })
529        }
530    }
531
532    pub fn iter_tag<'a, T: Tag<'a>>(&'a self) -> TagIter<'a, T> {
533        TagIter::new(self)
534    }
535
536    pub fn iter_tag_generic(&self, tag_name: impl IntoGStr) -> GenericTagIter {
537        let tag_name = glib::Quark::from_str(tag_name).as_str();
538        GenericTagIter::new(self, tag_name)
539    }
540
541    pub fn iter_generic(&self) -> GenericIter {
542        GenericIter::new(self)
543    }
544
545    pub fn iter(&self) -> Iter {
546        Iter::new(self)
547    }
548
549    #[doc(alias = "gst_tag_list_insert")]
550    pub fn insert(&mut self, other: &TagListRef, mode: TagMergeMode) {
551        unsafe { ffi::gst_tag_list_insert(self.as_mut_ptr(), other.as_ptr(), mode.into_glib()) }
552    }
553
554    #[doc(alias = "gst_tag_list_merge")]
555    pub fn merge(&self, other: &TagListRef, mode: TagMergeMode) -> TagList {
556        unsafe {
557            from_glib_full(ffi::gst_tag_list_merge(
558                self.as_ptr(),
559                other.as_ptr(),
560                mode.into_glib(),
561            ))
562        }
563    }
564
565    #[doc(alias = "get_scope")]
566    #[doc(alias = "gst_tag_list_get_scope")]
567    pub fn scope(&self) -> TagScope {
568        unsafe { from_glib(ffi::gst_tag_list_get_scope(self.as_ptr())) }
569    }
570
571    #[doc(alias = "gst_tag_list_set_scope")]
572    pub fn set_scope(&mut self, scope: TagScope) {
573        unsafe { ffi::gst_tag_list_set_scope(self.as_mut_ptr(), scope.into_glib()) }
574    }
575}
576
577impl fmt::Debug for TagList {
578    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
579        <TagListRef as fmt::Debug>::fmt(self, f)
580    }
581}
582
583impl fmt::Display for TagList {
584    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
585        <TagListRef as fmt::Display>::fmt(self, f)
586    }
587}
588
589impl PartialEq for TagList {
590    fn eq(&self, other: &TagList) -> bool {
591        TagListRef::eq(self, other)
592    }
593}
594
595impl Eq for TagList {}
596
597impl PartialEq<TagListRef> for TagList {
598    fn eq(&self, other: &TagListRef) -> bool {
599        TagListRef::eq(self, other)
600    }
601}
602
603impl PartialEq<TagList> for TagListRef {
604    fn eq(&self, other: &TagList) -> bool {
605        TagListRef::eq(other, self)
606    }
607}
608
609impl fmt::Debug for TagListRef {
610    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
611        let mut debug = f.debug_struct("TagList");
612
613        for (key, value) in self.iter() {
614            debug.field(key, &value);
615        }
616
617        debug.finish()
618    }
619}
620
621impl fmt::Display for TagListRef {
622    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
623        let s =
624            unsafe { glib::GString::from_glib_full(ffi::gst_tag_list_to_string(self.as_ptr())) };
625        f.write_str(&s)
626    }
627}
628
629impl PartialEq for TagListRef {
630    #[doc(alias = "gst_tag_list_is_equal")]
631    fn eq(&self, other: &TagListRef) -> bool {
632        unsafe { from_glib(ffi::gst_tag_list_is_equal(self.as_ptr(), other.as_ptr())) }
633    }
634}
635
636impl Eq for TagListRef {}
637
638#[derive(Debug)]
639pub struct TagIter<'a, T: Tag<'a>> {
640    taglist: &'a TagListRef,
641    idx: usize,
642    size: usize,
643    phantom: PhantomData<T>,
644}
645
646impl<'a, T: Tag<'a>> TagIter<'a, T> {
647    fn new(taglist: &'a TagListRef) -> TagIter<'a, T> {
648        skip_assert_initialized!();
649        TagIter {
650            taglist,
651            idx: 0,
652            size: taglist.size::<T>(),
653            phantom: PhantomData,
654        }
655    }
656}
657
658impl<'a, T: Tag<'a>> Iterator for TagIter<'a, T>
659where
660    <T as Tag<'a>>::TagType: 'a,
661    T: 'a,
662{
663    type Item = &'a TagValue<T::TagType>;
664
665    fn next(&mut self) -> Option<Self::Item> {
666        if self.idx >= self.size {
667            return None;
668        }
669
670        let item = self.taglist.index::<T>(self.idx).unwrap();
671        self.idx += 1;
672
673        Some(item)
674    }
675
676    fn size_hint(&self) -> (usize, Option<usize>) {
677        let remaining = self.size - self.idx;
678
679        (remaining, Some(remaining))
680    }
681
682    fn count(self) -> usize {
683        self.size - self.idx
684    }
685
686    fn nth(&mut self, n: usize) -> Option<Self::Item> {
687        let (end, overflow) = self.idx.overflowing_add(n);
688        if end >= self.size || overflow {
689            self.idx = self.size;
690            None
691        } else {
692            self.idx = end + 1;
693            Some(self.taglist.index::<T>(end).unwrap())
694        }
695    }
696
697    fn last(self) -> Option<Self::Item> {
698        if self.idx == self.size {
699            None
700        } else {
701            Some(self.taglist.index::<T>(self.size - 1).unwrap())
702        }
703    }
704}
705
706impl<'a, T: Tag<'a>> DoubleEndedIterator for TagIter<'a, T>
707where
708    <T as Tag<'a>>::TagType: 'a,
709    T: 'a,
710{
711    fn next_back(&mut self) -> Option<Self::Item> {
712        if self.idx == self.size {
713            return None;
714        }
715
716        self.size -= 1;
717        Some(self.taglist.index::<T>(self.size).unwrap())
718    }
719
720    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
721        let (end, overflow) = self.size.overflowing_sub(n);
722        if end <= self.idx || overflow {
723            self.idx = self.size;
724            None
725        } else {
726            self.size = end - 1;
727            Some(self.taglist.index::<T>(self.size).unwrap())
728        }
729    }
730}
731
732impl<'a, T: Tag<'a>> ExactSizeIterator for TagIter<'a, T>
733where
734    <T as Tag<'a>>::TagType: 'a,
735    T: 'a,
736{
737}
738
739impl<'a, T: Tag<'a>> std::iter::FusedIterator for TagIter<'a, T>
740where
741    <T as Tag<'a>>::TagType: 'a,
742    T: 'a,
743{
744}
745
746#[derive(Debug)]
747pub struct GenericTagIter<'a> {
748    taglist: &'a TagListRef,
749    name: &'static glib::GStr,
750    idx: usize,
751    size: usize,
752}
753
754impl<'a> GenericTagIter<'a> {
755    fn new(taglist: &'a TagListRef, name: &'static glib::GStr) -> GenericTagIter<'a> {
756        skip_assert_initialized!();
757        GenericTagIter {
758            taglist,
759            name,
760            idx: 0,
761            size: taglist.size_by_name(name),
762        }
763    }
764}
765
766impl<'a> Iterator for GenericTagIter<'a> {
767    type Item = &'a SendValue;
768
769    fn next(&mut self) -> Option<Self::Item> {
770        if self.idx >= self.size {
771            return None;
772        }
773
774        let item = self.taglist.index_generic(self.name, self.idx).unwrap();
775        self.idx += 1;
776
777        Some(item)
778    }
779
780    fn size_hint(&self) -> (usize, Option<usize>) {
781        let remaining = self.size - self.idx;
782
783        (remaining, Some(remaining))
784    }
785
786    fn count(self) -> usize {
787        self.size - self.idx
788    }
789
790    fn nth(&mut self, n: usize) -> Option<Self::Item> {
791        let (end, overflow) = self.idx.overflowing_add(n);
792        if end >= self.size || overflow {
793            self.idx = self.size;
794            None
795        } else {
796            self.idx = end + 1;
797            Some(self.taglist.index_generic(self.name, end).unwrap())
798        }
799    }
800
801    fn last(self) -> Option<Self::Item> {
802        if self.idx == self.size {
803            None
804        } else {
805            Some(
806                self.taglist
807                    .index_generic(self.name, self.size - 1)
808                    .unwrap(),
809            )
810        }
811    }
812}
813
814impl DoubleEndedIterator for GenericTagIter<'_> {
815    fn next_back(&mut self) -> Option<Self::Item> {
816        if self.idx == self.size {
817            return None;
818        }
819
820        self.size -= 1;
821        Some(self.taglist.index_generic(self.name, self.size).unwrap())
822    }
823
824    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
825        let (end, overflow) = self.size.overflowing_sub(n);
826        if end <= self.idx || overflow {
827            self.idx = self.size;
828            None
829        } else {
830            self.size = end - 1;
831            Some(self.taglist.index_generic(self.name, self.size).unwrap())
832        }
833    }
834}
835
836impl ExactSizeIterator for GenericTagIter<'_> {}
837
838impl std::iter::FusedIterator for GenericTagIter<'_> {}
839
840#[derive(Debug)]
841pub struct GenericIter<'a> {
842    taglist: &'a TagListRef,
843    idx: usize,
844    size: usize,
845}
846
847impl<'a> GenericIter<'a> {
848    fn new(taglist: &'a TagListRef) -> GenericIter<'a> {
849        skip_assert_initialized!();
850        let size = taglist.n_tags();
851        GenericIter {
852            taglist,
853            idx: 0,
854            size: if size > 0 { size } else { 0 },
855        }
856    }
857}
858
859impl<'a> Iterator for GenericIter<'a> {
860    type Item = (&'a glib::GStr, GenericTagIter<'a>);
861
862    fn next(&mut self) -> Option<Self::Item> {
863        if self.idx >= self.size {
864            return None;
865        }
866
867        let name = self.taglist.nth_tag_name(self.idx).unwrap();
868        let item = (name, self.taglist.iter_tag_generic(name));
869        self.idx += 1;
870
871        Some(item)
872    }
873
874    fn size_hint(&self) -> (usize, Option<usize>) {
875        let remaining = self.size - self.idx;
876
877        (remaining, Some(remaining))
878    }
879
880    fn count(self) -> usize {
881        self.size - self.idx
882    }
883
884    fn nth(&mut self, n: usize) -> Option<Self::Item> {
885        let (end, overflow) = self.idx.overflowing_add(n);
886        if end >= self.size || overflow {
887            self.idx = self.size;
888            None
889        } else {
890            self.idx = end + 1;
891            let name = self.taglist.nth_tag_name(end).unwrap();
892            Some((name, self.taglist.iter_tag_generic(name)))
893        }
894    }
895
896    fn last(self) -> Option<Self::Item> {
897        if self.idx == self.size {
898            None
899        } else {
900            let name = self.taglist.nth_tag_name(self.size - 1).unwrap();
901            Some((name, self.taglist.iter_tag_generic(name)))
902        }
903    }
904}
905
906impl DoubleEndedIterator for GenericIter<'_> {
907    fn next_back(&mut self) -> Option<Self::Item> {
908        if self.idx == self.size {
909            return None;
910        }
911
912        self.size -= 1;
913        let name = self.taglist.nth_tag_name(self.idx).unwrap();
914        Some((name, self.taglist.iter_tag_generic(name)))
915    }
916
917    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
918        let (end, overflow) = self.size.overflowing_sub(n);
919        if end <= self.idx || overflow {
920            self.idx = self.size;
921            None
922        } else {
923            self.size = end - 1;
924            let name = self.taglist.nth_tag_name(self.size).unwrap();
925            Some((name, self.taglist.iter_tag_generic(name)))
926        }
927    }
928}
929
930impl ExactSizeIterator for GenericIter<'_> {}
931
932impl std::iter::FusedIterator for GenericIter<'_> {}
933
934#[derive(Debug)]
935pub struct Iter<'a> {
936    taglist: &'a TagListRef,
937    idx: usize,
938    size: usize,
939}
940
941impl<'a> Iter<'a> {
942    fn new(taglist: &'a TagListRef) -> Iter<'a> {
943        skip_assert_initialized!();
944        let size = taglist.n_tags();
945        Iter {
946            taglist,
947            idx: 0,
948            size: if size > 0 { size } else { 0 },
949        }
950    }
951}
952
953impl<'a> Iterator for Iter<'a> {
954    type Item = (&'a glib::GStr, glib::SendValue);
955
956    fn next(&mut self) -> Option<Self::Item> {
957        if self.idx >= self.size {
958            return None;
959        }
960
961        let name = self.taglist.nth_tag_name(self.idx).unwrap();
962        let item = (name, self.taglist.generic(name).unwrap());
963        self.idx += 1;
964
965        Some(item)
966    }
967
968    fn size_hint(&self) -> (usize, Option<usize>) {
969        let remaining = self.size - self.idx;
970
971        (remaining, Some(remaining))
972    }
973
974    fn count(self) -> usize {
975        self.size - self.idx
976    }
977
978    fn nth(&mut self, n: usize) -> Option<Self::Item> {
979        let (end, overflow) = self.idx.overflowing_add(n);
980        if end >= self.size || overflow {
981            self.idx = self.size;
982            None
983        } else {
984            self.idx = end + 1;
985            let name = self.taglist.nth_tag_name(end).unwrap();
986            Some((name, self.taglist.generic(name).unwrap()))
987        }
988    }
989
990    fn last(self) -> Option<Self::Item> {
991        if self.idx == self.size {
992            None
993        } else {
994            let name = self.taglist.nth_tag_name(self.size - 1).unwrap();
995            Some((name, self.taglist.generic(name).unwrap()))
996        }
997    }
998}
999
1000impl DoubleEndedIterator for Iter<'_> {
1001    fn next_back(&mut self) -> Option<Self::Item> {
1002        if self.idx == self.size {
1003            return None;
1004        }
1005
1006        self.size -= 1;
1007        let name = self.taglist.nth_tag_name(self.idx).unwrap();
1008        Some((name, self.taglist.generic(name).unwrap()))
1009    }
1010
1011    fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
1012        let (end, overflow) = self.size.overflowing_sub(n);
1013        if end <= self.idx || overflow {
1014            self.idx = self.size;
1015            None
1016        } else {
1017            self.size = end - 1;
1018            let name = self.taglist.nth_tag_name(self.size).unwrap();
1019            Some((name, self.taglist.generic(name).unwrap()))
1020        }
1021    }
1022}
1023
1024impl ExactSizeIterator for Iter<'_> {}
1025
1026impl std::iter::FusedIterator for Iter<'_> {}
1027
1028#[doc(alias = "gst_tag_exists")]
1029pub fn tag_exists(name: impl IntoGStr) -> bool {
1030    skip_assert_initialized!();
1031    unsafe { name.run_with_gstr(|name| from_glib(ffi::gst_tag_exists(name.as_ptr()))) }
1032}
1033
1034#[doc(alias = "gst_tag_get_type")]
1035pub fn tag_get_type(name: impl IntoGStr) -> glib::Type {
1036    skip_assert_initialized!();
1037    unsafe { name.run_with_gstr(|name| from_glib(ffi::gst_tag_get_type(name.as_ptr()))) }
1038}
1039
1040#[doc(alias = "gst_tag_get_nick")]
1041pub fn tag_get_nick<'b>(name: impl IntoGStr) -> &'b glib::GStr {
1042    skip_assert_initialized!();
1043    unsafe {
1044        let ptr = name.run_with_gstr(|name| ffi::gst_tag_get_nick(name.as_ptr()));
1045        glib::GStr::from_ptr(ptr)
1046    }
1047}
1048
1049#[doc(alias = "gst_tag_get_description")]
1050pub fn tag_get_description<'b>(name: impl IntoGStr) -> Option<&'b glib::GStr> {
1051    skip_assert_initialized!();
1052    unsafe {
1053        let ptr = name.run_with_gstr(|name| ffi::gst_tag_get_description(name.as_ptr()));
1054
1055        if ptr.is_null() {
1056            None
1057        } else {
1058            Some(glib::GStr::from_ptr(ptr))
1059        }
1060    }
1061}
1062
1063#[doc(alias = "gst_tag_get_flag")]
1064pub fn tag_get_flag(name: impl IntoGStr) -> crate::TagFlag {
1065    skip_assert_initialized!();
1066    unsafe { name.run_with_gstr(|name| from_glib(ffi::gst_tag_get_flag(name.as_ptr()))) }
1067}
1068
1069pub trait CustomTag<'a>: Tag<'a> {
1070    const FLAG: crate::TagFlag;
1071    const NICK: &'static glib::GStr;
1072    const DESCRIPTION: &'static glib::GStr;
1073
1074    fn merge_func(src: &Value) -> Value {
1075        skip_assert_initialized!();
1076        merge_use_first(src)
1077    }
1078}
1079
1080#[doc(alias = "gst_tag_register")]
1081pub fn register<T: for<'a> CustomTag<'a>>() {
1082    assert!(!tag_exists(T::TAG_NAME));
1083
1084    unsafe extern "C" fn merge_func_trampoline<T: for<'a> CustomTag<'a>>(
1085        dest: *mut glib::gobject_ffi::GValue,
1086        src: *const glib::gobject_ffi::GValue,
1087    ) {
1088        *dest = T::merge_func(&*(src as *const Value)).into_raw();
1089    }
1090
1091    unsafe {
1092        ffi::gst_tag_register(
1093            T::TAG_NAME.as_ptr(),
1094            T::FLAG.into_glib(),
1095            T::TagType::static_type().into_glib(),
1096            T::NICK.as_ptr(),
1097            T::DESCRIPTION.as_ptr(),
1098            Some(merge_func_trampoline::<T>),
1099        )
1100    }
1101}
1102
1103#[doc(alias = "gst_tag_merge_use_first")]
1104pub fn merge_use_first(src: &Value) -> Value {
1105    skip_assert_initialized!();
1106    assert_eq!(src.type_(), crate::List::static_type());
1107
1108    unsafe {
1109        let mut res = Value::uninitialized();
1110        ffi::gst_tag_merge_use_first(res.to_glib_none_mut().0, src.to_glib_none().0);
1111        res
1112    }
1113}
1114
1115#[doc(alias = "gst_tag_merge_strings_with_comma")]
1116pub fn merge_strings_with_comma(src: &Value) -> Value {
1117    skip_assert_initialized!();
1118    assert_eq!(src.type_(), crate::List::static_type());
1119
1120    unsafe {
1121        let mut res = Value::uninitialized();
1122        ffi::gst_tag_merge_strings_with_comma(res.to_glib_none_mut().0, src.to_glib_none().0);
1123        res
1124    }
1125}
1126
1127#[cfg(test)]
1128mod tests {
1129    use super::*;
1130    use crate::ClockTime;
1131
1132    #[test]
1133    fn test_add() {
1134        crate::init().unwrap();
1135
1136        let mut tags = TagList::new();
1137        assert_eq!(tags.to_string(), "taglist;");
1138        {
1139            let tags = tags.get_mut().unwrap();
1140            tags.add::<Title>(&"some title", TagMergeMode::Append);
1141            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append);
1142        }
1143        assert_eq!(
1144            tags.to_string(),
1145            "taglist, title=(string)\"some\\ title\", duration=(guint64)120000000000;"
1146        );
1147    }
1148
1149    #[test]
1150    fn test_get() {
1151        crate::init().unwrap();
1152
1153        let mut tags = TagList::new();
1154        assert_eq!(tags.to_string(), "taglist;");
1155        {
1156            let tags = tags.get_mut().unwrap();
1157            tags.add::<Title>(&"some title", TagMergeMode::Append);
1158            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append);
1159        }
1160
1161        assert_eq!(tags.get::<Title>().unwrap().get(), "some title");
1162        assert_eq!(
1163            tags.get::<Duration>().unwrap().get(),
1164            ClockTime::SECOND * 120,
1165        );
1166        assert_eq!(tags.index::<Title>(0).unwrap().get(), "some title");
1167        assert_eq!(tags.index::<Title>(0).unwrap().get(), "some title");
1168        assert_eq!(
1169            tags.index::<Duration>(0).unwrap().get(),
1170            ClockTime::SECOND * 120,
1171        );
1172    }
1173
1174    #[test]
1175    fn test_scope() {
1176        crate::init().unwrap();
1177
1178        let mut tags = TagList::new();
1179        assert_eq!(tags.scope(), TagScope::Stream);
1180        {
1181            let tags = tags.get_mut().unwrap();
1182            tags.set_scope(TagScope::Global);
1183        }
1184        assert_eq!(tags.scope(), TagScope::Global);
1185    }
1186
1187    #[test]
1188    #[allow(clippy::cognitive_complexity)]
1189    fn test_generic() {
1190        crate::init().unwrap();
1191
1192        let mut tags = TagList::new();
1193        {
1194            let tags = tags.get_mut().unwrap();
1195            assert!(tags
1196                .add_generic(Title::TAG_NAME, "some title", TagMergeMode::Append)
1197                .is_ok());
1198            assert!(tags
1199                .add_generic(Title::TAG_NAME, "second title", TagMergeMode::Append)
1200                .is_ok());
1201            assert!(tags
1202                .add_generic(
1203                    Duration::TAG_NAME,
1204                    ClockTime::SECOND * 120,
1205                    TagMergeMode::Append
1206                )
1207                .is_ok());
1208            assert!(tags
1209                .add_generic(Title::TAG_NAME, "third title", TagMergeMode::Append)
1210                .is_ok());
1211
1212            assert_eq!(
1213                tags.add_generic(
1214                    Image::TAG_NAME,
1215                    "`&[str] instead of `Sample`",
1216                    TagMergeMode::Append
1217                ),
1218                Err(TagError::TypeMismatch),
1219            );
1220        }
1221
1222        assert_eq!(
1223            tags.index_generic(Title::TAG_NAME, 0).unwrap().get(),
1224            Ok(Some("some title"))
1225        );
1226        assert_eq!(
1227            tags.index_generic(Title::TAG_NAME, 1).unwrap().get(),
1228            Ok(Some("second title"))
1229        );
1230        assert_eq!(
1231            tags.index_generic(Duration::TAG_NAME, 0).unwrap().get(),
1232            Ok(Some(ClockTime::SECOND * 120))
1233        );
1234        assert_eq!(
1235            tags.index_generic(Title::TAG_NAME, 2).unwrap().get(),
1236            Ok(Some("third title"))
1237        );
1238
1239        assert_eq!(
1240            tags.generic(Title::TAG_NAME).unwrap().get(),
1241            Ok(Some("some title, second title, third title"))
1242        );
1243
1244        assert_eq!(tags.n_tags(), 2);
1245        assert_eq!(tags.nth_tag_name(0), Some(Title::TAG_NAME));
1246        assert_eq!(tags.size_by_name(Title::TAG_NAME), 3);
1247        assert_eq!(tags.nth_tag_name(1), Some(Duration::TAG_NAME));
1248        assert_eq!(tags.size_by_name(Duration::TAG_NAME), 1);
1249
1250        // GenericTagIter
1251        let mut title_iter = tags.iter_tag_generic(Title::TAG_NAME);
1252        assert_eq!(title_iter.size_hint(), (3, Some(3)));
1253        let first_title = title_iter.next().unwrap();
1254        assert_eq!(first_title.get(), Ok(Some("some title")));
1255        let second_title = title_iter.next().unwrap();
1256        assert_eq!(second_title.get(), Ok(Some("second title")));
1257        let third_title = title_iter.next().unwrap();
1258        assert_eq!(third_title.get(), Ok(Some("third title")));
1259        assert!(title_iter.next().is_none());
1260
1261        // GenericIter
1262        let mut tag_list_iter = tags.iter_generic();
1263        assert_eq!(tag_list_iter.size_hint(), (2, Some(2)));
1264
1265        let (tag_name, mut tag_iter) = tag_list_iter.next().unwrap();
1266        assert_eq!(tag_name, Title::TAG_NAME);
1267        let first_title = tag_iter.next().unwrap();
1268        assert_eq!(first_title.get(), Ok(Some("some title")));
1269        let second_title = tag_iter.next().unwrap();
1270        assert_eq!(second_title.get(), Ok(Some("second title")));
1271        let third_title = tag_iter.next().unwrap();
1272        assert_eq!(third_title.get(), Ok(Some("third title")));
1273        assert!(tag_iter.next().is_none());
1274
1275        let (tag_name, mut tag_iter) = tag_list_iter.next().unwrap();
1276        assert_eq!(tag_name, Duration::TAG_NAME);
1277        let first_duration = tag_iter.next().unwrap();
1278        assert_eq!(first_duration.get(), Ok(Some(ClockTime::SECOND * 120)));
1279        assert!(tag_iter.next().is_none());
1280
1281        // Iter
1282        let mut tag_list_iter = tags.iter();
1283        assert_eq!(tag_list_iter.size_hint(), (2, Some(2)));
1284
1285        let (tag_name, tag_value) = tag_list_iter.next().unwrap();
1286        assert_eq!(tag_name, Title::TAG_NAME);
1287        assert_eq!(
1288            tag_value.get(),
1289            Ok(Some("some title, second title, third title"))
1290        );
1291
1292        let (tag_name, tag_value) = tag_list_iter.next().unwrap();
1293        assert_eq!(tag_name, Duration::TAG_NAME);
1294        assert_eq!(tag_value.get(), Ok(Some(ClockTime::SECOND * 120)));
1295        assert!(tag_iter.next().is_none());
1296    }
1297
1298    #[test]
1299    fn test_custom_tags() {
1300        crate::init().unwrap();
1301
1302        enum MyCustomTag {}
1303
1304        impl<'a> Tag<'a> for MyCustomTag {
1305            type TagType = &'a str;
1306            const TAG_NAME: &'static glib::GStr = glib::gstr!("my-custom-tag");
1307        }
1308
1309        impl CustomTag<'_> for MyCustomTag {
1310            const FLAG: crate::TagFlag = crate::TagFlag::Meta;
1311            const NICK: &'static glib::GStr = glib::gstr!("my custom tag");
1312            const DESCRIPTION: &'static glib::GStr =
1313                glib::gstr!("My own custom tag type for testing");
1314
1315            fn merge_func(src: &Value) -> Value {
1316                skip_assert_initialized!();
1317                merge_strings_with_comma(src)
1318            }
1319        }
1320
1321        register::<MyCustomTag>();
1322
1323        assert!(tag_exists(MyCustomTag::TAG_NAME));
1324        assert_eq!(
1325            tag_get_type(MyCustomTag::TAG_NAME),
1326            <MyCustomTag as Tag>::TagType::static_type()
1327        );
1328        assert_eq!(tag_get_nick(MyCustomTag::TAG_NAME), MyCustomTag::NICK);
1329        assert_eq!(
1330            tag_get_description(MyCustomTag::TAG_NAME),
1331            Some(MyCustomTag::DESCRIPTION)
1332        );
1333        assert_eq!(tag_get_flag(MyCustomTag::TAG_NAME), MyCustomTag::FLAG);
1334
1335        let mut tags = TagList::new();
1336        {
1337            let tags = tags.get_mut().unwrap();
1338            tags.add::<MyCustomTag>(&"first one", TagMergeMode::Append);
1339        }
1340
1341        assert_eq!(tags.get::<MyCustomTag>().unwrap().get(), "first one");
1342
1343        {
1344            let tags = tags.get_mut().unwrap();
1345            tags.add::<MyCustomTag>(&"second one", TagMergeMode::Append);
1346        }
1347
1348        assert_eq!(
1349            tags.get::<MyCustomTag>().unwrap().get(),
1350            "first one, second one"
1351        );
1352    }
1353
1354    #[test]
1355    fn test_display() {
1356        crate::init().unwrap();
1357
1358        let _ = format!("{}", TagList::new());
1359    }
1360
1361    #[test]
1362    fn test_debug() {
1363        crate::init().unwrap();
1364
1365        let mut tags = TagList::new();
1366        assert_eq!(format!("{tags:?}"), "TagList");
1367        {
1368            let tags = tags.get_mut().unwrap();
1369            tags.add::<Title>(&"some title", TagMergeMode::Append);
1370            tags.add::<Duration>(&(ClockTime::SECOND * 120), TagMergeMode::Append);
1371        }
1372        assert_eq!(
1373            format!("{tags:?}"),
1374            "TagList { title: (gchararray) \"some title\", duration: (guint64) 120000000000 }"
1375        );
1376    }
1377}