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 | prelude::*, |
7 | translate::*, |
8 | value::{FromValue, SendValue, ToSendValue, Value}, |
9 | }; |
10 | |
11 | use crate::{ffi, 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 | #[cfg (feature = "v1_24" )] |
326 | #[cfg_attr (docsrs, doc(cfg(feature = "v1_24" )))] |
327 | mod 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" )))] |
340 | pub use v1_24::ContainerSpecificTrackId; |
341 | |
342 | mini_object_wrapper!(TagList, TagListRef, ffi::GstTagList, || { |
343 | ffi::gst_tag_list_get_type() |
344 | }); |
345 | |
346 | impl TagList { |
347 | #[doc (alias = "gst_tag_list_new_empty" )] |
348 | pub fn new() -> Self { |
349 | assert_initialized_main_thread!(); |
350 | unsafe { from_glib_full(ptr:ffi::gst_tag_list_new_empty()) } |
351 | } |
352 | } |
353 | |
354 | impl Default for TagList { |
355 | fn default() -> Self { |
356 | Self::new() |
357 | } |
358 | } |
359 | |
360 | #[derive (Debug, Clone)] |
361 | #[repr (transparent)] |
362 | pub struct TagValue<T>(SendValue, PhantomData<T>); |
363 | |
364 | impl<T> TagValue<T> { |
365 | pub fn get<'a>(&'a self) -> T |
366 | where |
367 | T: StaticType + FromValue<'a>, |
368 | { |
369 | self.0.get().expect(msg:"Invalid tag type" ) |
370 | } |
371 | } |
372 | |
373 | impl TagListRef { |
374 | #[doc (alias = "gst_tag_list_add" )] |
375 | pub fn add<'a, T: Tag<'a>>(&mut self, value: &T::TagType, mode: TagMergeMode) { |
376 | // result can be safely ignored here as `value`'s type is tied to `T::TAG_NAME` |
377 | 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 | |
577 | impl 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 | |
583 | impl 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 | |
589 | impl PartialEq for TagList { |
590 | fn eq(&self, other: &TagList) -> bool { |
591 | TagListRef::eq(self, other) |
592 | } |
593 | } |
594 | |
595 | impl Eq for TagList {} |
596 | |
597 | impl PartialEq<TagListRef> for TagList { |
598 | fn eq(&self, other: &TagListRef) -> bool { |
599 | TagListRef::eq(self, other) |
600 | } |
601 | } |
602 | |
603 | impl PartialEq<TagList> for TagListRef { |
604 | fn eq(&self, other: &TagList) -> bool { |
605 | TagListRef::eq(self:other, self) |
606 | } |
607 | } |
608 | |
609 | impl fmt::Debug for TagListRef { |
610 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
611 | let mut debug: DebugStruct<'_, '_> = f.debug_struct(name:"TagList" ); |
612 | |
613 | for (key: &GStr, value: SendValue) in self.iter() { |
614 | debug.field(name:key, &value); |
615 | } |
616 | |
617 | debug.finish() |
618 | } |
619 | } |
620 | |
621 | impl fmt::Display for TagListRef { |
622 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
623 | let s: GString = |
624 | unsafe { glib::GString::from_glib_full(ptr:ffi::gst_tag_list_to_string(self.as_ptr())) }; |
625 | f.write_str(&s) |
626 | } |
627 | } |
628 | |
629 | impl PartialEq for TagListRef { |
630 | #[doc (alias = "gst_tag_list_is_equal" )] |
631 | fn eq(&self, other: &TagListRef) -> bool { |
632 | unsafe { from_glib(val:ffi::gst_tag_list_is_equal(self.as_ptr(), list2:other.as_ptr())) } |
633 | } |
634 | } |
635 | |
636 | impl Eq for TagListRef {} |
637 | |
638 | #[derive (Debug)] |
639 | pub struct TagIter<'a, T: Tag<'a>> { |
640 | taglist: &'a TagListRef, |
641 | idx: usize, |
642 | size: usize, |
643 | phantom: PhantomData<T>, |
644 | } |
645 | |
646 | impl<'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 | |
658 | impl<'a, T: Tag<'a>> Iterator for TagIter<'a, T> |
659 | where |
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 | |
706 | impl<'a, T: Tag<'a>> DoubleEndedIterator for TagIter<'a, T> |
707 | where |
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: usize, overflow: bool) = 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 | |
732 | impl<'a, T: Tag<'a>> ExactSizeIterator for TagIter<'a, T> |
733 | where |
734 | <T as Tag<'a>>::TagType: 'a, |
735 | T: 'a, |
736 | { |
737 | } |
738 | |
739 | impl<'a, T: Tag<'a>> std::iter::FusedIterator for TagIter<'a, T> |
740 | where |
741 | <T as Tag<'a>>::TagType: 'a, |
742 | T: 'a, |
743 | { |
744 | } |
745 | |
746 | #[derive (Debug)] |
747 | pub struct GenericTagIter<'a> { |
748 | taglist: &'a TagListRef, |
749 | name: &'static glib::GStr, |
750 | idx: usize, |
751 | size: usize, |
752 | } |
753 | |
754 | impl<'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 | |
766 | impl<'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 | |
814 | impl 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: usize, overflow: bool) = 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 | |
836 | impl ExactSizeIterator for GenericTagIter<'_> {} |
837 | |
838 | impl std::iter::FusedIterator for GenericTagIter<'_> {} |
839 | |
840 | #[derive (Debug)] |
841 | pub struct GenericIter<'a> { |
842 | taglist: &'a TagListRef, |
843 | idx: usize, |
844 | size: usize, |
845 | } |
846 | |
847 | impl<'a> GenericIter<'a> { |
848 | fn new(taglist: &'a TagListRef) -> GenericIter<'a> { |
849 | skip_assert_initialized!(); |
850 | let size: usize = taglist.n_tags(); |
851 | GenericIter { |
852 | taglist, |
853 | idx: 0, |
854 | size: if size > 0 { size } else { 0 }, |
855 | } |
856 | } |
857 | } |
858 | |
859 | impl<'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 | |
906 | impl 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: &GStr = 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: usize, overflow: bool) = 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: &GStr = self.taglist.nth_tag_name(self.size).unwrap(); |
925 | Some((name, self.taglist.iter_tag_generic(name))) |
926 | } |
927 | } |
928 | } |
929 | |
930 | impl ExactSizeIterator for GenericIter<'_> {} |
931 | |
932 | impl std::iter::FusedIterator for GenericIter<'_> {} |
933 | |
934 | #[derive (Debug)] |
935 | pub struct Iter<'a> { |
936 | taglist: &'a TagListRef, |
937 | idx: usize, |
938 | size: usize, |
939 | } |
940 | |
941 | impl<'a> Iter<'a> { |
942 | fn new(taglist: &'a TagListRef) -> Iter<'a> { |
943 | skip_assert_initialized!(); |
944 | let size: usize = taglist.n_tags(); |
945 | Iter { |
946 | taglist, |
947 | idx: 0, |
948 | size: if size > 0 { size } else { 0 }, |
949 | } |
950 | } |
951 | } |
952 | |
953 | impl<'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 | |
1000 | impl 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: &GStr = 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: usize, overflow: bool) = 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: &GStr = self.taglist.nth_tag_name(self.size).unwrap(); |
1019 | Some((name, self.taglist.generic(name).unwrap())) |
1020 | } |
1021 | } |
1022 | } |
1023 | |
1024 | impl ExactSizeIterator for Iter<'_> {} |
1025 | |
1026 | impl std::iter::FusedIterator for Iter<'_> {} |
1027 | |
1028 | #[doc (alias = "gst_tag_exists" )] |
1029 | pub fn tag_exists(name: impl IntoGStr) -> bool { |
1030 | skip_assert_initialized!(); |
1031 | unsafe { name.run_with_gstr(|name: &GStr| from_glib(val:ffi::gst_tag_exists(tag:name.as_ptr()))) } |
1032 | } |
1033 | |
1034 | #[doc (alias = "gst_tag_get_type" )] |
1035 | pub fn tag_get_type(name: impl IntoGStr) -> glib::Type { |
1036 | skip_assert_initialized!(); |
1037 | unsafe { name.run_with_gstr(|name: &GStr| from_glib(val:ffi::gst_tag_get_type(tag:name.as_ptr()))) } |
1038 | } |
1039 | |
1040 | #[doc (alias = "gst_tag_get_nick" )] |
1041 | pub fn tag_get_nick<'b>(name: impl IntoGStr) -> &'b glib::GStr { |
1042 | skip_assert_initialized!(); |
1043 | unsafe { |
1044 | let ptr: *const {unknown} = name.run_with_gstr(|name: &GStr| ffi::gst_tag_get_nick(tag:name.as_ptr())); |
1045 | glib::GStr::from_ptr(ptr) |
1046 | } |
1047 | } |
1048 | |
1049 | #[doc (alias = "gst_tag_get_description" )] |
1050 | pub fn tag_get_description<'b>(name: impl IntoGStr) -> Option<&'b glib::GStr> { |
1051 | skip_assert_initialized!(); |
1052 | unsafe { |
1053 | let ptr: *const {unknown} = name.run_with_gstr(|name: &GStr| ffi::gst_tag_get_description(tag: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" )] |
1064 | pub fn tag_get_flag(name: impl IntoGStr) -> crate::TagFlag { |
1065 | skip_assert_initialized!(); |
1066 | unsafe { name.run_with_gstr(|name: &GStr| from_glib(val:ffi::gst_tag_get_flag(tag:name.as_ptr()))) } |
1067 | } |
1068 | |
1069 | pub 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" )] |
1081 | pub fn register<T: for<'a> dynCustomTag<'a>>() { |
1082 | assert!(!tag_exists(T::TAG_NAME)); |
1083 | |
1084 | unsafe extern "C" fn merge_func_trampoline<T: for<'a> dynCustomTag<'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 | func:Some(merge_func_trampoline::<T>), |
1099 | ) |
1100 | } |
1101 | } |
1102 | |
1103 | #[doc (alias = "gst_tag_merge_use_first" )] |
1104 | pub 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 = Value::uninitialized(); |
1110 | ffi::gst_tag_merge_use_first(dest: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" )] |
1116 | pub 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 = Value::uninitialized(); |
1122 | ffi::gst_tag_merge_strings_with_comma(dest:res.to_glib_none_mut().0, src.to_glib_none().0); |
1123 | res |
1124 | } |
1125 | } |
1126 | |
1127 | #[cfg (test)] |
1128 | mod 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 | // GenericTagIter |
1251 | 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 | // GenericIter |
1262 | 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 | // Iter |
1282 | 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 | } |
1378 | |