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