use std::env;
use std::path::Path;
use std::fs;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use std::thread;
use std::time::Duration;
use lufsgen::{LufsCalculator, LufsResult, is_audio_file};
fn process_single_file(path: &Path) -> LufsResult {
let filename = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
let path_str = path.to_string_lossy().to_string();
let file_size = fs::metadata(path)
.ok()
.map(|m| m.len())
.unwrap_or(0);
if file_size > 1024 * 1024 {
eprintln!("[LUFS] Processing: {} ({:.1} MB)", filename, file_size as f64 / 1024.0 / 1024.0);
} else {
eprintln!("[LUFS] Processing: {}", filename);
}
let progress = if file_size > 1024 * 1024 {
Some(Arc::new(AtomicU64::new(0)))
} else {
None
};
let progress_clone = progress.clone();
let handle = if progress.is_some() {
let filename_clone = filename.clone();
Some(thread::spawn(move || {
show_file_progress(&filename_clone, file_size, progress_clone.unwrap());
}))
} else {
None
};
let calc = LufsCalculator::default();
let lufs = match calc.calculate_from_file_with_progress(path, progress) {
Ok(result) => result,
Err(e) => {
eprintln!("Error processing {}: {}", filename, e);
None
}
};
if let Some(h) = handle {
let _ = h.join();
eprintln!(); }
LufsResult { filename, path: path_str, lufs }
}
fn show_file_progress(filename: &str, total_size: u64, progress: Arc<AtomicU64>) {
use indicatif::{ProgressBar, ProgressStyle};
let pb = ProgressBar::new(total_size);
pb.set_style(
ProgressStyle::default_bar()
.template(" [{elapsed_precise}] [{bar:40.cyan/blue}] {bytes}/{total_bytes} ({percent}%) {msg}")
.expect("invalid template")
.progress_chars("##-")
);
pb.set_message(filename.to_string());
let mut last_bytes = 0;
loop {
let bytes_read = progress.load(Ordering::Relaxed);
if bytes_read >= total_size {
pb.finish();
break;
}
pb.set_position(bytes_read);
if bytes_read == last_bytes {
thread::sleep(Duration::from_millis(100));
} else {
last_bytes = bytes_read;
thread::sleep(Duration::from_millis(50));
}
}
}
fn process_directory(root_dir: &Path) -> Vec<LufsResult> {
eprintln!("Scanning: {}", root_dir.display());
let mut audio_files = Vec::new();
collect_audio_files(root_dir, &mut audio_files);
let total = audio_files.len();
if total == 0 {
eprintln!("No audio files found.");
return Vec::new();
}
eprintln!("Found {} audio file(s)", total);
use indicatif::{ProgressBar, ProgressStyle};
let progress = ProgressBar::new(total as u64);
progress.set_style(
ProgressStyle::default_bar()
.template("[{elapsed_precise}] [{bar:40.cyan/blue}] {pos}/{len} {msg}")
.expect("invalid template")
.progress_chars("##-")
);
let mut results = Vec::new();
for path in audio_files {
let filename = path
.file_name()
.unwrap_or_default()
.to_string_lossy()
.to_string();
progress.set_message(filename.clone());
let calc = LufsCalculator::default();
let lufs = match calc.calculate_from_file(&path) {
Ok(result) => result,
Err(e) => {
eprintln!("\nError: {}", e);
None
}
};
let path_str = path.to_string_lossy().to_string();
results.push(LufsResult { filename, path: path_str, lufs });
progress.inc(1);
}
progress.finish();
eprintln!();
results
}
fn collect_audio_files(dir: &Path, files: &mut Vec<std::path::PathBuf>) {
if let Ok(entries) = fs::read_dir(dir) {
for entry in entries.flatten() {
let path = entry.path();
if path.is_file() && is_audio_file(&path) {
files.push(path);
} else if path.is_dir() {
collect_audio_files(&path, files);
}
}
}
}
fn output_results(results: &[LufsResult]) {
for result in results {
match result.lufs {
Some(lufs) => println!("{}|{}", result.filename, lufs),
None => println!("{}|FAILED", result.filename),
}
}
}
fn write_results(results: &[LufsResult], output_path: &str) -> std::io::Result<()> {
let mut content = String::new();
for result in results {
if let Some(lufs) = result.lufs {
content.push_str(&format!("{}: {:.2} LUFS\n", result.filename, lufs));
}
}
fs::write(output_path, content)
}
fn print_usage() {
println!("LUFS Generator for Android (Pure Rust)");
println!();
println!("Usage:");
println!(" lufsgen <audio-file> Calculate LUFS for a single file");
println!(" lufsgen <directory> Scan directory for audio files");
println!(" lufsgen <file/dir> <output> Save results to file");
println!();
println!("Examples:");
println!(" adb shell /data/local/tmp/lufsgen /sdcard/music.mp3");
println!(" adb shell /data/local/tmp/lufsgen /sdcard/Music");
println!();
}
fn main() {
let args: Vec<String> = env::args().collect();
if args.len() < 2 {
print_usage();
std::process::exit(1);
}
let input = Path::new(&args[1]);
let results = if input.is_file() {
vec![process_single_file(input)]
} else if input.is_dir() {
process_directory(input)
} else {
eprintln!("Error: Path does not exist: {}", input.display());
std::process::exit(1);
};
output_results(&results);
if args.len() >= 3 {
let output_path = &args[2];
if let Err(e) = write_results(&results, output_path) {
eprintln!("Failed to write output: {}", e);
}
}
let failed = results.iter().filter(|r| r.lufs.is_none()).count();
if failed > 0 {
std::process::exit(1);
}
}