1use std::collections::HashMap;
55use std::path::{Path, PathBuf};
56use std::time::Duration;
57
58use crate::chapter::ChapterInfo;
59use crate::stream::{AudioStreamInfo, SubtitleStreamInfo, VideoStreamInfo};
60
61#[derive(Debug, Clone)]
82pub struct MediaInfo {
83 path: PathBuf,
85 format: String,
87 format_long_name: Option<String>,
89 duration: Duration,
91 file_size: u64,
93 bitrate: Option<u64>,
95 video_streams: Vec<VideoStreamInfo>,
97 audio_streams: Vec<AudioStreamInfo>,
99 subtitle_streams: Vec<SubtitleStreamInfo>,
101 chapters: Vec<ChapterInfo>,
103 metadata: HashMap<String, String>,
105}
106
107impl MediaInfo {
108 #[must_use]
124 pub fn builder() -> MediaInfoBuilder {
125 MediaInfoBuilder::default()
126 }
127
128 #[must_use]
130 #[inline]
131 pub fn path(&self) -> &Path {
132 &self.path
133 }
134
135 #[must_use]
137 #[inline]
138 pub fn format(&self) -> &str {
139 &self.format
140 }
141
142 #[must_use]
144 #[inline]
145 pub fn format_long_name(&self) -> Option<&str> {
146 self.format_long_name.as_deref()
147 }
148
149 #[must_use]
151 #[inline]
152 pub const fn duration(&self) -> Duration {
153 self.duration
154 }
155
156 #[must_use]
158 #[inline]
159 pub const fn file_size(&self) -> u64 {
160 self.file_size
161 }
162
163 #[must_use]
165 #[inline]
166 pub const fn bitrate(&self) -> Option<u64> {
167 self.bitrate
168 }
169
170 #[must_use]
172 #[inline]
173 pub fn video_streams(&self) -> &[VideoStreamInfo] {
174 &self.video_streams
175 }
176
177 #[must_use]
179 #[inline]
180 pub fn audio_streams(&self) -> &[AudioStreamInfo] {
181 &self.audio_streams
182 }
183
184 #[must_use]
186 #[inline]
187 pub fn subtitle_streams(&self) -> &[SubtitleStreamInfo] {
188 &self.subtitle_streams
189 }
190
191 #[must_use]
193 #[inline]
194 pub fn chapters(&self) -> &[ChapterInfo] {
195 &self.chapters
196 }
197
198 #[must_use]
200 #[inline]
201 pub fn has_chapters(&self) -> bool {
202 !self.chapters.is_empty()
203 }
204
205 #[must_use]
207 #[inline]
208 pub fn chapter_count(&self) -> usize {
209 self.chapters.len()
210 }
211
212 #[must_use]
214 #[inline]
215 pub fn metadata(&self) -> &HashMap<String, String> {
216 &self.metadata
217 }
218
219 #[must_use]
221 #[inline]
222 pub fn metadata_value(&self, key: &str) -> Option<&str> {
223 self.metadata.get(key).map(String::as_str)
224 }
225
226 #[must_use]
230 #[inline]
231 pub fn has_video(&self) -> bool {
232 !self.video_streams.is_empty()
233 }
234
235 #[must_use]
237 #[inline]
238 pub fn has_audio(&self) -> bool {
239 !self.audio_streams.is_empty()
240 }
241
242 #[must_use]
244 #[inline]
245 pub fn has_subtitles(&self) -> bool {
246 !self.subtitle_streams.is_empty()
247 }
248
249 #[must_use]
251 #[inline]
252 pub fn video_stream_count(&self) -> usize {
253 self.video_streams.len()
254 }
255
256 #[must_use]
258 #[inline]
259 pub fn audio_stream_count(&self) -> usize {
260 self.audio_streams.len()
261 }
262
263 #[must_use]
265 #[inline]
266 pub fn subtitle_stream_count(&self) -> usize {
267 self.subtitle_streams.len()
268 }
269
270 #[must_use]
272 #[inline]
273 pub fn stream_count(&self) -> usize {
274 self.video_streams.len() + self.audio_streams.len() + self.subtitle_streams.len()
275 }
276
277 #[must_use]
284 #[inline]
285 pub fn primary_video(&self) -> Option<&VideoStreamInfo> {
286 self.video_streams.first()
287 }
288
289 #[must_use]
294 #[inline]
295 pub fn primary_audio(&self) -> Option<&AudioStreamInfo> {
296 self.audio_streams.first()
297 }
298
299 #[must_use]
301 #[inline]
302 pub fn video_stream(&self, index: usize) -> Option<&VideoStreamInfo> {
303 self.video_streams.get(index)
304 }
305
306 #[must_use]
308 #[inline]
309 pub fn audio_stream(&self, index: usize) -> Option<&AudioStreamInfo> {
310 self.audio_streams.get(index)
311 }
312
313 #[must_use]
315 #[inline]
316 pub fn subtitle_stream(&self, index: usize) -> Option<&SubtitleStreamInfo> {
317 self.subtitle_streams.get(index)
318 }
319
320 #[must_use]
326 #[inline]
327 pub fn resolution(&self) -> Option<(u32, u32)> {
328 self.primary_video().map(|v| (v.width(), v.height()))
329 }
330
331 #[must_use]
335 #[inline]
336 pub fn frame_rate(&self) -> Option<f64> {
337 self.primary_video().map(VideoStreamInfo::fps)
338 }
339
340 #[must_use]
344 #[inline]
345 pub fn sample_rate(&self) -> Option<u32> {
346 self.primary_audio().map(AudioStreamInfo::sample_rate)
347 }
348
349 #[must_use]
353 #[inline]
354 pub fn channels(&self) -> Option<u32> {
355 self.primary_audio().map(AudioStreamInfo::channels)
356 }
357
358 #[must_use]
360 #[inline]
361 pub fn is_video_only(&self) -> bool {
362 self.has_video() && !self.has_audio()
363 }
364
365 #[must_use]
367 #[inline]
368 pub fn is_audio_only(&self) -> bool {
369 self.has_audio() && !self.has_video()
370 }
371
372 #[must_use]
374 #[inline]
375 pub fn file_name(&self) -> Option<&str> {
376 self.path.file_name().and_then(|n| n.to_str())
377 }
378
379 #[must_use]
381 #[inline]
382 pub fn extension(&self) -> Option<&str> {
383 self.path.extension().and_then(|e| e.to_str())
384 }
385
386 #[must_use]
393 #[inline]
394 pub fn title(&self) -> Option<&str> {
395 self.metadata_value("title")
396 }
397
398 #[must_use]
402 #[inline]
403 pub fn artist(&self) -> Option<&str> {
404 self.metadata_value("artist")
405 }
406
407 #[must_use]
411 #[inline]
412 pub fn album(&self) -> Option<&str> {
413 self.metadata_value("album")
414 }
415
416 #[must_use]
422 #[inline]
423 pub fn creation_time(&self) -> Option<&str> {
424 self.metadata_value("creation_time")
425 }
426
427 #[must_use]
431 #[inline]
432 pub fn date(&self) -> Option<&str> {
433 self.metadata_value("date")
434 }
435
436 #[must_use]
440 #[inline]
441 pub fn comment(&self) -> Option<&str> {
442 self.metadata_value("comment")
443 }
444
445 #[must_use]
450 #[inline]
451 pub fn encoder(&self) -> Option<&str> {
452 self.metadata_value("encoder")
453 }
454}
455
456impl Default for MediaInfo {
457 fn default() -> Self {
458 Self {
459 path: PathBuf::new(),
460 format: String::new(),
461 format_long_name: None,
462 duration: Duration::ZERO,
463 file_size: 0,
464 bitrate: None,
465 video_streams: Vec::new(),
466 audio_streams: Vec::new(),
467 subtitle_streams: Vec::new(),
468 chapters: Vec::new(),
469 metadata: HashMap::new(),
470 }
471 }
472}
473
474#[derive(Debug, Clone, Default)]
493pub struct MediaInfoBuilder {
494 path: PathBuf,
495 format: String,
496 format_long_name: Option<String>,
497 duration: Duration,
498 file_size: u64,
499 bitrate: Option<u64>,
500 video_streams: Vec<VideoStreamInfo>,
501 audio_streams: Vec<AudioStreamInfo>,
502 subtitle_streams: Vec<SubtitleStreamInfo>,
503 chapters: Vec<ChapterInfo>,
504 metadata: HashMap<String, String>,
505}
506
507impl MediaInfoBuilder {
508 #[must_use]
510 pub fn path(mut self, path: impl Into<PathBuf>) -> Self {
511 self.path = path.into();
512 self
513 }
514
515 #[must_use]
517 pub fn format(mut self, format: impl Into<String>) -> Self {
518 self.format = format.into();
519 self
520 }
521
522 #[must_use]
524 pub fn format_long_name(mut self, name: impl Into<String>) -> Self {
525 self.format_long_name = Some(name.into());
526 self
527 }
528
529 #[must_use]
531 pub fn duration(mut self, duration: Duration) -> Self {
532 self.duration = duration;
533 self
534 }
535
536 #[must_use]
538 pub fn file_size(mut self, size: u64) -> Self {
539 self.file_size = size;
540 self
541 }
542
543 #[must_use]
545 pub fn bitrate(mut self, bitrate: u64) -> Self {
546 self.bitrate = Some(bitrate);
547 self
548 }
549
550 #[must_use]
552 pub fn video_stream(mut self, stream: VideoStreamInfo) -> Self {
553 self.video_streams.push(stream);
554 self
555 }
556
557 #[must_use]
559 pub fn video_streams(mut self, streams: Vec<VideoStreamInfo>) -> Self {
560 self.video_streams = streams;
561 self
562 }
563
564 #[must_use]
566 pub fn audio_stream(mut self, stream: AudioStreamInfo) -> Self {
567 self.audio_streams.push(stream);
568 self
569 }
570
571 #[must_use]
573 pub fn audio_streams(mut self, streams: Vec<AudioStreamInfo>) -> Self {
574 self.audio_streams = streams;
575 self
576 }
577
578 #[must_use]
580 pub fn subtitle_stream(mut self, stream: SubtitleStreamInfo) -> Self {
581 self.subtitle_streams.push(stream);
582 self
583 }
584
585 #[must_use]
587 pub fn subtitle_streams(mut self, streams: Vec<SubtitleStreamInfo>) -> Self {
588 self.subtitle_streams = streams;
589 self
590 }
591
592 #[must_use]
594 pub fn chapter(mut self, chapter: ChapterInfo) -> Self {
595 self.chapters.push(chapter);
596 self
597 }
598
599 #[must_use]
601 pub fn chapters(mut self, chapters: Vec<ChapterInfo>) -> Self {
602 self.chapters = chapters;
603 self
604 }
605
606 #[must_use]
608 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
609 self.metadata.insert(key.into(), value.into());
610 self
611 }
612
613 #[must_use]
615 pub fn metadata_map(mut self, metadata: HashMap<String, String>) -> Self {
616 self.metadata = metadata;
617 self
618 }
619
620 #[must_use]
622 pub fn build(self) -> MediaInfo {
623 MediaInfo {
624 path: self.path,
625 format: self.format,
626 format_long_name: self.format_long_name,
627 duration: self.duration,
628 file_size: self.file_size,
629 bitrate: self.bitrate,
630 video_streams: self.video_streams,
631 audio_streams: self.audio_streams,
632 subtitle_streams: self.subtitle_streams,
633 chapters: self.chapters,
634 metadata: self.metadata,
635 }
636 }
637}
638
639#[cfg(test)]
640mod tests {
641 use super::*;
642 use crate::codec::{AudioCodec, SubtitleCodec, VideoCodec};
643 use crate::time::Rational;
644 use crate::{PixelFormat, SampleFormat};
645
646 fn sample_video_stream() -> VideoStreamInfo {
647 VideoStreamInfo::builder()
648 .index(0)
649 .codec(VideoCodec::H264)
650 .codec_name("h264")
651 .width(1920)
652 .height(1080)
653 .frame_rate(Rational::new(30, 1))
654 .pixel_format(PixelFormat::Yuv420p)
655 .duration(Duration::from_secs(120))
656 .build()
657 }
658
659 fn sample_audio_stream() -> AudioStreamInfo {
660 AudioStreamInfo::builder()
661 .index(1)
662 .codec(AudioCodec::Aac)
663 .codec_name("aac")
664 .sample_rate(48000)
665 .channels(2)
666 .sample_format(SampleFormat::F32)
667 .duration(Duration::from_secs(120))
668 .build()
669 }
670
671 fn sample_subtitle_stream() -> SubtitleStreamInfo {
672 SubtitleStreamInfo::builder()
673 .index(2)
674 .codec(SubtitleCodec::Srt)
675 .codec_name("srt")
676 .language("eng")
677 .build()
678 }
679
680 mod media_info_tests {
681 use super::*;
682
683 #[test]
684 fn test_builder_basic() {
685 let info = MediaInfo::builder()
686 .path("/path/to/video.mp4")
687 .format("mp4")
688 .duration(Duration::from_secs(120))
689 .file_size(1_000_000)
690 .build();
691
692 assert_eq!(info.path(), Path::new("/path/to/video.mp4"));
693 assert_eq!(info.format(), "mp4");
694 assert_eq!(info.duration(), Duration::from_secs(120));
695 assert_eq!(info.file_size(), 1_000_000);
696 assert!(info.format_long_name().is_none());
697 assert!(info.bitrate().is_none());
698 }
699
700 #[test]
701 fn test_builder_full() {
702 let video = sample_video_stream();
703 let audio = sample_audio_stream();
704
705 let info = MediaInfo::builder()
706 .path("/path/to/video.mp4")
707 .format("mp4")
708 .format_long_name("QuickTime / MOV")
709 .duration(Duration::from_secs(120))
710 .file_size(150_000_000)
711 .bitrate(10_000_000)
712 .video_stream(video)
713 .audio_stream(audio)
714 .metadata("title", "Test Video")
715 .metadata("artist", "Test Artist")
716 .build();
717
718 assert_eq!(info.format_long_name(), Some("QuickTime / MOV"));
719 assert_eq!(info.bitrate(), Some(10_000_000));
720 assert_eq!(info.video_stream_count(), 1);
721 assert_eq!(info.audio_stream_count(), 1);
722 assert_eq!(info.metadata_value("title"), Some("Test Video"));
723 assert_eq!(info.metadata_value("artist"), Some("Test Artist"));
724 assert!(info.metadata_value("nonexistent").is_none());
725 }
726
727 #[test]
728 fn test_default() {
729 let info = MediaInfo::default();
730 assert_eq!(info.path(), Path::new(""));
731 assert_eq!(info.format(), "");
732 assert_eq!(info.duration(), Duration::ZERO);
733 assert_eq!(info.file_size(), 0);
734 assert!(!info.has_video());
735 assert!(!info.has_audio());
736 }
737
738 #[test]
739 fn test_has_streams() {
740 let empty = MediaInfo::default();
742 assert!(!empty.has_video());
743 assert!(!empty.has_audio());
744
745 let video_only = MediaInfo::builder()
747 .video_stream(sample_video_stream())
748 .build();
749 assert!(video_only.has_video());
750 assert!(!video_only.has_audio());
751 assert!(video_only.is_video_only());
752 assert!(!video_only.is_audio_only());
753
754 let audio_only = MediaInfo::builder()
756 .audio_stream(sample_audio_stream())
757 .build();
758 assert!(!audio_only.has_video());
759 assert!(audio_only.has_audio());
760 assert!(!audio_only.is_video_only());
761 assert!(audio_only.is_audio_only());
762
763 let both = MediaInfo::builder()
765 .video_stream(sample_video_stream())
766 .audio_stream(sample_audio_stream())
767 .build();
768 assert!(both.has_video());
769 assert!(both.has_audio());
770 assert!(!both.is_video_only());
771 assert!(!both.is_audio_only());
772 }
773
774 #[test]
775 fn test_primary_streams() {
776 let video1 = VideoStreamInfo::builder()
777 .index(0)
778 .width(1920)
779 .height(1080)
780 .build();
781 let video2 = VideoStreamInfo::builder()
782 .index(2)
783 .width(1280)
784 .height(720)
785 .build();
786 let audio1 = AudioStreamInfo::builder()
787 .index(1)
788 .sample_rate(48000)
789 .build();
790 let audio2 = AudioStreamInfo::builder()
791 .index(3)
792 .sample_rate(44100)
793 .build();
794
795 let info = MediaInfo::builder()
796 .video_stream(video1)
797 .video_stream(video2)
798 .audio_stream(audio1)
799 .audio_stream(audio2)
800 .build();
801
802 let primary_video = info.primary_video().unwrap();
804 assert_eq!(primary_video.width(), 1920);
805 assert_eq!(primary_video.index(), 0);
806
807 let primary_audio = info.primary_audio().unwrap();
808 assert_eq!(primary_audio.sample_rate(), 48000);
809 assert_eq!(primary_audio.index(), 1);
810 }
811
812 #[test]
813 fn test_stream_access_by_index() {
814 let video1 = VideoStreamInfo::builder().width(1920).build();
815 let video2 = VideoStreamInfo::builder().width(1280).build();
816 let audio1 = AudioStreamInfo::builder().sample_rate(48000).build();
817
818 let info = MediaInfo::builder()
819 .video_stream(video1)
820 .video_stream(video2)
821 .audio_stream(audio1)
822 .build();
823
824 assert_eq!(info.video_stream(0).unwrap().width(), 1920);
825 assert_eq!(info.video_stream(1).unwrap().width(), 1280);
826 assert!(info.video_stream(2).is_none());
827
828 assert_eq!(info.audio_stream(0).unwrap().sample_rate(), 48000);
829 assert!(info.audio_stream(1).is_none());
830 }
831
832 #[test]
833 fn test_resolution_and_frame_rate() {
834 let info = MediaInfo::builder()
835 .video_stream(sample_video_stream())
836 .build();
837
838 assert_eq!(info.resolution(), Some((1920, 1080)));
839 assert!((info.frame_rate().unwrap() - 30.0).abs() < 0.001);
840
841 let no_video = MediaInfo::default();
843 assert!(no_video.resolution().is_none());
844 assert!(no_video.frame_rate().is_none());
845 }
846
847 #[test]
848 fn test_sample_rate_and_channels() {
849 let info = MediaInfo::builder()
850 .audio_stream(sample_audio_stream())
851 .build();
852
853 assert_eq!(info.sample_rate(), Some(48000));
854 assert_eq!(info.channels(), Some(2));
855
856 let no_audio = MediaInfo::default();
858 assert!(no_audio.sample_rate().is_none());
859 assert!(no_audio.channels().is_none());
860 }
861
862 #[test]
863 fn test_stream_counts() {
864 let info = MediaInfo::builder()
865 .video_stream(sample_video_stream())
866 .video_stream(sample_video_stream())
867 .audio_stream(sample_audio_stream())
868 .audio_stream(sample_audio_stream())
869 .audio_stream(sample_audio_stream())
870 .build();
871
872 assert_eq!(info.video_stream_count(), 2);
873 assert_eq!(info.audio_stream_count(), 3);
874 assert_eq!(info.stream_count(), 5);
875 }
876
877 #[test]
878 fn has_subtitles_should_return_true_when_subtitle_streams_present() {
879 let no_subs = MediaInfo::default();
880 assert!(!no_subs.has_subtitles());
881 assert_eq!(no_subs.subtitle_stream_count(), 0);
882
883 let with_subs = MediaInfo::builder()
884 .subtitle_stream(sample_subtitle_stream())
885 .subtitle_stream(sample_subtitle_stream())
886 .build();
887 assert!(with_subs.has_subtitles());
888 assert_eq!(with_subs.subtitle_stream_count(), 2);
889 }
890
891 #[test]
892 fn subtitle_stream_count_should_be_included_in_stream_count() {
893 let info = MediaInfo::builder()
894 .video_stream(sample_video_stream())
895 .audio_stream(sample_audio_stream())
896 .subtitle_stream(sample_subtitle_stream())
897 .build();
898 assert_eq!(info.stream_count(), 3);
899 }
900
901 #[test]
902 fn subtitle_stream_by_index_should_return_correct_stream() {
903 let sub1 = SubtitleStreamInfo::builder()
904 .index(2)
905 .codec(SubtitleCodec::Srt)
906 .language("eng")
907 .build();
908 let sub2 = SubtitleStreamInfo::builder()
909 .index(3)
910 .codec(SubtitleCodec::Ass)
911 .language("jpn")
912 .build();
913
914 let info = MediaInfo::builder()
915 .subtitle_stream(sub1)
916 .subtitle_stream(sub2)
917 .build();
918
919 assert_eq!(info.subtitle_stream(0).unwrap().language(), Some("eng"));
920 assert_eq!(info.subtitle_stream(1).unwrap().language(), Some("jpn"));
921 assert!(info.subtitle_stream(2).is_none());
922 }
923
924 #[test]
925 fn test_file_name_and_extension() {
926 let info = MediaInfo::builder().path("/path/to/my_video.mp4").build();
927
928 assert_eq!(info.file_name(), Some("my_video.mp4"));
929 assert_eq!(info.extension(), Some("mp4"));
930
931 let empty = MediaInfo::default();
933 assert!(empty.file_name().is_none());
934 assert!(empty.extension().is_none());
935 }
936
937 #[test]
938 fn test_metadata_operations() {
939 let mut map = HashMap::new();
940 map.insert("key1".to_string(), "value1".to_string());
941 map.insert("key2".to_string(), "value2".to_string());
942
943 let info = MediaInfo::builder()
944 .metadata_map(map)
945 .metadata("key3", "value3")
946 .build();
947
948 assert_eq!(info.metadata().len(), 3);
949 assert_eq!(info.metadata_value("key1"), Some("value1"));
950 assert_eq!(info.metadata_value("key2"), Some("value2"));
951 assert_eq!(info.metadata_value("key3"), Some("value3"));
952 }
953
954 #[test]
955 fn test_clone() {
956 let info = MediaInfo::builder()
957 .path("/path/to/video.mp4")
958 .format("mp4")
959 .format_long_name("QuickTime / MOV")
960 .duration(Duration::from_secs(120))
961 .file_size(1_000_000)
962 .video_stream(sample_video_stream())
963 .audio_stream(sample_audio_stream())
964 .metadata("title", "Test")
965 .build();
966
967 let cloned = info.clone();
968 assert_eq!(info.path(), cloned.path());
969 assert_eq!(info.format(), cloned.format());
970 assert_eq!(info.format_long_name(), cloned.format_long_name());
971 assert_eq!(info.duration(), cloned.duration());
972 assert_eq!(info.file_size(), cloned.file_size());
973 assert_eq!(info.video_stream_count(), cloned.video_stream_count());
974 assert_eq!(info.audio_stream_count(), cloned.audio_stream_count());
975 assert_eq!(info.metadata_value("title"), cloned.metadata_value("title"));
976 }
977
978 #[test]
979 fn test_debug() {
980 let info = MediaInfo::builder()
981 .path("/path/to/video.mp4")
982 .format("mp4")
983 .duration(Duration::from_secs(120))
984 .file_size(1_000_000)
985 .build();
986
987 let debug = format!("{info:?}");
988 assert!(debug.contains("MediaInfo"));
989 assert!(debug.contains("mp4"));
990 }
991
992 #[test]
993 fn test_video_streams_setter() {
994 let streams = vec![sample_video_stream(), sample_video_stream()];
995
996 let info = MediaInfo::builder().video_streams(streams).build();
997
998 assert_eq!(info.video_stream_count(), 2);
999 }
1000
1001 #[test]
1002 fn test_audio_streams_setter() {
1003 let streams = vec![
1004 sample_audio_stream(),
1005 sample_audio_stream(),
1006 sample_audio_stream(),
1007 ];
1008
1009 let info = MediaInfo::builder().audio_streams(streams).build();
1010
1011 assert_eq!(info.audio_stream_count(), 3);
1012 }
1013 }
1014
1015 mod media_info_builder_tests {
1016 use super::*;
1017
1018 #[test]
1019 fn test_builder_default() {
1020 let builder = MediaInfoBuilder::default();
1021 let info = builder.build();
1022 assert_eq!(info.path(), Path::new(""));
1023 assert_eq!(info.format(), "");
1024 assert_eq!(info.duration(), Duration::ZERO);
1025 }
1026
1027 #[test]
1028 fn test_builder_clone() {
1029 let builder = MediaInfo::builder()
1030 .path("/path/to/video.mp4")
1031 .format("mp4")
1032 .duration(Duration::from_secs(120));
1033
1034 let cloned = builder.clone();
1035 let info1 = builder.build();
1036 let info2 = cloned.build();
1037
1038 assert_eq!(info1.path(), info2.path());
1039 assert_eq!(info1.format(), info2.format());
1040 assert_eq!(info1.duration(), info2.duration());
1041 }
1042
1043 #[test]
1044 fn test_builder_debug() {
1045 let builder = MediaInfo::builder()
1046 .path("/path/to/video.mp4")
1047 .format("mp4");
1048
1049 let debug = format!("{builder:?}");
1050 assert!(debug.contains("MediaInfoBuilder"));
1051 }
1052 }
1053
1054 mod metadata_convenience_tests {
1055 use super::*;
1056
1057 #[test]
1058 fn test_title() {
1059 let info = MediaInfo::builder()
1060 .metadata("title", "Sample Video Title")
1061 .build();
1062
1063 assert_eq!(info.title(), Some("Sample Video Title"));
1064 }
1065
1066 #[test]
1067 fn test_title_missing() {
1068 let info = MediaInfo::default();
1069 assert!(info.title().is_none());
1070 }
1071
1072 #[test]
1073 fn test_artist() {
1074 let info = MediaInfo::builder()
1075 .metadata("artist", "Test Artist")
1076 .build();
1077
1078 assert_eq!(info.artist(), Some("Test Artist"));
1079 }
1080
1081 #[test]
1082 fn test_album() {
1083 let info = MediaInfo::builder().metadata("album", "Test Album").build();
1084
1085 assert_eq!(info.album(), Some("Test Album"));
1086 }
1087
1088 #[test]
1089 fn test_creation_time() {
1090 let info = MediaInfo::builder()
1091 .metadata("creation_time", "2024-01-15T10:30:00.000000Z")
1092 .build();
1093
1094 assert_eq!(info.creation_time(), Some("2024-01-15T10:30:00.000000Z"));
1095 }
1096
1097 #[test]
1098 fn test_date() {
1099 let info = MediaInfo::builder().metadata("date", "2024-01-15").build();
1100
1101 assert_eq!(info.date(), Some("2024-01-15"));
1102 }
1103
1104 #[test]
1105 fn test_comment() {
1106 let info = MediaInfo::builder()
1107 .metadata("comment", "This is a test comment")
1108 .build();
1109
1110 assert_eq!(info.comment(), Some("This is a test comment"));
1111 }
1112
1113 #[test]
1114 fn test_encoder() {
1115 let info = MediaInfo::builder()
1116 .metadata("encoder", "Lavf58.76.100")
1117 .build();
1118
1119 assert_eq!(info.encoder(), Some("Lavf58.76.100"));
1120 }
1121
1122 #[test]
1123 fn test_multiple_metadata_fields() {
1124 let info = MediaInfo::builder()
1125 .metadata("title", "My Video")
1126 .metadata("artist", "John Doe")
1127 .metadata("album", "My Collection")
1128 .metadata("date", "2024")
1129 .metadata("comment", "A great video")
1130 .metadata("encoder", "FFmpeg")
1131 .metadata("custom_field", "custom_value")
1132 .build();
1133
1134 assert_eq!(info.title(), Some("My Video"));
1135 assert_eq!(info.artist(), Some("John Doe"));
1136 assert_eq!(info.album(), Some("My Collection"));
1137 assert_eq!(info.date(), Some("2024"));
1138 assert_eq!(info.comment(), Some("A great video"));
1139 assert_eq!(info.encoder(), Some("FFmpeg"));
1140 assert_eq!(info.metadata_value("custom_field"), Some("custom_value"));
1141 assert_eq!(info.metadata().len(), 7);
1142 }
1143 }
1144}