1pub mod io;
2pub mod processing;
3pub mod transcoding;
4pub mod errors;
5
6use std::time::Duration;
7use crate::errors::AudioError;
8use crate::transcoding::AudioFormat;
9use crate::processing::{AudioEffect, effect_to_filter};
10
11#[derive(Debug, Clone)]
13pub struct AudioProcessor {
14 pub file_path: String,
15}
16
17impl AudioProcessor {
18 pub fn new(file_path: &str) -> Result<Self, AudioError> {
20 std::fs::metadata(file_path).map_err(|e| AudioError::IoError(e))?;
22 println!("Initializing audio processor for file: {}", file_path);
23 io::load_audio(file_path)?;
24 Ok(AudioProcessor {
25 file_path: file_path.to_string(),
26 })
27 }
28
29 pub fn seek(&self, position: Duration) -> Result<Self, AudioError> {
31 let output_file = format!("seeked_{}", self.file_path);
32 let pos_str = format!("{}", position.as_secs());
33 let status = std::process::Command::new("ffmpeg")
35 .args(&["-ss", &pos_str, "-i", &self.file_path, "-c", "copy", &output_file, "-y"])
36 .status()
37 .map_err(|e| AudioError::IoError(e))?;
38 if status.success() {
39 println!("Seeked {} seconds into {} -> {}", pos_str, self.file_path, output_file);
40 Ok(AudioProcessor { file_path: output_file })
41 } else {
42 Err(AudioError::FfmpegError("ffmpeg seek failed".to_string()))
43 }
44 }
45
46 pub fn trim(&self, start: Duration, end: Duration) -> Result<Self, AudioError> {
49 let output_file = format!("trimmed_{}", self.file_path);
50 let start_str = format!("{}", start.as_secs());
51 let end_str = format!("{}", end.as_secs());
52 let status = std::process::Command::new("ffmpeg")
54 .args(&["-ss", &start_str, "-to", &end_str, "-i", &self.file_path, "-c", "copy", &output_file, "-y"])
55 .status()
56 .map_err(|e| AudioError::IoError(e))?;
57 if status.success() {
58 println!("Trimmed {} from {} to {} seconds -> {}", self.file_path, start_str, end_str, output_file);
59 Ok(AudioProcessor { file_path: output_file })
60 } else {
61 Err(AudioError::FfmpegError("ffmpeg trim failed".to_string()))
62 }
63 }
64
65 pub fn transcode(&self, output_format: AudioFormat, output_path: &str) -> Result<(), AudioError> {
67 let status = std::process::Command::new("ffmpeg")
69 .args(&["-i", &self.file_path, output_path, "-y"])
70 .status()
71 .map_err(|e| AudioError::IoError(e))?;
72 if status.success() {
73 println!("Transcoded {} to format {:?} -> {}", self.file_path, output_format, output_path);
74 Ok(())
75 } else {
76 Err(AudioError::FfmpegError("ffmpeg transcode failed".to_string()))
77 }
78 }
79
80 pub fn adjust_volume(&self, factor: f32) -> Result<Self, AudioError> {
82 let output_file = format!("volume_adjusted_{}", self.file_path);
83 let filter = format!("volume={}", factor);
84 let status = std::process::Command::new("ffmpeg")
85 .args(&["-i", &self.file_path, "-af", &filter, &output_file, "-y"])
86 .status()
87 .map_err(|e| AudioError::IoError(e))?;
88 if status.success() {
89 println!("Adjusted volume of {} by factor {} -> {}", self.file_path, factor, output_file);
90 Ok(AudioProcessor { file_path: output_file })
91 } else {
92 Err(AudioError::FfmpegError("ffmpeg adjust volume failed".to_string()))
93 }
94 }
95
96 pub fn change_speed(&self, factor: f32) -> Result<Self, AudioError> {
98 let output_file = format!("speed_changed_{}", self.file_path);
99 let filter = format!("atempo={}", factor);
101 let status = std::process::Command::new("ffmpeg")
102 .args(&["-i", &self.file_path, "-filter:a", &filter, &output_file, "-y"])
103 .status()
104 .map_err(|e| AudioError::IoError(e))?;
105 if status.success() {
106 println!("Changed speed of {} by factor {} -> {}", self.file_path, factor, output_file);
107 Ok(AudioProcessor { file_path: output_file })
108 } else {
109 Err(AudioError::FfmpegError("ffmpeg change speed failed".to_string()))
110 }
111 }
112
113 pub fn apply_effect(&self, effect: AudioEffect) -> Result<Self, AudioError> {
115 let output_file = format!("effected_{}", self.file_path);
116 let filter = effect_to_filter(&effect);
118 let status = std::process::Command::new("ffmpeg")
119 .args(&["-i", &self.file_path, "-af", &filter, &output_file, "-y"])
120 .status()
121 .map_err(|e| AudioError::IoError(e))?;
122 if status.success() {
123 println!("Applied effect {:?} on {} -> {}", effect, self.file_path, output_file);
124 Ok(AudioProcessor { file_path: output_file })
125 } else {
126 Err(AudioError::FfmpegError("ffmpeg apply effect failed".to_string()))
127 }
128 }
129
130 pub fn merge_audios(audios: &[AudioProcessor], output_path: &str) -> Result<Self, AudioError> {
133 use std::io::Write;
134 use tempfile::NamedTempFile;
135
136 let mut list_file = NamedTempFile::new().map_err(|e| AudioError::IoError(e))?;
138 for audio in audios {
139 writeln!(list_file, "file '{}'", audio.file_path).map_err(|e| AudioError::IoError(e))?;
141 }
142 list_file.flush().map_err(|e| AudioError::IoError(e))?;
143
144 let status = std::process::Command::new("ffmpeg")
145 .args(&["-f", "concat", "-safe", "0", "-i", list_file.path().to_str().unwrap(), "-c", "copy", output_path, "-y"])
146 .status()
147 .map_err(|e| AudioError::IoError(e))?;
148
149 if status.success() {
150 println!("Merged {} audio files -> {}", audios.len(), output_path);
151 Ok(AudioProcessor { file_path: output_path.to_string() })
152 } else {
153 Err(AudioError::FfmpegError("ffmpeg merge failed".to_string()))
154 }
155 }
156
157 pub fn reverse(&self) -> Result<Self, AudioError> {
159 let output_file = format!("reversed_{}", self.file_path);
160 let status = std::process::Command::new("ffmpeg")
161 .args(&["-i", &self.file_path, "-af", "areverse", &output_file, "-y"])
162 .status()
163 .map_err(|e| AudioError::IoError(e))?;
164 if status.success() {
165 println!("Reversed audio {} -> {}", self.file_path, output_file);
166 Ok(AudioProcessor { file_path: output_file })
167 } else {
168 Err(AudioError::FfmpegError("ffmpeg reverse failed".to_string()))
169 }
170 }
171
172 pub fn normalize(&self) -> Result<Self, AudioError> {
174 let output_file = format!("normalized_{}", self.file_path);
175 let status = std::process::Command::new("ffmpeg")
177 .args(&["-i", &self.file_path, "-af", "loudnorm", &output_file, "-y"])
178 .status()
179 .map_err(|e| AudioError::IoError(e))?;
180 if status.success() {
181 println!("Normalized audio {} -> {}", self.file_path, output_file);
182 Ok(AudioProcessor { file_path: output_file })
183 } else {
184 Err(AudioError::FfmpegError("ffmpeg normalize failed".to_string()))
185 }
186 }
187
188 pub fn overlay(&self, overlay_audio: &AudioProcessor, start_time: Duration) -> Result<Self, AudioError> {
190 let output_file = format!("overlayed_{}", self.file_path);
191 let delay_ms = start_time.as_millis();
194 let filter = format!("[1]adelay={}|{delay}|{delay}[d]; [0][d]amix=inputs=2:duration=first", delay=delay_ms);
195 let status = std::process::Command::new("ffmpeg")
196 .args(&["-i", &self.file_path, "-i", &overlay_audio.file_path, "-filter_complex", &filter, &output_file, "-y"])
197 .status()
198 .map_err(|e| AudioError::IoError(e))?;
199 if status.success() {
200 println!("Overlayed {} onto {} at {} seconds -> {}", overlay_audio.file_path, self.file_path, start_time.as_secs(), output_file);
201 Ok(AudioProcessor { file_path: output_file })
202 } else {
203 Err(AudioError::FfmpegError("ffmpeg overlay failed".to_string()))
204 }
205 }
206}