@@ -2,7 +2,7 @@ use crate::ape::ApeFile;
22use crate :: error:: { LoftyError , Result } ;
33use crate :: iff:: aiff:: AiffFile ;
44use crate :: iff:: wav:: WavFile ;
5- use crate :: mp3:: header:: verify_frame_sync ;
5+ use crate :: mp3:: header:: search_for_frame_sync ;
66use crate :: mp3:: Mp3File ;
77use crate :: mp4:: Mp4File ;
88use crate :: ogg:: flac:: FlacFile ;
@@ -148,42 +148,59 @@ impl<R: Read + Seek> Probe<R> {
148148 }
149149
150150 #[ allow( clippy:: shadow_unrelated) ]
151- fn guess_inner ( & mut self ) -> Result < Option < FileType > > {
151+ fn guess_inner ( & mut self ) -> std:: io:: Result < Option < FileType > > {
152+ // temporary buffer for storing 36 bytes
153+ // (36 is just a guess as to how long the data for estimating the file type might be)
152154 let mut buf = [ 0 ; 36 ] ;
153155
154- let pos = self . inner . seek ( SeekFrom :: Current ( 0 ) ) ?;
156+ // read the first 36 bytes and seek back to the starting position
157+ let starting_position = self . inner . stream_position ( ) ?;
155158 let buf_len = std:: io:: copy (
156- & mut self . inner . by_ref ( ) . take ( 36 ) ,
159+ & mut self . inner . by_ref ( ) . take ( buf . len ( ) as u64 ) ,
157160 & mut Cursor :: new ( & mut buf[ ..] ) ,
158161 ) ? as usize ;
162+ self . inner . seek ( SeekFrom :: Start ( starting_position) ) ?;
159163
160- self . inner . seek ( SeekFrom :: Start ( pos ) ) ? ;
161-
164+ // estimate the file type by using these 36 bytes
165+ // note that any error from `from_buffer_inner` are suppressed, as it returns an error on unknown format
162166 match FileType :: from_buffer_inner ( & buf[ ..buf_len] ) {
167+ // the file type was guessed based on these bytes
163168 Ok ( ( Some ( f_ty) , _) ) => Ok ( Some ( f_ty) ) ,
169+ // the first data block is ID3 data; this means other data can follow (e.g. APE or MP3 frames)
164170 Ok ( ( None , id3_len) ) => {
165- self . inner
171+ // the position right after the ID3 block is the internal size value (id3_len)
172+ // added to the length of the ID3 header (which is 10 bytes),
173+ // as the size does not include the header itself
174+ let position_after_id3_block = self
175+ . inner
166176 . seek ( SeekFrom :: Current ( i64:: from ( 10 + id3_len) ) ) ?;
167177
168- let mut ident = [ 0 ; 3 ] ;
169- let buf_len = std:: io:: copy (
170- & mut self . inner . by_ref ( ) . take ( 3 ) ,
171- & mut Cursor :: new ( & mut ident[ ..] ) ,
172- ) ?;
173-
174- self . inner . seek ( SeekFrom :: Start ( pos) ) ?;
175-
176- if buf_len < 3 {
177- return Err ( LoftyError :: UnknownFormat ) ;
178- }
179-
180- if & ident == b"MAC" {
181- Ok ( Some ( FileType :: APE ) )
182- } else if verify_frame_sync ( [ ident[ 0 ] , ident[ 1 ] ] ) {
183- Ok ( Some ( FileType :: MP3 ) )
184- } else {
185- Err ( LoftyError :: UnknownFormat )
186- }
178+ let file_type_after_id3_block = {
179+ // try to guess the file type after the ID3 block by inspecting the first 3 bytes
180+ let mut ident = [ 0 ; 3 ] ;
181+ std:: io:: copy (
182+ & mut self . inner . by_ref ( ) . take ( ident. len ( ) as u64 ) ,
183+ & mut Cursor :: new ( & mut ident[ ..] ) ,
184+ ) ?;
185+
186+ if & ident == b"MAC" {
187+ Ok ( Some ( FileType :: APE ) )
188+ } else {
189+ // potentially some junk bytes are between the ID3 block and the following MP3 block
190+ // search for any possible sync bits after the ID3 block
191+ self . inner . seek ( SeekFrom :: Start ( position_after_id3_block) ) ?;
192+ if search_for_frame_sync ( & mut self . inner ) ?. is_some ( ) {
193+ Ok ( Some ( FileType :: MP3 ) )
194+ } else {
195+ Ok ( None )
196+ }
197+ }
198+ } ;
199+
200+ // before returning any result for a file type, seek back to the front
201+ self . inner . seek ( SeekFrom :: Start ( starting_position) ) ?;
202+
203+ file_type_after_id3_block
187204 } ,
188205 _ => Ok ( None ) ,
189206 }
@@ -247,3 +264,34 @@ where
247264{
248265 Probe :: open ( path) ?. read ( read_properties)
249266}
267+
268+ #[ cfg( test) ]
269+ mod tests {
270+ use crate :: Probe ;
271+
272+ #[ test]
273+ fn mp3_file_id3v2_3 ( ) {
274+ // test data that contains 4 bytes of junk (0x20) between the ID3 portion and the first MP3 frame
275+ let data: [ & [ u8 ] ; 4 ] = [
276+ // ID3v2.3 header (10 bytes)
277+ & [ 0x49 , 0x44 , 0x33 , 0x03 , 0x00 , 0x00 , 0x00 , 0x00 , 0x00 , 0x23 ] ,
278+ // TALB frame
279+ & [
280+ 0x54 , 0x41 , 0x4C , 0x42 , 0x00 , 0x00 , 0x00 , 0x19 , 0x00 , 0x00 , 0x01 , 0xFF , 0xFE , 0x61 ,
281+ 0x00 , 0x61 , 0x00 , 0x61 , 0x00 , 0x61 , 0x00 , 0x61 , 0x00 , 0x61 , 0x00 , 0x61 , 0x00 , 0x61 ,
282+ 0x00 , 0x61 , 0x00 , 0x61 , 0x00 , 0x61 , 0x00 ,
283+ ] ,
284+ // 4 bytes of junk
285+ & [ 0x20 , 0x20 , 0x20 , 0x20 ] ,
286+ // start of MP3 frame (not all bytes are shown in this slice)
287+ & [
288+ 0xFF , 0xFB , 0x50 , 0xC4 , 0x00 , 0x03 , 0xC0 , 0x00 , 0x01 , 0xA4 , 0x00 , 0x00 , 0x00 , 0x20 ,
289+ 0x00 , 0x00 , 0x34 , 0x80 , 0x00 , 0x00 , 0x04 ,
290+ ] ,
291+ ] ;
292+ let data: Vec < u8 > = data. into_iter ( ) . flatten ( ) . cloned ( ) . collect ( ) ;
293+ let data = std:: io:: Cursor :: new ( & data) ;
294+ let probe = Probe :: new ( data) . guess_file_type ( ) . unwrap ( ) ;
295+ matches ! ( probe. file_type( ) , Some ( crate :: FileType :: MP3 ) ) ;
296+ }
297+ }
0 commit comments