Coverage Report - org.jaudiotagger.tag.id3.ID3v24Frame
 
Classes in this File Line Coverage Branch Coverage Complexity
ID3v24Frame
64%
154/238
57%
56/98
2.914
ID3v24Frame$EncodingFlags
91%
31/34
70%
14/20
2.914
ID3v24Frame$StatusFlags
100%
32/32
100%
6/6
2.914
 
 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.audio.generic.Utils;
 20  
 import org.jaudiotagger.audio.mp3.MP3File;
 21  
 import org.jaudiotagger.logging.ErrorMessage;
 22  
 import org.jaudiotagger.tag.*;
 23  
 import org.jaudiotagger.tag.datatype.Lyrics3Line;
 24  
 import org.jaudiotagger.tag.id3.framebody.*;
 25  
 import org.jaudiotagger.tag.lyrics3.*;
 26  
 
 27  
 import java.io.ByteArrayOutputStream;
 28  
 import java.io.IOException;
 29  
 import java.nio.ByteBuffer;
 30  
 import java.util.Iterator;
 31  
 import java.util.regex.Matcher;
 32  
 import java.util.regex.Pattern;
 33  
 
 34  
 /**
 35  
  * Represents an ID3v2.4 frame.
 36  
  *
 37  
  * @author : Paul Taylor
 38  
  * @author : Eric Farng
 39  
  * @version $Id: ID3v24Frame.java 836 2009-11-12 15:44:07Z paultaylor $
 40  
  */
 41  
 public class ID3v24Frame extends AbstractID3v2Frame
 42  
 {
 43  4
     private static Pattern validFrameIdentifier = Pattern.compile("[A-Z][0-9A-Z]{3}");
 44  
 
 45  
     protected static final int FRAME_DATA_LENGTH_SIZE = 4;
 46  
 
 47  
     protected static final int FRAME_ID_SIZE = 4;
 48  
     protected static final int FRAME_FLAGS_SIZE = 2;
 49  
     protected static final int FRAME_SIZE_SIZE = 4;
 50  
     protected static final int FRAME_ENCRYPTION_INDICATOR_SIZE = 1;
 51  
     protected static final int FRAME_GROUPING_INDICATOR_SIZE = 1;
 52  
 
 53  
     protected static final int FRAME_HEADER_SIZE = FRAME_ID_SIZE + FRAME_SIZE_SIZE + FRAME_FLAGS_SIZE;
 54  
 
 55  
 
 56  
     public ID3v24Frame()
 57  0
     {
 58  0
     }
 59  
 
 60  
     /**
 61  
      * Creates a new ID3v2_4Frame of type identifier. An empty
 62  
      * body of the correct type will be automatically created.
 63  
      * This constructor should be used when wish to create a new
 64  
      * frame from scratch using user input
 65  
      *
 66  
      * @param identifier defines the type of body to be created
 67  
      */
 68  
     public ID3v24Frame(String identifier)
 69  
     {
 70  
         //Super Constructor creates a frame with empty body of type specified
 71  733
         super(identifier);
 72  733
         statusFlags = new StatusFlags();
 73  733
         encodingFlags = new EncodingFlags();
 74  
 
 75  733
     }
 76  
 
 77  
     /**
 78  
      * Copy Constructor:Creates a new ID3v2_4Frame datatype based on another frame.
 79  
      * @param frame
 80  
      */
 81  
     public ID3v24Frame(ID3v24Frame frame)
 82  
     {
 83  0
         super(frame);
 84  0
         statusFlags = new StatusFlags(frame.getStatusFlags().getOriginalFlags());
 85  0
         encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags());
 86  0
     }
 87  
 
 88  
     private void createV24FrameFromV23Frame(ID3v23Frame frame) throws InvalidFrameException
 89  
     {
 90  
        // Is it a straight conversion e.g TALB - TALB
 91  4120
         identifier = ID3Tags.convertFrameID23To24(frame.getIdentifier());
 92  4120
         logger.finer("Creating V24frame from v23:"+frame.getIdentifier()+":"+identifier);
 93  
 
 94  
 
 95  
         //We cant convert unsupported bodies properly
 96  4120
         if(frame.getBody() instanceof FrameBodyUnsupported)
 97  
         {
 98  38
             this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
 99  38
             this.frameBody.setHeader(this);
 100  38
             identifier = frame.getIdentifier();
 101  38
             logger.finer("V3:UnsupportedBody:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 102  
         }//Simple Copy
 103  4082
         else if (identifier != null)
 104  
         {
 105  
             //Special Case
 106  3693
             if(
 107  
                     (frame.getIdentifier().equals(ID3v23Frames.FRAME_ID_V3_USER_DEFINED_INFO))
 108  
                     &&
 109  
                     (((FrameBodyTXXX)frame.getBody()).getDescription().equals(FrameBodyTXXX.MOOD)))
 110  
             {
 111  60
                 this.frameBody = new FrameBodyTMOO((FrameBodyTXXX)frame.getBody());
 112  60
                 this.frameBody.setHeader(this);
 113  60
                 identifier = frameBody.getIdentifier();
 114  
             }
 115  
             else
 116  
             {
 117  3633
                 logger.finer("V3:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 118  3633
                 this.frameBody = (AbstractTagFrameBody) ID3Tags.copyObject(frame.getBody());
 119  3633
                 this.frameBody.setHeader(this);
 120  
             }
 121  
         }
 122  
         // Is it a known v3 frame which needs forcing to v4 frame e.g. TYER - TDRC
 123  389
         else if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier()))
 124  
         {
 125  389
             identifier = ID3Tags.forceFrameID23To24(frame.getIdentifier());
 126  389
             if (identifier != null)
 127  
             {
 128  389
                 logger.info("V3:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 129  389
                 this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
 130  389
                 this.frameBody.setHeader(this);
 131  
             }
 132  
             // No mechanism exists to convert it to a v24 frame, e.g deprecated frame e.g TSIZ, so hold
 133  
             // as a deprecated frame consisting of an array of bytes*/
 134  
             else
 135  
             {
 136  0
                 this.frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frame.getBody());
 137  0
                 this.frameBody.setHeader(this);
 138  0
                 identifier = frame.getIdentifier();
 139  0
                 logger.finer("V3:Deprecated:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 140  
             }
 141  
         }
 142  
         // Unknown Frame e.g NCON or TDRL (because TDRL unknown to V23)
 143  
         else
 144  
         {
 145  0
             this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
 146  0
             this.frameBody.setHeader(this);
 147  0
             identifier = frame.getIdentifier();
 148  0
             logger.finer("V3:Unknown:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 149  
         }
 150  4120
     }
 151  
 
 152  
     /**
 153  
      * Creates a new ID3v2_4Frame datatype based on another frame of different version
 154  
      * Converts the framebody to the equivalent v24 framebody or to UnsupportedFrameBody if identifier
 155  
      * is unknown.
 156  
      *
 157  
      * @param frame to construct a new frame from
 158  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 159  
      */
 160  
     public ID3v24Frame(AbstractID3v2Frame frame) throws InvalidFrameException
 161  4120
     {
 162  
         //Should not be called
 163  4120
         if ((frame instanceof ID3v24Frame))
 164  
         {
 165  0
             throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 166  
         }
 167  
         //Flags
 168  4120
         if (frame instanceof ID3v23Frame)
 169  
         {
 170  2981
             statusFlags = new StatusFlags((ID3v23Frame.StatusFlags) frame.getStatusFlags());
 171  2981
             encodingFlags = new EncodingFlags(frame.getEncodingFlags().getFlags());
 172  
         }
 173  
         else
 174  
         {
 175  1139
             statusFlags = new StatusFlags();
 176  1139
             encodingFlags = new EncodingFlags();
 177  
         }
 178  
 
 179  
         // Convert Identifier. If the id was a known id for the original
 180  
         // version we should be able to convert it to an v24 frame, although it may mean minor
 181  
         // modification to the data. If it was not recognised originally it should remain
 182  
         // unknown.
 183  4120
         if (frame instanceof ID3v23Frame)
 184  
         {
 185  2981
             createV24FrameFromV23Frame((ID3v23Frame) frame);
 186  
         }
 187  1139
         else if (frame instanceof ID3v22Frame)
 188  
         {
 189  1139
             ID3v23Frame v23Frame = new ID3v23Frame(frame);
 190  1139
             createV24FrameFromV23Frame(v23Frame);
 191  
         }
 192  4120
         this.frameBody.setHeader(this);
 193  4120
     }
 194  
 
 195  
     /**
 196  
      * Creates a new ID3v2_4Frame datatype based on Lyrics3.
 197  
      *
 198  
      * @param field
 199  
      * @throws InvalidTagException
 200  
      */
 201  
     public ID3v24Frame(Lyrics3v2Field field) throws InvalidTagException
 202  0
     {
 203  0
         String id = field.getIdentifier();
 204  
         String value;
 205  0
         if (id.equals("IND"))
 206  
         {
 207  0
             throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 indications field.");
 208  
         }
 209  0
         else if (id.equals("LYR"))
 210  
         {
 211  0
             FieldFrameBodyLYR lyric = (FieldFrameBodyLYR) field.getBody();
 212  
             Lyrics3Line line;
 213  0
             Iterator<Lyrics3Line> iterator = lyric.iterator();
 214  
             FrameBodySYLT sync;
 215  
             FrameBodyUSLT unsync;
 216  0
             boolean hasTimeStamp = lyric.hasTimeStamp();
 217  
             // we'll create only one frame here.
 218  
             // if there is any timestamp at all, we will create a sync'ed frame.
 219  0
             sync = new FrameBodySYLT((byte) 0, "ENG", (byte) 2, (byte) 1, "", new byte[0]);
 220  0
             unsync = new FrameBodyUSLT((byte) 0, "ENG", "", "");
 221  0
             while (iterator.hasNext())
 222  
             {
 223  0
                 line = iterator.next();
 224  0
                 if (hasTimeStamp)
 225  
                 {
 226  
                     // sync.addLyric(line);
 227  
                 }
 228  
                 else
 229  
                 {
 230  0
                     unsync.addLyric(line);
 231  
                 }
 232  
             }
 233  0
             if (hasTimeStamp)
 234  
             {
 235  0
                 this.frameBody = sync;
 236  0
                 this.frameBody.setHeader(this);
 237  
             }
 238  
             else
 239  
             {
 240  0
                 this.frameBody = unsync;
 241  0
                 this.frameBody.setHeader(this);
 242  
             }
 243  0
         }
 244  0
         else if (id.equals("INF"))
 245  
         {
 246  0
             value = ((FieldFrameBodyINF) field.getBody()).getAdditionalInformation();
 247  0
             this.frameBody = new FrameBodyCOMM((byte) 0, "ENG", "", value);
 248  0
             this.frameBody.setHeader(this);
 249  
         }
 250  0
         else if (id.equals("AUT"))
 251  
         {
 252  0
             value = ((FieldFrameBodyAUT) field.getBody()).getAuthor();
 253  0
             this.frameBody = new FrameBodyTCOM((byte) 0, value);
 254  0
             this.frameBody.setHeader(this);
 255  
         }
 256  0
         else if (id.equals("EAL"))
 257  
         {
 258  0
             value = ((FieldFrameBodyEAL) field.getBody()).getAlbum();
 259  0
             this.frameBody = new FrameBodyTALB((byte) 0, value);
 260  0
             this.frameBody.setHeader(this);
 261  
         }
 262  0
         else if (id.equals("EAR"))
 263  
         {
 264  0
             value = ((FieldFrameBodyEAR) field.getBody()).getArtist();
 265  0
             this.frameBody = new FrameBodyTPE1((byte) 0, value);
 266  0
             this.frameBody.setHeader(this);
 267  
         }
 268  0
         else if (id.equals("ETT"))
 269  
         {
 270  0
             value = ((FieldFrameBodyETT) field.getBody()).getTitle();
 271  0
             this.frameBody = new FrameBodyTIT2((byte) 0, value);
 272  0
             this.frameBody.setHeader(this);
 273  
         }
 274  0
         else if (id.equals("IMG"))
 275  
         {
 276  0
             throw new InvalidTagException("Cannot create ID3v2.40 frame from Lyrics3 image field.");
 277  
         }
 278  
         else
 279  
         {
 280  0
             throw new InvalidTagException("Cannot caret ID3v2.40 frame from " + id + " Lyrics3 field");
 281  
         }
 282  0
     }
 283  
 
 284  
     /**
 285  
      * Creates a new ID3v24Frame datatype by reading from byteBuffer.
 286  
      *
 287  
      * @param byteBuffer to read from
 288  
      * @param loggingFilename
 289  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 290  
      */
 291  
     public ID3v24Frame(ByteBuffer byteBuffer, String loggingFilename) throws InvalidFrameException
 292  3857
     {
 293  3857
         setLoggingFilename(loggingFilename);
 294  3857
         read(byteBuffer);
 295  3287
     }
 296  
 
 297  
     /**
 298  
      * Creates a new ID3v24Frame datatype by reading from byteBuffer.
 299  
      *
 300  
      * @param byteBuffer to read from
 301  
      * @deprecated use {@link #ID3v24Frame(ByteBuffer,String)} instead
 302  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 303  
      */
 304  
     public ID3v24Frame(ByteBuffer byteBuffer) throws InvalidFrameException
 305  
     {
 306  0
         this(byteBuffer, "");
 307  0
     }
 308  
 
 309  
     /**
 310  
      * @param obj
 311  
      * @return if obj is equivalent to this frame
 312  
      */
 313  
     public boolean equals(Object obj)
 314  
     {
 315  0
         return (obj instanceof ID3v24Frame) && super.equals(obj);
 316  
     }
 317  
 
 318  
 
 319  
     /**
 320  
      * Return size of frame
 321  
      *
 322  
      * @return int frame size
 323  
      */
 324  
     public int getSize()
 325  
     {
 326  4
         return frameBody.getSize() + ID3v24Frame.FRAME_HEADER_SIZE;
 327  
     }
 328  
 
 329  
 
 330  
     /**
 331  
      * Read the frame from the specified file.
 332  
      * Read the frame header then delegate reading of data to frame body.
 333  
      *
 334  
      * @param byteBuffer to read the frame from
 335  
      */
 336  
     public void read(ByteBuffer byteBuffer) throws InvalidFrameException
 337  
     {
 338  3857
         byte[] buffer = new byte[FRAME_ID_SIZE];
 339  
 
 340  3857
         if (byteBuffer.position() + FRAME_HEADER_SIZE >= byteBuffer.limit())
 341  
         {
 342  0
             logger.warning(getLoggingFilename() + ":" + "No space to find another frame:");
 343  0
             throw new InvalidFrameException(getLoggingFilename() + ":" + "No space to find another frame");
 344  
         }
 345  
 
 346  
         //Read the Frame Identifier
 347  3857
         byteBuffer.get(buffer, 0, FRAME_ID_SIZE);
 348  3857
         identifier = new String(buffer);
 349  3857
         logger.fine(getLoggingFilename() + ":" + "Identifier is" + identifier);
 350  
 
 351  
         //Is this a valid identifier?
 352  3857
         if (!isValidID3v2FrameIdentifier(identifier))
 353  
         {
 354  
             //If not valid move file pointer back to one byte after
 355  
             //the original check so can try again.
 356  566
             logger.info(getLoggingFilename() + ":" + "Invalid identifier:" + identifier);
 357  566
             byteBuffer.position(byteBuffer.position() - (FRAME_ID_SIZE - 1));
 358  566
             throw new InvalidFrameIdentifierException(identifier + " is not a valid ID3v2.40 frame");
 359  
         }
 360  
 
 361  
         //Read frame size as syncsafe integer
 362  3291
         frameSize = ID3SyncSafeInteger.bufferToValue(byteBuffer);
 363  
 
 364  3291
         if (frameSize < 0)
 365  
         {
 366  0
             logger.warning(getLoggingFilename() + ":" + "Invalid Frame size:" + identifier);
 367  0
             throw new InvalidFrameException(identifier + " is invalid frame");
 368  
         }
 369  3291
         else if (frameSize == 0)
 370  
         {
 371  4
             logger.warning(getLoggingFilename() + ":" + "Empty Frame:" + identifier);
 372  
             //We dont process this frame or add to framemap becuase contains no useful information
 373  
             //Skip the two flag bytes so in correct position for subsequent frames
 374  4
             byteBuffer.get();
 375  4
             byteBuffer.get();
 376  4
             throw new EmptyFrameException(identifier + " is empty frame");
 377  
         }
 378  3287
         else if (frameSize > (byteBuffer.remaining() - FRAME_FLAGS_SIZE))
 379  
         {
 380  0
             logger.warning(getLoggingFilename() + ":" + "Invalid Frame size larger than size before mp3 audio:" + identifier);
 381  0
             throw new InvalidFrameException(identifier + " is invalid frame");
 382  
         }
 383  
 
 384  3287
         if (frameSize > ID3SyncSafeInteger.MAX_SAFE_SIZE)
 385  
         {
 386  
             //Set Just after size field this is where we want to be when we leave this if statement
 387  189
             int currentPosition = byteBuffer.position();
 388  
 
 389  
             //Read as nonsync safe integer
 390  189
             byteBuffer.position(currentPosition - FRAME_ID_SIZE);
 391  189
             int nonSyncSafeFrameSize = byteBuffer.getInt();
 392  
 
 393  
             //Is the frame size syncsafe, should always be BUT some encoders such as Itunes do not do it properly
 394  
             //so do an easy check now.
 395  189
             byteBuffer.position(currentPosition - FRAME_ID_SIZE);
 396  189
             boolean isNotSyncSafe = ID3SyncSafeInteger.isBufferNotSyncSafe(byteBuffer);
 397  
 
 398  
             //not relative so need to move position
 399  189
             byteBuffer.position(currentPosition);
 400  
 
 401  189
             if (isNotSyncSafe)
 402  
             {
 403  16
                 logger.warning(getLoggingFilename() + ":" + "Frame size is NOT stored as a sync safe integer:" + identifier);
 404  
 
 405  
                 //This will return a larger frame size so need to check against buffer size if too large then we are
 406  
                 //buggered , give up
 407  16
                 if (nonSyncSafeFrameSize > (byteBuffer.remaining() - -FRAME_FLAGS_SIZE))
 408  
                 {
 409  0
                     logger.warning(getLoggingFilename() + ":" + "Invalid Frame size larger than size before mp3 audio:" + identifier);
 410  0
                     throw new InvalidFrameException(identifier + " is invalid frame");
 411  
                 }
 412  
                 else
 413  
                 {
 414  16
                     frameSize = nonSyncSafeFrameSize;
 415  
                 }
 416  
             }
 417  
             else
 418  
             {
 419  
                 //appears to be sync safe but lets look at the bytes just after the reported end of this
 420  
                 //frame to see if find a valid frame header
 421  
 
 422  
                 //Read the Frame Identifier
 423  173
                 byte[] readAheadbuffer = new byte[FRAME_ID_SIZE];
 424  173
                 byteBuffer.position(currentPosition + frameSize + FRAME_FLAGS_SIZE);
 425  
 
 426  173
                 if (byteBuffer.remaining() < FRAME_ID_SIZE)
 427  
                 {
 428  
                     //There is no padding or framedata we are at end so assume syncsafe
 429  
                     //reset position to just after framesize
 430  4
                     byteBuffer.position(currentPosition);
 431  
                 }
 432  
                 else
 433  
                 {
 434  169
                     byteBuffer.get(readAheadbuffer, 0, FRAME_ID_SIZE);
 435  
 
 436  
                     //reset position to just after framesize
 437  169
                     byteBuffer.position(currentPosition);
 438  
 
 439  169
                     String readAheadIdentifier = new String(readAheadbuffer);
 440  169
                     if (isValidID3v2FrameIdentifier(readAheadIdentifier))
 441  
                     {
 442  
                         //Everything ok, so continue
 443  
                     }
 444  68
                     else if (ID3SyncSafeInteger.isBufferEmpty(readAheadbuffer))
 445  
                     {
 446  
                         //no data found so assume entered padding in which case assume it is last
 447  
                         //frame and we are ok
 448  
                     }
 449  
                     //havent found identifier so maybe not syncsafe or maybe there are no more frames, just padding
 450  
                     else
 451  
                     {
 452  
                         //Ok lets try using a non-syncsafe integer
 453  
 
 454  
                         //size returned will be larger so is it valid
 455  12
                         if (nonSyncSafeFrameSize > byteBuffer.remaining() - FRAME_FLAGS_SIZE)
 456  
                         {
 457  
                             //invalid so assume syncsafe
 458  0
                             byteBuffer.position(currentPosition);
 459  
                         }
 460  
                         else
 461  
                         {
 462  12
                             readAheadbuffer = new byte[FRAME_ID_SIZE];
 463  12
                             byteBuffer.position(currentPosition + nonSyncSafeFrameSize + FRAME_FLAGS_SIZE);
 464  
 
 465  12
                             if (byteBuffer.remaining() >= FRAME_ID_SIZE)
 466  
                             {
 467  12
                                 byteBuffer.get(readAheadbuffer, 0, FRAME_ID_SIZE);
 468  12
                                 readAheadIdentifier = new String(readAheadbuffer);
 469  
 
 470  
                                 //reset position to just after framesize
 471  12
                                 byteBuffer.position(currentPosition);
 472  
 
 473  
                                 //ok found a valid identifier using non-syncsafe so assume non-syncsafe size
 474  
                                 //and continue
 475  12
                                 if (isValidID3v2FrameIdentifier(readAheadIdentifier))
 476  
                                 {
 477  4
                                     frameSize = nonSyncSafeFrameSize;
 478  4
                                     logger.warning(getLoggingFilename() + ":" + "Assuming frame size is NOT stored as a sync safe integer:" + identifier);
 479  
                                 }
 480  
                                 //no data found so assume entered padding in which case assume it is last
 481  
                                 //frame and we are ok whereas we didnt hit padding when using syncsafe integer
 482  
                                 //or we wouldnt have got to this point. So assume syncsafe ineteger ended within
 483  
                                 //the frame data whereas this has reached end of frames.
 484  8
                                 else if (ID3SyncSafeInteger.isBufferEmpty(readAheadbuffer))
 485  
                                 {
 486  8
                                     frameSize = nonSyncSafeFrameSize;
 487  8
                                     logger.warning(getLoggingFilename() + ":" + "Assuming frame size is NOT stored as a sync safe integer:" + identifier);
 488  
                                 }
 489  
                                 //invalid so assume syncsafe as that is is the standard
 490  
                                 else
 491  
                                 {
 492  
                                     }
 493  
                             }
 494  
                             else
 495  
                             {
 496  
                                 //reset position to just after framesize
 497  0
                                 byteBuffer.position(currentPosition);
 498  
 
 499  
                                 //If the unsync framesize matches exactly the remaining bytes then assume it has the
 500  
                                 //correct size for the last frame
 501  0
                                 if (byteBuffer.remaining() == 0)
 502  
                                 {
 503  0
                                     frameSize = nonSyncSafeFrameSize;
 504  
                                 }
 505  
                                 //Inconclusive stick with syncsafe
 506  
                                 else
 507  
                                 {
 508  
                                     }
 509  
 
 510  
                             }
 511  
                         }
 512  
                     }
 513  
                 }
 514  
             }
 515  
         }
 516  
 
 517  
         //Read the flag bytes
 518  3287
         statusFlags = new StatusFlags(byteBuffer.get());
 519  3287
         encodingFlags = new EncodingFlags(byteBuffer.get());
 520  
 
 521  
         //Read extra bits appended to frame header for various encodings
 522  
         //These are not included in header size but are included in frame size but wont be read when we actually
 523  
         //try to read the frame body data
 524  3287
         int extraHeaderBytesCount = 0;
 525  3287
         if (((EncodingFlags) encodingFlags).isGrouping())
 526  
         {
 527  0
             extraHeaderBytesCount = ID3v24Frame.FRAME_GROUPING_INDICATOR_SIZE;
 528  0
             byteBuffer.get();
 529  
         }
 530  
 
 531  3287
         if (((EncodingFlags) encodingFlags).isEncryption())
 532  
         {
 533  
             //Read the Encryption byte, but do nothing with it
 534  0
             extraHeaderBytesCount += ID3v24Frame.FRAME_ENCRYPTION_INDICATOR_SIZE;
 535  0
             byteBuffer.get();
 536  
         }
 537  
 
 538  3287
         if (((EncodingFlags) encodingFlags).isDataLengthIndicator())
 539  
         {
 540  
             //Read the sync safe size field
 541  8
             int datalengthSize = ID3SyncSafeInteger.bufferToValue(byteBuffer);
 542  
             //Read the Grouping byte, but do nothing with it
 543  8
             extraHeaderBytesCount += FRAME_DATA_LENGTH_SIZE;
 544  8
             logger.info(getLoggingFilename() + ":" + "Frame Size Is:" + frameSize + "Data Length Size:" + datalengthSize);
 545  
         }
 546  
 
 547  
         //Work out the real size of the framebody data
 548  3287
         int realFrameSize = frameSize - extraHeaderBytesCount;
 549  
 
 550  
         //Create Buffer that only contains the body of this frame rather than the remainder of tag
 551  3287
         ByteBuffer frameBodyBuffer = byteBuffer.slice();
 552  3287
         frameBodyBuffer.limit(realFrameSize);
 553  
 
 554  
         //Do we need to synchronize the frame body
 555  3287
         int syncSize = realFrameSize;
 556  3287
         if (((EncodingFlags) encodingFlags).isUnsynchronised())
 557  
         {
 558  
             //We only want to synchronize the buffer upto the end of this frame (remember this
 559  
             //buffer contains the remainder of this tag not just this frame), and we cant just
 560  
             //create a new buffer because when this method returns the position of the buffer is used
 561  
             //to look for the next frame, so we need to modify the buffer. The action of synchronizing causes
 562  
             //bytes to be dropped so the existing buffer is large enough to hold the modifications
 563  24
             frameBodyBuffer = ID3Unsynchronization.synchronize(frameBodyBuffer);
 564  24
             syncSize = frameBodyBuffer.limit();
 565  24
             logger.info(getLoggingFilename() + ":" + "Frame Size After Syncing is:" + syncSize);
 566  
         }
 567  
 
 568  
         //Read the body data
 569  
         try
 570  
         {
 571  3287
             frameBody = readBody(identifier, frameBodyBuffer, syncSize);
 572  3287
             if (!(frameBody instanceof ID3v24FrameBody))
 573  
             {
 574  28
                 logger.info(getLoggingFilename() + ":" + "Converted frame body with:" + identifier + " to deprecated framebody");
 575  28
                 frameBody = new FrameBodyDeprecated((AbstractID3v2FrameBody) frameBody);
 576  
             }
 577  3287
         }
 578  
         finally
 579  
         {
 580  
             //Update position of main buffer, so no attempt is made to reread  these bytes
 581  0
             byteBuffer.position(byteBuffer.position() + realFrameSize);
 582  3287
         }
 583  3287
     }
 584  
 
 585  
     /**
 586  
      * Write the frame. Writes the frame header but writing the data is delegated to the
 587  
      * frame body.
 588  
      *
 589  
      * @throws IOException
 590  
      */
 591  
     public void write(ByteArrayOutputStream tagBuffer)
 592  
     {
 593  
         boolean unsynchronization;
 594  
 
 595  1935
         logger.info("Writing frame to file:" + getIdentifier());
 596  
 
 597  
         //This is where we will write header, move position to where we can
 598  
         //write bodybuffer
 599  1935
         ByteBuffer headerBuffer = ByteBuffer.allocate(FRAME_HEADER_SIZE);
 600  
 
 601  
         //Write Frame Body Data to a new stream
 602  1935
         ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
 603  1935
         ((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream);
 604  
 
 605  
         //Does it need unsynchronizing, and are we allowing unsychronizing
 606  1935
         byte[] bodyBuffer = bodyOutputStream.toByteArray();
 607  1935
         unsynchronization = TagOptionSingleton.getInstance().isUnsyncTags() && ID3Unsynchronization.requiresUnsynchronization(bodyBuffer);
 608  1935
         if (unsynchronization)
 609  
         {
 610  4
             bodyBuffer = ID3Unsynchronization.unsynchronize(bodyBuffer);
 611  4
             logger.info("bodybytebuffer:sizeafterunsynchronisation:" + bodyBuffer.length);
 612  
         }
 613  
 
 614  
         //Write Frame Header
 615  
         //Write Frame ID, the identifier must be 4 bytes bytes long it may not be
 616  
         //because converted an unknown v2.2 id (only 3 bytes long)
 617  1935
         if (getIdentifier().length() == 3)
 618  
         {
 619  0
             identifier = identifier + ' ';
 620  
         }
 621  1935
         headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, FRAME_ID_SIZE);
 622  
 
 623  
         //Write Frame Size based on size of body buffer (if it has been unsynced then it size
 624  
         //will have increased accordingly
 625  1935
         int size = bodyBuffer.length;
 626  1935
         logger.fine("Frame Size Is:" + size);
 627  1935
         headerBuffer.put(ID3SyncSafeInteger.valueToBuffer(size));
 628  
 
 629  
         //Write the Flags
 630  
         //Status Flags:leave as they were when we read
 631  1935
         headerBuffer.put(statusFlags.getWriteFlags());
 632  
 
 633  
         //Enclosing Flags, first reset
 634  1935
         encodingFlags.resetFlags();
 635  
         //Encoding we only support unsynchrnization
 636  1935
         if (unsynchronization)
 637  
         {
 638  4
             ((ID3v24Frame.EncodingFlags) encodingFlags).setUnsynchronised();
 639  
         }
 640  1935
         headerBuffer.put(encodingFlags.getFlags());
 641  
 
 642  
         try
 643  
         {
 644  
             //Add header to the Byte Array Output Stream
 645  1935
             tagBuffer.write(headerBuffer.array());
 646  
 
 647  
             //Add bodybuffer to the Byte Array Output Stream
 648  1935
             tagBuffer.write(bodyBuffer);
 649  
         }
 650  0
         catch (IOException ioe)
 651  
         {
 652  
             //This could never happen coz not writing to file, so convert to RuntimeException
 653  0
             throw new RuntimeException(ioe);
 654  1935
         }
 655  1935
     }
 656  
 
 657  
     /**
 658  
      * Get Status Flags Object
 659  
      */
 660  
     protected AbstractID3v2Frame.StatusFlags getStatusFlags()
 661  
     {
 662  584
         return statusFlags;
 663  
     }
 664  
 
 665  
     /**
 666  
      * Get Encoding Flags Object
 667  
      */
 668  
     protected AbstractID3v2Frame.EncodingFlags getEncodingFlags()
 669  
     {
 670  608
         return encodingFlags;
 671  
     }
 672  
 
 673  
     /**
 674  
      * Member Class This represents a frame headers Status Flags
 675  
      * Make adjustments if necessary based on frame type and specification.
 676  
      */
 677  
     class StatusFlags extends AbstractID3v2Frame.StatusFlags
 678  
     {
 679  
         public static final String TYPE_TAGALTERPRESERVATION = "typeTagAlterPreservation";
 680  
         public static final String TYPE_FILEALTERPRESERVATION = "typeFileAlterPreservation";
 681  
         public static final String TYPE_READONLY = "typeReadOnly";
 682  
 
 683  
 
 684  
         /**
 685  
          * Discard frame if tag altered
 686  
          */
 687  
         public static final int MASK_TAG_ALTER_PRESERVATION = FileConstants.BIT6;
 688  
 
 689  
         /**
 690  
          * Discard frame if audio part of file altered
 691  
          */
 692  
         public static final int MASK_FILE_ALTER_PRESERVATION = FileConstants.BIT5;
 693  
 
 694  
         /**
 695  
          * Frame tagged as read only
 696  
          */
 697  
         public static final int MASK_READ_ONLY = FileConstants.BIT4;
 698  
 
 699  
         /**
 700  
          * Use this when creating a frame from scratch
 701  
          */
 702  
         StatusFlags()
 703  1872
         {
 704  1872
             super();
 705  1872
         }
 706  
 
 707  
         /**
 708  
          * Use this constructor when reading from file or from another v4 frame
 709  
          * @param flags
 710  
          */
 711  
         StatusFlags(byte flags)
 712  3287
         {
 713  3287
             originalFlags = flags;
 714  3287
             writeFlags = flags;
 715  3287
             modifyFlags();
 716  3287
         }
 717  
 
 718  
         /**
 719  
          * Use this constructor when convert a v23 frame
 720  
          * @param statusFlags
 721  
          */
 722  
         StatusFlags(ID3v23Frame.StatusFlags statusFlags)
 723  2981
         {
 724  2981
             originalFlags = convertV3ToV4Flags(statusFlags.getOriginalFlags());
 725  2981
             writeFlags = originalFlags;
 726  2981
             modifyFlags();
 727  2981
         }
 728  
 
 729  
         /**
 730  
          * Convert V3 Flags to equivalent V4 Flags
 731  
          * @param v3Flag
 732  
          * @return
 733  
          */
 734  
         private byte convertV3ToV4Flags(byte v3Flag)
 735  
         {
 736  2981
             byte v4Flag = (byte) 0;
 737  2981
             if ((v3Flag & ID3v23Frame.StatusFlags.MASK_FILE_ALTER_PRESERVATION) != 0)
 738  
             {
 739  94
                 v4Flag |= (byte) MASK_FILE_ALTER_PRESERVATION;
 740  
             }
 741  2981
             if ((v3Flag & ID3v23Frame.StatusFlags.MASK_TAG_ALTER_PRESERVATION) != 0)
 742  
             {
 743  12
                 v4Flag |= (byte) MASK_TAG_ALTER_PRESERVATION;
 744  
             }
 745  2981
             return v4Flag;
 746  
         }
 747  
 
 748  
         /**
 749  
          * Makes modifications to flags based on specification and frameid
 750  
          */
 751  
         protected void modifyFlags()
 752  
         {
 753  6268
             String str = getIdentifier();
 754  6268
             if (ID3v24Frames.getInstanceOf().isDiscardIfFileAltered(str))
 755  
             {
 756  33
                 writeFlags |= (byte) MASK_FILE_ALTER_PRESERVATION;
 757  33
                 writeFlags &= (byte) ~MASK_TAG_ALTER_PRESERVATION;
 758  
             }
 759  
             else
 760  
             {
 761  6235
                 writeFlags &= (byte) ~MASK_FILE_ALTER_PRESERVATION;
 762  6235
                 writeFlags &= (byte) ~MASK_TAG_ALTER_PRESERVATION;
 763  
             }
 764  6268
         }
 765  
 
 766  
         public void createStructure()
 767  
         {
 768  380
             MP3File.getStructureFormatter().openHeadingElement(TYPE_FLAGS, "");
 769  380
             MP3File.getStructureFormatter().addElement(TYPE_TAGALTERPRESERVATION, originalFlags & MASK_TAG_ALTER_PRESERVATION);
 770  380
             MP3File.getStructureFormatter().addElement(TYPE_FILEALTERPRESERVATION, originalFlags & MASK_FILE_ALTER_PRESERVATION);
 771  380
             MP3File.getStructureFormatter().addElement(TYPE_READONLY, originalFlags & MASK_READ_ONLY);
 772  380
             MP3File.getStructureFormatter().closeHeadingElement(TYPE_FLAGS);
 773  380
         }
 774  
     }
 775  
 
 776  
     /**
 777  
      * This represents a frame headers Encoding Flags
 778  
      */
 779  
     class EncodingFlags extends AbstractID3v2Frame.EncodingFlags
 780  
     {
 781  
         public static final String TYPE_COMPRESSION = "compression";
 782  
         public static final String TYPE_ENCRYPTION = "encryption";
 783  
         public static final String TYPE_GROUPIDENTITY = "groupidentity";
 784  
         public static final String TYPE_FRAMEUNSYNCHRONIZATION = "frameUnsynchronisation";
 785  
         public static final String TYPE_DATALENGTHINDICATOR = "dataLengthIndicator";
 786  
 
 787  
         /**
 788  
          * Frame is part of a group
 789  
          */
 790  
         public static final int MASK_GROUPING_IDENTITY = FileConstants.BIT6;
 791  
 
 792  
         /**
 793  
          * Frame is compressed
 794  
          */
 795  
         public static final int MASK_COMPRESSION = FileConstants.BIT3;
 796  
 
 797  
         /**
 798  
          * Frame is encrypted
 799  
          */
 800  
         public static final int MASK_ENCRYPTION = FileConstants.BIT2;
 801  
 
 802  
         /**
 803  
          * Unsynchronisation
 804  
          */
 805  
         public static final int MASK_FRAME_UNSYNCHRONIZATION = FileConstants.BIT1;
 806  
 
 807  
         /**
 808  
          * Length
 809  
          */
 810  
         public static final int MASK_DATA_LENGTH_INDICATOR = FileConstants.BIT0;
 811  
 
 812  
         /**
 813  
          * Use this when creating a frame from scratch
 814  
          */
 815  
         EncodingFlags()
 816  1872
         {
 817  1872
             super();
 818  1872
         }
 819  
 
 820  
         /**
 821  
          * Use this when creating a frame from existing flags in another v4 frame
 822  
          * @param flags
 823  
          */
 824  
         EncodingFlags(byte flags)
 825  6268
         {
 826  6268
             super(flags);
 827  6268
             logEnabledFlags();
 828  6268
         }
 829  
 
 830  
         public void logEnabledFlags()
 831  
         {
 832  6268
             if (isCompression())
 833  
             {
 834  0
                 logger.warning(ErrorMessage.MP3_FRAME_IS_COMPRESSED.getMsg(getLoggingFilename(),identifier));
 835  
             }
 836  
 
 837  6268
             if (isEncryption())
 838  
             {
 839  0
                 logger.warning(ErrorMessage.MP3_FRAME_IS_ENCRYPTED.getMsg(getLoggingFilename(),identifier));
 840  
             }
 841  
 
 842  6268
             if (isGrouping())
 843  
             {
 844  0
                 logger.info(ErrorMessage.MP3_FRAME_IS_GROUPED.getMsg(getLoggingFilename(),identifier));
 845  
             }
 846  
 
 847  6268
             if (isUnsynchronised())
 848  
             {
 849  28
                 logger.info(ErrorMessage.MP3_FRAME_IS_UNSYNCHRONISED.getMsg(getLoggingFilename(),identifier));
 850  
             }
 851  
 
 852  6268
             if (isDataLengthIndicator())
 853  
             {
 854  8
                 logger.info(ErrorMessage.MP3_FRAME_IS_DATA_LENGTH_INDICATOR.getMsg(getLoggingFilename(),identifier));
 855  
             }
 856  6268
         }
 857  
 
 858  
         public byte getFlags()
 859  
         {
 860  2519
             return flags;
 861  
         }
 862  
 
 863  
         public boolean isCompression()
 864  
         {
 865  6268
             return (flags & MASK_COMPRESSION) > 0;
 866  
         }
 867  
 
 868  
         public boolean isEncryption()
 869  
         {
 870  9555
             return (flags & MASK_ENCRYPTION) > 0;
 871  
         }
 872  
 
 873  
         public boolean isGrouping()
 874  
         {
 875  9555
             return (flags & MASK_GROUPING_IDENTITY) > 0;
 876  
         }
 877  
 
 878  
         public boolean isUnsynchronised()
 879  
         {
 880  9579
             return (flags & MASK_FRAME_UNSYNCHRONIZATION) > 0;
 881  
         }
 882  
 
 883  
         public boolean isDataLengthIndicator()
 884  
         {
 885  9555
             return (flags & MASK_DATA_LENGTH_INDICATOR) > 0;
 886  
         }
 887  
 
 888  
         public void setUnsynchronised()
 889  
         {
 890  4
             flags |= MASK_FRAME_UNSYNCHRONIZATION;
 891  4
         }
 892  
 
 893  
         public void createStructure()
 894  
         {
 895  380
             MP3File.getStructureFormatter().openHeadingElement(TYPE_FLAGS, "");
 896  380
             MP3File.getStructureFormatter().addElement(TYPE_COMPRESSION, flags & MASK_COMPRESSION);
 897  380
             MP3File.getStructureFormatter().addElement(TYPE_ENCRYPTION, flags & MASK_ENCRYPTION);
 898  380
             MP3File.getStructureFormatter().addElement(TYPE_GROUPIDENTITY, flags & MASK_GROUPING_IDENTITY);
 899  380
             MP3File.getStructureFormatter().addElement(TYPE_FRAMEUNSYNCHRONIZATION, flags & MASK_FRAME_UNSYNCHRONIZATION);
 900  380
             MP3File.getStructureFormatter().addElement(TYPE_DATALENGTHINDICATOR, flags & MASK_DATA_LENGTH_INDICATOR);
 901  380
             MP3File.getStructureFormatter().closeHeadingElement(TYPE_FLAGS);
 902  380
         }
 903  
     }
 904  
 
 905  
     /**
 906  
      * Does the frame identifier meet the syntax for a idv3v2 frame identifier.
 907  
      * must start with a capital letter and only contain capital letters and numbers
 908  
      *
 909  
      * @param identifier to be checked
 910  
      * @return whether the identifier is valid
 911  
      */
 912  
     public boolean isValidID3v2FrameIdentifier(String identifier)
 913  
     {
 914  4038
         Matcher m = ID3v24Frame.validFrameIdentifier.matcher(identifier);
 915  4038
         return m.matches();
 916  
     }
 917  
 
 918  
     /**
 919  
      * Return String Representation of body
 920  
      */
 921  
     public void createStructure()
 922  
     {
 923  380
         MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
 924  380
         MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize);
 925  380
         statusFlags.createStructure();
 926  380
         encodingFlags.createStructure();
 927  380
         frameBody.createStructure();
 928  380
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
 929  380
     }
 930  
 
 931  
     /**
 932  
      * @return true if considered a common frame
 933  
      */
 934  
     public boolean isCommon()
 935  
     {
 936  0
         return ID3v24Frames.getInstanceOf().isCommon(getId());
 937  
     }
 938  
 
 939  
     /**
 940  
      * @return true if considered a common frame
 941  
      */
 942  
     public boolean isBinary()
 943  
     {
 944  0
         return ID3v24Frames.getInstanceOf().isBinary(getId());
 945  
     }
 946  
 }