Coverage Report - org.jaudiotagger.tag.id3.ID3v22Tag
 
Classes in this File Line Coverage Branch Coverage Complexity
ID3v22Tag
72%
172/238
56%
51/90
2.647
 
 1  
 /*
 2  
  *  MusicTag Copyright (C)2003,2004
 3  
  *
 4  
  *  This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser
 5  
  *  General Public  License as published by the Free Software Foundation; either version 2.1 of the License,
 6  
  *  or (at your option) any later version.
 7  
  *
 8  
  *  This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
 9  
  *  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 10  
  *  See the GNU Lesser General Public License for more details.
 11  
  *
 12  
  *  You should have received a copy of the GNU Lesser General Public License along with this library; if not,
 13  
  *  you can getFields a copy from http://www.opensource.org/licenses/lgpl-license.php or write to the Free Software
 14  
  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 15  
  */
 16  
 package org.jaudiotagger.tag.id3;
 17  
 
 18  
 import org.jaudiotagger.FileConstants;
 19  
 import org.jaudiotagger.audio.mp3.MP3File;
 20  
 import org.jaudiotagger.logging.ErrorMessage;
 21  
 import org.jaudiotagger.tag.*;
 22  
 import org.jaudiotagger.tag.datatype.Artwork;
 23  
 import org.jaudiotagger.tag.datatype.DataTypes;
 24  
 import org.jaudiotagger.tag.id3.framebody.*;
 25  
 import org.jaudiotagger.tag.id3.valuepair.ImageFormats;
 26  
 import org.jaudiotagger.tag.reference.PictureTypes;
 27  
 
 28  
 import java.io.File;
 29  
 import java.io.IOException;
 30  
 import java.nio.ByteBuffer;
 31  
 import java.nio.channels.WritableByteChannel;
 32  
 import java.util.ArrayList;
 33  
 import java.util.Comparator;
 34  
 import java.util.LinkedHashMap;
 35  
 import java.util.List;
 36  
 import java.util.logging.Level;
 37  
 
 38  
 /**
 39  
  * Represents an ID3v2.2 tag.
 40  
  *
 41  
  * @author : Paul Taylor
 42  
  * @author : Eric Farng
 43  
  * @version $Id: ID3v22Tag.java 845 2009-11-13 14:10:57Z paultaylor $
 44  
  */
 45  136
 public class ID3v22Tag extends AbstractID3v2Tag
 46  
 {
 47  
 
 48  
     protected static final String TYPE_COMPRESSION = "compression";
 49  
     protected static final String TYPE_UNSYNCHRONISATION = "unsyncronisation";
 50  
 
 51  
     /**
 52  
      * Bit mask to indicate tag is Unsychronization
 53  
      */
 54  
     public static final int MASK_V22_UNSYNCHRONIZATION = FileConstants.BIT7;
 55  
 
 56  
     /**
 57  
      * Bit mask to indicate tag is compressed, although compression is not
 58  
      * actually defined in v22 so just ignored
 59  
      */
 60  
     public static final int MASK_V22_COMPRESSION = FileConstants.BIT6;
 61  
 
 62  
     /**
 63  
      * The tag is compressed, although no compression scheme is defined in ID3v22
 64  
      */
 65  487
     protected boolean compression = false;
 66  
 
 67  
     /**
 68  
      * If set all frames in the tag uses unsynchronisation
 69  
      */
 70  487
     protected boolean unsynchronization = false;
 71  
 
 72  
     public static final byte RELEASE = 2;
 73  
     public static final byte MAJOR_VERSION = 2;
 74  
     public static final byte REVISION = 0;
 75  
 
 76  
     /**
 77  
      * Retrieve the Release
 78  
      */
 79  
     public byte getRelease()
 80  
     {
 81  12
         return RELEASE;
 82  
     }
 83  
 
 84  
     /**
 85  
      * Retrieve the Major Version
 86  
      */
 87  
     public byte getMajorVersion()
 88  
     {
 89  534
         return MAJOR_VERSION;
 90  
     }
 91  
 
 92  
     /**
 93  
      * Retrieve the Revision
 94  
      */
 95  
     public byte getRevision()
 96  
     {
 97  534
         return REVISION;
 98  
     }
 99  
 
 100  
     /**
 101  
      * Creates a new empty ID3v2_2 tag.
 102  
      */
 103  
     public ID3v22Tag()
 104  129
     {
 105  129
         frameMap = new LinkedHashMap();
 106  129
     }
 107  
 
 108  
     /**
 109  
      * Copy primitives applicable to v2.2
 110  
      */
 111  
     protected void copyPrimitives(AbstractID3v2Tag copyObj)
 112  
     {
 113  80
         logger.info("Copying primitives");
 114  80
         super.copyPrimitives(copyObj);
 115  
 
 116  
         //Set the primitive types specific to v2_2.
 117  80
         if (copyObj instanceof ID3v22Tag)
 118  
         {
 119  0
             ID3v22Tag copyObject = (ID3v22Tag) copyObj;
 120  0
             this.compression = copyObject.compression;
 121  0
             this.unsynchronization = copyObject.unsynchronization;
 122  0
         }
 123  80
         else if (copyObj instanceof ID3v23Tag)
 124  
         {
 125  0
             ID3v23Tag copyObject = (ID3v23Tag) copyObj;
 126  0
             this.compression = copyObject.compression;
 127  0
             this.unsynchronization = copyObject.unsynchronization;
 128  0
         }
 129  80
         else if (copyObj instanceof ID3v24Tag)
 130  
         {
 131  80
             ID3v24Tag copyObject = (ID3v24Tag) copyObj;
 132  80
             this.compression = false;
 133  80
             this.unsynchronization = copyObject.unsynchronization;
 134  
         }
 135  80
     }
 136  
 
 137  
 
 138  
 
 139  
     /**
 140  
      * Copy Constructor, creates a new ID3v2_2 Tag based on another ID3v2_2 Tag
 141  
      * @param copyObject
 142  
      */
 143  
     public ID3v22Tag(ID3v22Tag copyObject)
 144  
     {
 145  
         //This doesnt do anything.
 146  0
         super(copyObject);
 147  0
         logger.info("Creating tag from another tag of same type");
 148  0
         copyPrimitives(copyObject);
 149  0
         copyFrames(copyObject);
 150  0
     }
 151  
 
 152  
     /**
 153  
      * Constructs a new tag based upon another tag of different version/type
 154  
      * @param mp3tag
 155  
      */
 156  
     public ID3v22Tag(AbstractTag mp3tag)
 157  80
     {
 158  80
         frameMap = new LinkedHashMap();
 159  80
         logger.info("Creating tag from a tag of a different version");
 160  
         //Default Superclass constructor does nothing
 161  80
         if (mp3tag != null)
 162  
         {
 163  
             ID3v24Tag convertedTag;
 164  
             //Should use the copy constructor instead
 165  80
             if ((!(mp3tag instanceof ID3v23Tag)) && (mp3tag instanceof ID3v22Tag))
 166  
             {
 167  0
                 throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 168  
             }
 169  
             //If v2.4 can getFields variables from this
 170  80
             else if (mp3tag instanceof ID3v24Tag)
 171  
             {
 172  36
                 convertedTag = (ID3v24Tag) mp3tag;
 173  
             }
 174  
             //Any tag (e.g lyrics3 and idv1.1,idv2.3 can be converted to id32.4 so do that
 175  
             //to simplify things
 176  
             else
 177  
             {
 178  44
                 convertedTag = new ID3v24Tag(mp3tag);
 179  
             }
 180  
             //Set the primitive types specific to v2_2.
 181  80
             copyPrimitives(convertedTag);
 182  
             //Set v2.2 Frames
 183  80
             copyFrames(convertedTag);
 184  80
             logger.info("Created tag from a tag of a different version");
 185  
         }
 186  80
     }
 187  
 
 188  
     /**
 189  
      * Creates a new ID3v2_2 datatype.
 190  
      *
 191  
      * @param buffer
 192  
      * @param loggingFilename
 193  
      * @throws TagException
 194  
      */
 195  
     public ID3v22Tag(ByteBuffer buffer, String loggingFilename) throws TagException
 196  278
     {
 197  278
         setLoggingFilename(loggingFilename);
 198  278
         this.read(buffer);
 199  266
     }
 200  
 
 201  
 
 202  
     /**
 203  
      * Creates a new ID3v2_2 datatype.
 204  
      *
 205  
      * @param buffer
 206  
      * @throws TagException
 207  
      * @deprecated use {@link #ID3v22Tag(ByteBuffer,String)} instead
 208  
      */
 209  
     public ID3v22Tag(ByteBuffer buffer) throws TagException
 210  
     {
 211  0
         this(buffer, "");
 212  0
     }
 213  
 
 214  
     /**
 215  
      * @return an indentifier of the tag type
 216  
      */
 217  
     public String getIdentifier()
 218  
     {
 219  12
         return "ID3v2_2.20";
 220  
     }
 221  
 
 222  
     /**
 223  
      * Return frame size based upon the sizes of the frames rather than the size
 224  
      * including padding recorded in the tag header
 225  
      *
 226  
      * @return size
 227  
      */
 228  
     public int getSize()
 229  
     {
 230  0
         int size = TAG_HEADER_LENGTH;
 231  0
         size += super.getSize();
 232  0
         return size;
 233  
     }
 234  
 
 235  
 
 236  
     /**
 237  
      * @param obj
 238  
      * @return equality
 239  
      */
 240  
     public boolean equals(Object obj)
 241  
     {
 242  0
         if (!(obj instanceof ID3v22Tag))
 243  
         {
 244  0
             return false;
 245  
         }
 246  0
         ID3v22Tag object = (ID3v22Tag) obj;
 247  0
         if (this.compression != object.compression)
 248  
         {
 249  0
             return false;
 250  
         }
 251  0
         return this.unsynchronization == object.unsynchronization && super.equals(obj);
 252  
     }
 253  
 
 254  
 
 255  
     protected void addFrame(AbstractID3v2Frame frame)
 256  
     {
 257  
         try
 258  
         {
 259  
             //Special case to handle TDRC frame from V24 that needs breaking up into seperate frame in V23
 260  228
             if ((frame.getIdentifier().equals(ID3v24Frames.FRAME_ID_YEAR)) && (frame.getBody() instanceof FrameBodyTDRC))
 261  
             {
 262  12
                 translateFrame(frame);
 263  
             }
 264  
             else
 265  
             {
 266  216
                 ID3v22Frame newFrame = new ID3v22Frame(frame);
 267  216
                 copyFrameIntoMap(newFrame.getIdentifier(), newFrame);
 268  
             }
 269  
         }
 270  0
         catch (InvalidFrameException ife)
 271  
         {
 272  0
             logger.log(Level.SEVERE, "Unable to convert frame:" + frame.getIdentifier());
 273  228
         }
 274  228
     }
 275  
     /**
 276  
      * Read the size of a tag, based on  the value written in the tag header
 277  
      *
 278  
      * @param buffer
 279  
      * @return
 280  
      * @throws TagException
 281  
      */
 282  
     public int readSize(ByteBuffer buffer)
 283  
     {
 284  
         //Skip over flags
 285  0
         byte flags = buffer.get();
 286  
 
 287  
         // Read the size, this is size of tag not including  the tag header
 288  0
         int size = ID3SyncSafeInteger.bufferToValue(buffer);
 289  
 
 290  
         //Return the exact size of tag as setField in the tag header
 291  0
         return size + TAG_HEADER_LENGTH;
 292  
     }
 293  
 
 294  
     /**
 295  
      * Read tag Header Flags
 296  
      *
 297  
      * @param byteBuffer
 298  
      * @throws TagException
 299  
      */
 300  
     private void readHeaderFlags(ByteBuffer byteBuffer) throws TagException
 301  
     {
 302  
         //Flags
 303  266
         byte flags = byteBuffer.get();
 304  266
         unsynchronization = (flags & MASK_V22_UNSYNCHRONIZATION) != 0;
 305  266
         compression = (flags & MASK_V22_COMPRESSION) != 0;
 306  
 
 307  266
         if (unsynchronization)
 308  
         {
 309  24
             logger.info(ErrorMessage.ID3_TAG_UNSYNCHRONIZED.getMsg(getLoggingFilename()));
 310  
         }
 311  
 
 312  266
         if (compression)
 313  
         {
 314  8
             logger.info(ErrorMessage.ID3_TAG_COMPRESSED.getMsg(getLoggingFilename()));           
 315  
         }
 316  
 
 317  
         //Not allowable/Unknown Flags
 318  266
         if ((flags & FileConstants.BIT5) != 0)
 319  
         {
 320  0
             logger.warning(ErrorMessage.ID3_INVALID_OR_UNKNOWN_FLAG_SET.getMsg(getLoggingFilename(), FileConstants.BIT5));
 321  
         }
 322  266
         if ((flags & FileConstants.BIT4) != 0)
 323  
         {
 324  0
             logger.warning(ErrorMessage.ID3_INVALID_OR_UNKNOWN_FLAG_SET.getMsg(getLoggingFilename(), FileConstants.BIT4));
 325  
         }
 326  266
         if ((flags & FileConstants.BIT3) != 0)
 327  
         {
 328  0
             logger.warning(ErrorMessage.ID3_INVALID_OR_UNKNOWN_FLAG_SET.getMsg(getLoggingFilename(), FileConstants.BIT3));
 329  
         }
 330  266
         if ((flags & FileConstants.BIT2) != 0)
 331  
         {
 332  0
             logger.warning(ErrorMessage.ID3_INVALID_OR_UNKNOWN_FLAG_SET.getMsg(getLoggingFilename(), FileConstants.BIT2));
 333  
         }
 334  266
         if ((flags & FileConstants.BIT1) != 0)
 335  
         {
 336  0
             logger.warning(ErrorMessage.ID3_INVALID_OR_UNKNOWN_FLAG_SET.getMsg(getLoggingFilename(), FileConstants.BIT1));
 337  
         }
 338  266
         if ((flags & FileConstants.BIT0) != 0)
 339  
         {
 340  0
             logger.warning(ErrorMessage.ID3_INVALID_OR_UNKNOWN_FLAG_SET.getMsg(getLoggingFilename(), FileConstants.BIT3));
 341  
         }
 342  266
     }
 343  
 
 344  
     /**
 345  
      * {@inheritDoc}
 346  
      */
 347  
     @Override
 348  
     public void read(ByteBuffer byteBuffer) throws TagException
 349  
     {
 350  
         int size;
 351  278
         if (!seek(byteBuffer))
 352  
         {
 353  12
             throw new TagNotFoundException("ID3v2.20 tag not found");
 354  
         }
 355  266
         logger.info(getLoggingFilename() + ":" + "Reading tag from file");
 356  
 
 357  
         //Read the flags
 358  266
         readHeaderFlags(byteBuffer);
 359  
 
 360  
         // Read the size
 361  266
         size = ID3SyncSafeInteger.bufferToValue(byteBuffer);
 362  
 
 363  
         //Slice Buffer, so position markers tally with size (i.e do not include tagheader)
 364  266
         ByteBuffer bufferWithoutHeader = byteBuffer.slice();
 365  
 
 366  
         //We need to synchronize the buffer
 367  266
         if (unsynchronization)
 368  
         {
 369  24
             bufferWithoutHeader = ID3Unsynchronization.synchronize(bufferWithoutHeader);
 370  
         }
 371  266
         readFrames(bufferWithoutHeader, size);
 372  266
         logger.info(getLoggingFilename() + ":" + "Loaded Frames,there are:" + frameMap.keySet().size());
 373  266
     }
 374  
 
 375  
     /**
 376  
      * Read frames from tag
 377  
      * @param byteBuffer
 378  
      * @param size
 379  
      */
 380  
     protected void readFrames(ByteBuffer byteBuffer, int size)
 381  
     {
 382  
         //Now start looking for frames
 383  
         ID3v22Frame next;
 384  266
         frameMap = new LinkedHashMap();
 385  
         //Read the size from the Tag Header
 386  266
         this.fileReadSize = size;
 387  266
         logger.finest(getLoggingFilename() + ":" + "Start of frame body at:" + byteBuffer.position() + ",frames sizes and padding is:" + size);
 388  
         /* todo not done yet. Read the first Frame, there seems to be quite a
 389  
          ** common case of extra data being between the tag header and the first
 390  
          ** frame so should we allow for this when reading first frame, but not subsequent frames
 391  
          */
 392  
         // Read the frames until got to upto the size as specified in header
 393  1221
         while (byteBuffer.position() < size)
 394  
         {
 395  
             try
 396  
             {
 397  
                 //Read Frame
 398  1209
                 logger.finest(getLoggingFilename() + ":" + "looking for next frame at:" + byteBuffer.position());
 399  1209
                 next = new ID3v22Frame(byteBuffer, getLoggingFilename());
 400  951
                 String id = next.getIdentifier();
 401  951
                 loadFrameIntoMap(id, next);
 402  
             }
 403  
             //Found Empty Frame
 404  4
             catch (EmptyFrameException ex)
 405  
             {
 406  4
                 logger.warning(getLoggingFilename() + ":" + "Empty Frame:" + ex.getMessage());
 407  4
                 this.emptyFrameBytes += ID3v22Frame.FRAME_HEADER_SIZE;
 408  
             }
 409  253
             catch (InvalidFrameIdentifierException ifie)
 410  
             {
 411  253
                 logger.info(getLoggingFilename() + ":" + "Invalid Frame Identifier:" + ifie.getMessage());
 412  253
                 this.invalidFrameBytes++;
 413  
                 //Dont try and find any more frames
 414  253
                 break;
 415  
             }
 416  
             //Problem trying to find frame
 417  1
             catch (InvalidFrameException ife)
 418  
             {
 419  1
                 logger.warning(getLoggingFilename() + ":" + "Invalid Frame:" + ife.getMessage());
 420  1
                 this.invalidFrameBytes++;
 421  
                 //Dont try and find any more frames
 422  1
                 break;
 423  955
             }
 424  
             }
 425  266
     }
 426  
 
 427  
     /**
 428  
      * This is used when we need to translate a single frame into multiple frames,
 429  
      * currently required for TDRC frames.
 430  
      * @param frame
 431  
      */
 432  
     //TODO will overwrite any existing TYER or TIME frame, do we ever want multiples of these
 433  
     protected void translateFrame(AbstractID3v2Frame frame)
 434  
     {
 435  12
         FrameBodyTDRC tmpBody = (FrameBodyTDRC) frame.getBody();
 436  
         ID3v22Frame newFrame;
 437  12
         if (tmpBody.getYear().length() != 0)
 438  
         {
 439  
             //Create Year frame (v2.2 id,but uses v2.3 body)
 440  12
             newFrame = new ID3v22Frame(ID3v22Frames.FRAME_ID_V2_TYER);
 441  12
             ((AbstractFrameBodyTextInfo) newFrame.getBody()).setText(tmpBody.getYear());
 442  12
             frameMap.put(newFrame.getIdentifier(), newFrame);
 443  
         }
 444  12
         if (tmpBody.getTime().length() != 0)
 445  
         {
 446  
             //Create Time frame (v2.2 id,but uses v2.3 body)
 447  0
             newFrame = new ID3v22Frame(ID3v22Frames.FRAME_ID_V2_TIME);
 448  0
             ((AbstractFrameBodyTextInfo) newFrame.getBody()).setText(tmpBody.getTime());
 449  0
             frameMap.put(newFrame.getIdentifier(), newFrame);
 450  
         }
 451  12
     }
 452  
 
 453  
 
 454  
     /**
 455  
      * Write the ID3 header to the ByteBuffer.
 456  
      * <p/>
 457  
      *
 458  
      * @param padding
 459  
      * @param size
 460  
      * @return ByteBuffer
 461  
      * @throws IOException
 462  
      */
 463  
     private ByteBuffer writeHeaderToBuffer(int padding, int size) throws IOException
 464  
     {
 465  252
         compression = false;
 466  
 
 467  
         //Create Header Buffer
 468  252
         ByteBuffer headerBuffer = ByteBuffer.allocate(TAG_HEADER_LENGTH);
 469  
 
 470  
         //TAGID
 471  252
         headerBuffer.put(TAG_ID);
 472  
         //Major Version
 473  252
         headerBuffer.put(getMajorVersion());
 474  
         //Minor Version
 475  252
         headerBuffer.put(getRevision());
 476  
 
 477  
         //Flags
 478  252
         byte flags = (byte) 0;
 479  252
         if (unsynchronization)
 480  
         {
 481  16
             flags |= (byte) MASK_V22_UNSYNCHRONIZATION;
 482  
         }
 483  252
         if (compression)
 484  
         {
 485  0
             flags |= (byte) MASK_V22_COMPRESSION;
 486  
         }
 487  
 
 488  252
         headerBuffer.put(flags);
 489  
 
 490  
         //Size As Recorded in Header, don't include the main header length
 491  252
         headerBuffer.put(ID3SyncSafeInteger.valueToBuffer(padding + size));
 492  252
         headerBuffer.flip();
 493  252
         return headerBuffer;
 494  
     }
 495  
 
 496  
     /**
 497  
      * {@inheritDoc}
 498  
      */
 499  
     @Override
 500  
     public void write(File file, long audioStartLocation) throws IOException
 501  
     {
 502  252
         logger.info("Writing tag to file");
 503  
 
 504  
         // Write Body Buffer
 505  252
         byte[] bodyByteBuffer = writeFramesToBuffer().toByteArray();
 506  
 
 507  
         // Unsynchronize if option enabled and unsync required
 508  252
         unsynchronization = TagOptionSingleton.getInstance().isUnsyncTags() && ID3Unsynchronization.requiresUnsynchronization(bodyByteBuffer);
 509  252
         if (isUnsynchronization())
 510  
         {
 511  16
             bodyByteBuffer = ID3Unsynchronization.unsynchronize(bodyByteBuffer);
 512  16
             logger.info(getLoggingFilename() + ":bodybytebuffer:sizeafterunsynchronisation:" + bodyByteBuffer.length);
 513  
         }
 514  
 
 515  252
         int sizeIncPadding = calculateTagSize(bodyByteBuffer.length + TAG_HEADER_LENGTH, (int) audioStartLocation);
 516  252
         int padding = sizeIncPadding - (bodyByteBuffer.length + TAG_HEADER_LENGTH);
 517  252
         logger.info(getLoggingFilename() + ":Current audiostart:" + audioStartLocation);
 518  252
         logger.info(getLoggingFilename() + ":Size including padding:" + sizeIncPadding);
 519  252
         logger.info(getLoggingFilename() + ":Padding:" + padding);
 520  
 
 521  252
         ByteBuffer headerBuffer = writeHeaderToBuffer(padding, bodyByteBuffer.length);
 522  252
         writeBufferToFile(file,headerBuffer, bodyByteBuffer,padding,sizeIncPadding,audioStartLocation);
 523  252
     }
 524  
 
 525  
 
 526  
     /**
 527  
      * {@inheritDoc}
 528  
      */
 529  
     @Override
 530  
     public void write(WritableByteChannel channel) throws IOException
 531  
     {
 532  0
         logger.info(getLoggingFilename() + ":Writing tag to channel");
 533  
 
 534  0
         byte[] bodyByteBuffer = writeFramesToBuffer().toByteArray();
 535  0
         logger.info(getLoggingFilename() + ":bodybytebuffer:sizebeforeunsynchronisation:" + bodyByteBuffer.length);
 536  
 
 537  
         //Unsynchronize if option enabled and unsync required
 538  0
         unsynchronization = TagOptionSingleton.getInstance().isUnsyncTags() && ID3Unsynchronization.requiresUnsynchronization(bodyByteBuffer);
 539  0
         if (isUnsynchronization())
 540  
         {
 541  0
             bodyByteBuffer = ID3Unsynchronization.unsynchronize(bodyByteBuffer);
 542  0
             logger.info(getLoggingFilename() + ":bodybytebuffer:sizeafterunsynchronisation:" + bodyByteBuffer.length);
 543  
         }
 544  0
         ByteBuffer headerBuffer = writeHeaderToBuffer(0, bodyByteBuffer.length);
 545  
 
 546  0
         channel.write(headerBuffer);
 547  0
         channel.write(ByteBuffer.wrap(bodyByteBuffer));
 548  0
     }
 549  
 
 550  
     public void createStructure()
 551  
     {
 552  12
         MP3File.getStructureFormatter().openHeadingElement(TYPE_TAG, getIdentifier());
 553  
 
 554  12
         super.createStructureHeader();
 555  
 
 556  
         //Header
 557  12
         MP3File.getStructureFormatter().openHeadingElement(TYPE_HEADER, "");
 558  12
         MP3File.getStructureFormatter().addElement(TYPE_COMPRESSION, this.compression);
 559  12
         MP3File.getStructureFormatter().addElement(TYPE_UNSYNCHRONISATION, this.unsynchronization);
 560  12
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_HEADER);
 561  
         //Body
 562  12
         super.createStructureBody();
 563  
 
 564  12
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_TAG);
 565  12
     }
 566  
 
 567  
     /**
 568  
      * @return is tag unsynchronized
 569  
      */
 570  
     public boolean isUnsynchronization()
 571  
     {
 572  296
         return unsynchronization;
 573  
     }
 574  
 
 575  
     /**
 576  
      * @return is tag compressed
 577  
      */
 578  
     public boolean isCompression()
 579  
     {
 580  36
         return compression;
 581  
     }
 582  
 
 583  
     /**
 584  
      * Create Frame
 585  
      *
 586  
      * @param id frameid
 587  
      * @return
 588  
      */
 589  
     public ID3v22Frame createFrame(String id)
 590  
     {
 591  148
         return new ID3v22Frame(id);
 592  
     }
 593  
 
 594  
 
 595  
     /**
 596  
      * Create Frame for Id3 Key
 597  
      * <p/>
 598  
      * Only textual data supported at the moment, should only be used with frames that
 599  
      * support a simple string argument.
 600  
      *
 601  
      * @param id3Key
 602  
      * @param value
 603  
      * @return
 604  
      * @throws KeyNotFoundException
 605  
      * @throws FieldDataInvalidException
 606  
      */
 607  
     public TagField createField(ID3v22FieldKey id3Key, String value) throws KeyNotFoundException, FieldDataInvalidException
 608  
     {
 609  0
         if (id3Key == null)
 610  
         {
 611  0
             throw new KeyNotFoundException();
 612  
         }
 613  0
         return super.doCreateTagField(new FrameAndSubId(id3Key.getFrameId(), id3Key.getSubId()), value);
 614  
     }
 615  
 
 616  
     /**
 617  
      * Retrieve the first value that exists for this id3v22key
 618  
      *
 619  
      * @param id3v22FieldKey
 620  
      * @return
 621  
      * @throws org.jaudiotagger.tag.KeyNotFoundException
 622  
      */
 623  
     public String getFirst(ID3v22FieldKey id3v22FieldKey) throws KeyNotFoundException
 624  
     {
 625  48
         if (id3v22FieldKey == null)
 626  
         {
 627  0
             throw new KeyNotFoundException();
 628  
         }
 629  
 
 630  48
         FrameAndSubId frameAndSubId = new FrameAndSubId(id3v22FieldKey.getFrameId(), id3v22FieldKey.getSubId());
 631  48
         if (id3v22FieldKey == ID3v22FieldKey.TRACK)
 632  
         {
 633  4
             AbstractID3v2Frame frame = getFirstField(frameAndSubId.getFrameId());
 634  4
             return String.valueOf(((FrameBodyTRCK)frame.getBody()).getTrackNo());
 635  
         }
 636  44
         else if (id3v22FieldKey == ID3v22FieldKey.TRACK_TOTAL)
 637  
         {
 638  0
             AbstractID3v2Frame frame = getFirstField(frameAndSubId.getFrameId());
 639  0
             return String.valueOf(((FrameBodyTRCK)frame.getBody()).getTrackTotal());
 640  
         }
 641  44
         else if (id3v22FieldKey == ID3v22FieldKey.DISC_NO)
 642  
         {
 643  0
             AbstractID3v2Frame frame = getFirstField(frameAndSubId.getFrameId());
 644  0
             return String.valueOf(((FrameBodyTPOS)frame.getBody()).getDiscNo());
 645  
         }
 646  44
         else if (id3v22FieldKey == ID3v22FieldKey.DISC_TOTAL)
 647  
         {
 648  0
             AbstractID3v2Frame frame = getFirstField(frameAndSubId.getFrameId());
 649  0
             return String.valueOf(((FrameBodyTPOS)frame.getBody()).getDiscTotal());
 650  
         }
 651  
         else
 652  
         {
 653  44
             return super.doGetFirst(frameAndSubId);
 654  
         }
 655  
     }
 656  
 
 657  
     /**
 658  
      * Delete fields with this id3v22FieldKey
 659  
      *
 660  
      * @param id3v22FieldKey
 661  
      * @throws org.jaudiotagger.tag.KeyNotFoundException
 662  
      */
 663  
     public void deleteField(ID3v22FieldKey id3v22FieldKey) throws KeyNotFoundException
 664  
     {
 665  0
         if (id3v22FieldKey == null)
 666  
         {
 667  0
             throw new KeyNotFoundException();
 668  
         }
 669  0
         super.doDeleteTagField(new FrameAndSubId(id3v22FieldKey.getFrameId(), id3v22FieldKey.getSubId()));
 670  0
     }
 671  
 
 672  
 
 673  
     protected FrameAndSubId getFrameAndSubIdFromGenericKey(FieldKey genericKey)
 674  
     {
 675  565
         ID3v22FieldKey id3v22FieldKey = ID3v22Frames.getInstanceOf().getId3KeyFromGenericKey(genericKey);
 676  565
         if (id3v22FieldKey == null)
 677  
         {
 678  0
             throw new KeyNotFoundException();
 679  
         }
 680  565
         return new FrameAndSubId(id3v22FieldKey.getFrameId(), id3v22FieldKey.getSubId());
 681  
     }
 682  
 
 683  
     protected ID3Frames getID3Frames()
 684  
     {
 685  64
         return ID3v22Frames.getInstanceOf();
 686  
     }
 687  
 
 688  
     /**
 689  
      *
 690  
      * @return comparator used to order frames in preffrred order for writing to file
 691  
      * so that most important frames are written first.
 692  
      */
 693  
     public Comparator getPreferredFrameOrderComparator()
 694  
     {
 695  252
         return ID3v22PreferredFrameOrderComparator.getInstanceof();
 696  
     }
 697  
 
 698  
     /**
 699  
      * {@inheritDoc}
 700  
      */
 701  
     public List<Artwork> getArtworkList()
 702  
     {
 703  24
         List<TagField> coverartList = getFields(FieldKey.COVER_ART);
 704  24
         List<Artwork> artworkList   = new ArrayList<Artwork>(coverartList.size());
 705  
 
 706  24
         for(TagField next:coverartList)
 707  
         {
 708  12
             FrameBodyPIC coverArt = (FrameBodyPIC) ((AbstractID3v2Frame) next).getBody();
 709  12
             Artwork artwork = new Artwork();
 710  12
             artwork.setMimeType(ImageFormats.getMimeTypeForFormat(coverArt.getFormatType()));
 711  12
             artwork.setPictureType(coverArt.getPictureType());
 712  12
             artwork.setBinaryData(coverArt.getImageData());
 713  12
             artworkList.add(artwork);
 714  12
         }
 715  24
         return artworkList;
 716  
     }
 717  
 
 718  
      /**
 719  
      * {@inheritDoc}
 720  
      */
 721  
     public TagField createField(Artwork artwork) throws FieldDataInvalidException
 722  
     {
 723  4
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(FieldKey.COVER_ART).getFrameId());
 724  4
         FrameBodyPIC body = (FrameBodyPIC) frame.getBody();
 725  4
         body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, artwork.getBinaryData());
 726  4
         body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, artwork.getPictureType());
 727  4
         body.setObjectValue(DataTypes.OBJ_IMAGE_FORMAT, ImageFormats.getFormatForMimeType(artwork.getMimeType()));
 728  4
         body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 729  4
         return frame;
 730  
     }
 731  
 
 732  
      public TagField createArtworkField(byte[] data, String mimeType)
 733  
     {
 734  8
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(FieldKey.COVER_ART).getFrameId());
 735  8
         FrameBodyPIC body = (FrameBodyPIC) frame.getBody();
 736  8
         body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, data);
 737  8
         body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
 738  8
         body.setObjectValue(DataTypes.OBJ_IMAGE_FORMAT, ImageFormats.getFormatForMimeType(mimeType));
 739  8
         body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 740  8
         return frame;
 741  
     }
 742  
 }