Coverage Report - org.jaudiotagger.audio.mp3.MP3File
 
Classes in this File Line Coverage Branch Coverage Complexity
MP3File
67%
159/235
63%
46/72
2.8
 
 1  
 /**
 2  
  *  @author : Paul Taylor
 3  
  *  @author : Eric Farng
 4  
  *
 5  
  *  Version @version:$Id: MP3File.java 836 2009-11-12 15:44:07Z paultaylor $
 6  
  *
 7  
  *  MusicTag Copyright (C)2003,2004
 8  
  *
 9  
  *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 10  
  *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
 11  
  *  or (at your option) any later version.
 12  
  *
 13  
  *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 14  
  *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 15  
  *  See the GNU Lesser General Public License for more details.
 16  
  *
 17  
  *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
 18  
  *  you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
 19  
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 20  
  *
 21  
  */
 22  
 package org.jaudiotagger.audio.mp3;
 23  
 
 24  
 import org.jaudiotagger.audio.AudioFile;
 25  
 import org.jaudiotagger.audio.exceptions.CannotWriteException;
 26  
 import org.jaudiotagger.audio.exceptions.InvalidAudioFrameException;
 27  
 import org.jaudiotagger.audio.exceptions.ReadOnlyFileException;
 28  
 import org.jaudiotagger.logging.*;
 29  
 import org.jaudiotagger.tag.Tag;
 30  
 import org.jaudiotagger.tag.TagException;
 31  
 import org.jaudiotagger.tag.TagNotFoundException;
 32  
 import org.jaudiotagger.tag.TagOptionSingleton;
 33  
 import org.jaudiotagger.tag.id3.*;
 34  
 import org.jaudiotagger.tag.lyrics3.AbstractLyrics3;
 35  
 
 36  
 import java.io.*;
 37  
 import java.nio.ByteBuffer;
 38  
 import java.nio.channels.FileChannel;
 39  
 import java.util.logging.Level;
 40  
 
 41  
 /**
 42  
  * This class represents a physical MP3 File
 43  
  */
 44  
 public class MP3File extends AudioFile
 45  
 {
 46  
     private static final int MINIMUM_FILESIZE = 150;
 47  
 
 48  
     protected static AbstractTagDisplayFormatter tagFormatter;
 49  
 
 50  
     /**
 51  
      * the ID3v2 tag that this file contains.
 52  
      */
 53  2061
     private AbstractID3v2Tag id3v2tag = null;
 54  
 
 55  
     /**
 56  
      * Representation of the idv2 tag as a idv24 tag
 57  
      */
 58  2061
     private ID3v24Tag id3v2Asv24tag = null;
 59  
 
 60  
     /**
 61  
      * The Lyrics3 tag that this file contains.
 62  
      */
 63  2061
     private AbstractLyrics3 lyrics3tag = null;
 64  
 
 65  
 
 66  
     /**
 67  
      * The ID3v1 tag that this file contains.
 68  
      */
 69  2061
     private ID3v1Tag id3v1tag = null;
 70  
 
 71  
     /**
 72  
      * Creates a new empty MP3File datatype that is not associated with a
 73  
      * specific file.
 74  
      */
 75  
     public MP3File()
 76  0
     {
 77  0
     }
 78  
 
 79  
     /**
 80  
      * Creates a new MP3File datatype and parse the tag from the given filename.
 81  
      *
 82  
      * @param filename MP3 file
 83  
      * @throws IOException  on any I/O error
 84  
      * @throws TagException on any exception generated by this library.
 85  
      * @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
 86  
      * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
 87  
      */
 88  
     public MP3File(String filename) throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException
 89  
     {
 90  0
         this(new File(filename));
 91  0
     }
 92  
 
 93  
 
 94  
     /* Load ID3V1tag if exists */
 95  
     public static final int LOAD_IDV1TAG = 2;
 96  
 
 97  
     /* Load ID3V2tag if exists */
 98  
     public static final int LOAD_IDV2TAG = 4;
 99  
 
 100  
     /**
 101  
      * This option is currently ignored
 102  
      */
 103  
     public static final int LOAD_LYRICS3 = 8;
 104  
 
 105  
     public static final int LOAD_ALL = LOAD_IDV1TAG | LOAD_IDV2TAG | LOAD_LYRICS3;
 106  
 
 107  
     /**
 108  
      * Creates a new MP3File datatype and parse the tag from the given file
 109  
      * Object, files must be writable to use this constructor.
 110  
      *
 111  
      * @param file        MP3 file
 112  
      * @param loadOptions decide what tags to load
 113  
      * @throws IOException  on any I/O error
 114  
      * @throws TagException on any exception generated by this library.
 115  
      * @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
 116  
      * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
 117  
      */
 118  
     public MP3File(File file, int loadOptions) throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException
 119  
     {
 120  1416
         this(file, loadOptions, false);
 121  1400
     }
 122  
 
 123  
     /**
 124  
      * Read v1 tag
 125  
      *
 126  
      * @param file
 127  
      * @param newFile
 128  
      * @param loadOptions
 129  
      * @throws IOException
 130  
      */
 131  
     private void readV1Tag(File file, RandomAccessFile newFile, int loadOptions) throws IOException
 132  
     {
 133  2045
         if ((loadOptions & LOAD_IDV1TAG) != 0)
 134  
         {
 135  2045
             logger.finer("Attempting to read id3v1tags");
 136  
             try
 137  
             {
 138  2045
                 id3v1tag = new ID3v11Tag(newFile, file.getName());
 139  
             }
 140  2012
             catch (TagNotFoundException ex)
 141  
             {
 142  2012
                 logger.info("No ids3v11 tag found");
 143  33
             }
 144  
 
 145  
             try
 146  
             {
 147  2045
                 if (id3v1tag == null)
 148  
                 {
 149  2012
                     id3v1tag = new ID3v1Tag(newFile, file.getName());
 150  
                 }
 151  
             }
 152  1730
             catch (TagNotFoundException ex)
 153  
             {
 154  1730
                 logger.info("No id3v1 tag found");
 155  315
             }
 156  
         }
 157  2045
     }
 158  
 
 159  
     /**
 160  
      * Read V2tag if exists
 161  
      * <p/>
 162  
      * TODO:shouldnt we be handing TagExceptions:when will they be thrown
 163  
      *
 164  
      * @param file
 165  
      * @param loadOptions
 166  
      * @throws IOException
 167  
      * @throws TagException
 168  
      */
 169  
     private void readV2Tag(File file, int loadOptions) throws IOException, TagException
 170  
     {
 171  
         //We know where the Actual Audio starts so load all the file from start to that point into
 172  
         //a buffer then we can read the IDv2 information without needing any more file I/O
 173  2045
         int startByte = (int) ((MP3AudioHeader) audioHeader).getMp3StartByte();
 174  2045
         if (startByte >= AbstractID3v2Tag.TAG_HEADER_LENGTH)
 175  
         {
 176  1404
             logger.finer("Attempting to read id3v2tags");
 177  1404
             FileInputStream fis = null;
 178  1404
             FileChannel fc = null;
 179  1404
             ByteBuffer bb = null;
 180  
             try
 181  
             {
 182  1404
                 fis = new FileInputStream(file);
 183  1404
                 fc = fis.getChannel();
 184  
                 //Read into Byte Buffer
 185  1404
                 bb = ByteBuffer.allocate(startByte);
 186  1404
                 fc.read(bb);
 187  1404
             }
 188  
             finally
 189  
             {
 190  0
                 if (fc != null)
 191  
                 {
 192  1404
                     fc.close();
 193  
                 }
 194  
 
 195  1404
                 if (fis != null)
 196  
                 {
 197  1404
                     fis.close();
 198  
                 }
 199  1404
             }
 200  
 
 201  1404
             bb.rewind();
 202  
 
 203  1404
             if ((loadOptions & LOAD_IDV2TAG) != 0)
 204  
             {
 205  1404
                 logger.info("Attempting to read id3v2tags");
 206  
                 try
 207  
                 {
 208  1404
                     this.setID3v2Tag(new ID3v24Tag(bb, file.getName()));
 209  
                 }
 210  819
                 catch (TagNotFoundException ex)
 211  
                 {
 212  819
                     logger.info("No id3v24 tag found");
 213  585
                 }
 214  
 
 215  
                 try
 216  
                 {
 217  1404
                     if (id3v2tag == null)
 218  
                     {
 219  819
                         this.setID3v2Tag(new ID3v23Tag(bb, file.getName()));
 220  
                     }
 221  
                 }
 222  278
                 catch (TagNotFoundException ex)
 223  
                 {
 224  278
                     logger.info("No id3v23 tag found");
 225  1126
                 }
 226  
 
 227  
                 try
 228  
                 {
 229  1404
                     if (id3v2tag == null)
 230  
                     {
 231  278
                         this.setID3v2Tag(new ID3v22Tag(bb, file.getName()));
 232  
                     }
 233  
                 }
 234  12
                 catch (TagNotFoundException ex)
 235  
                 {
 236  12
                     logger.info("No id3v22 tag found");
 237  1392
                 }
 238  
             }
 239  1404
         }
 240  
         else
 241  
         {
 242  641
             logger.info("Not enough room for valid id3v2 tag:" + startByte);
 243  
         }
 244  2045
     }
 245  
 
 246  
     /**
 247  
      * Read lyrics3 Tag
 248  
      * <p/>
 249  
      * TODO:not working
 250  
      *
 251  
      * @param file
 252  
      * @param newFile
 253  
      * @param loadOptions
 254  
      * @throws IOException
 255  
      */
 256  
     private void readLyrics3Tag(File file, RandomAccessFile newFile, int loadOptions) throws IOException
 257  
     {
 258  
         /*if ((loadOptions & LOAD_LYRICS3) != 0)
 259  
         {
 260  
             try
 261  
             {
 262  
                 lyrics3tag = new Lyrics3v2(newFile);
 263  
             }
 264  
             catch (TagNotFoundException ex)
 265  
             {
 266  
             }
 267  
             try
 268  
             {
 269  
                 if (lyrics3tag == null)
 270  
                 {
 271  
                     lyrics3tag = new Lyrics3v1(newFile);
 272  
                 }
 273  
             }
 274  
             catch (TagNotFoundException ex)
 275  
             {
 276  
             }
 277  
         }
 278  
         */
 279  0
     }
 280  
 
 281  
     /**
 282  
      * Regets the audio header starting from start of file, and write appropriate logging to indicate
 283  
      * potential problem to user.
 284  
      *
 285  
      * @param startByte
 286  
      * @param currentHeader
 287  
      * @return
 288  
      * @throws IOException
 289  
      * @throws InvalidAudioFrameException
 290  
      */
 291  
     private MP3AudioHeader checkAudioStart(long startByte, MP3AudioHeader currentHeader) throws IOException, InvalidAudioFrameException
 292  
     {
 293  
         MP3AudioHeader newAudioHeader;
 294  
         MP3AudioHeader nextAudioHeader;
 295  
 
 296  47
         logger.warning(ErrorMessage.MP3_ID3TAG_LENGTH_INCORRECT.getMsg(file.getPath(), Hex.asHex(startByte), Hex.asHex(currentHeader.getMp3StartByte())));
 297  
 
 298  
         //because we cant agree on start location we reread the audioheader from the start of the file, at least
 299  
         //this way we cant overwrite the audio although we might overwrite part of the tag if we write this file
 300  
         //back later
 301  47
         newAudioHeader = new MP3AudioHeader(file, 0);
 302  47
         logger.info("Checking from start:" + newAudioHeader);
 303  
 
 304  47
         if (currentHeader.getMp3StartByte() == newAudioHeader.getMp3StartByte())
 305  
         {
 306  
             //Although the tag size appears to be incorrect at least we have found the same location for the start
 307  
             //of audio whether we start searching from start of file or at the end of the alleged of file so no real
 308  
             //problem
 309  46
             logger.info(ErrorMessage.MP3_START_OF_AUDIO_CONFIRMED.getMsg(file.getPath(), Hex.asHex(newAudioHeader.getMp3StartByte())));
 310  46
             return currentHeader;
 311  
         }
 312  
         else
 313  
         {
 314  
             //We get a different value if read from start, can't guarantee 100% correct lets do some more checks
 315  1
             logger.info((ErrorMessage.MP3_RECALCULATED_POSSIBLE_START_OF_MP3_AUDIO.getMsg(file.getPath(), Hex.asHex(newAudioHeader.getMp3StartByte()))));
 316  
 
 317  
             //Frame counts dont match so eiither currentHeader or newAudioHeader isnt really audio header
 318  1
             if (currentHeader.getNumberOfFrames() != newAudioHeader.getNumberOfFrames())
 319  
             {
 320  
                 //Skip to the next header (header 2, counting from start of file)
 321  1
                 nextAudioHeader = new MP3AudioHeader(file, newAudioHeader.getMp3StartByte() + newAudioHeader.mp3FrameHeader.getFrameLength());
 322  1
                 logger.info("Checking next:" + nextAudioHeader);
 323  
 
 324  
                 //It matches the header we found when doing the original search from after the ID3Tag therefore it
 325  
                 //seems that newAudioHeader was a false match and the original header was correct
 326  1
                 if (nextAudioHeader.getMp3StartByte() == currentHeader.getMp3StartByte())
 327  
                 {
 328  1
                     logger.warning((ErrorMessage.MP3_START_OF_AUDIO_CONFIRMED.getMsg(file.getPath(), Hex.asHex(currentHeader.getMp3StartByte()))));
 329  1
                     return currentHeader;
 330  
                 }
 331  
                 //it matches the header we just found so lends weight to the fact that the audio does indeed start at new header
 332  0
                 else if (nextAudioHeader.getNumberOfFrames() == newAudioHeader.getNumberOfFrames())
 333  
                 {
 334  0
                     logger.warning((ErrorMessage.MP3_RECALCULATED_START_OF_MP3_AUDIO.getMsg(file.getPath(), Hex.asHex(newAudioHeader.getMp3StartByte()))));
 335  0
                     return newAudioHeader;
 336  
                 }
 337  
                 ///Not sure but safer to return earlier audio beause stops jaudiotagger overwriting when writing tag
 338  
                 else
 339  
                 {
 340  0
                     logger.warning((ErrorMessage.MP3_RECALCULATED_START_OF_MP3_AUDIO.getMsg(file.getPath(), Hex.asHex(newAudioHeader.getMp3StartByte()))));
 341  0
                     return newAudioHeader;
 342  
                 }
 343  
             }
 344  
             //Same frame count so probably both audio headers with newAudioHeader being the firt one
 345  
             else
 346  
             {
 347  0
                 logger.warning((ErrorMessage.MP3_RECALCULATED_START_OF_MP3_AUDIO.getMsg(file.getPath(), Hex.asHex(newAudioHeader.getMp3StartByte()))));
 348  0
                 return newAudioHeader;
 349  
             }
 350  
         }
 351  
     }
 352  
 
 353  
     /**
 354  
      * Creates a new MP3File datatype and parse the tag from the given file
 355  
      * Object, files can be onpened read only if required.
 356  
      *
 357  
      * @param file        MP3 file
 358  
      * @param loadOptions decide what tags to load
 359  
      * @param readOnly    causes the files to be opened readonly
 360  
      * @throws IOException  on any I/O error
 361  
      * @throws TagException on any exception generated by this library.
 362  
      * @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
 363  
      * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
 364  
      */
 365  
     public MP3File(File file, int loadOptions, boolean readOnly) throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException
 366  2061
     {
 367  2061
         RandomAccessFile newFile = null;
 368  
         try
 369  
         {
 370  2061
             this.file = file;
 371  
 
 372  
             //Check File accessibility
 373  2061
             newFile = checkFilePermissions(file, readOnly);
 374  
 
 375  
             //Read ID3v2 tag size (if tag exists) to allow audioheader parsing to skip over tag
 376  2057
             long startByte = AbstractID3v2Tag.getV2TagSizeIfExists(file);
 377  
 
 378  
             //If exception reading Mpeg then we should give up no point continuing
 379  2057
             audioHeader = new MP3AudioHeader(file, startByte);
 380  
 
 381  2045
             if (startByte != ((MP3AudioHeader) audioHeader).getMp3StartByte())
 382  
             {
 383  47
                 logger.info("First header found after tag:" + audioHeader);
 384  47
                 audioHeader = checkAudioStart(startByte, (MP3AudioHeader) audioHeader);
 385  
             }
 386  
 
 387  
             //Read v1 tags (if any)
 388  2045
             readV1Tag(file, newFile, loadOptions);
 389  
 
 390  
             //Read v2 tags (if any)
 391  2045
             readV2Tag(file, loadOptions);
 392  
 
 393  
             //If we have a v2 tag use that, if we dont but have v1 tag use that
 394  
             //otherwise use nothing
 395  
             //TODO:if have both should we merge
 396  
             //rather than just returning specific ID3v22 tag, would it be better to return v24 version ?
 397  2045
             if (this.getID3v2Tag() != null)
 398  
             {
 399  1392
                 tag = this.getID3v2Tag();
 400  
             }
 401  653
             else if (id3v1tag != null)
 402  
             {
 403  24
                 tag = id3v1tag;
 404  
             }
 405  
 
 406  
             //Read Lyrics 3
 407  
             //readLyrics3Tag(File file,RandomAccessFile  newFile,int loadOptions)
 408  2045
         }
 409  
         finally
 410  
         {
 411  16
             if (newFile != null)
 412  
             {
 413  2057
                 newFile.close();
 414  
             }
 415  2045
         }
 416  2045
     }
 417  
 
 418  
     /**
 419  
      * Used by tags when writing to calculate the location of the music file
 420  
      *
 421  
      * @param file
 422  
      * @return the location within the file that the audio starts
 423  
      * @throws java.io.IOException
 424  
      * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
 425  
      */
 426  
     public long getMP3StartByte(File file) throws InvalidAudioFrameException, IOException
 427  
     {
 428  
         try
 429  
         {
 430  
             //Read ID3v2 tag size (if tag exists) to allow audioheader parsing to skip over tag
 431  0
             long startByte = AbstractID3v2Tag.getV2TagSizeIfExists(file);
 432  
 
 433  0
             MP3AudioHeader audioHeader = new MP3AudioHeader(file, startByte);
 434  0
             if (startByte != audioHeader.getMp3StartByte())
 435  
             {
 436  0
                 logger.info("First header found after tag:" + audioHeader);
 437  0
                 audioHeader = checkAudioStart(startByte, audioHeader);
 438  
             }
 439  0
             return audioHeader.getMp3StartByte();
 440  
         }
 441  0
         catch (InvalidAudioFrameException iafe)
 442  
         {
 443  0
             throw iafe;
 444  
         }
 445  0
         catch (IOException ioe)
 446  
         {
 447  0
             throw ioe;
 448  
         }
 449  
     }
 450  
 
 451  
     /**
 452  
      * Extracts the raw ID3v2 tag data into a file.
 453  
      * <p/>
 454  
      * This provides access to the raw data before manipulation, the data is written from the start of the file
 455  
      * to the start of the Audio Data. This is primarily useful for manipulating corrupted tags that are not
 456  
      * (fully) loaded using the standard methods.
 457  
      *
 458  
      * @param outputFile to write the data to
 459  
      * @return
 460  
      * @throws TagNotFoundException
 461  
      * @throws IOException
 462  
      */
 463  
     public File extractID3v2TagDataIntoFile(File outputFile) throws TagNotFoundException, IOException
 464  
     {
 465  0
         int startByte = (int) ((MP3AudioHeader) audioHeader).getMp3StartByte();
 466  0
         if (startByte >= 0)
 467  
         {
 468  
 
 469  
             //Read byte into buffer
 470  0
             FileInputStream fis = new FileInputStream(file);
 471  0
             FileChannel fc = fis.getChannel();
 472  0
             ByteBuffer bb = ByteBuffer.allocate(startByte);
 473  0
             fc.read(bb);
 474  
 
 475  
             //Write bytes to outputFile
 476  0
             FileOutputStream out = new FileOutputStream(outputFile);
 477  0
             out.write(bb.array());
 478  0
             out.close();
 479  0
             fc.close();
 480  0
             fis.close();
 481  0
             return outputFile;
 482  
         }
 483  0
         throw new TagNotFoundException("There is no ID3v2Tag data in this file");
 484  
     }
 485  
 
 486  
     /**
 487  
      * Return audio header
 488  
      * @return
 489  
      */
 490  
     public MP3AudioHeader getMP3AudioHeader()
 491  
     {
 492  112
         return (MP3AudioHeader) getAudioHeader();
 493  
     }
 494  
 
 495  
     /**
 496  
      * Returns true if this datatype contains an <code>Id3v1</code> tag
 497  
      *
 498  
      * @return true if this datatype contains an <code>Id3v1</code> tag
 499  
      */
 500  
     public boolean hasID3v1Tag()
 501  
     {
 502  89
         return (id3v1tag != null);
 503  
     }
 504  
 
 505  
     /**
 506  
      * Returns true if this datatype contains an <code>Id3v2</code> tag
 507  
      *
 508  
      * @return true if this datatype contains an <code>Id3v2</code> tag
 509  
      */
 510  
     public boolean hasID3v2Tag()
 511  
     {
 512  113
         return (id3v2tag != null);
 513  
     }
 514  
 
 515  
     /**
 516  
      * Returns true if this datatype contains a <code>Lyrics3</code> tag
 517  
      * TODO disabled until Lyrics3 fixed
 518  
      * @return true if this datatype contains a <code>Lyrics3</code> tag
 519  
      */
 520  
     /*
 521  
     public boolean hasLyrics3Tag()
 522  
     {
 523  
         return (lyrics3tag != null);
 524  
     }
 525  
     */
 526  
 
 527  
     /**
 528  
      * Creates a new MP3File datatype and parse the tag from the given file
 529  
      * Object.
 530  
      *
 531  
      * @param file MP3 file
 532  
      * @throws IOException  on any I/O error
 533  
      * @throws TagException on any exception generated by this library.
 534  
      * @throws org.jaudiotagger.audio.exceptions.ReadOnlyFileException
 535  
      * @throws org.jaudiotagger.audio.exceptions.InvalidAudioFrameException
 536  
      */
 537  
     public MP3File(File file) throws IOException, TagException, ReadOnlyFileException, InvalidAudioFrameException
 538  
     {
 539  1416
         this(file, LOAD_ALL);
 540  1400
     }
 541  
 
 542  
     /**
 543  
      * Sets the ID3v1(_1)tag to the tag provided as an argument.
 544  
      *
 545  
      * @param id3v1tag
 546  
      */
 547  
     public void setID3v1Tag(ID3v1Tag id3v1tag)
 548  
     {
 549  37
         logger.info("setting tagv1:v1 tag");
 550  37
         this.id3v1tag = id3v1tag;
 551  37
     }
 552  
 
 553  
     public void setID3v1Tag(Tag id3v1tag)
 554  
     {
 555  0
         logger.info("setting tagv1:v1 tag");
 556  0
         this.id3v1tag = (ID3v1Tag) id3v1tag;
 557  0
     }
 558  
 
 559  
     /**
 560  
      * Sets the <code>ID3v1</code> tag for this datatype. A new
 561  
      * <code>ID3v1_1</code> datatype is created from the argument and then used
 562  
      * here.
 563  
      *
 564  
      * @param mp3tag Any MP3Tag datatype can be used and will be converted into a
 565  
      *               new ID3v1_1 datatype.
 566  
      */
 567  
     public void setID3v1Tag(AbstractTag mp3tag)
 568  
     {
 569  0
         logger.info("setting tagv1:abstract");
 570  0
         id3v1tag = new ID3v11Tag(mp3tag);
 571  0
     }
 572  
 
 573  
     /**
 574  
      * Returns the <code>ID3v1</code> tag for this datatype.
 575  
      *
 576  
      * @return the <code>ID3v1</code> tag for this datatype
 577  
      */
 578  
     public ID3v1Tag getID3v1Tag()
 579  
     {
 580  74
         return id3v1tag;
 581  
     }
 582  
 
 583  
     /**
 584  
      * Sets the <code>ID3v2</code> tag for this datatype. A new
 585  
      * <code>ID3v2_4</code> datatype is created from the argument and then used
 586  
      * here.
 587  
      *
 588  
      * @param mp3tag Any MP3Tag datatype can be used and will be converted into a
 589  
      *               new ID3v2_4 datatype.
 590  
      */
 591  
     public void setID3v2Tag(AbstractTag mp3tag)
 592  
     {
 593  4
         id3v2tag = new ID3v24Tag(mp3tag);
 594  
 
 595  4
     }
 596  
 
 597  
     /**
 598  
      * Sets the v2 tag to the v2 tag provided as an argument.
 599  
      * Also store a v24 version of tag as v24 is the interface to be used
 600  
      * when talking with client applications.
 601  
      *
 602  
      * @param id3v2tag
 603  
      */
 604  
     public void setID3v2Tag(AbstractID3v2Tag id3v2tag)
 605  
     {
 606  1898
         this.id3v2tag = id3v2tag;
 607  1898
         if (id3v2tag instanceof ID3v24Tag)
 608  
         {
 609  865
             this.id3v2Asv24tag = (ID3v24Tag) this.id3v2tag;
 610  
         }
 611  
         else
 612  
         {
 613  1033
             this.id3v2Asv24tag = new ID3v24Tag(id3v2tag);
 614  
         }
 615  1898
     }
 616  
 
 617  
     /**
 618  
      * Set v2 tag ,dont need to set v24 tag because saving
 619  
      * <p/>
 620  
      * TODO temp its rather messy
 621  
      * @param id3v2tag
 622  
      */
 623  
     public void setID3v2TagOnly(AbstractID3v2Tag id3v2tag)
 624  
     {
 625  248
         this.id3v2tag = id3v2tag;
 626  248
         this.id3v2Asv24tag = null;
 627  248
     }
 628  
 
 629  
     /**
 630  
      * Returns the <code>ID3v2</code> tag for this datatype.
 631  
      *
 632  
      * @return the <code>ID3v2</code> tag for this datatype
 633  
      */
 634  
     public AbstractID3v2Tag getID3v2Tag()
 635  
     {
 636  4408
         return id3v2tag;
 637  
     }
 638  
 
 639  
     /**
 640  
      * @return a representation of tag as v24
 641  
      */
 642  
     public ID3v24Tag getID3v2TagAsv24()
 643  
     {
 644  97
         return id3v2Asv24tag;
 645  
     }
 646  
 
 647  
     /**
 648  
      * Sets the <code>Lyrics3</code> tag for this datatype. A new
 649  
      * <code>Lyrics3v2</code> datatype is created from the argument and then
 650  
      * used here.
 651  
      *
 652  
      * @param mp3tag Any MP3Tag datatype can be used and will be converted into a
 653  
      *               new Lyrics3v2 datatype.
 654  
      */
 655  
     /*
 656  
     public void setLyrics3Tag(AbstractTag mp3tag)
 657  
     {
 658  
         lyrics3tag = new Lyrics3v2(mp3tag);
 659  
     }
 660  
     */
 661  
 
 662  
     /**
 663  
      *
 664  
      *
 665  
      * @param lyrics3tag
 666  
      */
 667  
     /*
 668  
     public void setLyrics3Tag(AbstractLyrics3 lyrics3tag)
 669  
     {
 670  
         this.lyrics3tag = lyrics3tag;
 671  
     }
 672  
     */
 673  
 
 674  
     /**
 675  
      * Returns the <code>ID3v1</code> tag for this datatype.
 676  
      *
 677  
      * @return the <code>ID3v1</code> tag for this datatype
 678  
      */
 679  
     /*
 680  
     public AbstractLyrics3 getLyrics3Tag()
 681  
     {
 682  
         return lyrics3tag;
 683  
     }
 684  
     */
 685  
 
 686  
     /**
 687  
      * Remove tag from file
 688  
      *
 689  
      * @param mp3tag
 690  
      * @throws FileNotFoundException
 691  
      * @throws IOException
 692  
      */
 693  
     public void delete(AbstractTag mp3tag) throws FileNotFoundException, IOException
 694  
     {
 695  0
         mp3tag.delete(new RandomAccessFile(this.file, "rws"));
 696  0
     }
 697  
 
 698  
     /**
 699  
      * Saves the tags in this datatype to the file referred to by this datatype.
 700  
      *
 701  
      * @throws IOException  on any I/O error
 702  
      * @throws TagException on any exception generated by this library.
 703  
      */
 704  
     public void save() throws IOException, TagException
 705  
     {
 706  1239
         save(this.file);
 707  1239
     }
 708  
 
 709  
     /**
 710  
      * Overriden for comptability with merged code
 711  
      *
 712  
      * @throws CannotWriteException
 713  
      */
 714  
     public void commit() throws CannotWriteException
 715  
     {
 716  
         try
 717  
         {
 718  398
             save();
 719  
         }
 720  0
         catch (IOException ioe)
 721  
         {
 722  0
             throw new CannotWriteException(ioe);
 723  
         }
 724  0
         catch (TagException te)
 725  
         {
 726  0
             throw new CannotWriteException(te);
 727  398
         }
 728  398
     }
 729  
 
 730  
     /**
 731  
      * Check can write to file
 732  
      *
 733  
      * @param file
 734  
      * @throws IOException
 735  
      */
 736  
     public void precheck(File file) throws IOException
 737  
     {
 738  1239
         if (!file.exists())
 739  
         {
 740  0
             logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()));
 741  0
             throw new IOException(ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()));
 742  
         }
 743  
 
 744  1239
         if (!file.canWrite())
 745  
         {
 746  0
             logger.severe(ErrorMessage.GENERAL_WRITE_FAILED.getMsg(file.getName()));
 747  0
             throw new IOException(ErrorMessage.GENERAL_WRITE_FAILED.getMsg(file.getName()));
 748  
         }
 749  
 
 750  1239
         if (file.length() <= MINIMUM_FILESIZE)
 751  
         {
 752  0
             logger.severe(ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_IS_TOO_SMALL.getMsg(file.getName()));
 753  0
             throw new IOException(ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_IS_TOO_SMALL.getMsg(file.getName()));
 754  
         }
 755  1239
     }
 756  
 
 757  
     /**
 758  
      * Saves the tags in this datatype to the file argument. It will be saved as
 759  
      * TagConstants.MP3_FILE_SAVE_WRITE
 760  
      *
 761  
      * @param file file to save the this datatype's tags to
 762  
      * @throws FileNotFoundException if unable to find file
 763  
      * @throws IOException           on any I/O error
 764  
      */
 765  
     public void save(File file) throws IOException
 766  
     {
 767  1239
         logger.info("Saving  : " + file.getAbsolutePath());
 768  
 
 769  
         //Checks before starting write
 770  1239
         precheck(file);
 771  
 
 772  1239
         RandomAccessFile rfile = null;
 773  
         try
 774  
         {
 775  
             //ID3v2 Tag
 776  1239
             if (TagOptionSingleton.getInstance().isId3v2Save())
 777  
             {
 778  1239
                 if (id3v2tag == null)
 779  
                 {
 780  41
                     rfile = new RandomAccessFile(file, "rws");
 781  41
                     (new ID3v24Tag()).delete(rfile);
 782  41
                     (new ID3v23Tag()).delete(rfile);
 783  41
                     (new ID3v22Tag()).delete(rfile);
 784  41
                     logger.info("Deleting ID3v2 tag:"+file.getName());
 785  41
                     rfile.close();
 786  
                 }
 787  
                 else
 788  
                 {
 789  1198
                     logger.info("Writing ID3v2 tag:"+file.getName());
 790  1198
                     id3v2tag.write(file, ((MP3AudioHeader) this.getAudioHeader()).getMp3StartByte());
 791  
                 }
 792  
             }
 793  1239
             rfile = new RandomAccessFile(file, "rws");
 794  
 
 795  
             //Lyrics 3 Tag
 796  1239
             if (TagOptionSingleton.getInstance().isLyrics3Save())
 797  
             {
 798  1239
                 if (lyrics3tag != null)
 799  
                 {
 800  0
                     lyrics3tag.write(rfile);
 801  
                 }
 802  
             }
 803  
             //ID3v1 tag
 804  1239
             if (TagOptionSingleton.getInstance().isId3v1Save())
 805  
             {
 806  1239
                 logger.info("Processing ID3v1");
 807  1239
                 if (id3v1tag == null)
 808  
                 {
 809  963
                     logger.info("Deleting ID3v1");
 810  963
                     (new ID3v1Tag()).delete(rfile);
 811  
                 }
 812  
                 else
 813  
                 {
 814  276
                     logger.info("Saving ID3v1");
 815  276
                     id3v1tag.write(rfile);
 816  
                 }
 817  
             }
 818  1239
         }
 819  0
         catch (FileNotFoundException ex)
 820  
         {
 821  0
             logger.log(Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE_FILE_NOT_FOUND.getMsg(file.getName()), ex);
 822  0
             throw ex;
 823  
         }
 824  0
         catch (IOException iex)
 825  
         {
 826  0
             logger.log(Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), iex.getMessage()), iex);
 827  0
             throw iex;
 828  
         }
 829  0
         catch (RuntimeException re)
 830  
         {
 831  0
             logger.log(Level.SEVERE, ErrorMessage.GENERAL_WRITE_FAILED_BECAUSE.getMsg(file.getName(), re.getMessage()), re);
 832  0
             throw re;
 833  
         }
 834  
         finally
 835  
         {
 836  0
             if (rfile != null)
 837  
             {
 838  1239
                 rfile.close();
 839  
             }
 840  1239
         }
 841  1239
     }
 842  
 
 843  
     /**
 844  
      * Displays MP3File Structure
 845  
      */
 846  
     public String displayStructureAsXML()
 847  
     {
 848  32
         createXMLStructureFormatter();
 849  32
         tagFormatter.openHeadingElement("file", this.getFile().getAbsolutePath());
 850  32
         if (this.getID3v1Tag() != null)
 851  
         {
 852  0
             this.getID3v1Tag().createStructure();
 853  
         }
 854  32
         if (this.getID3v2Tag() != null)
 855  
         {
 856  32
             this.getID3v2Tag().createStructure();
 857  
         }
 858  32
         tagFormatter.closeHeadingElement("file");
 859  32
         return tagFormatter.toString();
 860  
     }
 861  
 
 862  
     /**
 863  
      * Displays MP3File Structure
 864  
      */
 865  
     public String displayStructureAsPlainText()
 866  
     {
 867  0
         createPlainTextStructureFormatter();
 868  0
         tagFormatter.openHeadingElement("file", this.getFile().getAbsolutePath());
 869  0
         if (this.getID3v1Tag() != null)
 870  
         {
 871  0
             this.getID3v1Tag().createStructure();
 872  
         }
 873  0
         if (this.getID3v2Tag() != null)
 874  
         {
 875  0
             this.getID3v2Tag().createStructure();
 876  
         }
 877  0
         tagFormatter.closeHeadingElement("file");
 878  0
         return tagFormatter.toString();
 879  
     }
 880  
 
 881  
     private static void createXMLStructureFormatter()
 882  
     {
 883  32
         tagFormatter = new XMLTagDisplayFormatter();
 884  32
     }
 885  
 
 886  
     private static void createPlainTextStructureFormatter()
 887  
     {
 888  0
         tagFormatter = new PlainTextTagDisplayFormatter();
 889  0
     }
 890  
 
 891  
     public static AbstractTagDisplayFormatter getStructureFormatter()
 892  
     {
 893  8428
         return tagFormatter;
 894  
     }
 895  
 
 896  
     /**
 897  
      * Set the Tag
 898  
      * <p/>
 899  
      * If the parameter tag is a v1tag then the v1 tag is set if v2tag then the v2tag.
 900  
      *
 901  
      * @param tag
 902  
      */
 903  
     public void setTag(Tag tag)
 904  
     {
 905  81
         this.tag = tag;
 906  81
         if (tag instanceof ID3v1Tag)
 907  
         {
 908  8
             setID3v1Tag((ID3v1Tag) tag);
 909  
         }
 910  
         else
 911  
         {
 912  73
             setID3v2Tag((AbstractID3v2Tag) tag);
 913  
         }
 914  81
     }
 915  
 
 916  
 
 917  
     /** Create Default Tag
 918  
      *
 919  
      * @return
 920  
      */
 921  
     @Override
 922  
     //TODO Should be able to change the Default
 923  
     public Tag createDefaultTag()
 924  
     {
 925  25
         return new ID3v23Tag();
 926  
     }
 927  
 }
 928