diff --git a/.gitignore b/.gitignore index 73fab07..345b19a 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,5 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb + +.idea/ diff --git a/Cargo.lock b/Cargo.lock index f8a3bff..88a4003 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -122,7 +122,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4d13cdbd5dbb29f9c88095bbdc2590c9cba0d0a1269b983fef6b2cdd7e9f4db1" [[package]] -name = "hp-cli" +name = "hydrophonitor-cli" version = "0.1.0" dependencies = [ "anyhow", diff --git a/src/clean.rs b/src/clean.rs new file mode 100644 index 0000000..298cb9c --- /dev/null +++ b/src/clean.rs @@ -0,0 +1,21 @@ +use std::path::PathBuf; + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(about = "This command removes all deployment data from the given device's /output path")] +pub struct Clean { + ///Path to USB mass storage or SD card where data will be deleted from. + #[clap(short, long, required = true)] + pub device: PathBuf, + + ///Increases the CLI verbosity. + #[clap(short, long, action)] + pub verbose: bool, +} + +impl Clean { + pub fn clean(&self) { + println!("Cleaning device at {:?}", self.device); + } +} \ No newline at end of file diff --git a/src/import.rs b/src/import.rs new file mode 100644 index 0000000..0a970ee --- /dev/null +++ b/src/import.rs @@ -0,0 +1,171 @@ +use std::error::Error; +use std::fs; +use std::path::PathBuf; + +use clap::Parser; +use hound::{WavReader, WavWriter}; +use indicatif::ProgressBar; +use walkdir::WalkDir; + +const DATA_FOLDER: &str = "home/pi/data"; + +#[derive(Parser, Debug)] +#[clap(about = "Import audio from an SD card.")] +pub struct Import { + /// Path to USB mass storage or SD where data will be imported from. You can find the path to + /// the SD card by running `lsblk` in the terminal. + #[clap(short, long, required = true)] + pub device: PathBuf, + + /// Path to the output folder. If not specified, the output folder will be + /// the current directory. + #[clap(short, long)] + pub output: Option, + + ///Runs a clean after import is complete. + #[clap(long, action)] + pub clean_imported: bool, + + ///Generates compressed previews of audio files. + #[clap(long, action)] + pub audio_previews: bool, + + ///Increases the CLI verbosity. + #[clap(short, long, action)] + pub verbose: bool, +} + +impl Import { + //TODO old logic; has to be changed to match new commands + pub fn import(&mut self) { + println!("Importing audio from SD card at {:?}", self.device); + + if let Some(output_folder) = &self.output { + println!("Output folder: {:?}", output_folder); + import_from_sd(&mut self.device, Some(output_folder.clone())).unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }); + } else { + println!("Output folder: current directory"); + import_from_sd(&mut self.device, None).unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }); + } + + // Iterate folders inside output folder. Inside each iterated folder there is + // a folder called "audio" which contains the wav files. Merge them into a single + // wav file and delete the "audio" folder. + let output_folder = match self.output.clone() { + Some(output_folder) => output_folder, + None => std::env::current_dir().unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }) + }; + + for entry in WalkDir::new(output_folder.clone()) { + let entry = entry.unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }); + let path = entry.path(); + if path.is_dir() { + let audio_folder = path.join("audio"); + if audio_folder.exists() { + merge_wavs(&audio_folder, &PathBuf::from(path)).unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }); + fs::remove_dir_all(audio_folder).unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }); + } + } + } + } +} + +//TODO old logic +pub fn import_from_sd(sd_card: &mut PathBuf, output_folder: Option) -> Result<(), Box> { + let output_folder = match output_folder { + Some(output_folder) => output_folder, + None => std::env::current_dir().unwrap_or_else(|err| { + eprintln!("Error: {}", err); + std::process::exit(1); + }) + }; + + sd_card.push(DATA_FOLDER); + + let count = WalkDir::new(sd_card.clone()).into_iter().count(); + let progress_bar = ProgressBar::new(count as u64); + + for entry in WalkDir::new(sd_card.clone()) { + let entry = entry?; + let from = entry.path(); + let to = output_folder.join(from.strip_prefix(sd_card.clone())?); + + if entry.file_type().is_dir() { + fs::create_dir_all(to)?; + } else if entry.file_type().is_file() { + fs::copy(from, to)?; + } + progress_bar.inc(1); + } + progress_bar.finish(); + Ok(()) +} + +//TODO old logic +pub fn merge_wavs(input: &std::path::PathBuf, output: &std::path::PathBuf) -> Result<(), Box> { + // Read files from input directory + let mut files = std::fs::read_dir(input)? + .filter_map(|entry| entry.ok()) + .filter(|entry| entry.file_type().ok().map(|t| t.is_file()).unwrap_or(false)) + .filter(|entry| entry.path().extension().unwrap_or_default() == "wav") + .collect::>(); + + // If there are no wav files, return + if files.is_empty() { + println!("No wav files found in {:?}", input); + return Ok(()); + } + // Sort files by name + files.sort_by(|a, b| a.file_name().cmp(&b.file_name())); + + let output_name = files.first().unwrap().file_name(); + let output_name = output_name.to_str().unwrap(); + + // Get wav spec from file + let spec = WavReader::open(files.first().unwrap().path())?.spec(); + let mut writer = WavWriter::create(output.join(output_name), spec)?; + + let progress_bar = ProgressBar::new(files.len() as u64); + match spec.sample_format { + hound::SampleFormat::Float => { + for file in files { + let mut reader = WavReader::open(file.path())?; + for sample in reader.samples::() { + writer.write_sample(sample?)?; + } + progress_bar.inc(1); + } + } + hound::SampleFormat::Int => { + for file in files { + let mut reader = WavReader::open(file.path())?; + for sample in reader.samples::() { + writer.write_sample(sample?)?; + } + progress_bar.inc(1); + } + } + } + progress_bar.finish(); + writer.finalize()?; + Ok(()) +} + diff --git a/src/main.rs b/src/main.rs index eabbdb7..c72660c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,42 +1,18 @@ use clap::{Parser, Subcommand}; -use std::error::Error; -use std::fs; -use std::path::PathBuf; -use walkdir::WalkDir; -use indicatif::ProgressBar; -use hound::{WavReader, WavWriter}; -const DATA_FOLDER: &str = "home/pi/data"; +use crate::clean::Clean; +use crate::import::Import; + +mod import; +mod clean; #[derive(Subcommand)] #[clap(about = "A tool to record audio on Linux using the command line.")] pub enum Commands { - Install(Install), Import(Import), + Clean(Clean), } -#[derive(Parser, Debug)] -#[clap(about = "Install hydrophonitor on an SD card.")] -pub struct Install { - /// Path to the SD card. You can find the path to the SD card by running - /// `lsblk` in the terminal. - #[clap(short, long, required = true)] - pub sd_card: PathBuf, -} - -#[derive(Parser, Debug)] -#[clap(about = "Import audio from an SD card.")] -pub struct Import { - /// Path to the SD card. You can find the path to the SD card by running - /// `lsblk` in the terminal. - #[clap(short, long, required = true)] - pub sd_card: PathBuf, - - /// Path to the output folder. If not specified, the output folder will be - /// the current directory. - #[clap(short, long)] - pub output_folder: Option, -} #[derive(Parser)] #[clap(author, version, about, long_about = None)] @@ -46,141 +22,12 @@ pub struct Cli { pub commands: Commands, } -fn import_from_sd(sd_card: &mut PathBuf, output_folder: Option) -> Result<(), Box>{ - let output_folder = match output_folder { - Some(output_folder) => output_folder, - None => std::env::current_dir().unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }) - }; - - sd_card.push(DATA_FOLDER); - - let count = WalkDir::new(sd_card.clone()).into_iter().count(); - let progress_bar = ProgressBar::new(count as u64); - - for entry in WalkDir::new(sd_card.clone()) { - let entry = entry?; - let from = entry.path(); - let to = output_folder.join(from.strip_prefix(sd_card.clone())?); - - if entry.file_type().is_dir() { - fs::create_dir_all(to)?; - } else if entry.file_type().is_file() { - fs::copy(from, to)?; - } - progress_bar.inc(1); - } - progress_bar.finish(); - Ok(()) -} - - -pub fn merge_wavs(input: &std::path::PathBuf, output: &std::path::PathBuf) -> Result<(), Box> { - // Read files from input directory - let mut files = std::fs::read_dir(input)? - .filter_map(|entry| entry.ok()) - .filter(|entry| entry.file_type().ok().map(|t| t.is_file()).unwrap_or(false)) - .filter(|entry| entry.path().extension().unwrap_or_default() == "wav") - .collect::>(); - - // If there are no wav files, return - if files.is_empty() { - println!("No wav files found in {:?}", input); - return Ok(()); - } - // Sort files by name - files.sort_by(|a, b| a.file_name().cmp(&b.file_name())); - - let output_name = files.first().unwrap().file_name(); - let output_name = output_name.to_str().unwrap(); - - // Get wav spec from file - let spec = WavReader::open(files.first().unwrap().path())?.spec(); - let mut writer = WavWriter::create(output.join(output_name), spec)?; - - let progress_bar = ProgressBar::new(files.len() as u64); - match spec.sample_format { - hound::SampleFormat::Float => { - for file in files { - let mut reader = WavReader::open(file.path())?; - for sample in reader.samples::() { - writer.write_sample(sample?)?; - } - progress_bar.inc(1); - } - }, - hound::SampleFormat::Int => { - for file in files { - let mut reader = WavReader::open(file.path())?; - for sample in reader.samples::() { - writer.write_sample(sample?)?; - } - progress_bar.inc(1); - } - }, - } - progress_bar.finish(); - writer.finalize()?; - Ok(()) -} fn main() { let commands = Cli::parse(); match commands.commands { - Commands::Install(install) => { - println!("Installing hydrophonitor on SD card at {:?}", install.sd_card); - } - Commands::Import(mut import) => { - println!("Importing audio from SD card at {:?}", import.sd_card); - - if let Some(output_folder) = &import.output_folder { - println!("Output folder: {:?}", output_folder); - import_from_sd(&mut import.sd_card, Some(output_folder.clone())).unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }); - } else { - println!("Output folder: current directory"); - import_from_sd(&mut import.sd_card, None).unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }); - } - - // Iterate folders inside output folder. Inside each iterated folder there is - // a folder called "audio" which contains the wav files. Merge them into a single - // wav file and delete the "audio" folder. - let output_folder = match import.output_folder { - Some(output_folder) => output_folder, - None => std::env::current_dir().unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }) - }; - - for entry in WalkDir::new(output_folder.clone()) { - let entry = entry.unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }); - let path = entry.path(); - if path.is_dir() { - let audio_folder = path.join("audio"); - if audio_folder.exists() { - merge_wavs(&audio_folder, &PathBuf::from(path)).unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }); - fs::remove_dir_all(audio_folder).unwrap_or_else(|err| { - eprintln!("Error: {}", err); - std::process::exit(1); - }); - } - } - } - } + Commands::Import(mut import) => { import.import() } + Commands::Clean(mut clean) => { clean.clean() } } }