vapor-parser 0.1.0

A parser for MPEG files
Documentation
mod analyser;
mod error;
mod frames;
mod id3v1;
mod id3v2;
mod utils;

pub use analyser::Analyser;
pub use error::{Error, Result};
pub use frames::types::AudioFrame;
pub use id3v1::ID3V1Tag;
pub use id3v2::ID3V2Tag;

#[cfg(test)]
mod tests {
    use crate::{
        analyser::Analyser,
        error::{Error, Result},
        frames::utils::{compute_frame_index, get_frame_duration_from_header, get_offset_for_time},
    };
    use std::time::{Duration, Instant};
    use tokio::{fs::File, io::BufReader, join};

    async fn get_analyser(path: &str) -> Result<Analyser> {
        let file = match File::open(path).await {
            Ok(file) => file,
            Err(_) => return Err(Error::Io),
        };

        let reader = BufReader::new(file);
        let analyser = Analyser::new(reader);

        Ok(analyser)
    }

    #[tokio::test]
    async fn get_cbr320_audio_frames() {
        let analyser = get_analyser("test/CBR320.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn check_cbr320_xing_header() {
        let analyser = get_analyser("test/CBR320.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await.unwrap();
        let xing = audio_frames
            .iter()
            .find(|frame| frame.xing_header.is_some())
            .unwrap()
            .xing_header
            .as_ref()
            .unwrap();

        assert!(audio_frames.len().abs_diff(xing.frames.unwrap() as usize) < 10);
    }

    #[tokio::test]
    async fn get_apev2_audio_frames() {
        let analyser = get_analyser("test/APEv2.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_id3v1_audio_frames() {
        let analyser = get_analyser("test/ID3v1.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_id3v2_audio_frames() {
        let analyser = get_analyser("test/ID3v2.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_id3v2_with_bad_padding_audio_frames() {
        let analyser = get_analyser("test/ID3v2WithBadPadding.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_id3v2_with_image_audio_frames() {
        let analyser = get_analyser("test/ID3v2WithImage.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_sine_empty_id3_audio_frames() {
        let analyser = get_analyser("test/SineEmptyID3.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_source_audio_frames() {
        let analyser = get_analyser("test/source.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_truncated_audio_frames() {
        let analyser = get_analyser("test/Truncated.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_vbr9_audio_frames() {
        let analyser = get_analyser("test/VBR9.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn get_vbr0_audio_frames() {
        let analyser = get_analyser("test/VBR0.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await;
        assert!(audio_frames.is_ok());
        assert!(audio_frames.unwrap().len() > 0);
    }

    #[tokio::test]
    async fn compare_frame_count() {
        let source_analyser = get_analyser("test/source.mp3").await.unwrap();
        let id3v1_analyser = get_analyser("test/ID3v1.mp3").await.unwrap();
        let id3v2_analyser = get_analyser("test/ID3v2.mp3").await.unwrap();
        let cbr320_analyser = get_analyser("test/CBR320.mp3").await.unwrap();

        let results = join!(
            source_analyser.get_audio_frames(),
            id3v1_analyser.get_audio_frames(),
            id3v2_analyser.get_audio_frames(),
            cbr320_analyser.get_audio_frames()
        );

        let source_audio_frames = results.0.unwrap();
        let id3v1_audio_frames = results.1.unwrap();
        let id3v2_audio_frames = results.2.unwrap();
        let cbr320_audio_frames = results.3.unwrap();

        assert!(source_audio_frames.len().abs_diff(id3v1_audio_frames.len()) < 5);
        assert!(source_audio_frames.len().abs_diff(id3v2_audio_frames.len()) < 5);
        assert!(
            source_audio_frames
                .len()
                .abs_diff(cbr320_audio_frames.len())
                < 5
        );
    }

    #[tokio::test]
    async fn get_xing_duration() {
        let cbr320_analyser = get_analyser("test/CBR320.mp3").await.unwrap();
        let source_analyser = get_analyser("test/source.mp3").await.unwrap();

        let cbr320_start = Instant::now();
        let cbr320_duration = cbr320_analyser.get_duration().await.unwrap();
        let cbr320_elapsed_time = cbr320_start.elapsed();

        let source_start = Instant::now();
        let source_duration = source_analyser.get_duration().await.unwrap();
        let source_elapsed_time = source_start.elapsed();

        assert!(cbr320_elapsed_time < source_elapsed_time);
        assert!(
            cbr320_duration
                .as_secs()
                .abs_diff(source_duration.as_secs())
                < 1
        );
    }

    #[tokio::test]
    async fn test_frame_index() {
        let analyser = get_analyser("test/CBR320.mp3").await.unwrap();
        let audio_frames = analyser.get_audio_frames().await.unwrap();
        let frame_index = compute_frame_index(&audio_frames);

        let mut time_1 = Duration::from_secs(0);
        let mut time_2 = Duration::from_secs(1);
        let mut time_3 = Duration::from_secs(2);

        let mut offset_1 = 0;
        let mut offset_2 = 0;
        let mut offset_3 = 0;

        let mut time = Duration::from_secs(0);
        let mut offset = 0;
        for (index, frame) in audio_frames.iter().enumerate() {
            if index == 0 {
                time_1 = time;
                offset_1 = offset;
            }

            if index == 10 {
                time_2 = time;
                offset_2 = offset;
            }

            if index == 65 {
                time_3 = time;
                offset_3 = offset;
            }

            time += get_frame_duration_from_header(&frame.header);
            offset = frame.offset;

            if index > 65 {
                break;
            }
        }

        let frame_offset_1 = get_offset_for_time(&frame_index, time_1);
        let frame_offset_2 = get_offset_for_time(&frame_index, time_2);
        let frame_offset_3 = get_offset_for_time(&frame_index, time_3);

        assert!(frame_offset_1 == offset_1);
        assert!(frame_offset_2 == offset_2);
        assert!(frame_offset_3 == offset_3);
    }
}