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