vapor_parser/analyser/
mod.rs

1use std::{io::SeekFrom, time::Duration};
2
3use crate::{
4    error::{self, Result},
5    frames::{
6        decode_header,
7        types::AudioFrame,
8        utils::{
9            decode_xing_header, get_frame_duration, get_frame_duration_from_header,
10            get_frame_size_from_header, get_samples_per_frame_from_header,
11            get_xing_offset_from_header, is_xing_or_info,
12        },
13    },
14    id3v1::ID3V1Tag,
15    id3v2::ID3V2Tag,
16    utils::XING_BUFFER_SIZE,
17};
18use tokio::{
19    fs::File,
20    io::{AsyncReadExt, AsyncSeekExt, BufReader},
21};
22
23#[derive(Copy, Clone, PartialEq, Eq, Debug)]
24pub struct AudioFramesIndexItem {
25    offset: usize,
26    time: usize,
27}
28
29#[derive(Debug)]
30pub struct Analyser {
31    reader: BufReader<File>,
32    reader_offset: u64,
33}
34
35#[derive(Debug)]
36pub struct Analysis {
37    pub audio_frames: Vec<AudioFrame>,
38    pub id3v2: Option<ID3V2Tag>,
39    pub id3v1: Option<ID3V1Tag>,
40}
41
42impl Analyser {
43    pub fn new(reader: BufReader<File>) -> Self {
44        Self {
45            reader,
46            reader_offset: 0,
47        }
48    }
49
50    async fn read(&mut self, buffer: &mut [u8]) -> Result<u64> {
51        let bytes_read = match self.reader.read(buffer).await {
52            Ok(bytes_read) => bytes_read,
53            Err(_) => Err(error::Error::UnexpectedEof)?,
54        };
55
56        self.reader_offset += bytes_read as u64;
57        Ok(bytes_read as u64)
58    }
59
60    async fn seek(&mut self, pos: SeekFrom) -> Result<u64> {
61        let offset = self.reader.seek(pos).await.unwrap();
62        self.reader_offset = offset.try_into().unwrap();
63        Ok(offset)
64    }
65
66    async fn fetch_audio_frames(
67        &mut self,
68        hook: impl Fn(AudioFrame) -> bool + Copy,
69    ) -> Result<Vec<AudioFrame>> {
70        let mut found_first_header = false;
71        let mut found_first_xing = false;
72        let mut buffer = [0; 4];
73        let mut audio_frames: Vec<AudioFrame> = Vec::new();
74
75        self.seek(std::io::SeekFrom::Start(0)).await?;
76
77        loop {
78            // If we haven't found the first header yet, we need to shift the buffer
79            // and read the next byte into the buffer until we find the first one.
80            let bytes_read = if !found_first_header {
81                buffer[0] = buffer[1];
82                buffer[1] = buffer[2];
83                buffer[2] = buffer[3];
84
85                match self.read(&mut buffer[3..]).await {
86                    Ok(bytes_read) => bytes_read,
87                    Err(_) => break,
88                }
89            } else {
90                match self.read(&mut buffer).await {
91                    Ok(bytes_read) => bytes_read,
92                    Err(_) => break,
93                }
94            };
95
96            // If we've read 0 bytes, we've reached the end of the file
97            if bytes_read == 0 {
98                break;
99            }
100
101            let header = match decode_header(&buffer) {
102                Ok(header) => header,
103                Err(_) => continue,
104            };
105
106            let current_offset = self.reader_offset - buffer.len() as u64;
107            let xing_offset = get_xing_offset_from_header(&header);
108            let xing_header = if !found_first_xing {
109                self.seek(std::io::SeekFrom::Current(xing_offset.try_into().unwrap()))
110                    .await?;
111
112                let mut xing_buffer = [0u8; XING_BUFFER_SIZE];
113                self.read(&mut xing_buffer).await?;
114
115                if is_xing_or_info(&xing_buffer) {
116                    let xing_header = decode_xing_header(&xing_buffer)?;
117                    Some(xing_header)
118                } else {
119                    None
120                }
121            } else {
122                None
123            };
124
125            // Frame size includes the header size and XING header size. In order to get the
126            // actual frame size, we need to subtract the header size and XING header size.
127            let frame_size = get_frame_size_from_header(&header);
128            let bytes_to_next_frame = if !found_first_xing {
129                frame_size - buffer.len() as u64 - xing_offset - XING_BUFFER_SIZE as u64
130            } else {
131                frame_size - buffer.len() as u64
132            };
133
134            found_first_header = true;
135            if xing_header.is_some() {
136                found_first_xing = true;
137            }
138
139            let audio_frame = AudioFrame {
140                header,
141                xing_header,
142                offset: current_offset,
143                data: buffer.to_vec(),
144            };
145
146            audio_frames.push(audio_frame.clone());
147
148            if !hook(audio_frame.clone()) {
149                break;
150            }
151
152            self.seek(std::io::SeekFrom::Current(bytes_to_next_frame as i64))
153                .await
154                .unwrap();
155        }
156
157        Ok(audio_frames)
158    }
159
160    pub async fn get_audio_frames(mut self) -> Result<Vec<AudioFrame>> {
161        let ref mut this = self;
162        let hook = |_: AudioFrame| true;
163        let audio_frames = this.fetch_audio_frames(hook).await?;
164        Ok(audio_frames)
165    }
166
167    pub async fn get_duration(mut self) -> Result<Duration> {
168        let audio_frames = {
169            // If the Xing header is present, we can get the duration from it and return early
170            let hook = |audio_frame: AudioFrame| {
171                if audio_frame.xing_header.is_some() {
172                    let xing_header = audio_frame.xing_header.as_ref().unwrap();
173                    if xing_header.frames.is_some() {
174                        return false;
175                    }
176                }
177                true
178            };
179
180            async move {
181                let audio_frames = self.fetch_audio_frames(hook).await?;
182                Ok(audio_frames)
183            }
184        }
185        .await?;
186
187        let mut nanos = 0;
188        for audio_frame in audio_frames.iter() {
189            if audio_frame.xing_header.is_some() {
190                let xing_header = audio_frame.xing_header.as_ref().unwrap();
191                if xing_header.frames.is_some() {
192                    let samples_per_frame = get_samples_per_frame_from_header(&audio_frame.header);
193                    let frames = xing_header.frames.unwrap();
194                    let duration =
195                        get_frame_duration(samples_per_frame, audio_frame.header.sample_rate.hz())
196                            * frames as u32;
197
198                    return Ok(duration);
199                }
200            }
201
202            let frame_duration = get_frame_duration_from_header(&audio_frame.header).as_nanos();
203            nanos += frame_duration;
204        }
205
206        Ok(Duration::from_nanos(nanos as u64))
207    }
208}