Coverage Report - org.jaudiotagger.tag.id3.ID3v22Frame
 
Classes in this File Line Coverage Branch Coverage Complexity
ID3v22Frame
73%
105/143
68%
30/44
3.118
 
 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.audio.generic.Utils;
 19  
 import org.jaudiotagger.audio.mp3.MP3File;
 20  
 import org.jaudiotagger.tag.EmptyFrameException;
 21  
 import org.jaudiotagger.tag.InvalidFrameException;
 22  
 import org.jaudiotagger.tag.InvalidFrameIdentifierException;
 23  
 import org.jaudiotagger.tag.id3.framebody.AbstractID3v2FrameBody;
 24  
 import org.jaudiotagger.tag.id3.framebody.FrameBodyDeprecated;
 25  
 import org.jaudiotagger.tag.id3.framebody.FrameBodyUnsupported;
 26  
 
 27  
 import java.io.ByteArrayOutputStream;
 28  
 import java.io.IOException;
 29  
 import java.math.BigInteger;
 30  
 import java.nio.ByteBuffer;
 31  
 import java.util.logging.Level;
 32  
 import java.util.regex.Matcher;
 33  
 import java.util.regex.Pattern;
 34  
 
 35  
 /**
 36  
  * Represents an ID3v2.2 frame.
 37  
  *
 38  
  * @author : Paul Taylor
 39  
  * @author : Eric Farng
 40  
  * @version $Id: ID3v22Frame.java 836 2009-11-12 15:44:07Z paultaylor $
 41  
  */
 42  
 public class ID3v22Frame extends AbstractID3v2Frame
 43  
 {
 44  4
     private static Pattern validFrameIdentifier = Pattern.compile("[A-Z][0-9A-Z]{2}");
 45  
 
 46  
     protected static final int FRAME_ID_SIZE = 3;
 47  
     protected static final int FRAME_SIZE_SIZE = 3;
 48  
     protected static final int FRAME_HEADER_SIZE = FRAME_ID_SIZE + FRAME_SIZE_SIZE;
 49  
 
 50  
     public ID3v22Frame()
 51  0
     {
 52  
 
 53  0
     }
 54  
 
 55  
     /**
 56  
      * Creates a new ID3v22 Frame with given body
 57  
      *
 58  
      * @param body New body and frame is based on this
 59  
      */
 60  
     public ID3v22Frame(AbstractID3v2FrameBody body)
 61  
     {
 62  0
         super(body);
 63  0
     }
 64  
 
 65  
     /**
 66  
      * Creates a new ID3v22 Frame of type identifier.
 67  
      * <p/>
 68  
      * An empty body of the correct type will be automatically created. This constructor should be used when wish to
 69  
      * create a new frame from scratch using user values
 70  
      * @param identifier
 71  
      */
 72  
     @SuppressWarnings("unchecked")
 73  
     public ID3v22Frame(String identifier)
 74  256
     {
 75  
 
 76  256
         logger.info("Creating empty frame of type" + identifier);
 77  256
         String bodyIdentifier = identifier;
 78  256
         this.identifier = identifier;
 79  
 
 80  
         //If dealing with v22 identifier (Note this constructor is used by all three tag versions)
 81  256
         if (ID3Tags.isID3v22FrameIdentifier(bodyIdentifier))
 82  
         {
 83  
             //Does it have its own framebody (PIC,CRM) or are we using v23/v24 body (the normal case)
 84  256
             if (ID3Tags.forceFrameID22To23(bodyIdentifier) != null)
 85  
             {
 86  
                 //Do not convert
 87  
             }
 88  
             //TODO Improve messy fix for datetime
 89  
             //TODO need to check in case v22 body does exist before using V23 body(e.g PIC)
 90  
             else
 91  224
             if ((bodyIdentifier.equals(ID3v22Frames.FRAME_ID_V2_TYER)) || (bodyIdentifier.equals(ID3v22Frames.FRAME_ID_V2_TIME)))
 92  
             {
 93  16
                 bodyIdentifier = ID3v24Frames.FRAME_ID_YEAR;
 94  
             }
 95  
             // Have to check for v22 because most don't have own body they use v23 or v24
 96  
             // body to hold the data, the frame is identified by its identifier, the body identifier
 97  
             // is just to create a body suitable for writing the data to
 98  208
             else if (ID3Tags.isID3v22FrameIdentifier(bodyIdentifier))
 99  
             {
 100  208
                 bodyIdentifier = ID3Tags.convertFrameID22To23(bodyIdentifier);
 101  
             }
 102  
         }
 103  
 
 104  
         // Use reflection to map id to frame body, which makes things much easier
 105  
         // to keep things up to date.
 106  
         try
 107  
         {
 108  256
             Class<AbstractID3v2FrameBody> c = (Class<AbstractID3v2FrameBody>) Class.forName("org.jaudiotagger.tag.id3.framebody.FrameBody" + bodyIdentifier);
 109  256
             frameBody = c.newInstance();
 110  
         }
 111  0
         catch (ClassNotFoundException cnfe)
 112  
         {
 113  0
             logger.log(Level.SEVERE, cnfe.getMessage(), cnfe);
 114  0
             frameBody = new FrameBodyUnsupported(identifier);
 115  
         }
 116  
         //Instantiate Interface/Abstract should not happen
 117  0
         catch (InstantiationException ie)
 118  
         {
 119  0
             logger.log(Level.SEVERE, ie.getMessage(), ie);
 120  0
             throw new RuntimeException(ie);
 121  
         }
 122  
         //Private Constructor shouild not happen
 123  0
         catch (IllegalAccessException iae)
 124  
         {
 125  0
             logger.log(Level.SEVERE, iae.getMessage(), iae);
 126  0
             throw new RuntimeException(iae);
 127  256
         }
 128  256
         frameBody.setHeader(this);
 129  256
         logger.info("Created empty frame of type" + this.identifier + "with frame body of" + bodyIdentifier);
 130  
 
 131  256
     }
 132  
 
 133  
     /**
 134  
      * Copy Constructor
 135  
      * <p/>
 136  
      * Creates a new v22 frame based on another v22 frame
 137  
      * @param frame
 138  
      */
 139  
     public ID3v22Frame(ID3v22Frame frame)
 140  
     {
 141  0
         super(frame);
 142  0
         logger.info("Creating frame from a frame of same version");
 143  0
     }
 144  
 
 145  
     private void createV22FrameFromV23Frame(ID3v23Frame frame) throws InvalidFrameException
 146  
     {
 147  224
         identifier = ID3Tags.convertFrameID23To22(frame.getIdentifier());
 148  224
         if (identifier != null)
 149  
         {
 150  212
             logger.info("V2:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 151  212
             this.frameBody = (AbstractID3v2FrameBody) ID3Tags.copyObject(frame.getBody());
 152  
         }
 153  
         // Is it a known v3 frame which needs forcing to v2 frame e.g. APIC - PIC
 154  12
         else if (ID3Tags.isID3v23FrameIdentifier(frame.getIdentifier()))
 155  
         {
 156  8
             identifier = ID3Tags.forceFrameID23To22(frame.getIdentifier());
 157  8
             if (identifier != null)
 158  
             {
 159  8
                 logger.info("V2:Force:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 160  8
                 this.frameBody = this.readBody(identifier, (AbstractID3v2FrameBody) frame.getBody());
 161  
             }
 162  
             // No mechanism exists to convert it to a v22 frame
 163  
             else
 164  
             {
 165  0
                 throw new InvalidFrameException("Unable to convert v23 frame:" + frame.getIdentifier() + " to a v22 frame");
 166  
             }
 167  
         }
 168  
         //Deprecated frame for v23
 169  4
         else if (frame.getBody() instanceof FrameBodyDeprecated)
 170  
         {
 171  
             //Was it valid for this tag version, if so try and reconstruct
 172  0
             if (ID3Tags.isID3v22FrameIdentifier(frame.getIdentifier()))
 173  
             {
 174  0
                 this.frameBody = frame.getBody();
 175  0
                 identifier = frame.getIdentifier();
 176  0
                 logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 177  
             }
 178  
             //or was it still deprecated, if so leave as is
 179  
             else
 180  
             {
 181  0
                 this.frameBody = new FrameBodyDeprecated((FrameBodyDeprecated) frame.getBody());
 182  0
                 identifier = frame.getIdentifier();
 183  0
                 logger.info("DEPRECATED:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 184  
             }
 185  
         }
 186  
         // Unknown Frame e.g NCON
 187  
         else
 188  
         {
 189  4
             this.frameBody = new FrameBodyUnsupported((FrameBodyUnsupported) frame.getBody());
 190  4
             identifier = frame.getIdentifier();
 191  4
             logger.info("v2:UNKNOWN:Orig id is:" + frame.getIdentifier() + ":New id is:" + identifier);
 192  
         }
 193  224
     }
 194  
 
 195  
     /**
 196  
      * Creates a new ID3v22 Frame from another frame of a different tag version
 197  
      *
 198  
      * @param frame to construct the new frame from
 199  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 200  
      */
 201  
     public ID3v22Frame(AbstractID3v2Frame frame) throws InvalidFrameException
 202  224
     {
 203  224
         logger.info("Creating frame from a frame of a different version");
 204  224
         if (frame instanceof ID3v22Frame)
 205  
         {
 206  0
             throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 207  
         }
 208  
 
 209  
         // If it is a v24 frame is it possible to convert it into a v23 frame, anmd then convert from that
 210  224
         if (frame instanceof ID3v24Frame)
 211  
         {
 212  220
             ID3v23Frame v23Frame = new ID3v23Frame(frame);
 213  220
             createV22FrameFromV23Frame(v23Frame);
 214  220
         }
 215  
         //If it is a v23 frame is it possible to convert it into a v22 frame
 216  4
         else if (frame instanceof ID3v23Frame)
 217  
         {
 218  4
             createV22FrameFromV23Frame((ID3v23Frame) frame);
 219  
         }
 220  224
         this.frameBody.setHeader(this);
 221  224
         logger.info("Created frame from a frame of a different version");
 222  224
     }
 223  
 
 224  
     /**
 225  
      * Creates a new ID3v22Frame datatype by reading from byteBuffer.
 226  
      *
 227  
      * @param byteBuffer to read from
 228  
      * @param loggingFilename
 229  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 230  
      */
 231  
     public ID3v22Frame(ByteBuffer byteBuffer, String loggingFilename) throws InvalidFrameException
 232  1209
     {
 233  1209
         setLoggingFilename(loggingFilename);
 234  1209
         read(byteBuffer);
 235  951
     }
 236  
 
 237  
     /**
 238  
      * Creates a new ID3v23Frame datatype by reading from byteBuffer.
 239  
      *
 240  
      * @param byteBuffer to read from
 241  
      * @deprecated use {@link #ID3v22Frame(ByteBuffer,String)} instead
 242  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 243  
      */
 244  
     public ID3v22Frame(ByteBuffer byteBuffer) throws InvalidFrameException
 245  
     {
 246  0
         this(byteBuffer, "");
 247  0
     }
 248  
 
 249  
     /**
 250  
      * Return size of frame
 251  
      *
 252  
      * @return int size of frame
 253  
      */
 254  
     public int getSize()
 255  
     {
 256  4
         return frameBody.getSize() + FRAME_HEADER_SIZE;
 257  
     }
 258  
 
 259  
     /**
 260  
      * Read frame from file.
 261  
      * Read the frame header then delegate reading of data to frame body.
 262  
      *
 263  
      * @param byteBuffer
 264  
      */
 265  
     public void read(ByteBuffer byteBuffer) throws InvalidFrameException
 266  
     {
 267  1209
         byte[] buffer = new byte[FRAME_ID_SIZE];
 268  
 
 269  1209
         if (byteBuffer.position() + FRAME_HEADER_SIZE >= byteBuffer.limit())
 270  
         {
 271  0
             logger.warning("No space to find another frame:");
 272  0
             throw new InvalidFrameException(" No space to find another frame");
 273  
         }
 274  
 
 275  
         // Read the FrameID Identifier
 276  1209
         byteBuffer.get(buffer, 0, FRAME_ID_SIZE);
 277  1209
         identifier = new String(buffer);
 278  1209
         logger.info("Read Frame from file identifier is:" + identifier);
 279  
 
 280  
         // Is this a valid identifier?
 281  1209
         if (!isValidID3v2FrameIdentifier(identifier))
 282  
         {
 283  253
             logger.info("Invalid identifier:" + identifier);
 284  253
             byteBuffer.position(byteBuffer.position() - (FRAME_ID_SIZE - 1));
 285  253
             throw new InvalidFrameIdentifierException(identifier + " is not a valid ID3v2.20 frame");
 286  
         }
 287  
         //Read Frame Size (same size as Frame Id so reuse buffer)
 288  956
         byteBuffer.get(buffer, 0, FRAME_SIZE_SIZE);
 289  956
         frameSize = decodeSize(buffer);
 290  956
         if (frameSize < 0)
 291  
         {
 292  0
             throw new InvalidFrameException(identifier + " has invalid size of:" + frameSize);
 293  
         }
 294  956
         else if (frameSize == 0)
 295  
         {
 296  
             //We dont process this frame or add to framemap becuase contains no useful information
 297  4
             logger.warning("Empty Frame:" + identifier);
 298  4
             throw new EmptyFrameException(identifier + " is empty frame");
 299  
         }
 300  952
         else if (frameSize > byteBuffer.remaining())
 301  
         {
 302  0
             logger.warning("Invalid Frame size larger than size before mp3 audio:" + identifier);
 303  0
             throw new InvalidFrameException(identifier + " is invalid frame");
 304  
         }
 305  
         else
 306  
         {
 307  952
             logger.fine("Frame Size Is:" + frameSize);
 308  
             //Convert v2.2 to v2.4 id just for reading the data
 309  952
             String id = ID3Tags.convertFrameID22To24(identifier);
 310  952
             if (id == null)
 311  
             {
 312  
                 //OK,it may be convertable to a v.3 id even though not valid v.4
 313  148
                 id = ID3Tags.convertFrameID22To23(identifier);
 314  148
                 if (id == null)
 315  
                 {
 316  
                     // Is it a valid v22 identifier so should be able to find a
 317  
                     // frame body for it.
 318  37
                     if (ID3Tags.isID3v22FrameIdentifier(identifier))
 319  
                     {
 320  37
                         id = identifier;
 321  
                     }
 322  
                     // Unknown so will be created as FrameBodyUnsupported
 323  
                     else
 324  
                     {
 325  0
                         id = UNSUPPORTED_ID;
 326  
                     }
 327  
                 }
 328  
             }
 329  952
             logger.fine("Identifier was:" + identifier + " reading using:" + id);
 330  
 
 331  
             //Create Buffer that only contains the body of this frame rather than the remainder of tag
 332  952
             ByteBuffer frameBodyBuffer = byteBuffer.slice();
 333  952
             frameBodyBuffer.limit(frameSize);
 334  
 
 335  
             try
 336  
             {
 337  952
                 frameBody = readBody(id, frameBodyBuffer, frameSize);
 338  951
             }
 339  
             finally
 340  
             {
 341  
                 //Update position of main buffer, so no attempt is made to reread these bytes
 342  1
                 byteBuffer.position(byteBuffer.position() + frameSize);
 343  951
             }
 344  
         }
 345  951
     }
 346  
 
 347  
     /**
 348  
      * Read Frame Size, which has to be decoded
 349  
      * @param buffer
 350  
      * @return
 351  
      */
 352  
     private int decodeSize(byte[] buffer)
 353  
     {
 354  956
         BigInteger bi = new BigInteger(buffer);
 355  956
         int tmpSize = bi.intValue();
 356  956
         if (tmpSize < 0)
 357  
         {
 358  0
             logger.warning("Invalid Frame Size of:" + tmpSize + "Decoded from bin:" + Integer.toBinaryString(tmpSize) + "Decoded from hex:" + Integer.toHexString(tmpSize));
 359  
         }
 360  956
         return tmpSize;
 361  
     }
 362  
 
 363  
 
 364  
     /**
 365  
      * Write Frame raw data
 366  
      *
 367  
      * @throws IOException
 368  
      */
 369  
     public void write(ByteArrayOutputStream tagBuffer)
 370  
     {
 371  912
         logger.info("Write Frame to Buffer" + getIdentifier());
 372  
         //This is where we will write header, move position to where we can
 373  
         //write body
 374  912
         ByteBuffer headerBuffer = ByteBuffer.allocate(FRAME_HEADER_SIZE);
 375  
 
 376  
         //Write Frame Body Data
 377  912
         ByteArrayOutputStream bodyOutputStream = new ByteArrayOutputStream();
 378  912
         ((AbstractID3v2FrameBody) frameBody).write(bodyOutputStream);
 379  
 
 380  
         //Write Frame Header
 381  
         //Write Frame ID must adjust can only be 3 bytes long
 382  912
         headerBuffer.put(Utils.getDefaultBytes(getIdentifier(), "ISO-8859-1"), 0, FRAME_ID_SIZE);
 383  912
         encodeSize(headerBuffer, frameBody.getSize());
 384  
 
 385  
         //Add header to the Byte Array Output Stream
 386  
         try
 387  
         {
 388  912
             tagBuffer.write(headerBuffer.array());
 389  
 
 390  
             //Add body to the Byte Array Output Stream
 391  912
             tagBuffer.write(bodyOutputStream.toByteArray());
 392  
         }
 393  0
         catch (IOException ioe)
 394  
         {
 395  
             //This could never happen coz not writing to file, so convert to RuntimeException
 396  0
             throw new RuntimeException(ioe);
 397  912
         }
 398  912
     }
 399  
 
 400  
     /**
 401  
      * Write Frame Size (can now be accurately calculated, have to convert 4 byte int
 402  
      * to 3 byte format.
 403  
      * @param headerBuffer
 404  
      * @param size
 405  
      */
 406  
     private void encodeSize(ByteBuffer headerBuffer, int size)
 407  
     {
 408  912
         headerBuffer.put((byte) ((size & 0x00FF0000) >> 16));
 409  912
         headerBuffer.put((byte) ((size & 0x0000FF00) >> 8));
 410  912
         headerBuffer.put((byte) (size & 0x000000FF));
 411  912
         logger.fine("Frame Size Is Actual:" + size + ":Encoded bin:" + Integer.toBinaryString(size) + ":Encoded Hex" + Integer.toHexString(size));
 412  912
     }
 413  
 
 414  
     /**
 415  
      * Does the frame identifier meet the syntax for a idv3v2 frame identifier.
 416  
      * must start with a capital letter and only contain capital letters and numbers
 417  
      *
 418  
      * @param identifier
 419  
      * @return
 420  
      */
 421  
     public boolean isValidID3v2FrameIdentifier(String identifier)
 422  
     {
 423  1209
         Matcher m = ID3v22Frame.validFrameIdentifier.matcher(identifier);
 424  1209
         return m.matches();
 425  
     }
 426  
 
 427  
     /**
 428  
      * Return String Representation of body
 429  
      */
 430  
     public void createStructure()
 431  
     {
 432  48
         MP3File.getStructureFormatter().openHeadingElement(TYPE_FRAME, getIdentifier());
 433  48
         MP3File.getStructureFormatter().addElement(TYPE_FRAME_SIZE, frameSize);
 434  48
         frameBody.createStructure();
 435  48
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_FRAME);
 436  48
     }
 437  
 
 438  
     /**
 439  
      * @return true if considered a common frame
 440  
      */
 441  
     public boolean isCommon()
 442  
     {
 443  0
         return ID3v22Frames.getInstanceOf().isCommon(getId());
 444  
     }
 445  
 
 446  
     /**
 447  
      * @return true if considered a common frame
 448  
      */
 449  
     public boolean isBinary()
 450  
     {
 451  0
         return ID3v22Frames.getInstanceOf().isBinary(getId());
 452  
     }
 453  
 }