1use std::path::Path;
8
9#[derive(Debug, Clone, Copy, Default, PartialEq)]
11pub struct MediaMeta {
12 pub width: Option<u32>,
14 pub height: Option<u32>,
16 pub duration_secs: Option<f64>,
18}
19
20impl MediaMeta {
21 pub fn dimensions(&self) -> Option<(u32, u32)> {
23 match (self.width, self.height) {
24 (Some(w), Some(h)) if w > 0 && h > 0 => Some((w, h)),
25 _ => None,
26 }
27 }
28}
29
30pub fn probe_media_meta(path: &Path) -> Option<MediaMeta> {
32 let ext = path
33 .extension()
34 .and_then(|e| e.to_str())
35 .map(|s| s.to_ascii_lowercase())?;
36
37 #[cfg(feature = "video")]
38 if crate::mime_resolve::is_video_ext(&ext) {
39 return probe_video(path);
40 }
41
42 #[cfg(feature = "audio")]
43 if crate::mime_resolve::is_audio_ext(&ext) {
44 return probe_audio(path);
45 }
46
47 if matches!(ext.as_str(), "psd" | "psb") {
49 return probe_psd_header(path);
50 }
51
52 #[cfg(feature = "pdf")]
53 if ext == "ai" {
54 return probe_ai_page_size(path);
55 }
56
57 probe_image_dimensions(path)
60}
61
62#[cfg(feature = "video")]
64fn probe_video(path: &Path) -> Option<MediaMeta> {
65 let probe = crate::decode::ffmpeg_probe::probe_options_for_video(path);
66 let (dimensions, duration_secs) = crate::decode::ffmpeg_decode::probe_video_meta(path, probe);
67 let (width, height) = dimensions?;
68 Some(MediaMeta {
69 width: Some(width),
70 height: Some(height),
71 duration_secs,
72 })
73}
74
75#[cfg(feature = "audio")]
77fn probe_audio(path: &Path) -> Option<MediaMeta> {
78 use lofty::file::AudioFile;
79
80 let tagged = lofty::read_from_path(path).ok()?;
81 let duration = tagged.properties().duration();
82 if duration.is_zero() {
83 return None;
84 }
85 Some(MediaMeta {
86 width: None,
87 height: None,
88 duration_secs: Some(duration.as_secs_f64()),
89 })
90}
91
92fn probe_psd_header(path: &Path) -> Option<MediaMeta> {
95 use std::io::Read;
96
97 let mut file = std::fs::File::open(path).ok()?;
98 let mut header = [0u8; 22];
99 file.read_exact(&mut header).ok()?;
100 if &header[0..4] != b"8BPS" {
101 return None;
102 }
103 let height = u32::from_be_bytes(header[14..18].try_into().ok()?);
106 let width = u32::from_be_bytes(header[18..22].try_into().ok()?);
107 Some(MediaMeta {
108 width: Some(width),
109 height: Some(height),
110 duration_secs: None,
111 })
112}
113
114#[cfg(feature = "pdf")]
116fn probe_ai_page_size(path: &Path) -> Option<MediaMeta> {
117 let (width, height) = crate::thumbs::pdf::probe_page_size(path)?;
118 Some(MediaMeta {
119 width: Some(width),
120 height: Some(height),
121 duration_secs: None,
122 })
123}
124
125fn probe_image_dimensions(path: &Path) -> Option<MediaMeta> {
127 let (width, height) = image::ImageReader::open(path)
128 .ok()?
129 .into_dimensions()
130 .ok()?;
131 Some(MediaMeta {
132 width: Some(width),
133 height: Some(height),
134 duration_secs: None,
135 })
136}