Coverage Report - org.jaudiotagger.tag.id3.ID3v23Tag
 
Classes in this File Line Coverage Branch Coverage Complexity
ID3v23Tag
62%
201/324
46%
52/114
0
 
 1  
 /*
 2  
  *  MusicTag Copyright (C)2003,2004
 3  
  *
 4  
  *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 5  
  *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
 6  
  *  or (at your option) any later version.
 7  
  *
 8  
  *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 9  
  *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 10  
  *  See the GNU Lesser General Public License for more details.
 11  
  *
 12  
  *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
 13  
  *  you can get a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
 14  
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 15  
  */
 16  
 package org.jaudiotagger.tag.id3;
 17  
 
 18  
 import org.jaudiotagger.FileConstants;
 19  
 import org.jaudiotagger.logging.FileSystemMessage;
 20  
 import org.jaudiotagger.logging.ErrorMessage;
 21  
 import org.jaudiotagger.audio.mp3.MP3File;
 22  
 import org.jaudiotagger.audio.exceptions.UnableToCreateFileException;
 23  
 import org.jaudiotagger.audio.exceptions.UnableToModifyFileException;
 24  
 import org.jaudiotagger.tag.*;
 25  
 import org.jaudiotagger.tag.reference.PictureTypes;
 26  
 import org.jaudiotagger.tag.datatype.Artwork;
 27  
 import org.jaudiotagger.tag.datatype.DataTypes;
 28  
 import org.jaudiotagger.tag.id3.framebody.*;
 29  
 
 30  
 import java.io.File;
 31  
 import java.io.IOException;
 32  
 import java.io.RandomAccessFile;
 33  
 import java.io.FileNotFoundException;
 34  
 import java.nio.ByteBuffer;
 35  
 import java.nio.channels.FileChannel;
 36  
 import java.nio.channels.FileLock;
 37  
 import java.nio.channels.WritableByteChannel;
 38  
 import java.util.*;
 39  
 import java.util.logging.Level;
 40  
 
 41  
 /**
 42  
  * Represents an ID3v2.3 tag.
 43  
  *
 44  
  * @author : Paul Taylor
 45  
  * @author : Eric Farng
 46  
  * @version $Id: ID3v23Tag.java,v 1.43 2008/11/13 18:25:10 paultaylor Exp $
 47  
  */
 48  33
 public class ID3v23Tag extends AbstractID3v2Tag
 49  
 {
 50  
 
 51  
     protected static final String TYPE_CRCDATA = "crcdata";
 52  
     protected static final String TYPE_EXPERIMENTAL = "experimental";
 53  
     protected static final String TYPE_EXTENDED = "extended";
 54  
     protected static final String TYPE_PADDINGSIZE = "paddingsize";
 55  
     protected static final String TYPE_UNSYNCHRONISATION = "unsyncronisation";
 56  
 
 57  
 
 58  42
     protected static int TAG_EXT_HEADER_LENGTH = 10;
 59  42
     protected static int TAG_EXT_HEADER_CRC_LENGTH = 4;
 60  42
     protected static int FIELD_TAG_EXT_SIZE_LENGTH = 4;
 61  42
     protected static int TAG_EXT_HEADER_DATA_LENGTH = TAG_EXT_HEADER_LENGTH - FIELD_TAG_EXT_SIZE_LENGTH;
 62  
 
 63  
     /**
 64  
      * ID3v2.3 Header bit mask
 65  
      */
 66  
     public static final int MASK_V23_UNSYNCHRONIZATION = FileConstants.BIT7;
 67  
 
 68  
     /**
 69  
      * ID3v2.3 Header bit mask
 70  
      */
 71  
     public static final int MASK_V23_EXTENDED_HEADER = FileConstants.BIT6;
 72  
 
 73  
     /**
 74  
      * ID3v2.3 Header bit mask
 75  
      */
 76  
     public static final int MASK_V23_EXPERIMENTAL = FileConstants.BIT5;
 77  
 
 78  
     /**
 79  
      * ID3v2.3 Extended Header bit mask
 80  
      */
 81  
     public static final int MASK_V23_CRC_DATA_PRESENT = FileConstants.BIT7;
 82  
 
 83  
     /**
 84  
      * ID3v2.3 RBUF frame bit mask
 85  
      */
 86  
     public static final int MASK_V23_EMBEDDED_INFO_FLAG = FileConstants.BIT1;
 87  
 
 88  
     /**
 89  
      * CRC Checksum calculated
 90  
      */
 91  259
     protected boolean crcDataFlag = false;
 92  
 
 93  
     /**
 94  
      * Experiemntal tag
 95  
      */
 96  259
     protected boolean experimental = false;
 97  
 
 98  
     /**
 99  
      * Contains extended header
 100  
      */
 101  259
     protected boolean extended = false;
 102  
 
 103  
     /**
 104  
      * CRC Checksum
 105  
      */
 106  259
     protected int crcData = 0;
 107  
 
 108  
     /**
 109  
      * Tag padding
 110  
      */
 111  259
     protected int paddingSize = 0;
 112  
 
 113  
     /**
 114  
      * All frames in the tag uses unsynchronisation
 115  
      */
 116  259
     protected boolean unsynchronization = false;
 117  
 
 118  
     /**
 119  
      * The tag is compressed
 120  
      */
 121  259
     protected boolean compression = false;
 122  
 
 123  
 
 124  
     public static final byte RELEASE = 2;
 125  
     public static final byte MAJOR_VERSION = 3;
 126  
     public static final byte REVISION = 0;
 127  
 
 128  
     /**
 129  
      * Retrieve the Release
 130  
      */
 131  
     public byte getRelease()
 132  
     {
 133  2
         return RELEASE;
 134  
     }
 135  
 
 136  
     /**
 137  
      * Retrieve the Major Version
 138  
      */
 139  
     public byte getMajorVersion()
 140  
     {
 141  274
         return MAJOR_VERSION;
 142  
     }
 143  
 
 144  
     /**
 145  
      * Retrieve the Revision
 146  
      */
 147  
     public byte getRevision()
 148  
     {
 149  220
         return REVISION;
 150  
     }
 151  
 
 152  
 
 153  
     /**
 154  
      * Creates a new empty ID3v2_3 datatype.
 155  
      */
 156  
     public ID3v23Tag()
 157  76
     {
 158  76
         frameMap = new LinkedHashMap();
 159  76
     }
 160  
 
 161  
     /**
 162  
      * Copy primitives applicable to v2.3
 163  
      */
 164  
     protected void copyPrimitives(AbstractID3v2Tag copyObj)
 165  
     {
 166  17
         logger.info("Copying primitives");
 167  17
         super.copyPrimitives(copyObj);
 168  
 
 169  17
         if (copyObj instanceof ID3v23Tag)
 170  
         {
 171  0
             ID3v23Tag copyObject = (ID3v23Tag) copyObj;
 172  0
             this.crcDataFlag = copyObject.crcDataFlag;
 173  0
             this.experimental = copyObject.experimental;
 174  0
             this.extended = copyObject.extended;
 175  0
             this.crcData = copyObject.crcData;
 176  0
             this.paddingSize = copyObject.paddingSize;
 177  
         }
 178  17
     }
 179  
 
 180  
     /**
 181  
      * Copy frames from one tag into a v2.3 tag
 182  
      *
 183  
      * @param copyObject
 184  
      */
 185  
     protected void copyFrames(AbstractID3v2Tag copyObject)
 186  
     {
 187  17
         logger.info("Copying Frames,there are:" + copyObject.frameMap.keySet().size() + " different types");
 188  17
         frameMap = new LinkedHashMap();
 189  
 
 190  
         //Copy Frames that are a valid 2.3 type
 191  17
         Iterator<String> iterator = copyObject.frameMap.keySet().iterator();
 192  
         AbstractID3v2Frame frame;
 193  17
         ID3v23Frame newFrame = null;
 194  93
         while (iterator.hasNext())
 195  
         {
 196  76
             String id = iterator.next();
 197  76
             Object o = copyObject.frameMap.get(id);
 198  76
             if (o instanceof AbstractID3v2Frame)
 199  
             {
 200  72
                 frame = (AbstractID3v2Frame) o;
 201  72
                 logger.info("Frame is:" + frame.getIdentifier());
 202  
 
 203  
                 //Special case v24 tdrc may need converting to multiple frames, only convert when
 204  
                 //it is a valid tdrc to cope with tdrc being added illegally to a v23 tag which is then
 205  
                 //converted to v24 tag and back again.
 206  72
                 if ((frame.getIdentifier().equals(ID3v24Frames.FRAME_ID_YEAR)) && (frame.getBody() instanceof FrameBodyTDRC))
 207  
                 {
 208  3
                     translateFrame(frame);
 209  
                 }
 210  
                 //Usual Case
 211  
                 else
 212  
                 {
 213  
                     try
 214  
                     {
 215  
 
 216  69
                         newFrame = new ID3v23Frame(frame);
 217  69
                         logger.info("Adding Frame:" + newFrame.getIdentifier());
 218  69
                         frameMap.put(newFrame.getIdentifier(), newFrame);
 219  
                     }
 220  0
                     catch (InvalidFrameException ife)
 221  
                     {
 222  0
                         logger.log(Level.SEVERE, "Unable to convert frame:" + frame.getIdentifier());
 223  69
                     }
 224  
                 }
 225  
             }
 226  
             //Multi Frames
 227  4
             else if (o instanceof ArrayList)
 228  
             {
 229  4
                 ArrayList<AbstractID3v2Frame> multiFrame = new ArrayList<AbstractID3v2Frame>();
 230  4
                 for (ListIterator<AbstractID3v2Frame> li = ((ArrayList<AbstractID3v2Frame>) o).listIterator(); li.hasNext();)
 231  
                 {
 232  17
                     frame = li.next();
 233  17
                     logger.info("Frame is MultiFrame:" + frame.getIdentifier());
 234  
                     try
 235  
                     {
 236  17
                         newFrame = new ID3v23Frame(frame);
 237  17
                         multiFrame.add(newFrame);
 238  
                     }
 239  0
                     catch (InvalidFrameException ife)
 240  
                     {
 241  0
                         logger.log(Level.SEVERE, "Unable to convert frame:" + frame.getIdentifier(), ife);
 242  17
                     }
 243  
                 }
 244  
                 //Ensure that the list actually contains at lest one value before adding
 245  4
                 if(multiFrame.size()>0)
 246  
                 {
 247  4
                     if (newFrame != null)
 248  
                     {
 249  4
                         logger.info("Adding MultiFrame:" + newFrame.getIdentifier());
 250  4
                         frameMap.put(newFrame.getIdentifier(), multiFrame);
 251  
                     }
 252  
                 }
 253  
             }
 254  76
         }
 255  17
     }
 256  
 
 257  
     /**
 258  
      * This is used when we need to translate a single frame into multiple frames,
 259  
      * currently required for v24 TDRC frames.
 260  
      */
 261  
     protected void translateFrame(AbstractID3v2Frame frame)
 262  
     {
 263  3
         FrameBodyTDRC tmpBody = (FrameBodyTDRC) frame.getBody();
 264  
         ID3v23Frame newFrame;
 265  3
         if (!tmpBody.getYear().equals(""))
 266  
         {
 267  3
             newFrame = new ID3v23Frame(ID3v23Frames.FRAME_ID_V3_TYER);
 268  3
             ((FrameBodyTYER) newFrame.getBody()).setText(tmpBody.getYear());
 269  3
             logger.info("Adding Frame:" + newFrame.getIdentifier());
 270  3
             frameMap.put(newFrame.getIdentifier(), newFrame);
 271  
         }
 272  3
         if (!tmpBody.getDate().equals(""))
 273  
         {
 274  0
             newFrame = new ID3v23Frame(ID3v23Frames.FRAME_ID_V3_TDAT);
 275  0
             ((FrameBodyTDAT) newFrame.getBody()).setText(tmpBody.getDate());
 276  0
             logger.info("Adding Frame:" + newFrame.getIdentifier());
 277  0
             frameMap.put(newFrame.getIdentifier(), newFrame);
 278  
         }
 279  3
         if (!tmpBody.getTime().equals(""))
 280  
         {
 281  0
             newFrame = new ID3v23Frame(ID3v23Frames.FRAME_ID_V3_TIME);
 282  0
             ((FrameBodyTIME) newFrame.getBody()).setText(tmpBody.getTime());
 283  0
             logger.info("Adding Frame:" + newFrame.getIdentifier());
 284  0
             frameMap.put(newFrame.getIdentifier(), newFrame);
 285  
         }
 286  3
     }
 287  
 
 288  
     /**
 289  
      * Copy Constructor, creates a new ID3v2_3 Tag based on another ID3v2_3 Tag
 290  
      */
 291  
     public ID3v23Tag(ID3v23Tag copyObject)
 292  
     {
 293  
         //This doesnt do anything.
 294  0
         super(copyObject);
 295  0
         logger.info("Creating tag from another tag of same type");
 296  0
         copyPrimitives(copyObject);
 297  0
         copyFrames(copyObject);
 298  
 
 299  0
     }
 300  
 
 301  
     /**
 302  
      * Constructs a new tag based upon another tag of different version/type
 303  
      */
 304  
     public ID3v23Tag(AbstractTag mp3tag)
 305  17
     {
 306  17
         logger.info("Creating tag from a tag of a different version");
 307  17
         frameMap = new LinkedHashMap();
 308  
 
 309  17
         if (mp3tag != null)
 310  
         {
 311  
             ID3v24Tag convertedTag;
 312  
             //Should use simpler copy constructor
 313  17
             if ((mp3tag instanceof ID3v24Tag == false) && (mp3tag instanceof ID3v23Tag == true))
 314  
             {
 315  0
                 throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 316  
             }
 317  17
             if (mp3tag instanceof ID3v24Tag)
 318  
             {
 319  16
                 convertedTag = (ID3v24Tag) mp3tag;
 320  
             }
 321  
             //All tags types can be converted to v2.4 so do this to simplify things
 322  
             else
 323  
             {
 324  1
                 convertedTag = new ID3v24Tag(mp3tag);
 325  
             }
 326  
             //Copy Primitives
 327  17
             copyPrimitives(convertedTag);
 328  
             //Copy Frames
 329  17
             copyFrames(convertedTag);
 330  17
             logger.info("Created tag from a tag of a different version");
 331  
         }
 332  17
     }
 333  
 
 334  
     /**
 335  
      * Creates a new ID3v2_3 datatype.
 336  
      *
 337  
      * @param buffer
 338  
      * @param loggingFilename
 339  
      * @throws TagException
 340  
      */
 341  
     public ID3v23Tag(ByteBuffer buffer, String loggingFilename) throws TagException
 342  166
     {
 343  166
         setLoggingFilename(loggingFilename);
 344  166
         this.read(buffer);
 345  110
     }
 346  
 
 347  
 
 348  
     /**
 349  
      * Creates a new ID3v2_3 datatype.
 350  
      *
 351  
      * @param buffer
 352  
      * @throws TagException
 353  
      * @deprecated use {@link #ID3v23Tag(ByteBuffer,String)} instead
 354  
      */
 355  
     public ID3v23Tag(ByteBuffer buffer) throws TagException
 356  
     {
 357  0
         this(buffer, "");
 358  0
     }
 359  
 
 360  
     /**
 361  
      * @return textual tag identifier
 362  
      */
 363  
     public String getIdentifier()
 364  
     {
 365  56
         return "ID3v2.30";
 366  
     }
 367  
 
 368  
     /**
 369  
      * Return frame size based upon the sizes of the tags rather than the physical
 370  
      * no of bytes between start of ID3Tag and start of Audio Data.
 371  
      * <p/>
 372  
      * TODO this is incorrect, because of subclasses
 373  
      *
 374  
      * @return size of tag
 375  
      */
 376  
     public int getSize()
 377  
     {
 378  0
         int size = TAG_HEADER_LENGTH;
 379  0
         if (extended)
 380  
         {
 381  0
             size += this.TAG_EXT_HEADER_LENGTH;
 382  0
             if (crcDataFlag)
 383  
             {
 384  0
                 size += this.TAG_EXT_HEADER_CRC_LENGTH;
 385  
             }
 386  
         }
 387  0
         size += super.getSize();
 388  0
         return size;
 389  
     }
 390  
 
 391  
     /**
 392  
      * Is Tag Equivalent to another tag
 393  
      *
 394  
      * @param obj
 395  
      * @return true if tag is equivalent to another
 396  
      */
 397  
     public boolean equals(Object obj)
 398  
     {
 399  0
         if ((obj instanceof ID3v23Tag) == false)
 400  
         {
 401  0
             return false;
 402  
         }
 403  0
         ID3v23Tag object = (ID3v23Tag) obj;
 404  0
         if (this.crcData != object.crcData)
 405  
         {
 406  0
             return false;
 407  
         }
 408  0
         if (this.crcDataFlag != object.crcDataFlag)
 409  
         {
 410  0
             return false;
 411  
         }
 412  0
         if (this.experimental != object.experimental)
 413  
         {
 414  0
             return false;
 415  
         }
 416  0
         if (this.extended != object.extended)
 417  
         {
 418  0
             return false;
 419  
         }
 420  0
         if (this.paddingSize != object.paddingSize)
 421  
         {
 422  0
             return false;
 423  
         }
 424  0
         return super.equals(obj);
 425  
     }
 426  
 
 427  
     /**
 428  
      * Read the size of a tag, based on  the value written in the tag header
 429  
      *
 430  
      * @param buffer
 431  
      * @return
 432  
      * @throws TagException
 433  
      */
 434  
     public int readSize(ByteBuffer buffer)
 435  
     {
 436  
 
 437  
         //Skip over flags
 438  17
         byte flags = buffer.get();
 439  
 
 440  
         // Read the size, this is size of tag not including  the tag header
 441  17
         int size = ID3SyncSafeInteger.bufferToValue(buffer);
 442  
 
 443  
         //Return the exact size of tag as set in the tag header
 444  17
         return size + TAG_HEADER_LENGTH;
 445  
     }
 446  
 
 447  
     /**
 448  
      * Read tag from File
 449  
      *
 450  
      * @param buffer The buffer to read the ID3v23 Tag from
 451  
      */
 452  
     public void read(ByteBuffer buffer) throws TagException
 453  
     {
 454  
         int size;
 455  166
         if (seek(buffer) == false)
 456  
         {
 457  56
             throw new TagNotFoundException(getIdentifier() + " tag not found");
 458  
         }
 459  110
         logger.info(getLoggingFilename() + ":" + "Reading ID3v23 tag");
 460  
 
 461  
         //Flags
 462  110
         byte flags = buffer.get();
 463  110
         unsynchronization = (flags & MASK_V23_UNSYNCHRONIZATION) != 0;
 464  110
         extended = (flags & MASK_V23_EXTENDED_HEADER) != 0;
 465  110
         experimental = (flags & MASK_V23_EXPERIMENTAL) != 0;
 466  
 
 467  110
         if (isUnsynchronization())
 468  
         {
 469  2
             logger.info(getLoggingFilename() + ":" + "ID3v23 Tag is unsynchronized");
 470  
         }
 471  
 
 472  110
         if (extended)
 473  
         {
 474  0
             logger.warning(getLoggingFilename() + ":" + "ID3v23 Tag is extended");
 475  
         }
 476  
 
 477  110
         if (experimental)
 478  
         {
 479  0
             logger.warning(getLoggingFilename() + ":" + "ID3v23 Tag is experimental");
 480  
         }
 481  
 
 482  
         // Read the size, this is size of tag not including  the tag header
 483  110
         size = ID3SyncSafeInteger.bufferToValue(buffer);
 484  110
         logger.info(getLoggingFilename() + ":Tag size is:" + size + " according to header (does not include header size, add 10)");
 485  
 
 486  
         //Extended Header
 487  110
         if (extended)
 488  
         {
 489  
             // Int is 4 bytes.
 490  0
             int extendedHeaderSize = buffer.getInt();
 491  
             // Extended header without CRC Data
 492  0
             if (extendedHeaderSize == TAG_EXT_HEADER_DATA_LENGTH)
 493  
             {
 494  
                 //Flag
 495  0
                 byte extFlag = buffer.get();
 496  0
                 crcDataFlag = (extFlag & MASK_V23_CRC_DATA_PRESENT) != 0;
 497  0
                 if (crcDataFlag == true)
 498  
                 {
 499  0
                     throw new InvalidTagException(getLoggingFilename() + ":CRC Data flag not set correctly.");
 500  
                 }
 501  
                 //Flag Byte (not used)
 502  0
                 buffer.get();
 503  
                 //Take padding and ext header size off size to be read
 504  0
                 size = size - (buffer.getInt() + TAG_EXT_HEADER_LENGTH);
 505  0
             }
 506  0
             else if (extendedHeaderSize == TAG_EXT_HEADER_DATA_LENGTH + TAG_EXT_HEADER_CRC_LENGTH)
 507  
             {
 508  
                 //Flag
 509  0
                 byte extFlag = buffer.get();
 510  0
                 crcDataFlag = (extFlag & MASK_V23_CRC_DATA_PRESENT) != 0;
 511  0
                 if (crcDataFlag == false)
 512  
                 {
 513  0
                     throw new InvalidTagException(getLoggingFilename() + ":CRC Data flag not set correctly.");
 514  
                 }
 515  
                 //Flag Byte (not used)
 516  0
                 buffer.get();
 517  
                 //Take padding size of size to be read
 518  0
                 size = size - (buffer.getInt() + TAG_EXT_HEADER_LENGTH + TAG_EXT_HEADER_CRC_LENGTH);
 519  
                 //CRC Data
 520  0
                 crcData = buffer.getInt();
 521  0
             }
 522  
             else
 523  
             {
 524  0
                 throw new InvalidTagException("Invalid Extended Header Size.");
 525  
             }
 526  0
             logger.info(getLoggingFilename() + ":has Extended Header so adjusted Tag size is:" + size);
 527  
 
 528  
         }
 529  
 
 530  
         //Slice Buffer, so position markers tally with size (i.e do not include tagheader)
 531  110
         ByteBuffer bufferWithoutHeader = buffer.slice();
 532  
         //We need to synchronize the buffer
 533  110
         if (isUnsynchronization())
 534  
         {
 535  2
             bufferWithoutHeader = ID3Unsynchronization.synchronize(bufferWithoutHeader);
 536  
         }
 537  
 
 538  110
         readFrames(bufferWithoutHeader, size);
 539  110
         logger.info(getLoggingFilename() + ":Loaded Frames,there are:" + frameMap.keySet().size());
 540  
 
 541  110
     }
 542  
 
 543  
 
 544  
     /**
 545  
      * Read the frames
 546  
      * <p/>
 547  
      * Read from byteBuffer upto size
 548  
      *
 549  
      * @param byteBuffer
 550  
      * @param size
 551  
      */
 552  
     protected void readFrames(ByteBuffer byteBuffer, int size)
 553  
     {
 554  
         //Now start looking for frames
 555  
         ID3v23Frame next;
 556  110
         frameMap = new LinkedHashMap();
 557  
 
 558  
         //Read the size from the Tag Header
 559  110
         this.fileReadSize = size;
 560  110
         logger.finest(getLoggingFilename() + ":Start of frame body at:" + byteBuffer.position() + ",frames data size is:" + size);
 561  
 
 562  
         // Read the frames until got to upto the size as specified in header or until
 563  
         // we hit an invalid frame identifier
 564  611
         while (byteBuffer.position() < size)
 565  
         {
 566  
             String id;
 567  
             try
 568  
             {
 569  
                 //Read Frame
 570  602
                 logger.finest(getLoggingFilename() + ":Looking for next frame at:" + byteBuffer.position());
 571  602
                 next = new ID3v23Frame(byteBuffer, getLoggingFilename());
 572  500
                 id = next.getIdentifier();
 573  500
                 loadFrameIntoMap(id, next);
 574  
             }
 575  
             //Found Empty Frame , log it - empty frames should not exist
 576  1
             catch (EmptyFrameException ex)
 577  
             {
 578  1
                 logger.warning(getLoggingFilename() + ":Empty Frame:" + ex.getMessage());
 579  1
                 this.emptyFrameBytes += ID3v23Frame.FRAME_HEADER_SIZE;
 580  
             }
 581  99
             catch (InvalidFrameIdentifierException ifie)
 582  
             {
 583  99
                 logger.info(getLoggingFilename() + ":Invalid Frame Identifier:" + ifie.getMessage());
 584  99
                 this.invalidFrameBytes++;
 585  
                 //Dont try and find any more frames
 586  99
                 break;
 587  
             }
 588  
             //Problem trying to find frame, often just occurs because frameheader includes padding
 589  
             //and we have reached padding
 590  2
             catch (InvalidFrameException ife)
 591  
             {
 592  2
                 logger.warning(getLoggingFilename() + ":Invalid Frame:" + ife.getMessage());
 593  2
                 this.invalidFrameBytes++;
 594  
                 //Dont try and find any more frames
 595  2
                 break;
 596  501
             }
 597  
             ;
 598  501
         }
 599  110
     }
 600  
 
 601  
     /**
 602  
      * Write the ID3 header to the ByteBuffer.
 603  
      * <p/>
 604  
      * TODO Calculate the CYC Data Check
 605  
      * TODO Reintroduce Extended Header
 606  
      *
 607  
      * @param padding is the size of the padding portion of the tag
 608  
      * @param size    is the size of the body data
 609  
      * @return ByteBuffer
 610  
      * @throws IOException
 611  
      */
 612  
     private ByteBuffer writeHeaderToBuffer(int padding, int size) throws IOException
 613  
     {
 614  
         // Flags,currently we never calculate the CRC
 615  
         // and if we dont calculate them cant keep orig values. Tags are not
 616  
         // experimental and we never create extended header to keep things simple.
 617  88
         extended = false;
 618  88
         experimental = false;
 619  88
         crcDataFlag = false;
 620  
 
 621  
         // Create Header Buffer,allocate maximum possible size for the header
 622  88
         ByteBuffer headerBuffer = ByteBuffer.
 623  
                 allocate(TAG_HEADER_LENGTH + TAG_EXT_HEADER_LENGTH + TAG_EXT_HEADER_CRC_LENGTH);
 624  
 
 625  
         //TAGID
 626  88
         headerBuffer.put(TAG_ID);
 627  
 
 628  
         //Major Version
 629  88
         headerBuffer.put(getMajorVersion());
 630  
 
 631  
         //Minor Version
 632  88
         headerBuffer.put(getRevision());
 633  
 
 634  
         //Flags
 635  88
         byte flagsByte = 0;
 636  88
         if (isUnsynchronization())
 637  
         {
 638  2
             flagsByte |= MASK_V23_UNSYNCHRONIZATION;
 639  
         }
 640  88
         if (extended)
 641  
         {
 642  0
             flagsByte |= MASK_V23_EXTENDED_HEADER;
 643  
         }
 644  88
         if (experimental)
 645  
         {
 646  0
             flagsByte |= MASK_V23_EXPERIMENTAL;
 647  
         }
 648  88
         headerBuffer.put(flagsByte);
 649  
 
 650  
         //Additional Header Size,(for completeness we never actually write the extended header)
 651  88
         int additionalHeaderSize = 0;
 652  88
         if (extended)
 653  
         {
 654  0
             additionalHeaderSize += this.TAG_EXT_HEADER_LENGTH;
 655  0
             if (crcDataFlag)
 656  
             {
 657  0
                 additionalHeaderSize += this.TAG_EXT_HEADER_CRC_LENGTH;
 658  
             }
 659  
         }
 660  
 
 661  
         //Size As Recorded in Header, don't include the main header length
 662  88
         headerBuffer.put(ID3SyncSafeInteger.valueToBuffer(padding + size + additionalHeaderSize));
 663  
 
 664  
         //Write Extended Header
 665  88
         if (extended)
 666  
         {
 667  0
             byte extFlagsByte1 = 0;
 668  0
             byte extFlagsByte2 = 0;
 669  
 
 670  
             //Contains CRCData
 671  0
             if (crcDataFlag == true)
 672  
             {
 673  0
                 headerBuffer.putInt(TAG_EXT_HEADER_DATA_LENGTH + TAG_EXT_HEADER_CRC_LENGTH);
 674  0
                 extFlagsByte1 |= MASK_V23_CRC_DATA_PRESENT;
 675  0
                 headerBuffer.put(extFlagsByte1);
 676  0
                 headerBuffer.put(extFlagsByte2);
 677  0
                 headerBuffer.putInt(paddingSize);
 678  0
                 headerBuffer.putInt(crcData);
 679  
             }
 680  
             //Just extended Header
 681  
             else
 682  
             {
 683  0
                 headerBuffer.putInt(TAG_EXT_HEADER_DATA_LENGTH);
 684  0
                 headerBuffer.put(extFlagsByte1);
 685  0
                 headerBuffer.put(extFlagsByte2);
 686  
                 //Newly Calculated Padding As Recorded in Extended Header
 687  0
                 headerBuffer.putInt(padding);
 688  
             }
 689  
         }
 690  
 
 691  88
         headerBuffer.flip();
 692  88
         return headerBuffer;
 693  
     }
 694  
 
 695  
 
 696  
     /**
 697  
      * Write tag to file
 698  
      * <p/>
 699  
      * TODO:we currently never write the Extended header , but if we did the size calculation in this
 700  
      * method would be slightly incorrect
 701  
      *
 702  
      * @param file The file to write to
 703  
      * @throws IOException
 704  
      */
 705  
     public void write(File file, long audioStartLocation) throws IOException
 706  
     {
 707  88
         logger.info(getLoggingFilename() + ":Writing tag to file");
 708  
 
 709  
         //Write Body Buffer
 710  88
         byte[] bodyByteBuffer = writeFramesToBuffer().toByteArray();
 711  88
         logger.info(getLoggingFilename() + ":bodybytebuffer:sizebeforeunsynchronisation:" + bodyByteBuffer.length);
 712  
 
 713  
         // Unsynchronize if option enabled and unsync required
 714  88
         if (TagOptionSingleton.getInstance().isUnsyncTags())
 715  
         {
 716  2
             unsynchronization = ID3Unsynchronization.requiresUnsynchronization(bodyByteBuffer);
 717  
         }
 718  
         else
 719  
         {
 720  86
             unsynchronization = false;
 721  
         }
 722  88
         if (isUnsynchronization())
 723  
         {
 724  2
             bodyByteBuffer = ID3Unsynchronization.unsynchronize(bodyByteBuffer);
 725  2
             logger.info(getLoggingFilename() + ":bodybytebuffer:sizeafterunsynchronisation:" + bodyByteBuffer.length);
 726  
         }
 727  
 
 728  88
         int sizeIncPadding = calculateTagSize(bodyByteBuffer.length + TAG_HEADER_LENGTH, (int) audioStartLocation);
 729  88
         int padding = sizeIncPadding - (bodyByteBuffer.length + TAG_HEADER_LENGTH);
 730  88
         logger.info(getLoggingFilename() + ":Current audiostart:" + audioStartLocation);
 731  88
         logger.info(getLoggingFilename() + ":Size including padding:" + sizeIncPadding);
 732  88
         logger.info(getLoggingFilename() + ":Padding:" + padding);
 733  
 
 734  88
         ByteBuffer headerBuffer = writeHeaderToBuffer(padding, bodyByteBuffer.length);
 735  88
         writeBufferToFile(file,headerBuffer, bodyByteBuffer,padding,sizeIncPadding,audioStartLocation);
 736  88
     }
 737  
 
 738  
     /**
 739  
      * Write tag to channel
 740  
      *
 741  
      * @param channel
 742  
      * @throws IOException
 743  
      */
 744  
     public void write(WritableByteChannel channel) throws IOException
 745  
     {
 746  0
         logger.info(getLoggingFilename() + ":Writing tag to channel");
 747  
 
 748  0
         byte[] bodyByteBuffer = writeFramesToBuffer().toByteArray();
 749  0
         logger.info(getLoggingFilename() + ":bodybytebuffer:sizebeforeunsynchronisation:" + bodyByteBuffer.length);
 750  
 
 751  
         // Unsynchronize if option enabled and unsync required
 752  0
         if (TagOptionSingleton.getInstance().isUnsyncTags())
 753  
         {
 754  0
             unsynchronization = ID3Unsynchronization.requiresUnsynchronization(bodyByteBuffer);
 755  
         }
 756  
         else
 757  
         {
 758  0
             unsynchronization = false;
 759  
         }
 760  0
         if (isUnsynchronization())
 761  
         {
 762  0
             bodyByteBuffer = ID3Unsynchronization.unsynchronize(bodyByteBuffer);
 763  0
             logger.info(getLoggingFilename() + ":bodybytebuffer:sizeafterunsynchronisation:" + bodyByteBuffer.length);
 764  
         }
 765  0
         ByteBuffer headerBuffer = writeHeaderToBuffer(0, bodyByteBuffer.length);
 766  
 
 767  0
         channel.write(headerBuffer);
 768  0
         channel.write(ByteBuffer.wrap(bodyByteBuffer));
 769  0
     }
 770  
 
 771  
     /**
 772  
      * For representing the MP3File in an XML Format
 773  
      */
 774  
     public void createStructure()
 775  
     {
 776  
 
 777  0
         MP3File.getStructureFormatter().openHeadingElement(TYPE_TAG, getIdentifier());
 778  
 
 779  0
         super.createStructureHeader();
 780  
 
 781  
         //Header
 782  0
         MP3File.getStructureFormatter().openHeadingElement(TYPE_HEADER, "");
 783  0
         MP3File.getStructureFormatter().addElement(TYPE_UNSYNCHRONISATION, this.isUnsynchronization());
 784  0
         MP3File.getStructureFormatter().addElement(TYPE_EXTENDED, this.extended);
 785  0
         MP3File.getStructureFormatter().addElement(TYPE_EXPERIMENTAL, this.experimental);
 786  0
         MP3File.getStructureFormatter().addElement(TYPE_CRCDATA, this.crcData);
 787  0
         MP3File.getStructureFormatter().addElement(TYPE_PADDINGSIZE, this.paddingSize);
 788  0
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_HEADER);
 789  
         //Body
 790  0
         super.createStructureBody();
 791  0
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_TAG);
 792  0
     }
 793  
 
 794  
     /**
 795  
      * @return is tag unsynchronized
 796  
      */
 797  
     public boolean isUnsynchronization()
 798  
     {
 799  398
         return unsynchronization;
 800  
     }
 801  
 
 802  
     protected String getArtistId()
 803  
     {
 804  7
         return ID3v23Frames.FRAME_ID_V3_ARTIST;
 805  
     }
 806  
 
 807  
     protected String getAlbumId()
 808  
     {
 809  15
         return ID3v23Frames.FRAME_ID_V3_ALBUM;
 810  
     }
 811  
 
 812  
     protected String getTitleId()
 813  
     {
 814  7
         return ID3v23Frames.FRAME_ID_V3_TITLE;
 815  
     }
 816  
 
 817  
     protected String getTrackId()
 818  
     {
 819  6
         return ID3v23Frames.FRAME_ID_V3_TRACK;
 820  
     }
 821  
 
 822  
     protected String getYearId()
 823  
     {
 824  6
         return ID3v23Frames.FRAME_ID_V3_TYER;
 825  
     }
 826  
 
 827  
     protected String getCommentId()
 828  
     {
 829  6
         return ID3v23Frames.FRAME_ID_V3_COMMENT;
 830  
     }
 831  
 
 832  
     protected String getGenreId()
 833  
     {
 834  10
         return ID3v23Frames.FRAME_ID_V3_GENRE;
 835  
     }
 836  
 
 837  
     public ID3v23Frame createFrame(String id)
 838  
     {
 839  36
         return new ID3v23Frame(id);
 840  
     }
 841  
 
 842  
 
 843  
     /**
 844  
      * Create Frame for Id3 Key
 845  
      * <p/>
 846  
      * Only textual data supported at the moment, should only be used with frames that
 847  
      * support a simple string argument.
 848  
      *
 849  
      * @param id3Key
 850  
      * @param value
 851  
      * @return
 852  
      * @throws KeyNotFoundException
 853  
      * @throws FieldDataInvalidException
 854  
      */
 855  
     public TagField createTagField(ID3v23FieldKey id3Key, String value) throws KeyNotFoundException, FieldDataInvalidException
 856  
     {
 857  0
         if (id3Key == null)
 858  
         {
 859  0
             throw new KeyNotFoundException();
 860  
         }
 861  0
         return super.doCreateTagField(new FrameAndSubId(id3Key.getFrameId(), id3Key.getSubId()), value);
 862  
     }
 863  
 
 864  
     /**
 865  
      * Retrieve the first value that exists for this id3v23key
 866  
      *
 867  
      * @param id3v23FieldKey
 868  
      * @return
 869  
      */
 870  
     public String getFirst(ID3v23FieldKey id3v23FieldKey) throws KeyNotFoundException
 871  
     {
 872  12
         if (id3v23FieldKey == null)
 873  
         {
 874  0
             throw new KeyNotFoundException();
 875  
         }
 876  12
         return super.doGetFirst(new FrameAndSubId(id3v23FieldKey.getFrameId(), id3v23FieldKey.getSubId()));
 877  
     }
 878  
 
 879  
     /**
 880  
      * Delete fields with this id3v23FieldKey
 881  
      *
 882  
      * @param id3v23FieldKey
 883  
      */
 884  
     public void deleteTagField(ID3v23FieldKey id3v23FieldKey) throws KeyNotFoundException
 885  
     {
 886  0
         if (id3v23FieldKey == null)
 887  
         {
 888  0
             throw new KeyNotFoundException();
 889  
         }
 890  0
         super.doDeleteTagField(new FrameAndSubId(id3v23FieldKey.getFrameId(), id3v23FieldKey.getSubId()));
 891  0
     }
 892  
 
 893  
 
 894  
     protected FrameAndSubId getFrameAndSubIdFromGenericKey(TagFieldKey genericKey)
 895  
     {
 896  82
         ID3v23FieldKey id3v23FieldKey = ID3v23Frames.getInstanceOf().getId3KeyFromGenericKey(genericKey);
 897  82
         if (id3v23FieldKey == null)
 898  
         {
 899  0
             throw new KeyNotFoundException();
 900  
         }
 901  82
         return new FrameAndSubId(id3v23FieldKey.getFrameId(), id3v23FieldKey.getSubId());
 902  
     }
 903  
 
 904  
     protected ID3Frames getID3Frames()
 905  
     {
 906  8
         return ID3v23Frames.getInstanceOf();
 907  
     }
 908  
 
 909  
      /**
 910  
      *
 911  
      * @return comparator used to order frames in preferred order for writing to file
 912  
      * so that most important frames are written first.
 913  
      */
 914  
     public Comparator getPreferredFrameOrderComparator()
 915  
     {
 916  88
         return ID3v23PreferredFrameOrderComparator.getInstanceof();
 917  
     }
 918  
 
 919  
      public List<Artwork> getArtworkList()
 920  
     {
 921  4
         List<TagField> coverartList = get(TagFieldKey.COVER_ART);
 922  4
         List<Artwork> artworkList   = new ArrayList<Artwork>(coverartList.size());
 923  
 
 924  4
         for(TagField next:coverartList)
 925  
         {
 926  3
             FrameBodyAPIC coverArt = (FrameBodyAPIC) ((AbstractID3v2Frame) next).getBody();
 927  3
             Artwork artwork = new Artwork();
 928  3
             artwork.setMimeType(coverArt.getMimeType());
 929  3
             artwork.setPictureType(coverArt.getPictureType());
 930  3
             if(coverArt.isImageUrl())
 931  
             {
 932  0
                 artwork.setLinked(true);
 933  0
                 artwork.setImageUrl(coverArt.getImageUrl());
 934  
             }
 935  
             else
 936  
             {
 937  3
                 artwork.setBinaryData(coverArt.getImageData());
 938  
             }
 939  3
             artworkList.add(artwork);
 940  3
         }
 941  4
         return artworkList;
 942  
     }
 943  
 
 944  
      public TagField createArtworkField(Artwork artwork) throws FieldDataInvalidException
 945  
     {
 946  1
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(TagFieldKey.COVER_ART).getFrameId());
 947  1
         FrameBodyAPIC body = (FrameBodyAPIC) frame.getBody();
 948  1
         body.setObjectValue(DataTypes.OBJ_PICTURE_DATA,artwork.getBinaryData());
 949  1
         body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, artwork.getPictureType());
 950  1
         body.setObjectValue(DataTypes.OBJ_MIME_TYPE, artwork.getMimeType());
 951  1
         body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 952  1
         return frame;
 953  
     }
 954  
 
 955  
      /**
 956  
      * Create Artwork
 957  
      *
 958  
      * @param data
 959  
      * @param mimeType of the image
 960  
      * @see PictureTypes
 961  
      */
 962  
     public TagField createArtworkField(byte[] data, String mimeType)
 963  
     {
 964  2
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(TagFieldKey.COVER_ART).getFrameId());
 965  2
         FrameBodyAPIC body = (FrameBodyAPIC) frame.getBody();
 966  2
         body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, data);
 967  2
         body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
 968  2
         body.setObjectValue(DataTypes.OBJ_MIME_TYPE, mimeType);
 969  2
         body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 970  2
         return frame;
 971  
     }
 972  
 }