1#![warn(missing_docs)]
7#![warn(clippy::all)]
8#![warn(clippy::pedantic)]
9#![allow(clippy::module_name_repetitions)]
10#![allow(clippy::must_use_candidate)]
11
12pub mod error;
13pub mod process;
14pub mod types;
15pub mod utils;
16
17pub use error::{Error, Result, ResultExt};
19pub use process::{CommandBuilder, Process, ProcessConfig, ProcessOutput, Progress};
20pub use types::{
21 Codec, Duration, LogLevel, MediaPath, PixelFormat, SampleFormat, Size, StreamSpecifier,
22 StreamType,
23};
24
25#[derive(Debug, Clone)]
27pub struct Version {
28 pub major: u32,
30 pub minor: u32,
32 pub patch: u32,
34 pub version_string: String,
36 pub configuration: Vec<String>,
38}
39
40impl Version {
41 pub fn parse(output: &str) -> Result<Self> {
43 let lines: Vec<&str> = output.lines().collect();
49 if lines.is_empty() {
50 return Err(Error::ParseError("Empty version output".to_string()));
51 }
52
53 let version_line = lines[0];
55 let version_string = if let Some(start) = version_line.find("version ") {
56 let version_part = &version_line[start + 8..];
57 version_part.split_whitespace().next().unwrap_or("").to_string()
58 } else {
59 return Err(Error::ParseError("Version line not found".to_string()));
60 };
61
62 let parts: Vec<&str> = version_string.split(&['.', '-'][..]).collect();
64 let major = parts.get(0)
65 .and_then(|s| s.parse().ok())
66 .unwrap_or(0);
67 let minor = parts.get(1)
68 .and_then(|s| s.parse().ok())
69 .unwrap_or(0);
70 let patch = parts.get(2)
71 .and_then(|s| s.parse().ok())
72 .unwrap_or(0);
73
74 let configuration = lines.iter()
76 .find(|line| line.starts_with("configuration:"))
77 .map(|line| {
78 line[14..]
79 .split_whitespace()
80 .map(String::from)
81 .collect()
82 })
83 .unwrap_or_default();
84
85 Ok(Self {
86 major,
87 minor,
88 patch,
89 version_string,
90 configuration,
91 })
92 }
93
94 pub fn is_at_least(&self, major: u32, minor: u32, patch: u32) -> bool {
96 if self.major > major {
97 return true;
98 }
99 if self.major < major {
100 return false;
101 }
102 if self.minor > minor {
103 return true;
104 }
105 if self.minor < minor {
106 return false;
107 }
108 self.patch >= patch
109 }
110}
111
112pub async fn get_version(executable: &str) -> Result<Version> {
114 let path = process::find_executable(executable)?;
115 let config = ProcessConfig::new(path)
116 .capture_stdout(true)
117 .capture_stderr(false);
118
119 let output = Process::spawn(config, vec!["-version".to_string()])
120 .await?
121 .wait()
122 .await?
123 .into_result()?;
124
125 let version_output = output.stdout_str()
126 .ok_or_else(|| Error::ParseError("No version output".to_string()))?;
127
128 Version::parse(&version_output)
129}
130
131#[derive(Debug, Clone, Default)]
133pub struct Capabilities {
134 pub codecs: Vec<String>,
136 pub formats: Vec<String>,
138 pub filters: Vec<String>,
140 pub protocols: Vec<String>,
142 pub pixel_formats: Vec<String>,
144 pub sample_formats: Vec<String>,
146}
147
148impl Capabilities {
149 pub async fn detect(executable: &str) -> Result<Self> {
151 let caps = Self::default();
152
153 Ok(caps)
157 }
158
159 pub fn has_codec(&self, codec: &str) -> bool {
161 self.codecs.iter().any(|c| c == codec)
162 }
163
164 pub fn has_format(&self, format: &str) -> bool {
166 self.formats.iter().any(|f| f == format)
167 }
168
169 pub fn has_filter(&self, filter: &str) -> bool {
171 self.filters.iter().any(|f| f == filter)
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn test_version_parsing() {
181 let output = r#"ffmpeg version 4.4.2-0ubuntu0.22.04.1 Copyright (c) 2000-2021 the FFmpeg developers
182built with gcc 11 (Ubuntu 11.2.0-19ubuntu1)
183configuration: --prefix=/usr --extra-version=0ubuntu0.22.04.1 --toolchain=hardened"#;
184
185 let version = Version::parse(output).unwrap();
186 assert_eq!(version.major, 4);
187 assert_eq!(version.minor, 4);
188 assert_eq!(version.patch, 2);
189 assert!(version.version_string.starts_with("4.4.2"));
190 assert!(!version.configuration.is_empty());
191 }
192
193 #[test]
194 fn test_version_comparison() {
195 let version = Version {
196 major: 4,
197 minor: 4,
198 patch: 2,
199 version_string: "4.4.2".to_string(),
200 configuration: vec![],
201 };
202
203 assert!(version.is_at_least(4, 4, 2));
204 assert!(version.is_at_least(4, 4, 1));
205 assert!(version.is_at_least(4, 3, 5));
206 assert!(version.is_at_least(3, 9, 9));
207 assert!(!version.is_at_least(4, 4, 3));
208 assert!(!version.is_at_least(4, 5, 0));
209 assert!(!version.is_at_least(5, 0, 0));
210 }
211}