use anyhow::{Context, Result};
use clap::Parser;
use rayon::prelude::*;
use spectrs::io::audio::{read_audio_file_mono, resample};
use spectrs::io::image::{Colormap, save_spectrogram_image};
use spectrs::spectrogram::mel::{MelScale, convert_to_mel, par_convert_to_mel};
use spectrs::spectrogram::stft::{SpectrogramType, compute_spectrogram, par_compute_spectrogram};
use std::path::Path;
use walkdir::WalkDir;
#[derive(Parser)]
#[command(version, about, long_about = None)]
pub struct Cli {
#[arg(required = true)]
pub input: String,
#[arg(long)]
pub sr: Option<u32>,
#[arg(long, default_value = "2048")]
pub n_fft: usize,
#[arg(long, default_value = "512")]
pub hop_length: usize,
#[arg(long, default_value = "2048")]
pub win_length: usize,
#[arg(long, default_value = "true")]
pub center: bool,
#[arg(long, default_value = "power")]
pub spec_type: SpectrogramType,
#[arg(long)]
pub n_mels: Option<usize>,
#[arg(long, default_value = "0.0")]
pub f_min: Option<f32>,
#[arg(long)]
pub f_max: Option<f32>,
#[arg(long, default_value = "slaney")]
pub mel_scale: MelScale,
#[arg(long, default_value = "viridis")]
pub colormap: Colormap,
}
fn par_create_spectrogram(
input: &Path,
sr: Option<u32>,
n_fft: usize,
hop_length: usize,
win_length: usize,
center: bool,
spec_type: SpectrogramType,
n_mels: Option<usize>,
f_min: Option<f32>,
f_max: Option<f32>,
mel_scale: MelScale,
colormap: Colormap,
) -> Result<()> {
let (mut audio, original_sr) =
read_audio_file_mono(input).with_context(|| "Failed to read audio")?;
let target_sr;
if sr.is_some() && sr.unwrap() != original_sr {
audio = resample(audio, original_sr, sr.unwrap())
.with_context(|| "Failed to resample audio")?;
target_sr = sr.unwrap();
} else {
target_sr = original_sr;
}
let mut spec =
par_compute_spectrogram(&audio, n_fft, hop_length, win_length, center, spec_type);
if n_mels.is_some() {
spec = par_convert_to_mel(
&spec,
target_sr,
n_fft,
n_mels.unwrap(),
f_min,
f_max,
mel_scale,
);
}
let output = input.with_extension("png");
save_spectrogram_image(&spec, output, colormap).with_context(|| "Failed to save spectogram")?;
Ok(())
}
fn create_spectrogram(
input: &Path,
sr: Option<u32>,
n_fft: usize,
hop_length: usize,
win_length: usize,
center: bool,
spec_type: SpectrogramType,
n_mels: Option<usize>,
f_min: Option<f32>,
f_max: Option<f32>,
mel_scale: MelScale,
colormap: Colormap,
) -> Result<()> {
let (mut audio, original_sr) =
read_audio_file_mono(input).with_context(|| "Failed to read audio")?;
let target_sr;
if sr.is_some() && sr.unwrap() != original_sr {
audio = resample(audio, original_sr, sr.unwrap())
.with_context(|| "Failed to resample audio")?;
target_sr = sr.unwrap();
} else {
target_sr = original_sr;
}
let mut spec = compute_spectrogram(&audio, n_fft, hop_length, win_length, center, spec_type);
if n_mels.is_some() {
spec = convert_to_mel(
&spec,
target_sr,
n_fft,
n_mels.unwrap(),
f_min,
f_max,
mel_scale,
);
}
let output = input.with_extension("png");
save_spectrogram_image(&spec, output, colormap).with_context(|| "Failed to save spectogram")?;
Ok(())
}
fn main() -> Result<()> {
let args = Cli::parse();
let input = Path::new(&args.input);
if !input.exists() {
anyhow::bail!("Input path does not exist: {}", input.display());
}
if input.is_file() && input.extension().and_then(|ext| ext.to_str()) == Some("wav") {
par_create_spectrogram(
&input,
args.sr,
args.n_fft,
args.hop_length,
args.win_length,
args.center,
args.spec_type,
args.n_mels,
args.f_min,
args.f_max,
args.mel_scale,
args.colormap,
)
.with_context(|| "Failed to create spectrogram")?;
}
else {
let files: Vec<_> = WalkDir::new(input)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| e.path().extension().and_then(|ext| ext.to_str()) == Some("wav"))
.map(|e| e.path().to_path_buf())
.collect();
files
.par_iter()
.try_for_each(|file| -> Result<()> {
create_spectrogram(
&file,
args.sr,
args.n_fft,
args.hop_length,
args.win_length,
args.center,
args.spec_type,
args.n_mels,
args.f_min,
args.f_max,
args.mel_scale,
args.colormap,
)
})
.with_context(|| "Failed to create spectrogram")?;
};
Ok(())
}