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