1 | // Take a look at the license at the top of the repository in the LICENSE file. |
2 | |
3 | use std::{fmt, marker::PhantomData, mem}; |
4 | |
5 | use glib::{ |
6 | translate::*, |
7 | value::{FromValue, SendValue, ToSendValue, Value}, |
8 | IntoGStr, StaticType, |
9 | }; |
10 | |
11 | use crate::{Sample, TagError, TagMergeMode, TagScope}; |
12 | |
13 | pub trait Tag<'a> { |
14 | type TagType: StaticType + FromValue<'a> + ToSendValue + Send + Sync; |
15 | const TAG_NAME: &'static glib::GStr; |
16 | } |
17 | |
18 | macro_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 | |
28 | impl_tag!(Title, &'a str, TAG_TITLE, GST_TAG_TITLE); |
29 | impl_tag!( |
30 | TitleSortname, |
31 | &'a str, |
32 | TAG_TITLE_SORTNAME, |
33 | GST_TAG_TITLE_SORTNAME |
34 | ); |
35 | impl_tag!(Artist, &'a str, TAG_ARTIST, GST_TAG_ARTIST); |
36 | impl_tag!( |
37 | ArtistSortname, |
38 | &'a str, |
39 | TAG_ARTIST_SORTNAME, |
40 | GST_TAG_ARTIST_SORTNAME |
41 | ); |
42 | impl_tag!(Album, &'a str, TAG_ALBUM, GST_TAG_ALBUM); |
43 | impl_tag!( |
44 | AlbumSortname, |
45 | &'a str, |
46 | TAG_ALBUM_SORTNAME, |
47 | GST_TAG_ALBUM_SORTNAME |
48 | ); |
49 | impl_tag!(AlbumArtist, &'a str, TAG_ALBUM_ARTIST, GST_TAG_ALBUM_ARTIST); |
50 | impl_tag!( |
51 | AlbumArtistSortname, |
52 | &'a str, |
53 | TAG_ALBUM_ARTIST_SORTNAME, |
54 | GST_TAG_ALBUM_ARTIST_SORTNAME |
55 | ); |
56 | impl_tag!(Date, glib::Date, TAG_DATE, GST_TAG_DATE); |
57 | impl_tag!( |
58 | DateTime, |
59 | crate::auto::DateTime, |
60 | TAG_DATE_TIME, |
61 | GST_TAG_DATE_TIME |
62 | ); |
63 | impl_tag!(Genre, &'a str, TAG_GENRE, GST_TAG_GENRE); |
64 | impl_tag!(Comment, &'a str, TAG_COMMENT, GST_TAG_COMMENT); |
65 | impl_tag!( |
66 | ExtendedComment, |
67 | &'a str, |
68 | TAG_EXTENDED_COMMENT, |
69 | GST_TAG_EXTENDED_COMMENT |
70 | ); |
71 | impl_tag!(TrackNumber, u32, TAG_TRACK_NUMBER, GST_TAG_TRACK_NUMBER); |
72 | impl_tag!(TrackCount, u32, TAG_TRACK_COUNT, GST_TAG_TRACK_COUNT); |
73 | impl_tag!( |
74 | AlbumVolumeNumber, |
75 | u32, |
76 | TAG_ALBUM_VOLUME_NUMBER, |
77 | GST_TAG_ALBUM_VOLUME_NUMBER |
78 | ); |
79 | impl_tag!( |
80 | AlbumVolumeCount, |
81 | u32, |
82 | TAG_ALBUM_VOLUME_COUNT, |
83 | GST_TAG_ALBUM_VOLUME_COUNT |
84 | ); |
85 | impl_tag!(Location, &'a str, TAG_LOCATION, GST_TAG_LOCATION); |
86 | impl_tag!(Homepage, &'a str, TAG_HOMEPAGE, GST_TAG_HOMEPAGE); |
87 | impl_tag!(Description, &'a str, TAG_DESCRIPTION, GST_TAG_DESCRIPTION); |
88 | impl_tag!(Version, &'a str, TAG_VERSION, GST_TAG_VERSION); |
89 | impl_tag!(ISRC, &'a str, TAG_ISRC, GST_TAG_ISRC); |
90 | impl_tag!( |
91 | Organization, |
92 | &'a str, |
93 | TAG_ORGANIZATION, |
94 | GST_TAG_ORGANIZATION |
95 | ); |
96 | impl_tag!(Copyright, &'a str, TAG_COPYRIGHT, GST_TAG_COPYRIGHT); |
97 | impl_tag!( |
98 | CopyrightUri, |
99 | &'a str, |
100 | TAG_COPYRIGHT_URI, |
101 | GST_TAG_COPYRIGHT_URI |
102 | ); |
103 | impl_tag!(EncodedBy, &'a str, TAG_ENCODED_BY, GST_TAG_ENCODED_BY); |
104 | impl_tag!(Composer, &'a str, TAG_COMPOSER, GST_TAG_COMPOSER); |
105 | impl_tag!(Conductor, &'a str, TAG_CONDUCTOR, GST_TAG_CONDUCTOR); |
106 | impl_tag!(Contact, &'a str, TAG_CONTACT, GST_TAG_CONTACT); |
107 | impl_tag!(License, &'a str, TAG_LICENSE, GST_TAG_LICENSE); |
108 | impl_tag!(LicenseUri, &'a str, TAG_LICENSE_URI, GST_TAG_LICENSE_URI); |
109 | impl_tag!(Performer, &'a str, TAG_PERFORMER, GST_TAG_PERFORMER); |
110 | impl_tag!(Duration, crate::ClockTime, TAG_DURATION, GST_TAG_DURATION); |
111 | impl_tag!(Codec, &'a str, TAG_CODEC, GST_TAG_CODEC); |
112 | impl_tag!(VideoCodec, &'a str, TAG_VIDEO_CODEC, GST_TAG_VIDEO_CODEC); |
113 | impl_tag!(AudioCodec, &'a str, TAG_AUDIO_CODEC, GST_TAG_AUDIO_CODEC); |
114 | impl_tag!( |
115 | SubtitleCodec, |
116 | &'a str, |
117 | TAG_SUBTITLE_CODEC, |
118 | GST_TAG_SUBTITLE_CODEC |
119 | ); |
120 | impl_tag!( |
121 | ContainerFormat, |
122 | &'a str, |
123 | TAG_CONTAINER_FORMAT, |
124 | GST_TAG_CONTAINER_FORMAT |
125 | ); |
126 | impl_tag!(Bitrate, u32, TAG_BITRATE, GST_TAG_BITRATE); |
127 | impl_tag!( |
128 | NominalBitrate, |
129 | u32, |
130 | TAG_NOMINAL_BITRATE, |
131 | GST_TAG_NOMINAL_BITRATE |
132 | ); |
133 | impl_tag!( |
134 | MinimumBitrate, |
135 | u32, |
136 | TAG_MINIMUM_BITRATE, |
137 | GST_TAG_MINIMUM_BITRATE |
138 | ); |
139 | impl_tag!( |
140 | MaximumBitrate, |
141 | u32, |
142 | TAG_MAXIMUM_BITRATE, |
143 | GST_TAG_MAXIMUM_BITRATE |
144 | ); |
145 | impl_tag!(Serial, u32, TAG_SERIAL, GST_TAG_SERIAL); |
146 | impl_tag!(Encoder, &'a str, TAG_ENCODER, GST_TAG_ENCODER); |
147 | impl_tag!( |
148 | EncoderVersion, |
149 | u32, |
150 | TAG_ENCODER_VERSION, |
151 | GST_TAG_ENCODER_VERSION |
152 | ); |
153 | impl_tag!(TrackGain, f64, TAG_TRACK_GAIN, GST_TAG_TRACK_GAIN); |
154 | impl_tag!(TrackPeak, f64, TAG_TRACK_PEAK, GST_TAG_TRACK_PEAK); |
155 | impl_tag!(AlbumGain, f64, TAG_ALBUM_GAIN, GST_TAG_ALBUM_GAIN); |
156 | impl_tag!(AlbumPeak, f64, TAG_ALBUM_PEAK, GST_TAG_ALBUM_PEAK); |
157 | impl_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 |
164 | impl_tag!( |
165 | LanguageCode, |
166 | &'a str, |
167 | TAG_LANGUAGE_CODE, |
168 | GST_TAG_LANGUAGE_CODE |
169 | ); |
170 | impl_tag!( |
171 | LanguageName, |
172 | &'a str, |
173 | TAG_LANGUAGE_NAME, |
174 | GST_TAG_LANGUAGE_NAME |
175 | ); |
176 | impl_tag!(Image, Sample, TAG_IMAGE, GST_TAG_IMAGE); |
177 | impl_tag!( |
178 | PreviewImage, |
179 | Sample, |
180 | TAG_PREVIEW_IMAGE, |
181 | GST_TAG_PREVIEW_IMAGE |
182 | ); |
183 | impl_tag!(Attachment, Sample, TAG_ATTACHMENT, GST_TAG_ATTACHMENT); |
184 | impl_tag!( |
185 | BeatsPerMinute, |
186 | f64, |
187 | TAG_BEATS_PER_MINUTE, |
188 | GST_TAG_BEATS_PER_MINUTE |
189 | ); |
190 | impl_tag!(Keywords, &'a str, TAG_KEYWORDS, GST_TAG_KEYWORDS); |
191 | impl_tag!( |
192 | GeoLocationName, |
193 | &'a str, |
194 | TAG_GEO_LOCATION_NAME, |
195 | GST_TAG_GEO_LOCATION_NAME |
196 | ); |
197 | impl_tag!( |
198 | GeoLocationLatitude, |
199 | f64, |
200 | TAG_GEO_LOCATION_LATITUDE, |
201 | GST_TAG_GEO_LOCATION_LATITUDE |
202 | ); |
203 | impl_tag!( |
204 | GeoLocationLongitude, |
205 | f64, |
206 | TAG_GEO_LOCATION_LONGITUDE, |
207 | GST_TAG_GEO_LOCATION_LONGITUDE |
208 | ); |
209 | impl_tag!( |
210 | GeoLocationElevation, |
211 | f64, |
212 | TAG_GEO_LOCATION_ELEVATION, |
213 | GST_TAG_GEO_LOCATION_ELEVATION |
214 | ); |
215 | impl_tag!( |
216 | GeoLocationCity, |
217 | &'a str, |
218 | TAG_GEO_LOCATION_CITY, |
219 | GST_TAG_GEO_LOCATION_CITY |
220 | ); |
221 | impl_tag!( |
222 | GeoLocationCountry, |
223 | &'a str, |
224 | TAG_GEO_LOCATION_COUNTRY, |
225 | GST_TAG_GEO_LOCATION_COUNTRY |
226 | ); |
227 | impl_tag!( |
228 | GeoLocationSublocation, |
229 | &'a str, |
230 | TAG_GEO_LOCATION_SUBLOCATION, |
231 | GST_TAG_GEO_LOCATION_SUBLOCATION |
232 | ); |
233 | impl_tag!( |
234 | GeoLocationHorizontalError, |
235 | f64, |
236 | TAG_GEO_LOCATION_HORIZONTAL_ERROR, |
237 | GST_TAG_GEO_LOCATION_HORIZONTAL_ERROR |
238 | ); |
239 | impl_tag!( |
240 | GeoLocationMovementDirection, |
241 | f64, |
242 | TAG_GEO_LOCATION_MOVEMENT_DIRECTION, |
243 | GST_TAG_GEO_LOCATION_MOVEMENT_DIRECTION |
244 | ); |
245 | impl_tag!( |
246 | GeoLocationMovementSpeed, |
247 | f64, |
248 | TAG_GEO_LOCATION_MOVEMENT_SPEED, |
249 | GST_TAG_GEO_LOCATION_MOVEMENT_SPEED |
250 | ); |
251 | impl_tag!( |
252 | GeoLocationCaptureDirection, |
253 | f64, |
254 | TAG_GEO_LOCATION_CAPTURE_DIRECTION, |
255 | GST_TAG_GEO_LOCATION_CAPTURE_DIRECTION |
256 | ); |
257 | impl_tag!(ShowName, &'a str, TAG_SHOW_NAME, GST_TAG_SHOW_NAME); |
258 | impl_tag!( |
259 | ShowSortname, |
260 | &'a str, |
261 | TAG_SHOW_SORTNAME, |
262 | GST_TAG_SHOW_SORTNAME |
263 | ); |
264 | impl_tag!( |
265 | ShowEpisodeNumber, |
266 | u32, |
267 | TAG_SHOW_EPISODE_NUMBER, |
268 | GST_TAG_SHOW_EPISODE_NUMBER |
269 | ); |
270 | impl_tag!( |
271 | ShowSeasonNumber, |
272 | u32, |
273 | TAG_SHOW_SEASON_NUMBER, |
274 | GST_TAG_SHOW_SEASON_NUMBER |
275 | ); |
276 | impl_tag!(Lyrics, &'a str, TAG_LYRICS, GST_TAG_LYRICS); |
277 | impl_tag!( |
278 | ComposerSortname, |
279 | &'a str, |
280 | TAG_COMPOSER_SORTNAME, |
281 | GST_TAG_COMPOSER_SORTNAME |
282 | ); |
283 | impl_tag!(Grouping, &'a str, TAG_GROUPING, GST_TAG_GROUPING); |
284 | impl_tag!(UserRating, u32, TAG_USER_RATING, GST_TAG_USER_RATING); |
285 | impl_tag!( |
286 | DeviceManufacturer, |
287 | &'a str, |
288 | TAG_DEVICE_MANUFACTURER, |
289 | GST_TAG_DEVICE_MANUFACTURER |
290 | ); |
291 | impl_tag!(DeviceModel, &'a str, TAG_DEVICE_MODEL, GST_TAG_DEVICE_MODEL); |
292 | impl_tag!( |
293 | ApplicationName, |
294 | &'a str, |
295 | TAG_APPLICATION_NAME, |
296 | GST_TAG_APPLICATION_NAME |
297 | ); |
298 | impl_tag!( |
299 | ApplicationData, |
300 | Sample, |
301 | TAG_APPLICATION_DATA, |
302 | GST_TAG_APPLICATION_DATA |
303 | ); |
304 | impl_tag!( |
305 | ImageOrientation, |
306 | &'a str, |
307 | TAG_IMAGE_ORIENTATION, |
308 | GST_TAG_IMAGE_ORIENTATION |
309 | ); |
310 | impl_tag!(Publisher, &'a str, TAG_PUBLISHER, GST_TAG_PUBLISHER); |
311 | impl_tag!( |
312 | InterpretedBy, |
313 | &'a str, |
314 | TAG_INTERPRETED_BY, |
315 | GST_TAG_INTERPRETED_BY |
316 | ); |
317 | impl_tag!( |
318 | MidiBaseNote, |
319 | &'a str, |
320 | TAG_MIDI_BASE_NOTE, |
321 | GST_TAG_MIDI_BASE_NOTE |
322 | ); |
323 | impl_tag!(PrivateData, Sample, TAG_PRIVATE_DATA, GST_TAG_PRIVATE_DATA); |
324 | |
325 | mini_object_wrapper!(TagList, TagListRef, ffi::GstTagList, || { |
326 | ffi::gst_tag_list_get_type() |
327 | }); |
328 | |
329 | impl 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 | |
337 | impl Default for TagList { |
338 | fn default() -> Self { |
339 | Self::new() |
340 | } |
341 | } |
342 | |
343 | #[derive (Debug, Clone)] |
344 | #[repr (transparent)] |
345 | pub struct TagValue<T>(SendValue, PhantomData<T>); |
346 | |
347 | impl<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 | |
356 | impl 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 | |
559 | impl 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 | |
565 | impl 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 | |
571 | impl PartialEq for TagList { |
572 | fn eq(&self, other: &TagList) -> bool { |
573 | TagListRef::eq(self, other) |
574 | } |
575 | } |
576 | |
577 | impl Eq for TagList {} |
578 | |
579 | impl PartialEq<TagListRef> for TagList { |
580 | fn eq(&self, other: &TagListRef) -> bool { |
581 | TagListRef::eq(self, other) |
582 | } |
583 | } |
584 | |
585 | impl PartialEq<TagList> for TagListRef { |
586 | fn eq(&self, other: &TagList) -> bool { |
587 | TagListRef::eq(self:other, self) |
588 | } |
589 | } |
590 | |
591 | impl 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 | |
603 | impl 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 | |
611 | impl 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 | |
618 | impl Eq for TagListRef {} |
619 | |
620 | #[derive (Debug)] |
621 | pub struct TagIter<'a, T: Tag<'a>> { |
622 | taglist: &'a TagListRef, |
623 | idx: usize, |
624 | size: usize, |
625 | phantom: PhantomData<T>, |
626 | } |
627 | |
628 | impl<'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 | |
640 | impl<'a, T: Tag<'a>> Iterator for TagIter<'a, T> |
641 | where |
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 | |
688 | impl<'a, T: Tag<'a>> DoubleEndedIterator for TagIter<'a, T> |
689 | where |
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 | |
714 | impl<'a, T: Tag<'a>> ExactSizeIterator for TagIter<'a, T> |
715 | where |
716 | <T as Tag<'a>>::TagType: 'a, |
717 | T: 'a, |
718 | { |
719 | } |
720 | |
721 | impl<'a, T: Tag<'a>> std::iter::FusedIterator for TagIter<'a, T> |
722 | where |
723 | <T as Tag<'a>>::TagType: 'a, |
724 | T: 'a, |
725 | { |
726 | } |
727 | |
728 | #[derive (Debug)] |
729 | pub struct GenericTagIter<'a> { |
730 | taglist: &'a TagListRef, |
731 | name: &'static glib::GStr, |
732 | idx: usize, |
733 | size: usize, |
734 | } |
735 | |
736 | impl<'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 | |
748 | impl<'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 | |
799 | impl<'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 | |
829 | impl<'a> ExactSizeIterator for GenericTagIter<'a> {} |
830 | |
831 | impl<'a> std::iter::FusedIterator for GenericTagIter<'a> {} |
832 | |
833 | #[derive (Debug)] |
834 | pub struct GenericIter<'a> { |
835 | taglist: &'a TagListRef, |
836 | idx: usize, |
837 | size: usize, |
838 | } |
839 | |
840 | impl<'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 | |
852 | impl<'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 | |
899 | impl<'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 | |
923 | impl<'a> ExactSizeIterator for GenericIter<'a> {} |
924 | |
925 | impl<'a> std::iter::FusedIterator for GenericIter<'a> {} |
926 | |
927 | #[derive (Debug)] |
928 | pub struct Iter<'a> { |
929 | taglist: &'a TagListRef, |
930 | idx: usize, |
931 | size: usize, |
932 | } |
933 | |
934 | impl<'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 | |
946 | impl<'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 | |
993 | impl<'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 | |
1017 | impl<'a> ExactSizeIterator for Iter<'a> {} |
1018 | |
1019 | impl<'a> std::iter::FusedIterator for Iter<'a> {} |
1020 | |
1021 | #[doc (alias = "gst_tag_exists" )] |
1022 | pub 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" )] |
1028 | pub 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" )] |
1034 | pub 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" )] |
1043 | pub 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" )] |
1057 | pub 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 | |
1062 | pub 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" )] |
1074 | pub 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" )] |
1097 | pub 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" )] |
1109 | pub 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)] |
1121 | mod 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 | |