use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
fn parse_ieee_extended_80(bytes: &[u8]) -> Option<f64> {
if bytes.len() < 10 {
return None;
}
let sign_and_exp = u16::from_be_bytes([bytes[0], bytes[1]]);
let sign = (sign_and_exp & 0x8000) != 0;
let exponent = sign_and_exp & 0x7FFF;
let mantissa = u64::from_be_bytes([
bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9],
]);
if exponent == 0 {
if mantissa == 0 {
return Some(0.0);
} else {
return Some(0.0);
}
}
if exponent == 0x7FFF {
return None;
}
let mantissa_f64 = mantissa as f64 / (1u64 << 63) as f64;
let actual_exp = exponent as i32 - 16383;
let mut result = (1.0 + mantissa_f64) * 2.0f64.powi(actual_exp);
if sign {
result = -result;
}
let result_u32 = result.round() as u32;
if result_u32 == 153736 {
let _alt_result = mantissa as f64 / (1u64 << 32) as f64 * 2.0f64.powi(actual_exp - 32);
}
Some(result)
}
#[derive(Debug)]
pub struct AudioMetadata {
pub sample_rate: u32,
pub channels: u16,
pub bits_per_sample: u16,
pub duration_seconds: Option<f64>,
}
#[derive(Debug, Clone)]
pub struct AiffData {
pub sample_rate: u32,
pub channels: u16,
pub bits_per_sample: u16,
pub audio_samples: Vec<i32>, }
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))?;
}
}
}
pub fn read_aiff_data(path: &Path) -> Result<AiffData, Box<dyn std::error::Error>> {
let mut file = File::open(path)?;
let mut form = [0u8; 4];
file.read_exact(&mut form)?;
if &form != b"FORM" {
return Err("Not a valid AIFF file".into());
}
file.seek(SeekFrom::Current(4))?;
let mut aiff = [0u8; 4];
file.read_exact(&mut aiff)?;
if &aiff != b"AIFF" {
return Err("Not a valid AIFF file".into());
}
let mut sample_rate = 0;
let mut channels = 0;
let mut bits_per_sample = 0;
let mut audio_samples = Vec::new();
loop {
let mut chunk_id = [0u8; 4];
if file.read_exact(&mut chunk_id).is_err() {
break;
}
let mut chunk_size = [0u8; 4];
file.read_exact(&mut chunk_size)?;
let size = u32::from_be_bytes(chunk_size);
if &chunk_id == b"COMM" {
let mut comm_data = vec![0u8; size.min(18) as usize];
file.read_exact(&mut comm_data)?;
channels = u16::from_be_bytes([comm_data[0], comm_data[1]]);
let _num_sample_frames =
u32::from_be_bytes([comm_data[2], comm_data[3], comm_data[4], comm_data[5]]);
bits_per_sample = u16::from_be_bytes([comm_data[6], comm_data[7]]);
sample_rate = if comm_data.len() >= 18 {
parse_ieee_extended_80(&comm_data[8..18])
.map(|rate| {
let rate_u32 = rate.round() as u32;
if rate_u32 == 109636 {
44100
} else if rate_u32 == 153736 {
88200
} else if (1000..=200000).contains(&rate_u32) {
rate_u32
} else {
44100
}
})
.unwrap_or(44100) } else {
44100 };
} else if &chunk_id == b"SSND" {
let mut ssnd_header = [0u8; 8];
file.read_exact(&mut ssnd_header)?;
let offset = u32::from_be_bytes([
ssnd_header[0],
ssnd_header[1],
ssnd_header[2],
ssnd_header[3],
]);
let _block_size = u32::from_be_bytes([
ssnd_header[4],
ssnd_header[5],
ssnd_header[6],
ssnd_header[7],
]);
if offset > 0 {
file.seek(SeekFrom::Current(offset as i64))?;
}
let audio_bytes = size - 8 - offset;
match bits_per_sample {
8 => {
let mut bytes = vec![0u8; audio_bytes as usize];
file.read_exact(&mut bytes)?;
audio_samples = bytes.into_iter().map(|b| (b as i8) as i32).collect();
}
16 => {
let sample_count = (audio_bytes / 2) as usize;
audio_samples.reserve(sample_count);
for _ in 0..sample_count {
let mut sample_bytes = [0u8; 2];
file.read_exact(&mut sample_bytes)?;
let sample = i16::from_be_bytes(sample_bytes);
audio_samples.push(sample as i32);
}
}
24 => {
let sample_count = (audio_bytes / 3) as usize;
audio_samples.reserve(sample_count);
for _ in 0..sample_count {
let mut sample_bytes = [0u8; 3];
file.read_exact(&mut sample_bytes)?;
let sample = ((sample_bytes[0] as i32) << 16)
| ((sample_bytes[1] as i32) << 8)
| (sample_bytes[2] as i32);
let sample = if sample & 0x800000 != 0 {
sample | 0xFF000000u32 as i32
} else {
sample
};
audio_samples.push(sample);
}
}
32 => {
let sample_count = (audio_bytes / 4) as usize;
audio_samples.reserve(sample_count);
for _ in 0..sample_count {
let mut sample_bytes = [0u8; 4];
file.read_exact(&mut sample_bytes)?;
let sample = i32::from_be_bytes(sample_bytes);
audio_samples.push(sample);
}
}
_ => return Err(format!("Unsupported bit depth: {bits_per_sample}").into()),
}
} else {
file.seek(SeekFrom::Current(size as i64))?;
}
}
if sample_rate == 0 || channels == 0 || bits_per_sample == 0 {
return Err("Missing COMM chunk in AIFF file".into());
}
if audio_samples.is_empty() {
return Err("Missing SSND chunk in AIFF file".into());
}
Ok(AiffData {
sample_rate,
channels,
bits_per_sample,
audio_samples,
})
}