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 pub fn get_available_encoders() -> Vec<String> {
157 crate::audio::EncoderDetector::get_available_encoders()
158 .into_iter()
159 .map(|e| e.name().to_string())
160 .collect()
161 }
162
163 pub fn get_selected_encoder() -> String {
165 crate::audio::get_encoder().name().to_string()
166 }
167}
168
169impl std::fmt::Display for DependencyStatus {
170 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
171 if self.found {
172 write!(f, "✓ {}", self.name)?;
173 if let Some(ref version) = self.version {
174 write!(f, " ({})", version)?;
175 }
176 if let Some(ref path) = self.path {
177 write!(f, "\n Path: {}", path)?;
178 }
179 Ok(())
180 } else {
181 write!(f, "✗ {} - NOT FOUND", self.name)
182 }
183 }
184}
185
186#[cfg(test)]
187mod tests {
188 use super::*;
189
190 #[test]
191 fn test_check_dependencies() {
192 let deps = DependencyChecker::check_all();
193 assert_eq!(deps.len(), 3);
194
195 let ffmpeg = deps.iter().find(|d| d.name == "ffmpeg");
197 assert!(ffmpeg.is_some());
198 }
199}