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