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