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