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