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]
357 #[inline]
358 pub fn channels(&self) -> Option<u32> {
359 self.primary_audio().map(AudioStreamInfo::channels)
360 }
361
362 #[must_use]
364 #[inline]
365 pub fn is_video_only(&self) -> bool {
366 self.has_video() && !self.has_audio()
367 }
368
369 #[must_use]
371 #[inline]
372 pub fn is_audio_only(&self) -> bool {
373 self.has_audio() && !self.has_video()
374 }
375
376 #[must_use]
378 #[inline]
379 pub fn file_name(&self) -> Option<&str> {
380 self.path.file_name().and_then(|n| n.to_str())
381 }
382
383 #[must_use]
385 #[inline]
386 pub fn extension(&self) -> Option<&str> {
387 self.path.extension().and_then(|e| e.to_str())
388 }
389
390 #[must_use]
397 #[inline]
398 pub fn title(&self) -> Option<&str> {
399 self.metadata_value("title")
400 }
401
402 #[must_use]
406 #[inline]
407 pub fn artist(&self) -> Option<&str> {
408 self.metadata_value("artist")
409 }
410
411 #[must_use]
415 #[inline]
416 pub fn album(&self) -> Option<&str> {
417 self.metadata_value("album")
418 }
419
420 #[must_use]
426 #[inline]
427 pub fn creation_time(&self) -> Option<&str> {
428 self.metadata_value("creation_time")
429 }
430
431 #[must_use]
435 #[inline]
436 pub fn date(&self) -> Option<&str> {
437 self.metadata_value("date")
438 }
439
440 #[must_use]
444 #[inline]
445 pub fn comment(&self) -> Option<&str> {
446 self.metadata_value("comment")
447 }
448
449 #[must_use]
454 #[inline]
455 pub fn encoder(&self) -> Option<&str> {
456 self.metadata_value("encoder")
457 }
458}
459
460impl Default for MediaInfo {
461 fn default() -> Self {
462 Self {
463 path: PathBuf::new(),
464 format: String::new(),
465 format_long_name: None,
466 duration: Duration::ZERO,
467 file_size: 0,
468 bitrate: None,
469 video_streams: Vec::new(),
470 audio_streams: Vec::new(),
471 subtitle_streams: Vec::new(),
472 chapters: Vec::new(),
473 metadata: HashMap::new(),
474 }
475 }
476}
477
478#[derive(Debug, Clone, Default)]
497pub struct MediaInfoBuilder {
498 path: PathBuf,
499 format: String,
500 format_long_name: Option<String>,
501 duration: Duration,
502 file_size: u64,
503 bitrate: Option<u64>,
504 video_streams: Vec<VideoStreamInfo>,
505 audio_streams: Vec<AudioStreamInfo>,
506 subtitle_streams: Vec<SubtitleStreamInfo>,
507 chapters: Vec<ChapterInfo>,
508 metadata: HashMap<String, String>,
509}
510
511impl MediaInfoBuilder {
512 #[must_use]
514 pub fn path(mut self, path: impl Into<PathBuf>) -> Self {
515 self.path = path.into();
516 self
517 }
518
519 #[must_use]
521 pub fn format(mut self, format: impl Into<String>) -> Self {
522 self.format = format.into();
523 self
524 }
525
526 #[must_use]
528 pub fn format_long_name(mut self, name: impl Into<String>) -> Self {
529 self.format_long_name = Some(name.into());
530 self
531 }
532
533 #[must_use]
535 pub fn duration(mut self, duration: Duration) -> Self {
536 self.duration = duration;
537 self
538 }
539
540 #[must_use]
542 pub fn file_size(mut self, size: u64) -> Self {
543 self.file_size = size;
544 self
545 }
546
547 #[must_use]
549 pub fn bitrate(mut self, bitrate: u64) -> Self {
550 self.bitrate = Some(bitrate);
551 self
552 }
553
554 #[must_use]
556 pub fn video_stream(mut self, stream: VideoStreamInfo) -> Self {
557 self.video_streams.push(stream);
558 self
559 }
560
561 #[must_use]
563 pub fn video_streams(mut self, streams: Vec<VideoStreamInfo>) -> Self {
564 self.video_streams = streams;
565 self
566 }
567
568 #[must_use]
570 pub fn audio_stream(mut self, stream: AudioStreamInfo) -> Self {
571 self.audio_streams.push(stream);
572 self
573 }
574
575 #[must_use]
577 pub fn audio_streams(mut self, streams: Vec<AudioStreamInfo>) -> Self {
578 self.audio_streams = streams;
579 self
580 }
581
582 #[must_use]
584 pub fn subtitle_stream(mut self, stream: SubtitleStreamInfo) -> Self {
585 self.subtitle_streams.push(stream);
586 self
587 }
588
589 #[must_use]
591 pub fn subtitle_streams(mut self, streams: Vec<SubtitleStreamInfo>) -> Self {
592 self.subtitle_streams = streams;
593 self
594 }
595
596 #[must_use]
598 pub fn chapter(mut self, chapter: ChapterInfo) -> Self {
599 self.chapters.push(chapter);
600 self
601 }
602
603 #[must_use]
605 pub fn chapters(mut self, chapters: Vec<ChapterInfo>) -> Self {
606 self.chapters = chapters;
607 self
608 }
609
610 #[must_use]
612 pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
613 self.metadata.insert(key.into(), value.into());
614 self
615 }
616
617 #[must_use]
619 pub fn metadata_map(mut self, metadata: HashMap<String, String>) -> Self {
620 self.metadata = metadata;
621 self
622 }
623
624 #[must_use]
626 pub fn build(self) -> MediaInfo {
627 MediaInfo {
628 path: self.path,
629 format: self.format,
630 format_long_name: self.format_long_name,
631 duration: self.duration,
632 file_size: self.file_size,
633 bitrate: self.bitrate,
634 video_streams: self.video_streams,
635 audio_streams: self.audio_streams,
636 subtitle_streams: self.subtitle_streams,
637 chapters: self.chapters,
638 metadata: self.metadata,
639 }
640 }
641}
642
643#[cfg(test)]
644mod tests {
645 use super::*;
646 use crate::codec::{AudioCodec, SubtitleCodec, VideoCodec};
647 use crate::time::Rational;
648 use crate::{PixelFormat, SampleFormat};
649
650 fn sample_video_stream() -> VideoStreamInfo {
651 VideoStreamInfo::builder()
652 .index(0)
653 .codec(VideoCodec::H264)
654 .codec_name("h264")
655 .width(1920)
656 .height(1080)
657 .frame_rate(Rational::new(30, 1))
658 .pixel_format(PixelFormat::Yuv420p)
659 .duration(Duration::from_secs(120))
660 .build()
661 }
662
663 fn sample_audio_stream() -> AudioStreamInfo {
664 AudioStreamInfo::builder()
665 .index(1)
666 .codec(AudioCodec::Aac)
667 .codec_name("aac")
668 .sample_rate(48000)
669 .channels(2)
670 .sample_format(SampleFormat::F32)
671 .duration(Duration::from_secs(120))
672 .build()
673 }
674
675 fn sample_subtitle_stream() -> SubtitleStreamInfo {
676 SubtitleStreamInfo::builder()
677 .index(2)
678 .codec(SubtitleCodec::Srt)
679 .codec_name("srt")
680 .language("eng")
681 .build()
682 }
683
684 mod media_info_tests {
685 use super::*;
686
687 #[test]
688 fn test_builder_basic() {
689 let info = MediaInfo::builder()
690 .path("/path/to/video.mp4")
691 .format("mp4")
692 .duration(Duration::from_secs(120))
693 .file_size(1_000_000)
694 .build();
695
696 assert_eq!(info.path(), Path::new("/path/to/video.mp4"));
697 assert_eq!(info.format(), "mp4");
698 assert_eq!(info.duration(), Duration::from_secs(120));
699 assert_eq!(info.file_size(), 1_000_000);
700 assert!(info.format_long_name().is_none());
701 assert!(info.bitrate().is_none());
702 }
703
704 #[test]
705 fn test_builder_full() {
706 let video = sample_video_stream();
707 let audio = sample_audio_stream();
708
709 let info = MediaInfo::builder()
710 .path("/path/to/video.mp4")
711 .format("mp4")
712 .format_long_name("QuickTime / MOV")
713 .duration(Duration::from_secs(120))
714 .file_size(150_000_000)
715 .bitrate(10_000_000)
716 .video_stream(video)
717 .audio_stream(audio)
718 .metadata("title", "Test Video")
719 .metadata("artist", "Test Artist")
720 .build();
721
722 assert_eq!(info.format_long_name(), Some("QuickTime / MOV"));
723 assert_eq!(info.bitrate(), Some(10_000_000));
724 assert_eq!(info.video_stream_count(), 1);
725 assert_eq!(info.audio_stream_count(), 1);
726 assert_eq!(info.metadata_value("title"), Some("Test Video"));
727 assert_eq!(info.metadata_value("artist"), Some("Test Artist"));
728 assert!(info.metadata_value("nonexistent").is_none());
729 }
730
731 #[test]
732 fn test_default() {
733 let info = MediaInfo::default();
734 assert_eq!(info.path(), Path::new(""));
735 assert_eq!(info.format(), "");
736 assert_eq!(info.duration(), Duration::ZERO);
737 assert_eq!(info.file_size(), 0);
738 assert!(!info.has_video());
739 assert!(!info.has_audio());
740 }
741
742 #[test]
743 fn test_has_streams() {
744 let empty = MediaInfo::default();
746 assert!(!empty.has_video());
747 assert!(!empty.has_audio());
748
749 let video_only = MediaInfo::builder()
751 .video_stream(sample_video_stream())
752 .build();
753 assert!(video_only.has_video());
754 assert!(!video_only.has_audio());
755 assert!(video_only.is_video_only());
756 assert!(!video_only.is_audio_only());
757
758 let audio_only = MediaInfo::builder()
760 .audio_stream(sample_audio_stream())
761 .build();
762 assert!(!audio_only.has_video());
763 assert!(audio_only.has_audio());
764 assert!(!audio_only.is_video_only());
765 assert!(audio_only.is_audio_only());
766
767 let both = MediaInfo::builder()
769 .video_stream(sample_video_stream())
770 .audio_stream(sample_audio_stream())
771 .build();
772 assert!(both.has_video());
773 assert!(both.has_audio());
774 assert!(!both.is_video_only());
775 assert!(!both.is_audio_only());
776 }
777
778 #[test]
779 fn test_primary_streams() {
780 let video1 = VideoStreamInfo::builder()
781 .index(0)
782 .width(1920)
783 .height(1080)
784 .build();
785 let video2 = VideoStreamInfo::builder()
786 .index(2)
787 .width(1280)
788 .height(720)
789 .build();
790 let audio1 = AudioStreamInfo::builder()
791 .index(1)
792 .sample_rate(48000)
793 .build();
794 let audio2 = AudioStreamInfo::builder()
795 .index(3)
796 .sample_rate(44100)
797 .build();
798
799 let info = MediaInfo::builder()
800 .video_stream(video1)
801 .video_stream(video2)
802 .audio_stream(audio1)
803 .audio_stream(audio2)
804 .build();
805
806 let primary_video = info.primary_video().unwrap();
808 assert_eq!(primary_video.width(), 1920);
809 assert_eq!(primary_video.index(), 0);
810
811 let primary_audio = info.primary_audio().unwrap();
812 assert_eq!(primary_audio.sample_rate(), 48000);
813 assert_eq!(primary_audio.index(), 1);
814 }
815
816 #[test]
817 fn test_stream_access_by_index() {
818 let video1 = VideoStreamInfo::builder().width(1920).build();
819 let video2 = VideoStreamInfo::builder().width(1280).build();
820 let audio1 = AudioStreamInfo::builder().sample_rate(48000).build();
821
822 let info = MediaInfo::builder()
823 .video_stream(video1)
824 .video_stream(video2)
825 .audio_stream(audio1)
826 .build();
827
828 assert_eq!(info.video_stream(0).unwrap().width(), 1920);
829 assert_eq!(info.video_stream(1).unwrap().width(), 1280);
830 assert!(info.video_stream(2).is_none());
831
832 assert_eq!(info.audio_stream(0).unwrap().sample_rate(), 48000);
833 assert!(info.audio_stream(1).is_none());
834 }
835
836 #[test]
837 fn test_resolution_and_frame_rate() {
838 let info = MediaInfo::builder()
839 .video_stream(sample_video_stream())
840 .build();
841
842 assert_eq!(info.resolution(), Some((1920, 1080)));
843 assert!((info.frame_rate().unwrap() - 30.0).abs() < 0.001);
844
845 let no_video = MediaInfo::default();
847 assert!(no_video.resolution().is_none());
848 assert!(no_video.frame_rate().is_none());
849 }
850
851 #[test]
852 fn test_sample_rate_and_channels() {
853 let info = MediaInfo::builder()
854 .audio_stream(sample_audio_stream())
855 .build();
856
857 assert_eq!(info.sample_rate(), Some(48000));
858 assert_eq!(info.channels(), Some(2));
859
860 let no_audio = MediaInfo::default();
862 assert!(no_audio.sample_rate().is_none());
863 assert!(no_audio.channels().is_none());
864 }
865
866 #[test]
867 fn test_stream_counts() {
868 let info = MediaInfo::builder()
869 .video_stream(sample_video_stream())
870 .video_stream(sample_video_stream())
871 .audio_stream(sample_audio_stream())
872 .audio_stream(sample_audio_stream())
873 .audio_stream(sample_audio_stream())
874 .build();
875
876 assert_eq!(info.video_stream_count(), 2);
877 assert_eq!(info.audio_stream_count(), 3);
878 assert_eq!(info.stream_count(), 5);
879 }
880
881 #[test]
882 fn has_subtitles_should_return_true_when_subtitle_streams_present() {
883 let no_subs = MediaInfo::default();
884 assert!(!no_subs.has_subtitles());
885 assert_eq!(no_subs.subtitle_stream_count(), 0);
886
887 let with_subs = MediaInfo::builder()
888 .subtitle_stream(sample_subtitle_stream())
889 .subtitle_stream(sample_subtitle_stream())
890 .build();
891 assert!(with_subs.has_subtitles());
892 assert_eq!(with_subs.subtitle_stream_count(), 2);
893 }
894
895 #[test]
896 fn subtitle_stream_count_should_be_included_in_stream_count() {
897 let info = MediaInfo::builder()
898 .video_stream(sample_video_stream())
899 .audio_stream(sample_audio_stream())
900 .subtitle_stream(sample_subtitle_stream())
901 .build();
902 assert_eq!(info.stream_count(), 3);
903 }
904
905 #[test]
906 fn subtitle_stream_by_index_should_return_correct_stream() {
907 let sub1 = SubtitleStreamInfo::builder()
908 .index(2)
909 .codec(SubtitleCodec::Srt)
910 .language("eng")
911 .build();
912 let sub2 = SubtitleStreamInfo::builder()
913 .index(3)
914 .codec(SubtitleCodec::Ass)
915 .language("jpn")
916 .build();
917
918 let info = MediaInfo::builder()
919 .subtitle_stream(sub1)
920 .subtitle_stream(sub2)
921 .build();
922
923 assert_eq!(info.subtitle_stream(0).unwrap().language(), Some("eng"));
924 assert_eq!(info.subtitle_stream(1).unwrap().language(), Some("jpn"));
925 assert!(info.subtitle_stream(2).is_none());
926 }
927
928 #[test]
929 fn test_file_name_and_extension() {
930 let info = MediaInfo::builder().path("/path/to/my_video.mp4").build();
931
932 assert_eq!(info.file_name(), Some("my_video.mp4"));
933 assert_eq!(info.extension(), Some("mp4"));
934
935 let empty = MediaInfo::default();
937 assert!(empty.file_name().is_none());
938 assert!(empty.extension().is_none());
939 }
940
941 #[test]
942 fn test_metadata_operations() {
943 let mut map = HashMap::new();
944 map.insert("key1".to_string(), "value1".to_string());
945 map.insert("key2".to_string(), "value2".to_string());
946
947 let info = MediaInfo::builder()
948 .metadata_map(map)
949 .metadata("key3", "value3")
950 .build();
951
952 assert_eq!(info.metadata().len(), 3);
953 assert_eq!(info.metadata_value("key1"), Some("value1"));
954 assert_eq!(info.metadata_value("key2"), Some("value2"));
955 assert_eq!(info.metadata_value("key3"), Some("value3"));
956 }
957
958 #[test]
959 fn test_clone() {
960 let info = MediaInfo::builder()
961 .path("/path/to/video.mp4")
962 .format("mp4")
963 .format_long_name("QuickTime / MOV")
964 .duration(Duration::from_secs(120))
965 .file_size(1_000_000)
966 .video_stream(sample_video_stream())
967 .audio_stream(sample_audio_stream())
968 .metadata("title", "Test")
969 .build();
970
971 let cloned = info.clone();
972 assert_eq!(info.path(), cloned.path());
973 assert_eq!(info.format(), cloned.format());
974 assert_eq!(info.format_long_name(), cloned.format_long_name());
975 assert_eq!(info.duration(), cloned.duration());
976 assert_eq!(info.file_size(), cloned.file_size());
977 assert_eq!(info.video_stream_count(), cloned.video_stream_count());
978 assert_eq!(info.audio_stream_count(), cloned.audio_stream_count());
979 assert_eq!(info.metadata_value("title"), cloned.metadata_value("title"));
980 }
981
982 #[test]
983 fn test_debug() {
984 let info = MediaInfo::builder()
985 .path("/path/to/video.mp4")
986 .format("mp4")
987 .duration(Duration::from_secs(120))
988 .file_size(1_000_000)
989 .build();
990
991 let debug = format!("{info:?}");
992 assert!(debug.contains("MediaInfo"));
993 assert!(debug.contains("mp4"));
994 }
995
996 #[test]
997 fn test_video_streams_setter() {
998 let streams = vec![sample_video_stream(), sample_video_stream()];
999
1000 let info = MediaInfo::builder().video_streams(streams).build();
1001
1002 assert_eq!(info.video_stream_count(), 2);
1003 }
1004
1005 #[test]
1006 fn test_audio_streams_setter() {
1007 let streams = vec![
1008 sample_audio_stream(),
1009 sample_audio_stream(),
1010 sample_audio_stream(),
1011 ];
1012
1013 let info = MediaInfo::builder().audio_streams(streams).build();
1014
1015 assert_eq!(info.audio_stream_count(), 3);
1016 }
1017 }
1018
1019 mod media_info_builder_tests {
1020 use super::*;
1021
1022 #[test]
1023 fn test_builder_default() {
1024 let builder = MediaInfoBuilder::default();
1025 let info = builder.build();
1026 assert_eq!(info.path(), Path::new(""));
1027 assert_eq!(info.format(), "");
1028 assert_eq!(info.duration(), Duration::ZERO);
1029 }
1030
1031 #[test]
1032 fn test_builder_clone() {
1033 let builder = MediaInfo::builder()
1034 .path("/path/to/video.mp4")
1035 .format("mp4")
1036 .duration(Duration::from_secs(120));
1037
1038 let cloned = builder.clone();
1039 let info1 = builder.build();
1040 let info2 = cloned.build();
1041
1042 assert_eq!(info1.path(), info2.path());
1043 assert_eq!(info1.format(), info2.format());
1044 assert_eq!(info1.duration(), info2.duration());
1045 }
1046
1047 #[test]
1048 fn test_builder_debug() {
1049 let builder = MediaInfo::builder()
1050 .path("/path/to/video.mp4")
1051 .format("mp4");
1052
1053 let debug = format!("{builder:?}");
1054 assert!(debug.contains("MediaInfoBuilder"));
1055 }
1056 }
1057
1058 mod metadata_convenience_tests {
1059 use super::*;
1060
1061 #[test]
1062 fn test_title() {
1063 let info = MediaInfo::builder()
1064 .metadata("title", "Sample Video Title")
1065 .build();
1066
1067 assert_eq!(info.title(), Some("Sample Video Title"));
1068 }
1069
1070 #[test]
1071 fn test_title_missing() {
1072 let info = MediaInfo::default();
1073 assert!(info.title().is_none());
1074 }
1075
1076 #[test]
1077 fn test_artist() {
1078 let info = MediaInfo::builder()
1079 .metadata("artist", "Test Artist")
1080 .build();
1081
1082 assert_eq!(info.artist(), Some("Test Artist"));
1083 }
1084
1085 #[test]
1086 fn test_album() {
1087 let info = MediaInfo::builder().metadata("album", "Test Album").build();
1088
1089 assert_eq!(info.album(), Some("Test Album"));
1090 }
1091
1092 #[test]
1093 fn test_creation_time() {
1094 let info = MediaInfo::builder()
1095 .metadata("creation_time", "2024-01-15T10:30:00.000000Z")
1096 .build();
1097
1098 assert_eq!(info.creation_time(), Some("2024-01-15T10:30:00.000000Z"));
1099 }
1100
1101 #[test]
1102 fn test_date() {
1103 let info = MediaInfo::builder().metadata("date", "2024-01-15").build();
1104
1105 assert_eq!(info.date(), Some("2024-01-15"));
1106 }
1107
1108 #[test]
1109 fn test_comment() {
1110 let info = MediaInfo::builder()
1111 .metadata("comment", "This is a test comment")
1112 .build();
1113
1114 assert_eq!(info.comment(), Some("This is a test comment"));
1115 }
1116
1117 #[test]
1118 fn test_encoder() {
1119 let info = MediaInfo::builder()
1120 .metadata("encoder", "Lavf58.76.100")
1121 .build();
1122
1123 assert_eq!(info.encoder(), Some("Lavf58.76.100"));
1124 }
1125
1126 #[test]
1127 fn test_multiple_metadata_fields() {
1128 let info = MediaInfo::builder()
1129 .metadata("title", "My Video")
1130 .metadata("artist", "John Doe")
1131 .metadata("album", "My Collection")
1132 .metadata("date", "2024")
1133 .metadata("comment", "A great video")
1134 .metadata("encoder", "FFmpeg")
1135 .metadata("custom_field", "custom_value")
1136 .build();
1137
1138 assert_eq!(info.title(), Some("My Video"));
1139 assert_eq!(info.artist(), Some("John Doe"));
1140 assert_eq!(info.album(), Some("My Collection"));
1141 assert_eq!(info.date(), Some("2024"));
1142 assert_eq!(info.comment(), Some("A great video"));
1143 assert_eq!(info.encoder(), Some("FFmpeg"));
1144 assert_eq!(info.metadata_value("custom_field"), Some("custom_value"));
1145 assert_eq!(info.metadata().len(), 7);
1146 }
1147 }
1148}