1use 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 #[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);
168impl_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 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 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 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 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}