Skip to main content

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