1use std::borrow::Cow;
30use std::path::Path;
31use std::path::PathBuf;
32use std::process::Command;
33use std::time::Duration;
34
35use error::FfProbeError;
36#[cfg(feature = "streams")]
37mod attachment_stream;
38#[cfg(feature = "streams")]
39mod audio_stream;
40#[cfg(feature = "chapters")]
41mod chapter;
42mod config;
43#[cfg(feature = "streams")]
44mod data_stream;
45#[cfg(feature = "streams")]
46mod disposition;
47pub mod error;
48mod ffprobe;
49#[cfg(feature = "format")]
50mod format;
51mod ratio;
52#[cfg(feature = "streams")]
53mod streams;
54#[cfg(feature = "streams")]
55mod subtitle_stream;
56#[cfg(feature = "streams")]
57mod video_stream;
58
59#[cfg(feature = "streams")]
60pub use attachment_stream::AttachmentStream;
61#[cfg(feature = "streams")]
62pub use attachment_stream::AttachmentTags;
63#[cfg(feature = "streams")]
64pub use audio_stream::AudioStream;
65#[cfg(feature = "streams")]
66pub use audio_stream::AudioTags;
67#[cfg(feature = "chapters")]
68pub use chapter::Chapter;
69#[cfg(feature = "chapters")]
70pub use chapter::ChapterTags;
71pub use config::Config;
72#[cfg(feature = "streams")]
73pub use data_stream::DataStream;
74#[cfg(feature = "streams")]
75pub use data_stream::DataTags;
76#[cfg(feature = "streams")]
77pub use disposition::Disposition;
78pub use ffprobe::FfProbe;
79#[cfg(feature = "format")]
80pub use format::Format;
81#[cfg(feature = "format")]
82pub use format::FormatTags;
83pub use ratio::Ratio;
84use serde::Deserialize;
85use serde::Deserializer;
86#[cfg(feature = "streams")]
87pub use streams::SideData;
88#[cfg(feature = "streams")]
89pub use streams::Stream;
90#[cfg(feature = "streams")]
91pub use streams::StreamKinds;
92#[cfg(feature = "streams")]
93pub use streams::StreamTags;
94#[cfg(feature = "streams")]
95pub use subtitle_stream::SubtititleTags;
96#[cfg(feature = "streams")]
97pub use subtitle_stream::SubtitleStream;
98use url::Url;
99#[cfg(feature = "streams")]
100pub use video_stream::VideoStream;
101#[cfg(feature = "streams")]
102pub use video_stream::VideoTags;
103
104pub trait IntoFfprobeArg<'a> {
105 fn into_ffprobe_arg(self) -> Cow<'a, str>;
106}
107
108impl<'a> IntoFfprobeArg<'a> for &'a Path {
109 fn into_ffprobe_arg(self) -> Cow<'a, str> {
110 self.to_string_lossy()
111 }
112}
113
114impl<'a> IntoFfprobeArg<'a> for PathBuf {
115 fn into_ffprobe_arg(self) -> Cow<'a, str> {
116 Cow::Owned(self.to_string_lossy().into_owned())
117 }
118}
119
120impl<'a> IntoFfprobeArg<'a> for Url {
121 fn into_ffprobe_arg(self) -> Cow<'a, str> {
122 Cow::Owned(self.to_string())
123 }
124}
125
126impl<'a> IntoFfprobeArg<'a> for &'a str {
127 fn into_ffprobe_arg(self) -> Cow<'a, str> {
128 self.into()
129 }
130}
131
132impl<'a> IntoFfprobeArg<'a> for String {
133 fn into_ffprobe_arg(self) -> Cow<'a, str> {
134 self.into()
135 }
136}
137
138#[cfg(target_os = "windows")]
139use std::os::windows::process::CommandExt;
140
141pub fn ffprobe<'a, T: IntoFfprobeArg<'a>>(path: T) -> Result<FfProbe, FfProbeError> {
145 ffprobe_config(Config::new(), path)
146}
147
148pub fn ffprobe_config<'a, T: IntoFfprobeArg<'a>>(
151 config: Config,
152 path: T,
153) -> Result<FfProbe, FfProbeError> {
154 let path = path.into_ffprobe_arg();
155 let mut cmd = Command::new(config.ffprobe_bin);
156 cmd.args(["-v", "error", "-print_format", "json"]);
158 #[cfg(feature = "chapters")]
159 cmd.arg("-show_chapters");
160 #[cfg(feature = "format")]
161 cmd.arg("-show_format");
162 #[cfg(feature = "streams")]
163 cmd.arg("-show_streams");
164
165 if config.count_frames {
166 cmd.arg("-count_frames");
167 }
168
169 cmd.arg(path.as_ref());
170
171 #[cfg(target_os = "windows")]
173 cmd.creation_flags(0x08000000);
174
175 let out = cmd.output().map_err(FfProbeError::Io)?;
176
177 if !out.status.success() {
178 return Err(FfProbeError::Status(out));
179 }
180
181 serde_json::from_slice::<FfProbe>(&out.stdout).map_err(FfProbeError::Deserialize)
182}
183
184#[cfg(feature = "async")]
185pub async fn ffprobe_async<'a, T: IntoFfprobeArg<'a>>(path: T) -> Result<FfProbe, FfProbeError> {
186 ffprobe_async_config(Config::new(), path).await
187}
188
189#[cfg(feature = "async")]
190pub async fn ffprobe_async_config<'a, T: IntoFfprobeArg<'a>>(
191 config: Config,
192 path: T,
193) -> Result<FfProbe, FfProbeError> {
194 let path = path.into_ffprobe_arg();
195 let mut cmd = tokio::process::Command::new("ffprobe");
196 let path = path.as_ref();
197 cmd.args(["-v", "quiet", "-print_format", "json"]);
198 #[cfg(feature = "chapters")]
199 cmd.arg("-show_chapters");
200 #[cfg(feature = "format")]
201 cmd.arg("-show_format");
202 #[cfg(feature = "streams")]
203 cmd.arg("-show_streams");
204
205 if config.count_frames {
206 cmd.arg("-count_frames");
207 }
208
209 cmd.arg(path.as_ref());
210
211 let out = cmd.output().await.map_err(FfProbeError::Io)?;
212
213 if !out.status.success() {
214 return Err(FfProbeError::Status(out));
215 }
216
217 serde_json::from_slice::<FfProbe>(&out.stdout).map_err(FfProbeError::Deserialize)
218}
219
220pub fn option_string_to_duration<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
221where
222 D: Deserializer<'de>,
223{
224 let s: Option<String> = Option::deserialize(deserializer)?;
225 match s {
226 Some(s) => s
227 .parse::<f64>()
228 .map(|v| v.max(0.0))
229 .map(Duration::from_secs_f64)
230 .map(Some)
231 .map_err(serde::de::Error::custom),
232 None => Ok(None),
233 }
234}