audiobook_forge/utils/
validation.rs1use std::process::Command;
4use which::which;
5
6pub struct DependencyChecker;
8
9#[derive(Debug, Clone)]
10pub struct DependencyStatus {
11 pub name: String,
12 pub found: bool,
13 pub version: Option<String>,
14 pub path: Option<String>,
15}
16
17impl DependencyChecker {
18 pub fn check_ffmpeg() -> DependencyStatus {
20 match which("ffmpeg") {
21 Ok(path) => {
22 let version = Self::get_ffmpeg_version();
23 DependencyStatus {
24 name: "ffmpeg".to_string(),
25 found: true,
26 version,
27 path: Some(path.display().to_string()),
28 }
29 }
30 Err(_) => DependencyStatus {
31 name: "ffmpeg".to_string(),
32 found: false,
33 version: None,
34 path: None,
35 },
36 }
37 }
38
39 pub fn check_atomic_parsley() -> DependencyStatus {
41 match which("AtomicParsley") {
42 Ok(path) => {
43 let version = Self::get_atomic_parsley_version();
44 DependencyStatus {
45 name: "AtomicParsley".to_string(),
46 found: true,
47 version,
48 path: Some(path.display().to_string()),
49 }
50 }
51 Err(_) => DependencyStatus {
52 name: "AtomicParsley".to_string(),
53 found: false,
54 version: None,
55 path: None,
56 },
57 }
58 }
59
60 pub fn check_mp4box() -> DependencyStatus {
62 match which("MP4Box") {
63 Ok(path) => {
64 let version = Self::get_mp4box_version();
65 DependencyStatus {
66 name: "MP4Box".to_string(),
67 found: true,
68 version,
69 path: Some(path.display().to_string()),
70 }
71 }
72 Err(_) => DependencyStatus {
73 name: "MP4Box".to_string(),
74 found: false,
75 version: None,
76 path: None,
77 },
78 }
79 }
80
81 pub fn check_all() -> Vec<DependencyStatus> {
83 vec![
84 Self::check_ffmpeg(),
85 Self::check_atomic_parsley(),
86 Self::check_mp4box(),
87 ]
88 }
89
90 pub fn all_dependencies_met() -> bool {
92 Self::check_all().iter().all(|dep| dep.found)
93 }
94
95 fn get_ffmpeg_version() -> Option<String> {
97 let output = Command::new("ffmpeg")
98 .arg("-version")
99 .output()
100 .ok()?;
101
102 let stdout = String::from_utf8_lossy(&output.stdout);
103 stdout
104 .lines()
105 .next()
106 .and_then(|line| line.split_whitespace().nth(2))
107 .map(|s| s.to_string())
108 }
109
110 fn get_atomic_parsley_version() -> Option<String> {
112 let output = Command::new("AtomicParsley")
113 .arg("--version")
114 .output()
115 .ok()?;
116
117 let stdout = String::from_utf8_lossy(&output.stdout);
118 stdout
119 .lines()
120 .find(|line| line.contains("version"))
121 .and_then(|line| line.split_whitespace().last())
122 .map(|s| s.to_string())
123 }
124
125 fn get_mp4box_version() -> Option<String> {
127 let output = Command::new("MP4Box")
128 .arg("-version")
129 .output()
130 .ok()?;
131
132 let stdout = String::from_utf8_lossy(&output.stdout);
133 stdout
134 .lines()
135 .next()
136 .and_then(|line| line.split_whitespace().find(|s| s.contains("version")))
137 .map(|s| s.to_string())
138 }
139
140 pub fn check_aac_at_support() -> bool {
142 let output = Command::new("ffmpeg")
144 .args(&["-encoders"])
145 .output();
146
147 if let Ok(output) = output {
148 let stdout = String::from_utf8_lossy(&output.stdout);
149 stdout.contains("aac_at")
150 } else {
151 false
152 }
153 }
154}
155
156impl std::fmt::Display for DependencyStatus {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 if self.found {
159 write!(f, "✓ {}", self.name)?;
160 if let Some(ref version) = self.version {
161 write!(f, " ({})", version)?;
162 }
163 if let Some(ref path) = self.path {
164 write!(f, "\n Path: {}", path)?;
165 }
166 Ok(())
167 } else {
168 write!(f, "✗ {} - NOT FOUND", self.name)
169 }
170 }
171}
172
173#[cfg(test)]
174mod tests {
175 use super::*;
176
177 #[test]
178 fn test_check_dependencies() {
179 let deps = DependencyChecker::check_all();
180 assert_eq!(deps.len(), 3);
181
182 let ffmpeg = deps.iter().find(|d| d.name == "ffmpeg");
184 assert!(ffmpeg.is_some());
185 }
186}