use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
#[derive(Debug)]
pub struct AudioMetadata {
pub sample_rate: u32,
pub channels: u16,
pub bits_per_sample: u16,
pub duration_seconds: Option<f64>,
}
pub fn read_audio_metadata(path: &Path) -> Result<AudioMetadata, Box<dyn std::error::Error>> {
let extension = path
.extension()
.and_then(|e| e.to_str())
.map(|e| e.to_lowercase());
match extension.as_deref() {
Some("flac") => read_flac_metadata(path),
Some("wav") => read_wav_metadata(path),
_ => Err("Unsupported audio format".into()),
}
}
fn read_flac_metadata(path: &Path) -> Result<AudioMetadata, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut magic = [0u8; 4];
file.read_exact(&mut magic)?;
if &magic != b"fLaC" {
return Err("Not a valid FLAC file".into());
}
let mut header = [0u8; 4];
file.read_exact(&mut header)?;
let block_type = header[0] & 0x7F;
if block_type != 0 {
return Err("Expected STREAMINFO block".into());
}
let mut streaminfo = [0u8; 34]; file.read_exact(&mut streaminfo)?;
let sample_rate = u32::from_be_bytes([0, streaminfo[10], streaminfo[11], streaminfo[12]]) >> 4;
let channels = ((streaminfo[12] & 0x0E) >> 1) + 1;
let bits_per_sample = (((streaminfo[12] & 0x01) << 4) | ((streaminfo[13] & 0xF0) >> 4)) + 1;
let total_samples = ((streaminfo[13] as u64 & 0x0F) << 32)
| (streaminfo[14] as u64) << 24
| (streaminfo[15] as u64) << 16
| (streaminfo[16] as u64) << 8
| (streaminfo[17] as u64);
let duration_seconds = if sample_rate > 0 && total_samples > 0 {
Some(total_samples as f64 / sample_rate as f64)
} else {
None
};
Ok(AudioMetadata {
sample_rate,
channels: channels as u16,
bits_per_sample: bits_per_sample as u16,
duration_seconds,
})
}
fn read_wav_metadata(path: &Path) -> Result<AudioMetadata, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut riff = [0u8; 4];
file.read_exact(&mut riff)?;
if &riff != b"RIFF" {
return Err("Not a valid WAV file".into());
}
file.seek(SeekFrom::Current(4))?;
let mut wave = [0u8; 4];
file.read_exact(&mut wave)?;
if &wave != b"WAVE" {
return Err("Not a valid WAV file".into());
}
loop {
let mut chunk_id = [0u8; 4];
file.read_exact(&mut chunk_id)?;
let mut chunk_size = [0u8; 4];
file.read_exact(&mut chunk_size)?;
let size = u32::from_le_bytes(chunk_size);
if &chunk_id == b"fmt " {
let mut fmt_data = vec![0u8; size.min(16) as usize];
file.read_exact(&mut fmt_data)?;
let channels = u16::from_le_bytes([fmt_data[2], fmt_data[3]]);
let sample_rate =
u32::from_le_bytes([fmt_data[4], fmt_data[5], fmt_data[6], fmt_data[7]]);
let bits_per_sample = u16::from_le_bytes([fmt_data[14], fmt_data[15]]);
let mut duration_seconds = None;
while let Ok(()) = file.read_exact(&mut chunk_id) {
file.read_exact(&mut chunk_size)?;
let data_size = u32::from_le_bytes(chunk_size);
if &chunk_id == b"data" {
let bytes_per_sample = (bits_per_sample / 8) as u32;
let bytes_per_second = sample_rate * channels as u32 * bytes_per_sample;
if bytes_per_second > 0 {
duration_seconds = Some(data_size as f64 / bytes_per_second as f64);
}
break;
} else {
file.seek(SeekFrom::Current(data_size as i64))?;
}
}
return Ok(AudioMetadata {
sample_rate,
channels,
bits_per_sample,
duration_seconds,
});
} else {
file.seek(SeekFrom::Current(size as i64))?;
}
}
}