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