mp3_duration/
lib.rs

1use std::fs::File;
2use std::io::prelude::*;
3use std::io::BufReader;
4use std::path::Path;
5use std::time::Duration;
6
7mod constants;
8mod context;
9mod error;
10#[cfg(test)]
11mod test;
12
13use crate::constants::*;
14use crate::context::Context;
15
16pub use crate::error::{ErrorKind, MP3DurationError};
17
18fn get_bitrate<T: Read>(
19    context: &Context<T>,
20    version: Version,
21    layer: Layer,
22    encoded_bitrate: u8,
23) -> Result<u32, MP3DurationError> {
24    if encoded_bitrate >= 15 {
25        return Err(context.error(ErrorKind::InvalidBitrate {
26            bitrate: encoded_bitrate,
27        }));
28    }
29    if layer == Layer::NotDefined {
30        return Err(context.error(ErrorKind::ForbiddenLayer));
31    }
32    Ok(1000 * BIT_RATES[version as usize][layer as usize][encoded_bitrate as usize])
33}
34
35fn get_sampling_rate<T: Read>(
36    context: &Context<T>,
37    version: Version,
38    encoded_sampling_rate: u8,
39) -> Result<u32, MP3DurationError> {
40    if encoded_sampling_rate >= 3 {
41        return Err(context.error(ErrorKind::InvalidSamplingRate {
42            sampling_rate: encoded_sampling_rate,
43        }));
44    }
45    Ok(SAMPLING_RATES[version as usize][encoded_sampling_rate as usize])
46}
47
48fn get_samples_per_frame<T: Read>(
49    context: &Context<T>,
50    version: Version,
51    layer: Layer,
52) -> Result<u32, MP3DurationError> {
53    if layer == Layer::NotDefined {
54        return Err(context.error(ErrorKind::ForbiddenLayer));
55    }
56    Ok(SAMPLES_PER_FRAME[version as usize][layer as usize])
57}
58
59fn get_side_information_size(version: Version, mode: Mode) -> usize {
60    SIDE_INFORMATION_SIZES[version as usize][mode as usize] as usize
61}
62
63/// Measures the duration of a mp3 file contained in any struct implementing Read.
64///
65/// # Examples
66///
67/// ```
68/// use std::path::Path;
69/// use std::fs::File;
70/// use std::io::BufReader;
71/// use mp3_duration;
72///
73/// let path = Path::new("test/source.mp3");
74/// let file = File::open(path).unwrap();
75/// let mut reader = BufReader::new(file);
76/// let duration = mp3_duration::from_read(&mut reader).unwrap();
77/// println!("File duration: {:?}", duration);
78/// ```
79pub fn from_read<T>(reader: &mut T) -> Result<Duration, MP3DurationError>
80where
81    T: Read,
82{
83    let mut header_buffer = [0; 4];
84
85    let mut context = Context::new(reader);
86
87    loop {
88        // Skip over all 0x00 bytes (these are probably incorrectly added padding bytes for id3v2)
89        header_buffer[0] = 0;
90        while header_buffer[0] == 0 {
91            match context.read_exact(&mut header_buffer[0..1]) {
92                Ok(_) => (),
93                Err(_) if context.reached_eof() => break,
94                Err(e) => return Err(e),
95            };
96        }
97
98        match context.read_exact(&mut header_buffer[1..]) {
99            Ok(_) => (),
100            Err(_) if context.reached_eof() => break,
101            Err(e) => return Err(e),
102        };
103
104        // MPEG frame
105        let header = (header_buffer[0] as u32) << 24
106            | (header_buffer[1] as u32) << 16
107            | (header_buffer[2] as u32) << 8
108            | header_buffer[3] as u32;
109        let is_mp3 = header >> 21 == 0x7FF;
110        if is_mp3 {
111            let version = match (header >> 19) & 0b11 {
112                0 => Version::Mpeg25,
113                1 => return Err(context.error(ErrorKind::ForbiddenVersion)),
114                2 => Version::Mpeg2,
115                3 => Version::Mpeg1,
116                _ => unreachable!(),
117            };
118
119            let layer = match (header >> 17) & 0b11 {
120                0 => Layer::NotDefined,
121                1 => Layer::Layer3,
122                2 => Layer::Layer2,
123                3 => Layer::Layer1,
124                _ => unreachable!(),
125            };
126
127            let encoded_bitrate = (header >> 12) & 0b1111;
128            let encoded_sampling_rate = (header >> 10) & 0b11;
129            let padding = if 0 != ((header >> 9) & 1) { 1 } else { 0 };
130
131            let mode = match (header >> 6) & 0b11 {
132                0 => Mode::Stereo,
133                1 => Mode::JointStereo,
134                2 => Mode::DualChannel,
135                3 => Mode::Mono,
136                _ => unreachable!(),
137            };
138
139            let sampling_rate = get_sampling_rate(&context, version, encoded_sampling_rate as u8)?;
140            let num_samples = get_samples_per_frame(&context, version, layer)?;
141
142            let xing_offset = get_side_information_size(version, mode);
143            let mut xing_buffer = [0; 12];
144
145            context.skip(xing_offset)?;
146            context.read_exact(&mut xing_buffer)?;
147
148            let is_xing = xing_buffer[0] == 'X' as u8
149                && xing_buffer[1] == 'i' as u8
150                && xing_buffer[2] == 'n' as u8
151                && xing_buffer[3] == 'g' as u8;
152            let is_info = xing_buffer[0] == 'I' as u8
153                && xing_buffer[1] == 'n' as u8
154                && xing_buffer[2] == 'f' as u8
155                && xing_buffer[3] == 'o' as u8;
156            if is_xing || is_info {
157                let has_frames = 0 != (xing_buffer[7] & 1);
158                if has_frames {
159                    let num_frames = (xing_buffer[8] as u32) << 24
160                        | (xing_buffer[9] as u32) << 16
161                        | (xing_buffer[10] as u32) << 8
162                        | xing_buffer[11] as u32;
163                    let rate = sampling_rate as u64;
164                    let billion = 1_000_000_000;
165                    let frames_x_samples = num_frames as u64 * num_samples as u64;
166                    let seconds = frames_x_samples / rate;
167                    let nanoseconds = (billion * frames_x_samples) / rate - billion * seconds;
168                    return Ok(Duration::new(seconds, nanoseconds as u32));
169                }
170            }
171
172            let bitrate = get_bitrate(&context, version, layer, encoded_bitrate as u8)?;
173            let frame_length = (num_samples / 8 * bitrate / sampling_rate + padding) as usize;
174
175            let bytes_to_next_frame = frame_length
176                .checked_sub(header_buffer.len() + xing_offset + xing_buffer.len())
177                .ok_or(context.error(ErrorKind::MPEGFrameTooShort))?;
178
179            context.skip(bytes_to_next_frame)?;
180
181            let frame_duration = (num_samples as u64 * 1_000_000_000) / (sampling_rate as u64);
182            context.duration += Duration::new(0, frame_duration as u32);
183
184            continue;
185        }
186
187        // ID3v2 frame
188        let is_id3v2 = header_buffer[0] == 'I' as u8
189            && header_buffer[1] == 'D' as u8
190            && header_buffer[2] == '3' as u8;
191        if is_id3v2 {
192            let mut id3v2 = [0; 6]; // 4 bytes already read
193            context.read_exact(&mut id3v2)?;
194            let flags = id3v2[1];
195            let footer_size: usize = if 0 != (flags & 0b0001_0000) { 10 } else { 0 };
196            let tag_size: usize = ((id3v2[5] as u32)
197                | ((id3v2[4] as u32) << 7)
198                | ((id3v2[3] as u32) << 14)
199                | ((id3v2[2] as u32) << 21)) as usize;
200            context.skip(tag_size + footer_size)?;
201            continue;
202        }
203
204        // ID3v1 frame
205        let is_id3v1 = header_buffer[0] == 'T' as u8
206            && header_buffer[1] == 'A' as u8
207            && header_buffer[2] == 'G' as u8;
208        if is_id3v1 {
209            context.skip(128 - header_buffer.len())?;
210            continue;
211        }
212
213        // APEv2 frame
214        let maybe_is_ape_v2 = header_buffer[0] == 'A' as u8
215            && header_buffer[1] == 'P' as u8
216            && header_buffer[2] == 'E' as u8
217            && header_buffer[3] == 'T' as u8;
218        if maybe_is_ape_v2 {
219            let mut ape_header = [0; 12];
220            context.read_exact(&mut ape_header)?;
221            let is_really_ape_v2 = ape_header[0] == 'A' as u8
222                && ape_header[1] == 'G' as u8
223                && ape_header[2] == 'E' as u8
224                && ape_header[3] == 'X' as u8;
225            if is_really_ape_v2 {
226                let tag_size: usize = ((ape_header[8] as u32)
227                    | ((ape_header[9] as u32) << 8)
228                    | ((ape_header[10] as u32) << 16)
229                    | ((ape_header[11] as u32) << 24))
230                    as usize;
231                context.skip(tag_size + 16)?;
232                continue;
233            }
234        }
235
236        return Err(context.error(ErrorKind::UnexpectedFrame { header }));
237    }
238
239    Ok(context.duration)
240}
241
242/// Measures the duration of a file.
243///
244/// # Examples
245///
246/// ```
247/// use std::path::Path;
248/// use std::fs::File;
249/// use mp3_duration;
250///
251/// let path = Path::new("test/source.mp3");
252/// let file = File::open(path).unwrap();
253/// let duration = mp3_duration::from_file(&file).unwrap();
254/// println!("File duration: {:?}", duration);
255/// ```
256pub fn from_file(file: &File) -> Result<Duration, MP3DurationError> {
257    let mut reader = BufReader::new(file);
258    from_read(&mut reader)
259}
260
261/// Measures the duration of a file.
262///
263/// # Examples
264///
265/// ```
266/// use std::path::Path;
267/// use mp3_duration;
268///
269/// let path = Path::new("test/source.mp3");
270/// let duration = mp3_duration::from_path(&path).unwrap();
271/// println!("File duration: {:?}", duration);
272/// ```
273pub fn from_path<P>(path: P) -> Result<Duration, MP3DurationError>
274where
275    P: AsRef<Path>,
276{
277    File::open(path)
278        .map_err(|e| MP3DurationError {
279            kind: e.into(),
280            offset: 0,
281            at_duration: Duration::from_secs(0),
282        })
283        .and_then(|file| from_file(&file))
284}