use std::{io::SeekFrom, time::Duration};
use crate::{
error::{self, Result},
frames::{
decode_header,
types::AudioFrame,
utils::{
decode_xing_header, get_frame_duration, get_frame_duration_from_header,
get_frame_size_from_header, get_samples_per_frame_from_header,
get_xing_offset_from_header, is_xing_or_info,
},
},
id3v1::ID3V1Tag,
id3v2::ID3V2Tag,
utils::XING_BUFFER_SIZE,
};
use tokio::{
fs::File,
io::{AsyncReadExt, AsyncSeekExt, BufReader},
};
#[derive(Copy, Clone, PartialEq, Eq, Debug)]
pub struct AudioFramesIndexItem {
offset: usize,
time: usize,
}
#[derive(Debug)]
pub struct Analyser {
reader: BufReader<File>,
reader_offset: u64,
}
#[derive(Debug)]
pub struct Analysis {
pub audio_frames: Vec<AudioFrame>,
pub id3v2: Option<ID3V2Tag>,
pub id3v1: Option<ID3V1Tag>,
}
impl Analyser {
pub fn new(reader: BufReader<File>) -> Self {
Self {
reader,
reader_offset: 0,
}
}
async fn read(&mut self, buffer: &mut [u8]) -> Result<u64> {
let bytes_read = match self.reader.read(buffer).await {
Ok(bytes_read) => bytes_read,
Err(_) => Err(error::Error::UnexpectedEof)?,
};
self.reader_offset += bytes_read as u64;
Ok(bytes_read as u64)
}
async fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
let offset = self.reader.seek(pos).await.unwrap();
self.reader_offset = offset.try_into().unwrap();
Ok(offset)
}
async fn fetch_audio_frames(
&mut self,
hook: impl Fn(AudioFrame) -> bool + Copy,
) -> Result<Vec<AudioFrame>> {
let mut found_first_header = false;
let mut found_first_xing = false;
let mut buffer = [0; 4];
let mut audio_frames: Vec<AudioFrame> = Vec::new();
self.seek(std::io::SeekFrom::Start(0)).await?;
loop {
let bytes_read = if !found_first_header {
buffer[0] = buffer[1];
buffer[1] = buffer[2];
buffer[2] = buffer[3];
match self.read(&mut buffer[3..]).await {
Ok(bytes_read) => bytes_read,
Err(_) => break,
}
} else {
match self.read(&mut buffer).await {
Ok(bytes_read) => bytes_read,
Err(_) => break,
}
};
if bytes_read == 0 {
break;
}
let header = match decode_header(&buffer) {
Ok(header) => header,
Err(_) => continue,
};
let current_offset = self.reader_offset - buffer.len() as u64;
let xing_offset = get_xing_offset_from_header(&header);
let xing_header = if !found_first_xing {
self.seek(std::io::SeekFrom::Current(xing_offset.try_into().unwrap()))
.await?;
let mut xing_buffer = [0u8; XING_BUFFER_SIZE];
self.read(&mut xing_buffer).await?;
if is_xing_or_info(&xing_buffer) {
let xing_header = decode_xing_header(&xing_buffer)?;
Some(xing_header)
} else {
None
}
} else {
None
};
let frame_size = get_frame_size_from_header(&header);
let bytes_to_next_frame = if !found_first_xing {
frame_size - buffer.len() as u64 - xing_offset - XING_BUFFER_SIZE as u64
} else {
frame_size - buffer.len() as u64
};
found_first_header = true;
if xing_header.is_some() {
found_first_xing = true;
}
let audio_frame = AudioFrame {
header,
xing_header,
offset: current_offset,
data: buffer.to_vec(),
};
audio_frames.push(audio_frame.clone());
if !hook(audio_frame.clone()) {
break;
}
self.seek(std::io::SeekFrom::Current(bytes_to_next_frame as i64))
.await
.unwrap();
}
Ok(audio_frames)
}
pub async fn get_audio_frames(mut self) -> Result<Vec<AudioFrame>> {
let ref mut this = self;
let hook = |_: AudioFrame| true;
let audio_frames = this.fetch_audio_frames(hook).await?;
Ok(audio_frames)
}
pub async fn get_duration(mut self) -> Result<Duration> {
let audio_frames = {
let hook = |audio_frame: AudioFrame| {
if audio_frame.xing_header.is_some() {
let xing_header = audio_frame.xing_header.as_ref().unwrap();
if xing_header.frames.is_some() {
return false;
}
}
true
};
async move {
let audio_frames = self.fetch_audio_frames(hook).await?;
Ok(audio_frames)
}
}
.await?;
let mut nanos = 0;
for audio_frame in audio_frames.iter() {
if audio_frame.xing_header.is_some() {
let xing_header = audio_frame.xing_header.as_ref().unwrap();
if xing_header.frames.is_some() {
let samples_per_frame = get_samples_per_frame_from_header(&audio_frame.header);
let frames = xing_header.frames.unwrap();
let duration =
get_frame_duration(samples_per_frame, audio_frame.header.sample_rate.hz())
* frames as u32;
return Ok(duration);
}
}
let frame_duration = get_frame_duration_from_header(&audio_frame.header).as_nanos();
nanos += frame_duration;
}
Ok(Duration::from_nanos(nanos as u64))
}
}