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