1 package org.farng.mp3;
2 
3 import org.farng.mp3.filename.FilenameTag;
4 import org.farng.mp3.filename.FilenameTagBuilder;
5 import org.farng.mp3.id3.AbstractID3v2;
6 import org.farng.mp3.id3.AbstractID3v2Frame;
7 import org.farng.mp3.id3.ID3v1;
8 import org.farng.mp3.id3.ID3v1_1;
9 import org.farng.mp3.id3.ID3v2_2;
10 import org.farng.mp3.id3.ID3v2_3;
11 import org.farng.mp3.id3.ID3v2_4;
12 import org.farng.mp3.lyrics3.AbstractLyrics3;
13 import org.farng.mp3.lyrics3.Lyrics3v1;
14 import org.farng.mp3.lyrics3.Lyrics3v2;
15 
16 import java.io.EOFException;
17 import java.io.File;
18 import java.io.FileInputStream;
19 import java.io.FileNotFoundException;
20 import java.io.FileOutputStream;
21 import java.io.IOException;
22 import java.io.RandomAccessFile;
23 import java.util.ArrayList;
24 import java.util.HashSet;
25 import java.util.Iterator;
26 import java.util.List;
27 import java.util.Set;
28 
29 /**
30  * <TABLE border=0> <TBODY> <TR> <TD class=h2>How is MP3 built?</TD></TR></TBODY></TABLE> <TABLE border=0> <TBODY> <TR
31  * vAlign=top> <TD> <P>Most people with a little knowledge in MP3 files know that the sound is divided into smaller
32  * parts and compressed with a psycoacoustic model. This smaller pieces of the audio is then put into something called
33  * 'frames', which is a little datablock with a header. I'll focus on that header in this text.</P>
34  * <p/>
35  * <P>The header is 4 bytes, 32 bits, big and begins with something called sync. This sync is, at least according to the
36  * MPEG standard, 12 set bits in a row. Some add-on standards made later uses 11 set bits and one cleared bit. The sync
37  * is directly followed by a ID bit, indicating if the file is a MPEG-1 och MPEG-2 file. 0=MPEG-2 and 1=MPEG-1</P>
38  * <p/>
39  * <P>The layer is defined with the two layers bits. They are oddly defined as</P> <CENTER> <TABLE cellSpacing=0
40  * cellPadding=2 border=1> <TBODY> <TR> <TD>0 0</TD> <TD>Not defined</TD></TR> <TR> <TD>0 1</TD> <TD>Layer III</TD></TR>
41  * <TR> <TD>1 0</TD> <TD>Layer II</TD></TR> <TR> <TD>1 1</TD> <TD>Layer I</TD></TR></TBODY></TABLE> </CENTER>
42  * <p/>
43  * <P>With this information and the information in the bitrate field we can determine the bitrate of the audio (in
44  * kbit/s) according to this table.</P> <CENTER> <TABLE cellSpacing=0 cellPadding=2 border=1> <TBODY> <TR>
45  * <TD>Bitrate<BR>value</TD> <TD>MPEG-1,<BR>layer I</TD> <TD>MPEG-1,<BR>layer II</TD> <TD>MPEG-1,<BR>layer III</TD>
46  * <TD>MPEG-2,<BR>layer I</TD> <TD>MPEG-2,<BR>layer II</TD> <TD>MPEG-2,<BR>layer III</TD></TR> <TR> <TD>0 0 0 0</TD>
47  * <TD></TD> <TD></TD> <TD></TD> <TD></TD> <TD></TD> <TD></TD></TR> <TR> <TD>0 0 0 1</TD> <TD>32</TD> <TD>32</TD>
48  * <TD>32</TD> <TD>32</TD> <TD>32</TD> <TD>8</TD></TR> <TR> <TD>0 0 1 0</TD> <TD>64</TD> <TD>48</TD> <TD>40</TD>
49  * <TD>64</TD> <TD>48</TD> <TD>16</TD></TR> <TR> <TD>0 0 1 1</TD> <TD>96</TD> <TD>56</TD> <TD>48</TD> <TD>96</TD>
50  * <TD>56</TD> <TD>24</TD></TR> <TR> <TD>0 1 0 0</TD> <TD>128</TD> <TD>64</TD> <TD>56</TD> <TD>128</TD> <TD>64</TD>
51  * <TD>32</TD></TR> <TR> <TD>0 1 0 1</TD> <TD>160</TD> <TD>80</TD> <TD>64</TD> <TD>160</TD> <TD>80</TD> <TD>64</TD></TR>
52  * <TR> <TD>0 1 1 0</TD> <TD>192</TD> <TD>96</TD> <TD>80</TD> <TD>192</TD> <TD>96</TD> <TD>80</TD></TR> <TR> <TD>0 1 1
53  * 1</TD> <TD>224</TD> <TD>112</TD> <TD>96</TD> <TD>224</TD> <TD>112</TD> <TD>56</TD></TR> <TR> <TD>1 0 0 0</TD>
54  * <TD>256</TD> <TD>128</TD> <TD>112</TD> <TD>256</TD> <TD>128</TD> <TD>64</TD></TR> <TR> <TD>1 0 0 1</TD> <TD>288</TD>
55  * <TD>160</TD> <TD>128</TD> <TD>288</TD> <TD>160</TD> <TD>128</TD></TR> <TR> <TD>1 0 1 0</TD> <TD>320</TD> <TD>192</TD>
56  * <TD>160</TD> <TD>320</TD> <TD>192</TD> <TD>160</TD></TR> <TR> <TD>1 0 1 1</TD> <TD>352</TD> <TD>224</TD> <TD>192</TD>
57  * <TD>352</TD> <TD>224</TD> <TD>112</TD></TR> <TR> <TD>1 1 0 0</TD> <TD>384</TD> <TD>256</TD> <TD>224</TD> <TD>384</TD>
58  * <TD>256</TD> <TD>128</TD></TR> <TR> <TD>1 1 0 1</TD> <TD>416</TD> <TD>320</TD> <TD>256</TD> <TD>416</TD> <TD>320</TD>
59  * <TD>256</TD></TR> <TR> <TD>1 1 1 0</TD> <TD>448</TD> <TD>384</TD> <TD>320</TD> <TD>448</TD> <TD>384</TD>
60  * <TD>320</TD></TR> <TR> <TD>1 1 1 1</TD> <TD></TD> <TD></TD> <TD></TD> <TD></TD> <TD></TD>
61  * <TD></TD></TR></TBODY></TABLE> </CENTER>
62  * <p/>
63  * <P>The sample rate is described in the frequency field. These values is dependent of which MPEG standard is used
64  * according to the following table.</P> <CENTER> <TABLE cellSpacing=0 cellPadding=2 border=1> <TBODY> <TR>
65  * <TD>Frequency<BR>value</TD> <TD>MPEG-1</TD> <TD>MPEG-2</TD></TR> <TR> <TD>0 0</TD> <TD>44100 Hz</TD> <TD>22050
66  * Hz</TD></TR> <TR> <TD>0 1</TD> <TD>48000 Hz</TD> <TD>24000 Hz</TD></TR> <TR> <TD>1 0</TD> <TD>32000 Hz</TD> <TD>16000
67  * Hz</TD></TR> <TR> <TD>1 1</TD> <TD></TD> <TD></TD></TR></TBODY></TABLE> </CENTER>
68  * <p/>
69  * <P>Three bits is not needed in the decoding process at all. These are the copyright bit, original home bit and the
70  * private bit. The copyright has the same meaning as the copyright bit on CDs and DAT tapes, i.e. telling that it is
71  * illegal to copy the contents if the bit is set. The original home bit indicates, if set, that the frame is located on
72  * its original media. No one seems to know what the privat bit is good for.
73  * <p/>
74  * <p/>
75  * <p/>
76  * <P>If the protection bit is NOT set then the frame header is followed by a 16 bit checksum, inserted before the audio
77  * data. If the padding bit is set then the frame is padded with an extra byte. Knowing this the size of the complete
78  * frame can be calculated with the following formula</P> <CENTER> <P>FrameSize = 144 * BitRate / SampleRate<BR>when the
79  * padding bit is cleared and</P>
80  * <p/>
81  * <P>FrameSize = (144 * BitRate / SampleRate) + 1<BR>when the padding bit is set.
82  * <p/>
83  * <P></CENTER>
84  * <p/>
85  * <P>The frameSize is of course an integer. If for an example BitRate=128000, SampleRate=44100 and the padding bit is
86  * cleared, then the FrameSize = 144 * 128000 / 44100 = 417
87  * <p/>
88  * <p/>
89  * <p/>
90  * <P>The mode field is used to tell which sort of stereo/mono encoding that has been used. The purpose of the mode
91  * extension field is different for different layers, but I really don't know exactly what it's for.</P> <CENTER> <TABLE
92  * cellSpacing=0 cellPadding=2 border=1> <TBODY> <TR> <TD>Mode value</TD> <TD>mode</TD></TR> <TR> <TD>0 0</TD>
93  * <TD>Stereo</TD></TR> <TR> <TD>0 1</TD> <TD>Joint stereo</TD></TR> <TR> <TD>1 0</TD> <TD>Dual channel</TD></TR> <TR>
94  * <TD>1 1</TD> <TD>Mono</TD></TR></TBODY></TABLE> </CENTER>
95  * <p/>
96  * <P>The last field is the emphasis field. It is used to sort of 're-equalize' the sound after a Dolby-like noise
97  * supression. This is not very used and will probably never be. The following noise supression model is used</P>
98  * <CENTER> <TABLE cellSpacing=0 cellPadding=2 border=1> <TBODY> <TR> <TD>Emphasis value</TD> <TD>Emphasis
99  * method</TD></TR> <TR> <TD>0 0</TD> <TD>none</TD></TR> <TR> <TD>0 1</TD> <TD>50/15ms</TD></TR> <TR> <TD>1 0</TD>
100  * <TD></TD></TR> <TR> <TD>1 1</TD> <TD>CCITT j.17</TD></TR></TBODY></TABLE> </CENTER></TD> </TR></TBODY></TABLE>
101  *
102  * @author Eric Farng
103  * @version $Revision: 1.5 $
104  */
105 public class MP3File {
106 
107     /**
108      * the ID3v2 tag that this file contains.
109      */
110     private AbstractID3v2 id3v2tag;
111     /**
112      * the Lyrics3 tag that this file contains.
113      */
114     private AbstractLyrics3 lyrics3tag;
115     /**
116      * the mp3 file that this instance represents. This value can be null. This value is also used for any methods that
117      * are called without a file argument
118      */
119     private File mp3file;
120     /**
121      * the ID3v2_4 tag that represents the parsed filename.
122      */
123     private FilenameTag filenameTag;
124     /**
125      * the ID3v1 tag that this file contains.
126      */
127     private ID3v1 id3v1tag;
128     /**
129      * value read from the MP3 Frame header
130      */
131     private boolean copyProtected;
132     /**
133      * value read from the MP3 Frame header
134      */
135     private boolean home;
136     /**
137      * value read from the MP3 Frame header
138      */
139     private boolean padding;
140     /**
141      * value read from the MP3 Frame header
142      */
143     private boolean privacy;
144     /**
145      * value read from the MP3 Frame header
146      */
147     private boolean protection;
148     /**
149      * value read from the MP3 Frame header
150      */
151     private boolean variableBitRate;
152     /**
153      * value read from the MP3 Frame header
154      */
155     private byte emphasis;
156     /**
157      * value read from the MP3 Frame header
158      */
159     private byte layer;
160     /**
161      * value read from the MP3 Frame header
162      */
163     private byte mode;
164     /**
165      * value read from the MP3 Frame header
166      */
167     private byte modeExtension;
168     /**
169      * value read from the MP3 Frame header
170      */
171     private byte mpegVersion;
172     /**
173      * frequency determined from MP3 Version and frequency value read from the MP3 Frame header
174      */
175     private double frequency;
176     /**
177      * bitrate calculated from the frame MP3 Frame header
178      */
179     private int bitRate;
180 
181     /**
182      * Creates a new empty MP3File object that is not associated with a specific file.
183      */
MP3File()184     public MP3File() {
185         super();
186     }
187 
188     /**
189      * Creates a new MP3File object.
190      */
MP3File(final MP3File copyObject)191     public MP3File(final MP3File copyObject) {
192         super();
193         copyProtected = copyObject.copyProtected;
194         home = copyObject.home;
195         padding = copyObject.padding;
196         privacy = copyObject.privacy;
197         protection = copyObject.protection;
198         variableBitRate = copyObject.variableBitRate;
199         emphasis = copyObject.emphasis;
200         layer = copyObject.layer;
201         mode = copyObject.mode;
202         modeExtension = copyObject.modeExtension;
203         mpegVersion = copyObject.mpegVersion;
204         frequency = copyObject.frequency;
205         bitRate = copyObject.bitRate;
206         mp3file = new File(copyObject.mp3file.getAbsolutePath());
207         filenameTag = new FilenameTag(copyObject.filenameTag);
208         id3v2tag = (AbstractID3v2) TagUtility.copyObject(copyObject.id3v2tag);
209         lyrics3tag = (AbstractLyrics3) TagUtility.copyObject(copyObject.lyrics3tag);
210         id3v1tag = (ID3v1) TagUtility.copyObject(copyObject.id3v1tag);
211     }
212 
213     /**
214      * Creates a new MP3File object and parse the tag from the given filename.
215      *
216      * @param filename MP3 file
217      *
218      * @throws IOException  on any I/O error
219      * @throws TagException on any exception generated by this library.
220      */
MP3File(final String filename)221     public MP3File(final String filename) throws IOException, TagException {
222         this(new File(filename));
223     }
224 
225     /**
226      * Creates a new MP3File object and parse the tag from the given file Object.
227      *
228      * @param file MP3 file
229      *
230      * @throws IOException  on any I/O error
231      * @throws TagException on any exception generated by this library.
232      */
MP3File(final File file)233     public MP3File(final File file) throws IOException, TagException {
234         this(file, true);
235     }
236 
237     /**
238      * Creates a new MP3File object and parse the tag from the given file Object.
239      *
240      * @param file      MP3 file
241      * @param writeable open in read (false) or read-write (true) mode
242      *
243      * @throws IOException  on any I/O error
244      * @throws TagException on any exception generated by this library.
245      */
MP3File(final File file, final boolean writeable)246     public MP3File(final File file, final boolean writeable) throws IOException, TagException {
247         super();
248         mp3file = file;
249         final RandomAccessFile newFile = new RandomAccessFile(file, writeable ? "rw" : "r");
250         try {
251             id3v1tag = new ID3v1_1(newFile);
252         } catch (TagNotFoundException ex) {
253             // tag might be different version
254         }
255         try {
256             if (id3v1tag == null) {
257                 id3v1tag = new ID3v1(newFile);
258             }
259         } catch (TagNotFoundException ex) {
260             // ok if it's null
261         }
262         try {
263             id3v2tag = new ID3v2_4(newFile);
264         } catch (TagNotFoundException ex) {
265             // maybe different version
266         }
267         try {
268             if (id3v2tag == null) {
269                 id3v2tag = new ID3v2_3(newFile);
270             }
271         } catch (TagNotFoundException ex) {
272             // maybe a different version
273         }
274         try {
275             if (id3v2tag == null) {
276                 id3v2tag = new ID3v2_2(newFile);
277             }
278         } catch (TagNotFoundException ex) {
279             // it's ok to be null
280         }
281         try {
282             lyrics3tag = new Lyrics3v2(newFile);
283         } catch (TagNotFoundException ex) {
284             // maybe a different version
285         }
286         try {
287             if (lyrics3tag == null) {
288                 lyrics3tag = new Lyrics3v1(newFile);
289             }
290         } catch (TagNotFoundException ex) {
291             //it's ok to be null
292         }
293         newFile.close();
294         try {
295             filenameTag = FilenameTagBuilder.createFilenameTagFromMP3File(this);
296         } catch (Exception ex) {
297             throw new TagException("Unable to create FilenameTag", ex);
298         }
299     }
300 
getBitRate()301     public int getBitRate() {
302         return bitRate;
303     }
304 
isCopyProtected()305     public boolean isCopyProtected() {
306         return copyProtected;
307     }
308 
getEmphasis()309     public byte getEmphasis() {
310         return emphasis;
311     }
312 
313     /**
314      * Sets the filename tag for this MP3 File. Refer to <code>TagUtilities.parseFileName</code> and
315      * <code>TagUtilities.createID3v2Tag</code> for more information about parsing file names into <code>ID3v2_4</code>
316      * objects.
317      *
318      * @param filenameTag parsed <code>ID3v2_4</code> filename tag
319      */
setFilenameTag(final FilenameTag filenameTag)320     public void setFilenameTag(final FilenameTag filenameTag) {
321         this.filenameTag = filenameTag;
322     }
323 
324     /**
325      * Sets the filename tag for this MP3 File. Refer to <code>TagUtilities.parseFileName</code> and
326      * <code>TagUtilities.createID3v2Tag</code> for more information about parsing file names into <code>ID3v2_4</code>
327      * objects.
328      *
329      * @return parsed <code>ID3v2_4</code> filename tag
330      */
getFilenameTag()331     public FilenameTag getFilenameTag() {
332         return filenameTag;
333     }
334 
335     /**
336      * Sets all four (id3v1, lyrics3, filename, id3v2) tags in this instance to the <code>frame</code> argument if the
337      * tag exists. This method does not use the options inside the <code>tagOptions</code> object.
338      *
339      * @param frame frame to set / replace in all four tags.
340      */
341     //todo this method is very inefficient.
setFrameAcrossTags(final AbstractID3v2Frame frame)342     public void setFrameAcrossTags(final AbstractID3v2Frame frame) {
343         if (id3v1tag != null) {
344             final ID3v2_4 id3v1 = new ID3v2_4(id3v1tag);
345             id3v1.setFrame(frame);
346             id3v1tag.overwrite(id3v1);
347         }
348         if (id3v2tag != null) {
349             id3v2tag.setFrame(frame);
350         }
351         if (lyrics3tag != null) {
352             final ID3v2_4 lyrics3 = new ID3v2_4(lyrics3tag);
353             lyrics3.setFrame(frame);
354             lyrics3tag = new Lyrics3v2(lyrics3);
355         }
356         if (filenameTag != null) {
357             filenameTag.setFrame(frame);
358         }
359     }
360 
361     /**
362      * Gets the frames from all four (id3v1, lyrics3, filename, id3v2) mp3 tags in this instance for each tag that
363      * exists. This method does not use the options inside the <code>tagOptions</code> object.
364      *
365      * @param identifier ID3v2.4 Tag Frame Identifier.
366      *
367      * @return ArrayList of all instances of the desired frame. Each instance is returned as an
368      *         <code>ID3v2_4Frame</code>. The nature of the code returns the array in a specific order, but this order
369      *         is not guaranteed for future versions of this library.
370      */
371     //todo this method is very inefficient.
getFrameAcrossTags(final String identifier)372     public List getFrameAcrossTags(final String identifier) {
373         if (identifier != null && identifier.length() > 0) {
374             final List list = new ArrayList(32);
375             Iterator iterator;
376             if (id3v1tag != null) {
377                 final ID3v2_4 id3v1 = new ID3v2_4(id3v1tag);
378                 if (id3v1.hasFrameOfType(identifier)) {
379                     iterator = id3v1.getFrameOfType(identifier);
380                     while (iterator.hasNext()) {
381                         list.add(iterator.next());
382                     }
383                 }
384             }
385             if (id3v2tag != null) {
386                 if (id3v2tag.hasFrameOfType(identifier)) {
387                     iterator = id3v2tag.getFrameOfType(identifier);
388                     while (iterator.hasNext()) {
389                         list.add(iterator.next());
390                     }
391                 }
392             }
393             if (lyrics3tag != null) {
394                 final ID3v2_4 lyrics3 = new ID3v2_4(lyrics3tag);
395                 if (lyrics3.hasFrameOfType(identifier)) {
396                     iterator = lyrics3.getFrameOfType(identifier);
397                     while (iterator.hasNext()) {
398                         list.add(iterator.next());
399                     }
400                 }
401             }
402             if (filenameTag != null) {
403                 if (filenameTag.hasFrameOfType(identifier)) {
404                     iterator = filenameTag.getFrameOfType(identifier);
405                     while (iterator.hasNext()) {
406                         list.add(iterator.next());
407                     }
408                 }
409             }
410             return list;
411         }
412         return null;
413     }
414 
getFrequency()415     public double getFrequency() {
416         return frequency;
417     }
418 
isHome()419     public boolean isHome() {
420         return home;
421     }
422 
423     /**
424      * Sets the <code>ID3v1</code> tag for this object. A new <code>ID3v1_1</code> object is created from the argument
425      * and then used here.
426      *
427      * @param mp3tag Any MP3Tag object can be used and will be converted into a new ID3v1_1 object.
428      */
setID3v1Tag(final AbstractMP3Tag mp3tag)429     public void setID3v1Tag(final AbstractMP3Tag mp3tag) {
430         id3v1tag = new ID3v1_1(mp3tag);
431     }
432 
setID3v1Tag(final ID3v1 id3v1tag)433     public void setID3v1Tag(final ID3v1 id3v1tag) {
434         this.id3v1tag = id3v1tag;
435     }
436 
437     /**
438      * Returns the <code>ID3v1</code> tag for this object.
439      *
440      * @return the <code>ID3v1</code> tag for this object
441      */
getID3v1Tag()442     public ID3v1 getID3v1Tag() {
443         return id3v1tag;
444     }
445 
446     /**
447      * Sets the <code>ID3v2</code> tag for this object. A new <code>ID3v2_4</code> object is created from the argument
448      * and then used here.
449      *
450      * @param mp3tag Any MP3Tag object can be used and will be converted into a new ID3v2_4 object.
451      */
setID3v2Tag(final AbstractMP3Tag mp3tag)452     public void setID3v2Tag(final AbstractMP3Tag mp3tag) {
453         id3v2tag = new ID3v2_4(mp3tag);
454     }
455 
setID3v2Tag(final AbstractID3v2 id3v2tag)456     public void setID3v2Tag(final AbstractID3v2 id3v2tag) {
457         this.id3v2tag = id3v2tag;
458     }
459 
460     /**
461      * Returns the <code>ID3v2</code> tag for this object.
462      *
463      * @return the <code>ID3v2</code> tag for this object
464      */
getID3v2Tag()465     public AbstractID3v2 getID3v2Tag() {
466         return id3v2tag;
467     }
468 
getLayer()469     public byte getLayer() {
470         return layer;
471     }
472 
473     /**
474      * Sets the <code>Lyrics3</code> tag for this object. A new <code>Lyrics3v2</code> object is created from the
475      * argument and then used here.
476      *
477      * @param mp3tag Any MP3Tag object can be used and will be converted into a new Lyrics3v2 object.
478      */
setLyrics3Tag(final AbstractMP3Tag mp3tag)479     public void setLyrics3Tag(final AbstractMP3Tag mp3tag) {
480         lyrics3tag = new Lyrics3v2(mp3tag);
481     }
482 
setLyrics3Tag(final AbstractLyrics3 lyrics3tag)483     public void setLyrics3Tag(final AbstractLyrics3 lyrics3tag) {
484         this.lyrics3tag = lyrics3tag;
485     }
486 
487     /**
488      * Returns the <code>ID3v1</code> tag for this object.
489      *
490      * @return the <code>ID3v1</code> tag for this object
491      */
getLyrics3Tag()492     public AbstractLyrics3 getLyrics3Tag() {
493         return lyrics3tag;
494     }
495 
getMode()496     public byte getMode() {
497         return mode;
498     }
499 
getModeExtension()500     public byte getModeExtension() {
501         return modeExtension;
502     }
503 
504     /**
505      * Returns the byte position of the first MP3 Frame that this object refers to. This is the first byte of music data
506      * and not the ID3 Tag Frame.
507      *
508      * @return the byte position of the first MP3 Frame
509      *
510      * @throws IOException           on any I/O error
511      * @throws FileNotFoundException if the file exists but is a directory rather than a regular file or cannot be
512      *                               opened for any other reason
513      */
getMp3StartByte()514     public long getMp3StartByte() throws IOException, FileNotFoundException {
515         return getMp3StartByte(mp3file);
516     }
517 
518     /**
519      * Returns the byte position of the first MP3 Frame that the <code>file</code> arguement refers to. This is the
520      * first byte of music data and not the ID3 Tag Frame.
521      *
522      * @param file MP3 file to search
523      *
524      * @return the byte position of the first MP3 Frame
525      *
526      * @throws IOException           on any I/O error
527      * @throws FileNotFoundException if the file exists but is a directory rather than a regular file or cannot be
528      *                               opened for any other reason
529      */
getMp3StartByte(final File file)530     public long getMp3StartByte(final File file) throws IOException, FileNotFoundException {
531         RandomAccessFile rfile = null;
532         long startByte = 0L;
533         try {
534             rfile = new RandomAccessFile(file, "r");
535             seekMP3Frame(rfile);
536             startByte = rfile.getFilePointer();
537         } finally {
538             if (rfile != null) {
539                 rfile.close();
540             }
541         }
542         return startByte;
543     }
544 
setMp3file(final File mp3file)545     public void setMp3file(final File mp3file) {
546         this.mp3file = mp3file;
547     }
548 
getMp3file()549     public File getMp3file() {
550         return mp3file;
551     }
552 
getMpegVersion()553     public byte getMpegVersion() {
554         return mpegVersion;
555     }
556 
isPadding()557     public boolean isPadding() {
558         return padding;
559     }
560 
isPrivacy()561     public boolean isPrivacy() {
562         return privacy;
563     }
564 
isProtection()565     public boolean isProtection() {
566         return protection;
567     }
568 
569     /**
570      * Returns true if there are any unsynchronized tags in this object. A fragment is unsynchronized if it exists in
571      * two or more tags but is not equal across all of them.
572      *
573      * @return true of any fragments are unsynchronized.
574      */
575     //todo there might be a faster way to do this, other than calling
576     //getUnsynchronizedFragments()
isUnsynchronized()577     public boolean isUnsynchronized() {
578         return getUnsynchronizedFragments().size() > 0;
579     }
580 
581     /**
582      * Returns a HashSet of unsynchronized fragments across all tags in this object. A fragment is unsynchronized if it
583      * exists in two or more tags but is not equal across all of them.
584      *
585      * @return a HashSet of unsynchronized fragments
586      */
getUnsynchronizedFragments()587     public Set getUnsynchronizedFragments() {
588         final ID3v2_4 total = new ID3v2_4(id3v2tag);
589         final Set set = new HashSet(32);
590         total.append(id3v1tag);
591         total.append(lyrics3tag);
592         total.append(filenameTag);
593         total.append(id3v2tag);
594         final ID3v2_4 id3v1 = new ID3v2_4(id3v1tag);
595         final ID3v2_4 lyrics3 = new ID3v2_4(lyrics3tag);
596         final ID3v2_4 filename = new ID3v2_4(filenameTag);
597         final AbstractID3v2 id3v2 = id3v2tag;
598         final Iterator iterator = total.iterator();
599         while (iterator.hasNext()) {
600             final AbstractID3v2Frame frame = (AbstractID3v2Frame) iterator.next();
601             final String identifier = frame.getIdentifier();
602             if (id3v2 != null) {
603                 if (id3v2.hasFrame(identifier)) {
604                     if (!id3v2.getFrame(identifier).isSubsetOf(frame)) {
605                         set.add(identifier);
606                     }
607                 }
608             }
609             if (id3v1.hasFrame(identifier)) {
610                 if (!id3v1.getFrame(identifier).isSubsetOf(frame)) {
611                     set.add(identifier);
612                 }
613             }
614             if (lyrics3.hasFrame(identifier)) {
615                 if (!lyrics3.getFrame(identifier).isSubsetOf(frame)) {
616                     set.add(identifier);
617                 }
618             }
619             if (filename.hasFrame(identifier)) {
620                 if (!filename.getFrame(identifier).isSubsetOf(frame)) {
621                     set.add(identifier);
622                 }
623             }
624         }
625         return set;
626     }
627 
setVariableBitRate(final boolean variableBitRate)628     public void setVariableBitRate(final boolean variableBitRate) {
629         this.variableBitRate = variableBitRate;
630     }
631 
isVariableBitRate()632     public boolean isVariableBitRate() {
633         return variableBitRate;
634     }
635 
636     /**
637      * Adjust the lenght of the ID3v2 padding at the beginning of the MP3 file referred to in this object. The ID3v2
638      * size will be calculated, then a new file will be created with enough size to fit the <code>ID3v2</code> tag in
639      * this object. The old file will be deleted, and the new file renamed. All parameters will be taken from the
640      * <code>tagOptions</code> object.
641      *
642      * @throws FileNotFoundException if the file exists but is a directory rather than a regular file or cannot be
643      *                               opened for any other reason
644      * @throws IOException           on any I/O error
645      * @throws TagException          on any exception generated by this library.
646      */
adjustID3v2Padding()647     public boolean adjustID3v2Padding() throws FileNotFoundException, IOException, TagException {
648         return adjustID3v2Padding(TagOptionSingleton.getInstance().getId3v2PaddingSize(),
649                                   TagOptionSingleton.getInstance().isId3v2PaddingWillShorten(),
650                                   TagOptionSingleton.getInstance().isId3v2PaddingCopyTag(),
651                                   mp3file);
652     }
653 
654     /**
655      * Adjust the length of the ID3v2 padding at the beginning of the MP3 file this object refers to. The ID3v2 size
656      * will be calculated, then a new file will be created with enough size to fit the <code>ID3v2</code> tag. The old
657      * file will be deleted, and the new file renamed.
658      *
659      * @param paddingSize  Initial padding size. This size is doubled until the ID3v2 tag will fit.
660      * @param willShorten  if the newly calculated padding size is less than the padding length of the file, then news
661      *                     the new shorter padding size if this is true.
662      * @param copyID3v2Tag if true, write the <code>ID3v2</code> tag of this object into the file
663      *
664      * @throws FileNotFoundException if the file exists but is a directory rather than a regular file or cannot be
665      *                               opened for any other reason
666      * @throws IOException           on any I/O error
667      * @throws TagException          on any exception generated by this library.
668      */
adjustID3v2Padding(final int paddingSize, final boolean willShorten, final boolean copyID3v2Tag)669     public boolean adjustID3v2Padding(final int paddingSize, final boolean willShorten, final boolean copyID3v2Tag)
670             throws FileNotFoundException, IOException, TagException {
671         return adjustID3v2Padding(paddingSize, willShorten, copyID3v2Tag, mp3file);
672     }
673 
674     /**
675      * Adjust the length of the ID3v2 padding at the beginning of the MP3 file this object refers to. The ID3v2 size
676      * will be calculated, then a new file will be created with enough size to fit the <code>ID3v2</code> tag. The old
677      * file will be deleted, and the new file renamed.
678      *
679      * @param paddingSize  Initial padding size. This size is doubled until the ID3v2 tag will fit. A paddingSize of
680      *                     zero will create a padding length exactly equal to the tag size.
681      * @param willShorten  Shorten the padding size by halves if the ID3v2 tag will fit
682      * @param copyID3v2Tag if true, write the <code>ID3v2</code> tag of this object into the file
683      * @param file         The file to adjust the padding length of
684      *
685      * @throws FileNotFoundException if the file exists but is a directory rather than a regular file or cannot be
686      *                               opened for any other reason
687      * @throws IOException           on any I/O error
688      * @throws TagException          on any exception generated by this library.
689      */
adjustID3v2Padding(final int paddingSize, final boolean willShorten, final boolean copyID3v2Tag, final File file)690     public boolean adjustID3v2Padding(final int paddingSize,
691                                       final boolean willShorten,
692                                       final boolean copyID3v2Tag,
693                                       final File file) throws FileNotFoundException, IOException, TagException {
694         int id3v2TagSize = 0;
695         final long mp3start = getMp3StartByte(file);
696         long newPaddingSize = paddingSize;
697         FileOutputStream outStream = null;
698         FileInputStream inStream = null;
699         File backupFile = null;
700         File paddedFile = null;
701         if (newPaddingSize < 0) {
702             throw new TagException("Invalid paddingSize: " + newPaddingSize);
703         }
704         if (hasID3v2Tag()) {
705             id3v2TagSize = getID3v2Tag().getSize();
706         }
707         if (newPaddingSize != 0) {
708             // double padding size until it's large enough
709             while (newPaddingSize < id3v2TagSize) {
710                 newPaddingSize *= TagOptionSingleton.getInstance().getId3v2PaddingMultiplier();
711             }
712         }
713         if (newPaddingSize < mp3start && !willShorten) {
714             return false;
715         }
716         if (newPaddingSize == mp3start) {
717             return false;
718         }
719         try {
720             // we first copy everything to a new file, then replace the original
721             paddedFile = File.createTempFile("temp", ".mp3", file.getParentFile());
722             outStream = new FileOutputStream(paddedFile);
723             inStream = new FileInputStream(file);
724             byte[] buffer;
725             if (copyID3v2Tag == true) {
726                 // paddingSize < mp3start && willshorten == false
727                 // was already checked for outside of the try block.
728                 if ((newPaddingSize < mp3start) && willShorten) {
729                     // copy the current tag
730                     buffer = new byte[(int) newPaddingSize];
731                     inStream.read(buffer, 0, buffer.length);
732                     outStream.write(buffer, 0, buffer.length);
733                     buffer = new byte[(int) (mp3start - newPaddingSize)];
734 
735                     // skip the rest of the tag that didn't fit
736                     inStream.read(buffer, 0, buffer.length);
737 
738                     // paddingSize > mp3start
739                 } else {
740                     // copy the current tag
741                     buffer = new byte[(int) mp3start];
742                     inStream.read(buffer, 0, buffer.length);
743                     outStream.write(buffer, 0, buffer.length);
744 
745                     // add zeros for the rest of the padding
746                     if (newPaddingSize - mp3start > 0) {
747                         buffer = new byte[(int) (newPaddingSize - mp3start)];
748                         outStream.write(buffer, 0, buffer.length);
749                     }
750                 }
751             } else {
752                 buffer = new byte[(int) newPaddingSize];
753 
754                 // skip the tag
755                 inStream.skip(mp3start);
756 
757                 // write zeros for the tag
758                 outStream.write(buffer, 0, buffer.length);
759             }
760             buffer = new byte[1024];
761             int b = inStream.read(buffer, 0, buffer.length);
762             while (b == 1024) {
763                 outStream.write(buffer, 0, buffer.length);
764                 b = inStream.read(buffer, 0, buffer.length);
765             }
766             if (b != -1) {
767                 outStream.write(buffer, 0, b);
768             }
769             backupFile = new File(file.getParentFile(), TagUtility.appendBeforeExtension(file.getName(), ".original"));
770             TagUtility.copyFile(file, backupFile);
771             if (backupFile.exists()) {
772                 backupFile.setLastModified(file.lastModified());
773             } else {
774                 return false;
775             }
776             TagUtility.copyFile(paddedFile, file);
777             return true;
778         } finally {
779             if (inStream != null) {
780                 inStream.getFD().sync();
781                 inStream.close();
782             }
783             if (outStream != null) {
784                 outStream.getFD().sync();
785                 outStream.close();
786             }
787             if ((backupFile != null) &&
788                 (TagOptionSingleton.getInstance().isOriginalSavedAfterAdjustingID3v2Padding() == false)) {
789                 backupFile.delete();
790             }
791             if (paddedFile != null) {
792                 paddedFile.delete();
793             }
794         }
795     }
796 
delete(final AbstractMP3Tag mp3tag)797     public void delete(final AbstractMP3Tag mp3tag) throws FileNotFoundException, IOException {
798         mp3tag.delete(new RandomAccessFile(mp3file, "rw"));
799     }
800 
801     /**
802      * Returns true if this object contains an filename pseudo-tag
803      *
804      * @return true if this object contains an filename pseudo-tag
805      */
hasFilenameTag()806     public boolean hasFilenameTag() {
807         return (filenameTag != null);
808     }
809 
810     /**
811      * Returns true if this object contains an <code>Id3v1</code> tag
812      *
813      * @return true if this object contains an <code>Id3v1</code> tag
814      */
hasID3v1Tag()815     public boolean hasID3v1Tag() {
816         return (id3v1tag != null);
817     }
818 
819     /**
820      * Returns true if this object contains an <code>Id3v2</code> tag
821      *
822      * @return true if this object contains an <code>Id3v2</code> tag
823      */
hasID3v2Tag()824     public boolean hasID3v2Tag() {
825         return (id3v2tag != null);
826     }
827 
828     /**
829      * Returns true if this object contains an <code>Lyrics3</code> tag
830      *
831      * @return true if this object contains an <code>Lyrics3</code> tag
832      */
hasLyrics3Tag()833     public boolean hasLyrics3Tag() {
834         return (lyrics3tag != null);
835     }
836 
837     /**
838      * Saves the tags in this object to the file referred to by this object. It will be saved as
839      * TagConstants.MP3_FILE_SAVE_WRITE
840      *
841      * @throws IOException  on any I/O error
842      * @throws TagException on any exception generated by this library.
843      */
save()844     public void save() throws IOException, TagException {
845         save(mp3file, TagOptionSingleton.getInstance().getDefaultSaveMode());
846     }
847 
848     /**
849      * Saves the tags in this object to the file referred to by this object. It will be saved as
850      * TagConstants.MP3_FILE_SAVE_WRITE
851      *
852      * @param saveMode write, overwrite, or append. Defined as <code>TagConstants.MP3_FILE_SAVE_WRITE
853      *                 TagConstants.MP3_FILE_SAVE_OVERWRITE TagConstants.MP3_FILE_SAVE_APPEND </code>
854      *
855      * @throws IOException  on any I/O error
856      * @throws TagException on any exception generated by this library.
857      */
save(final int saveMode)858     public void save(final int saveMode) throws IOException, TagException {
859         save(mp3file, saveMode);
860     }
861 
862     /**
863      * Saves the tags in this object to the file argument. It will be saved as TagConstants.MP3_FILE_SAVE_WRITE
864      *
865      * @param filename file to save the this object's tags to
866      *
867      * @throws IOException  on any I/O error
868      * @throws TagException on any exception generated by this library.
869      */
save(final String filename)870     public void save(final String filename) throws IOException, TagException {
871         save(new File(filename), TagOptionSingleton.getInstance().getDefaultSaveMode());
872     }
873 
874     /**
875      * Saves the tags in this object to the file argument. It will be saved as TagConstants.MP3_FILE_SAVE_WRITE
876      *
877      * @param file file to save the this object's tags to
878      *
879      * @throws IOException  on any I/O error
880      * @throws TagException on any exception generated by this library.
881      */
save(final File file)882     public void save(final File file) throws IOException, TagException {
883         save(file, TagOptionSingleton.getInstance().getDefaultSaveMode());
884     }
885 
886     /**
887      * Saves the tags in this object to the file argument. It will be saved as TagConstants.MP3_FILE_SAVE_WRITE
888      *
889      * @param filename file to save the this object's tags to
890      * @param saveMode write, overwrite, or append. Defined as <code>TagConstants.MP3_FILE_SAVE_WRITE
891      *                 TagConstants.MP3_FILE_SAVE_OVERWRITE TagConstants.MP3_FILE_SAVE_APPEND </code>
892      *
893      * @throws IOException  on any I/O error
894      * @throws TagException on any exception generated by this library.
895      */
save(final String filename, final int saveMode)896     public void save(final String filename, final int saveMode) throws IOException, TagException {
897         save(new File(filename), saveMode);
898     }
899 
900     /**
901      * Saves the tags in this object to the file argument. It will be saved as TagConstants.MP3_FILE_SAVE_WRITE
902      *
903      * @param file     file to save the this object's tags to
904      * @param saveMode write, overwrite, or append. Defined as <code>TagConstants.MP3_FILE_SAVE_WRITE
905      *                 TagConstants.MP3_FILE_SAVE_OVERWRITE TagConstants.MP3_FILE_SAVE_APPEND </code>
906      *
907      * @throws IOException  on any I/O error
908      * @throws TagException on any exception generated by this library.
909      */
save(final File file, final int saveMode)910     public void save(final File file, final int saveMode) throws IOException, TagException {
911         if ((saveMode < TagConstant.MP3_FILE_SAVE_FIRST) || (saveMode > TagConstant.MP3_FILE_SAVE_LAST)) {
912             throw new TagException("Invalid Save Mode");
913         }
914         RandomAccessFile rfile = null;
915         try {
916             if (id3v2tag != null) {
917                 adjustID3v2Padding(TagOptionSingleton.getInstance().getId3v2PaddingSize(),
918                                    TagOptionSingleton.getInstance().isId3v2PaddingWillShorten(),
919                                    TagOptionSingleton.getInstance().isId3v2PaddingCopyTag(),
920                                    file);
921             }
922 
923             // we can't put these two if's together because
924             // adjustid3v2padding needs all handles on the file closed;
925             rfile = new RandomAccessFile(file, "rw");
926             if (TagOptionSingleton.getInstance().isId3v2Save()) {
927                 if (id3v2tag == null) {
928                     if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
929                         (new ID3v2_4()).delete(rfile);
930                     }
931                 } else {
932                     if (saveMode == TagConstant.MP3_FILE_SAVE_WRITE) {
933                         id3v2tag.write(rfile);
934                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_APPEND) {
935                         id3v2tag.append(rfile);
936                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
937                         id3v2tag.overwrite(rfile);
938                     }
939                 }
940             }
941             if (TagOptionSingleton.getInstance().isLyrics3Save()) {
942                 if (lyrics3tag == null) {
943                     if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
944                         (new Lyrics3v2()).delete(rfile);
945                     }
946                 } else {
947                     if (saveMode == TagConstant.MP3_FILE_SAVE_WRITE) {
948                         lyrics3tag.write(rfile);
949                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_APPEND) {
950                         lyrics3tag.append(rfile);
951                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
952                         lyrics3tag.overwrite(rfile);
953                     }
954                 }
955             }
956             if (TagOptionSingleton.getInstance().isId3v1Save()) {
957                 if (id3v1tag == null) {
958                     if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
959                         (new ID3v1()).delete(rfile);
960                     }
961                 } else {
962                     if (saveMode == TagConstant.MP3_FILE_SAVE_WRITE) {
963                         id3v1tag.write(rfile);
964                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_APPEND) {
965                         id3v1tag.append(rfile);
966                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
967                         id3v1tag.overwrite(rfile);
968                     }
969                 }
970             }
971             if (TagOptionSingleton.getInstance().isFilenameTagSave()) {
972                 if (filenameTag != null) {
973                     if (saveMode == TagConstant.MP3_FILE_SAVE_WRITE) {
974                         filenameTag.write(rfile);
975                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_APPEND) {
976                         filenameTag.append(rfile);
977                     } else if (saveMode == TagConstant.MP3_FILE_SAVE_OVERWRITE) {
978                         filenameTag.overwrite(rfile);
979                     }
980                 }
981             }
982         } finally {
983             if (rfile != null) {
984                 rfile.close();
985             }
986         }
987     }
988 
989     /**
990      * Returns true if the first MP3 frame can be found for the MP3 file that this object refers to. This is the first
991      * byte of music data and not the ID3 Tag Frame.
992      *
993      * @return true if the first MP3 frame can be found
994      *
995      * @throws IOException on any I/O error
996      */
seekMP3Frame()997     public boolean seekMP3Frame() throws IOException {
998         RandomAccessFile rfile = null;
999         boolean found = false;
1000         try {
1001             rfile = new RandomAccessFile(mp3file, "r");
1002             found = seekMP3Frame(rfile);
1003         } finally {
1004             if (rfile != null) {
1005                 rfile.close();
1006             }
1007         }
1008         return found;
1009     }
1010 
1011     /**
1012      * Returns true if the first MP3 frame can be found for the MP3 file argument. It tries to sync as many frame as
1013      * defined in <code>TagOptions.getNumberMP3SyncFrame</code> This is the first byte of music data and not the ID3 Tag
1014      * Frame.
1015      *
1016      * @param seekFile MP3 file to seek
1017      *
1018      * @return true if the first MP3 frame can be found
1019      *
1020      * @throws IOException on any I/O error
1021      */
seekMP3Frame(final RandomAccessFile seekFile)1022     public boolean seekMP3Frame(final RandomAccessFile seekFile) throws IOException {
1023         boolean syncFound = false;
1024         byte first;
1025         byte second;
1026         long filePointer = 1;
1027         variableBitRate = false;
1028         try {
1029             seekFile.seek(0);
1030             do {
1031                 first = seekFile.readByte();
1032                 if (first == (byte) 0xFF) {
1033                     filePointer = seekFile.getFilePointer();
1034                     second = (byte) (seekFile.readByte() & (byte) 0xE0);
1035                     if (second == (byte) 0xE0) {
1036                         seekFile.seek(filePointer - 1);
1037 
1038                         // seek the next frames, recursively
1039                         syncFound = seekNextMP3Frame(seekFile,
1040                                                      TagOptionSingleton.getInstance().getNumberMP3SyncFrame());
1041                     }
1042                     seekFile.seek(filePointer);
1043                 }
1044             } while (syncFound == false);
1045             seekFile.seek(filePointer - 1);
1046         } catch (EOFException ex) {
1047             syncFound = false;
1048         } catch (IOException ex) {
1049             throw ex;
1050         }
1051         return syncFound;
1052     }
1053 
1054     /**
1055      * Returns the MP3 frame size for the file this object refers to. It assumes that <code>seekNextMP3Frame</code> has
1056      * already been called.
1057      *
1058      * @return MP3 Frame size in bytes.
1059      */
getFrameSize()1060     private int getFrameSize() {
1061         if (frequency == 0) {
1062             return 0;
1063         }
1064         final int size;
1065         final int paddingByte = padding ? 1 : 0;
1066         if (layer == 3) { // Layer I
1067             size = (int) ((((12 * bitRate) / frequency) + paddingByte) * 4);
1068         } else {
1069             size = (int) (((144 * bitRate) / frequency) + paddingByte);
1070         }
1071         return size;
1072     }
1073 
1074     /**
1075      * Reads the mp3 frame header from the current posiiton in the file and sets this object's private variables to what
1076      * is found. It assumes the <code>RandomAccessFile</code> is already pointing to a valid MP3 Frame.
1077      *
1078      * @param file File to read frame header
1079      *
1080      * @throws IOException          on any I/O error
1081      * @throws TagNotFoundException if MP3 Frame sync bites were not immediately found
1082      * @throws InvalidTagException  if any of the header values are invlaid
1083      */
readFrameHeader(final RandomAccessFile file)1084     private void readFrameHeader(final RandomAccessFile file)
1085             throws IOException, TagNotFoundException, InvalidTagException {
1086         final byte[] buffer = new byte[4];
1087         file.read(buffer);
1088 
1089         // sync
1090         if ((buffer[0] != (byte) 0xFF) || ((buffer[1] & (byte) 0xE0) != (byte) 0xE0)) {
1091             throw new TagNotFoundException("MP3 Frame sync bits not found");
1092         }
1093         mpegVersion = (byte) ((buffer[1] & TagConstant.MASK_MP3_VERSION) >> 3);
1094         layer = (byte) ((buffer[1] & TagConstant.MASK_MP3_LAYER) >> 1);
1095         protection = (buffer[1] & TagConstant.MASK_MP3_PROTECTION) != 1;
1096         final int bitRateValue = (buffer[2] & TagConstant.MASK_MP3_BITRATE) |
1097                                  (buffer[1] & TagConstant.MASK_MP3_ID) |
1098                                  (buffer[1] & TagConstant.MASK_MP3_LAYER);
1099         final Long object = (Long) TagConstant.bitrate.get(new Long(bitRateValue));
1100         if (object != null) {
1101             if (object.longValue() != bitRate) {
1102                 variableBitRate = true;
1103             }
1104             bitRate = object.intValue();
1105         } else {
1106             throw new InvalidTagException("Invalid bit rate");
1107         }
1108         final int frequencyValue = (buffer[2] & TagConstant.MASK_MP3_FREQUENCY) >>> 2;
1109         if (mpegVersion == 3) { // Version 1.0
1110             switch (frequencyValue) {
1111                 case 0:
1112                     frequency = 44.1;
1113                     break;
1114                 case 1:
1115                     frequency = 48.0;
1116                     break;
1117                 case 2:
1118                     frequency = 32.0;
1119                     break;
1120             }
1121         } else if (mpegVersion == 2) { // Version 2.0
1122             switch (frequencyValue) {
1123                 case 0:
1124                     frequency = 22.05;
1125                     break;
1126                 case 1:
1127                     frequency = 24.00;
1128                     break;
1129                 case 2:
1130                     frequency = 16.00;
1131                     break;
1132             }
1133         } else if (mpegVersion == 00) { // Version 2.5
1134             switch (frequencyValue) {
1135                 case 0:
1136                     frequency = 11.025;
1137                     break;
1138                 case 1:
1139                     frequency = 12.00;
1140                     break;
1141                 case 2:
1142                     frequency = 8.00;
1143                     break;
1144             }
1145         } else {
1146             throw new InvalidTagException("Invalid MPEG version");
1147         }
1148         padding = (buffer[2] & TagConstant.MASK_MP3_PADDING) != 0;
1149         privacy = (buffer[2] & TagConstant.MASK_MP3_PRIVACY) != 0;
1150         mode = (byte) ((buffer[3] & TagConstant.MASK_MP3_MODE) >> 6);
1151         modeExtension = (byte) ((buffer[3] & TagConstant.MASK_MP3_MODE_EXTENSION) >> 4);
1152         copyProtected = (buffer[3] & TagConstant.MASK_MP3_COPY) != 0;
1153         home = (buffer[3] & TagConstant.MASK_MP3_HOME) != 0;
1154         emphasis = (byte) ((buffer[3] & TagConstant.MASK_MP3_EMPHASIS));
1155     }
1156 
1157     /**
1158      * Returns true if the first MP3 frame can be found for the MP3 file argument. It is recursive and called by
1159      * seekMP3Frame. This is the first byte of music data and not the ID3 Tag Frame.
1160      *
1161      * @param file       MP3 file to seek
1162      * @param iterations recursive counter
1163      *
1164      * @return true if the first MP3 frame can be found
1165      *
1166      * @throws IOException on any I/O error
1167      */
seekNextMP3Frame(final RandomAccessFile file, final int iterations)1168     private boolean seekNextMP3Frame(final RandomAccessFile file, final int iterations) throws IOException {
1169         final boolean syncFound;
1170         final byte[] buffer;
1171         final byte first;
1172         final byte second;
1173         final long filePointer;
1174         if (iterations == 0) {
1175             syncFound = true;
1176         } else {
1177             try {
1178                 readFrameHeader(file);
1179             } catch (TagException ex) {
1180                 return false;
1181             }
1182             final int size = getFrameSize();
1183             if ((size <= 0) || (size > file.length())) {
1184                 return false;
1185             }
1186             buffer = new byte[size - 4];
1187             file.read(buffer);
1188             filePointer = file.getFilePointer();
1189             first = file.readByte();
1190             if (first == (byte) 0xFF) {
1191                 second = (byte) (file.readByte() & (byte) 0xE0);
1192                 if (second == (byte) 0xE0) {
1193                     file.seek(filePointer);
1194 
1195                     // recursively find the next frames
1196                     syncFound = seekNextMP3Frame(file, iterations - 1);
1197                 } else {
1198                     syncFound = false;
1199                 }
1200             } else {
1201                 syncFound = false;
1202             }
1203         }
1204         return syncFound;
1205     }
1206 }