vapor-parser 0.1.0

A parser for MPEG files
Documentation
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 {
            // If we haven't found the first header yet, we need to shift the buffer
            // and read the next byte into the buffer until we find the first one.
            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 we've read 0 bytes, we've reached the end of the file
            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
            };

            // Frame size includes the header size and XING header size. In order to get the
            // actual frame size, we need to subtract the header size and XING header size.
            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 = {
            // If the Xing header is present, we can get the duration from it and return early
            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))
    }
}