Coverage Report - org.jaudiotagger.tag.id3.ID3v24Tag
 
Classes in this File Line Coverage Branch Coverage Complexity
ID3v24Tag
60%
242/401
32%
52/160
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.mp4.field.Mp4TagCoverField;
 26  
 import org.jaudiotagger.tag.datatype.Artwork;
 27  
 import org.jaudiotagger.tag.datatype.DataTypes;
 28  
 import org.jaudiotagger.tag.id3.framebody.*;
 29  
 import org.jaudiotagger.tag.id3.valuepair.ImageFormats;
 30  
 import org.jaudiotagger.tag.lyrics3.AbstractLyrics3;
 31  
 import org.jaudiotagger.tag.lyrics3.Lyrics3v2;
 32  
 import org.jaudiotagger.tag.lyrics3.Lyrics3v2Field;
 33  
 import org.jaudiotagger.tag.reference.GenreTypes;
 34  
 import org.jaudiotagger.tag.reference.PictureTypes;
 35  
 
 36  
 import java.io.File;
 37  
 import java.io.IOException;
 38  
 import java.io.RandomAccessFile;
 39  
 import java.io.FileNotFoundException;
 40  
 import java.nio.ByteBuffer;
 41  
 import java.nio.channels.FileChannel;
 42  
 import java.nio.channels.FileLock;
 43  
 import java.nio.channels.WritableByteChannel;
 44  
 import java.util.*;
 45  
 import java.util.logging.Level;
 46  
 
 47  
 /**
 48  
  * Represents an ID3v2.4 tag.
 49  
  *
 50  
  * @author : Paul Taylor
 51  
  * @author : Eric Farng
 52  
  * @version $Id: ID3v24Tag.java,v 1.50 2008/11/13 18:25:10 paultaylor Exp $
 53  
  */
 54  48
 public class ID3v24Tag extends AbstractID3v2Tag
 55  
 {
 56  
     protected static final String TYPE_FOOTER = "footer";
 57  
     protected static final String TYPE_IMAGEENCODINGRESTRICTION = "imageEncodingRestriction";
 58  
     protected static final String TYPE_IMAGESIZERESTRICTION = "imageSizeRestriction";
 59  
     protected static final String TYPE_TAGRESTRICTION = "tagRestriction";
 60  
     protected static final String TYPE_TAGSIZERESTRICTION = "tagSizeRestriction";
 61  
     protected static final String TYPE_TEXTENCODINGRESTRICTION = "textEncodingRestriction";
 62  
     protected static final String TYPE_TEXTFIELDSIZERESTRICTION = "textFieldSizeRestriction";
 63  
     protected static final String TYPE_UPDATETAG = "updateTag";
 64  
     protected static final String TYPE_CRCDATA = "crcdata";
 65  
     protected static final String TYPE_EXPERIMENTAL = "experimental";
 66  
     protected static final String TYPE_EXTENDED = "extended";
 67  
     protected static final String TYPE_PADDINGSIZE = "paddingsize";
 68  
     protected static final String TYPE_UNSYNCHRONISATION = "unsyncronisation";
 69  
 
 70  
 
 71  50
     protected static int TAG_EXT_HEADER_LENGTH = 6;
 72  50
     protected static int TAG_EXT_HEADER_UPDATE_LENGTH = 1;
 73  50
     protected static int TAG_EXT_HEADER_CRC_LENGTH = 6;
 74  50
     protected static int TAG_EXT_HEADER_RESTRICTION_LENGTH = 2;
 75  50
     protected static int TAG_EXT_HEADER_CRC_DATA_LENGTH = 5;
 76  50
     protected static int TAG_EXT_HEADER_RESTRICTION_DATA_LENGTH = 1;
 77  50
     protected static int TAG_EXT_NUMBER_BYTES_DATA_LENGTH = 1;
 78  
 
 79  
     /**
 80  
      * ID3v2.4 Header bit mask
 81  
      */
 82  
     public static final int MASK_V24_UNSYNCHRONIZATION = FileConstants.BIT7;
 83  
 
 84  
     /**
 85  
      * ID3v2.4 Header bit mask
 86  
      */
 87  
     public static final int MASK_V24_EXTENDED_HEADER = FileConstants.BIT6;
 88  
 
 89  
     /**
 90  
      * ID3v2.4 Header bit mask
 91  
      */
 92  
     public static final int MASK_V24_EXPERIMENTAL = FileConstants.BIT5;
 93  
 
 94  
     /**
 95  
      * ID3v2.4 Header bit mask
 96  
      */
 97  
     public static final int MASK_V24_FOOTER_PRESENT = FileConstants.BIT4;
 98  
 
 99  
     /**
 100  
      * ID3v2.4 Extended header bit mask
 101  
      */
 102  
     public static final int MASK_V24_TAG_UPDATE = FileConstants.BIT6;
 103  
 
 104  
     /**
 105  
      * ID3v2.4 Extended header bit mask
 106  
      */
 107  
     public static final int MASK_V24_CRC_DATA_PRESENT = FileConstants.BIT5;
 108  
 
 109  
     /**
 110  
      * ID3v2.4 Extended header bit mask
 111  
      */
 112  
     public static final int MASK_V24_TAG_RESTRICTIONS = FileConstants.BIT4;
 113  
 
 114  
     /**
 115  
      * ID3v2.4 Extended header bit mask
 116  
      */
 117  
     public static final int MASK_V24_TAG_SIZE_RESTRICTIONS = (byte) FileConstants.BIT7 | FileConstants.BIT6;
 118  
 
 119  
     /**
 120  
      * ID3v2.4 Extended header bit mask
 121  
      */
 122  
     public static final int MASK_V24_TEXT_ENCODING_RESTRICTIONS = FileConstants.BIT5;
 123  
 
 124  
     /**
 125  
      * ID3v2.4 Extended header bit mask
 126  
      */
 127  
     public static final int MASK_V24_TEXT_FIELD_SIZE_RESTRICTIONS = FileConstants.BIT4 | FileConstants.BIT3;
 128  
 
 129  
     /**
 130  
      * ID3v2.4 Extended header bit mask
 131  
      */
 132  
     public static final int MASK_V24_IMAGE_ENCODING = FileConstants.BIT2;
 133  
 
 134  
     /**
 135  
      * ID3v2.4 Extended header bit mask
 136  
      */
 137  
     public static final int MASK_V24_IMAGE_SIZE_RESTRICTIONS = FileConstants.BIT2 | FileConstants.BIT1;
 138  
 
 139  
     /**
 140  
      * ID3v2.4 Header Footer are the same as the header flags. WHY?!?! move the
 141  
      * flags from thier position in 2.3??????????
 142  
      */
 143  
     /**
 144  
      * ID3v2.4 Header Footer bit mask
 145  
      */
 146  
     public static final int MASK_V24_TAG_ALTER_PRESERVATION = FileConstants.BIT6;
 147  
 
 148  
     /**
 149  
      * ID3v2.4 Header Footer bit mask
 150  
      */
 151  
     public static final int MASK_V24_FILE_ALTER_PRESERVATION = FileConstants.BIT5;
 152  
 
 153  
     /**
 154  
      * ID3v2.4 Header Footer bit mask
 155  
      */
 156  
     public static final int MASK_V24_READ_ONLY = FileConstants.BIT4;
 157  
 
 158  
     /**
 159  
      * ID3v2.4 Header Footer bit mask
 160  
      */
 161  
     public static final int MASK_V24_GROUPING_IDENTITY = FileConstants.BIT6;
 162  
 
 163  
     /**
 164  
      * ID3v2.4 Header Footer bit mask
 165  
      */
 166  
     public static final int MASK_V24_COMPRESSION = FileConstants.BIT4;
 167  
 
 168  
     /**
 169  
      * ID3v2.4 Header Footer bit mask
 170  
      */
 171  
     public static final int MASK_V24_ENCRYPTION = FileConstants.BIT3;
 172  
 
 173  
     /**
 174  
      * ID3v2.4 Header Footer bit mask
 175  
      */
 176  
     public static final int MASK_V24_FRAME_UNSYNCHRONIZATION = FileConstants.BIT2;
 177  
 
 178  
     /**
 179  
      * ID3v2.4 Header Footer bit mask
 180  
      */
 181  
     public static final int MASK_V24_DATA_LENGTH_INDICATOR = FileConstants.BIT1;
 182  
 
 183  
     /**
 184  
      * CRC Checksum calculated
 185  
      */
 186  634
     protected boolean crcDataFlag = false;
 187  
 
 188  
     /**
 189  
      * Experiemntal tag
 190  
      */
 191  634
     protected boolean experimental = false;
 192  
 
 193  
     /**
 194  
      * Contains extended header
 195  
      */
 196  634
     protected boolean extended = false;
 197  
 
 198  
     /**
 199  
      * All frames in the tag uses unsynchronisation
 200  
      */
 201  634
     protected boolean unsynchronization = false;
 202  
 
 203  
     /**
 204  
      * CRC Checksum
 205  
      */
 206  634
     protected int crcData = 0;
 207  
 
 208  
 
 209  
     /**
 210  
      * Contains a footer
 211  
      */
 212  634
     protected boolean footer = false;
 213  
 
 214  
     /**
 215  
      * Tag is an update
 216  
      */
 217  634
     protected boolean updateTag = false;
 218  
 
 219  
     /**
 220  
      * Tag has restrictions
 221  
      */
 222  634
     protected boolean tagRestriction = false;
 223  
 
 224  
     /**
 225  
      *
 226  
      */
 227  634
     protected byte imageEncodingRestriction = 0;
 228  
 
 229  
     /**
 230  
      *
 231  
      */
 232  634
     protected byte imageSizeRestriction = 0;
 233  
 
 234  
     /**
 235  
      *
 236  
      */
 237  634
     protected byte tagSizeRestriction = 0;
 238  
 
 239  
     /**
 240  
      *
 241  
      */
 242  634
     protected byte textEncodingRestriction = 0;
 243  
 
 244  
     /**
 245  
      * Tag padding
 246  
      */
 247  634
     protected int paddingSize = 0;
 248  
 
 249  
 
 250  
     /**
 251  
      *
 252  
      */
 253  634
     protected byte textFieldSizeRestriction = 0;
 254  
 
 255  
     public static final byte RELEASE = 2;
 256  
     public static final byte MAJOR_VERSION = 4;
 257  
     public static final byte REVISION = 0;
 258  
 
 259  
     /**
 260  
      * Retrieve the Release
 261  
      */
 262  
     public byte getRelease()
 263  
     {
 264  2
         return RELEASE;
 265  
     }
 266  
 
 267  
     /**
 268  
      * Retrieve the Major Version
 269  
      */
 270  
     public byte getMajorVersion()
 271  
     {
 272  434
         return MAJOR_VERSION;
 273  
     }
 274  
 
 275  
     /**
 276  
      * Retrieve the Revision
 277  
      */
 278  
     public byte getRevision()
 279  
     {
 280  250
         return REVISION;
 281  
     }
 282  
 
 283  
 
 284  
     /**
 285  
      * Creates a new empty ID3v2_4 datatype.
 286  
      */
 287  
     public ID3v24Tag()
 288  93
     {
 289  93
         frameMap = new LinkedHashMap();
 290  93
     }
 291  
 
 292  
     /**
 293  
      * Copy primitives applicable to v2.4, this is used when cloning a v2.4 datatype
 294  
      * and other objects such as v2.3 so need to check instanceof
 295  
      */
 296  
     protected void copyPrimitives(AbstractID3v2Tag copyObj)
 297  
     {
 298  230
         logger.info("Copying primitives");
 299  230
         super.copyPrimitives(copyObj);
 300  
 
 301  230
         if (copyObj instanceof ID3v24Tag)
 302  
         {
 303  0
             ID3v24Tag copyObject = (ID3v24Tag) copyObj;
 304  0
             this.footer = copyObject.footer;
 305  0
             this.tagRestriction = copyObject.tagRestriction;
 306  0
             this.updateTag = copyObject.updateTag;
 307  0
             this.imageEncodingRestriction = copyObject.imageEncodingRestriction;
 308  0
             this.imageSizeRestriction = copyObject.imageSizeRestriction;
 309  0
             this.tagSizeRestriction = copyObject.tagSizeRestriction;
 310  0
             this.textEncodingRestriction = copyObject.textEncodingRestriction;
 311  0
             this.textFieldSizeRestriction = copyObject.textFieldSizeRestriction;
 312  
         }
 313  230
     }
 314  
 
 315  
     /**
 316  
      * Copy frames from one tag into a v2.4 tag
 317  
      */
 318  
     protected void copyFrames(AbstractID3v2Tag copyObject)
 319  
     {
 320  230
         logger.info("Copying Frames,there are:" + copyObject.frameMap.keySet().size() + " different types");
 321  230
         frameMap = new LinkedHashMap();
 322  
         //Copy Frames that are a valid 2.4 type
 323  230
         Iterator iterator = copyObject.frameMap.keySet().iterator();
 324  
         AbstractID3v2Frame frame;
 325  230
         ID3v24Frame newFrame = null;
 326  952
         while (iterator.hasNext())
 327  
         {
 328  722
             String id = (String) iterator.next();
 329  722
             Object o = copyObject.frameMap.get(id);
 330  
             //SingleFrames
 331  722
             if (o instanceof AbstractID3v2Frame)
 332  
             {
 333  682
                 frame = (AbstractID3v2Frame) o;
 334  
                 try
 335  
                 {
 336  682
                     newFrame = new ID3v24Frame(frame);
 337  682
                     logger.info("Adding Frame:" + newFrame.getIdentifier());
 338  682
                     copyFrameIntoMap(newFrame.getIdentifier(), newFrame);
 339  
                 }
 340  0
                 catch (InvalidFrameException ife)
 341  
                 {
 342  0
                     logger.log(Level.SEVERE, "Unable to convert frame:" + frame.getIdentifier(), ife);
 343  682
                 }
 344  
             }
 345  
             //MultiFrames
 346  40
             else if (o instanceof ArrayList)
 347  
             {
 348  40
                 ArrayList<AbstractID3v2Frame> multiFrame = new ArrayList<AbstractID3v2Frame>();
 349  40
                 for (ListIterator<AbstractID3v2Frame> li = ((ArrayList<AbstractID3v2Frame>) o).listIterator(); li.hasNext();)
 350  
                 {
 351  171
                     frame = li.next();
 352  
                     try
 353  
                     {
 354  171
                         newFrame = new ID3v24Frame(frame);
 355  171
                         multiFrame.add(newFrame);
 356  
                     }
 357  0
                     catch (InvalidFrameException ife)
 358  
                     {
 359  0
                         logger.log(Level.SEVERE, "Unable to convert frame:" + frame.getIdentifier());
 360  171
                     }
 361  
                 }
 362  
                 //Ensure that the list actually contains at lest one value before adding
 363  40
                 if(multiFrame.size()>0)
 364  
                 {
 365  40
                     if (newFrame != null)
 366  
                     {
 367  40
                         logger.finest("Adding multi frame list to map:" + newFrame.getIdentifier());
 368  40
                         frameMap.put(newFrame.getIdentifier(), multiFrame);
 369  
                     }
 370  
                 }
 371  
             }
 372  722
         }
 373  230
     }
 374  
 
 375  
     /**
 376  
      * Copy Constructor, creates a new ID3v2_4 Tag based on another ID3v2_4 Tag
 377  
      */
 378  
     public ID3v24Tag(ID3v24Tag copyObject)
 379  0
     {
 380  0
         logger.info("Creating tag from another tag of same type");
 381  0
         copyPrimitives(copyObject);
 382  0
         copyFrames(copyObject);
 383  0
     }
 384  
 
 385  
     /**
 386  
      * Creates a new ID3v2_4 datatype based on another (non 2.4) tag
 387  
      *
 388  
      * @param mp3tag
 389  
      */
 390  
     public ID3v24Tag(AbstractTag mp3tag)
 391  239
     {
 392  239
         logger.info("Creating tag from a tag of a different version");
 393  239
         frameMap = new LinkedHashMap();
 394  239
         if (mp3tag != null)
 395  
         {
 396  
             //Should use simpler copy constructor
 397  234
             if ((mp3tag instanceof ID3v24Tag))
 398  
             {
 399  0
                 throw new UnsupportedOperationException("Copy Constructor not called. Please type cast the argument");
 400  
             }
 401  
             /* If we get a tag, we want to convert to id3v2_4
 402  
              * both id3v1 and lyrics3 convert to this type
 403  
              * id3v1 needs to convert to id3v2_4 before converting to lyrics3
 404  
              */
 405  234
             else if (mp3tag instanceof AbstractID3v2Tag)
 406  
             {
 407  230
                 copyPrimitives((AbstractID3v2Tag) mp3tag);
 408  230
                 copyFrames((AbstractID3v2Tag) mp3tag);
 409  
             }
 410  
             //IDv1
 411  4
             else if (mp3tag instanceof ID3v1Tag)
 412  
             {
 413  
                 // convert id3v1 tags.
 414  4
                 ID3v1Tag id3tag = (ID3v1Tag) mp3tag;
 415  
                 ID3v24Frame newFrame;
 416  
                 AbstractID3v2FrameBody newBody;
 417  4
                 if (id3tag.title.length() > 0)
 418  
                 {
 419  4
                     newBody = new FrameBodyTIT2((byte) 0, id3tag.title);
 420  4
                     newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_TITLE);
 421  4
                     newFrame.setBody(newBody);
 422  4
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 423  
                 }
 424  4
                 if (id3tag.artist.length() > 0)
 425  
                 {
 426  4
                     newBody = new FrameBodyTPE1((byte) 0, id3tag.artist);
 427  4
                     newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_ARTIST);
 428  4
                     newFrame.setBody(newBody);
 429  4
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 430  
                 }
 431  4
                 if (id3tag.album.length() > 0)
 432  
                 {
 433  4
                     newBody = new FrameBodyTALB((byte) 0, id3tag.album);
 434  4
                     newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_ALBUM);
 435  4
                     newFrame.setBody(newBody);
 436  4
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 437  
                 }
 438  4
                 if (id3tag.year.length() > 0)
 439  
                 {
 440  4
                     newBody = new FrameBodyTDRC((byte) 0, id3tag.year);
 441  4
                     newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_YEAR);
 442  4
                     newFrame.setBody(newBody);
 443  4
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 444  
                 }
 445  4
                 if (id3tag.comment.length() > 0)
 446  
                 {
 447  4
                     newBody = new FrameBodyCOMM((byte) 0, "ENG", "", id3tag.comment);
 448  4
                     newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_COMMENT);
 449  4
                     newFrame.setBody(newBody);
 450  4
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 451  
                 }
 452  4
                 if (((id3tag.genre & ID3v1Tag.BYTE_TO_UNSIGNED) >= 0) && ((id3tag.genre & ID3v1Tag.BYTE_TO_UNSIGNED) != ID3v1Tag.BYTE_TO_UNSIGNED))
 453  
                 {
 454  4
                     Integer genreId = id3tag.genre & ID3v1Tag.BYTE_TO_UNSIGNED;
 455  4
                     String genre = "(" + genreId + ") " + GenreTypes.getInstanceOf().getValueForId(genreId);
 456  
 
 457  4
                     newBody = new FrameBodyTCON((byte) 0, genre);
 458  4
                     newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_GENRE);
 459  4
                     newFrame.setBody(newBody);
 460  4
                     frameMap.put(newFrame.getIdentifier(), newFrame);
 461  
                 }
 462  4
                 if (mp3tag instanceof ID3v11Tag)
 463  
                 {
 464  4
                     ID3v11Tag id3tag2 = (ID3v11Tag) mp3tag;
 465  4
                     if (id3tag2.track > 0)
 466  
                     {
 467  4
                         newBody = new FrameBodyTRCK((byte) 0, Byte.toString(id3tag2.track));
 468  4
                         newFrame = new ID3v24Frame(ID3v24Frames.FRAME_ID_TRACK);
 469  4
                         newFrame.setBody(newBody);
 470  4
                         frameMap.put(newFrame.getIdentifier(), newFrame);
 471  
                     }
 472  
                 }
 473  4
             }
 474  
             //Lyrics 3
 475  0
             else if (mp3tag instanceof AbstractLyrics3)
 476  
             {
 477  
                 //Put the conversion stuff in the individual frame code.
 478  
                 Lyrics3v2 lyric;
 479  0
                 if (mp3tag instanceof Lyrics3v2)
 480  
                 {
 481  0
                     lyric = new Lyrics3v2((Lyrics3v2) mp3tag);
 482  
                 }
 483  
                 else
 484  
                 {
 485  0
                     lyric = new Lyrics3v2(mp3tag);
 486  
                 }
 487  0
                 Iterator<Lyrics3v2Field> iterator = lyric.iterator();
 488  
                 Lyrics3v2Field field;
 489  
                 ID3v24Frame newFrame;
 490  0
                 while (iterator.hasNext())
 491  
                 {
 492  
                     try
 493  
                     {
 494  0
                         field = iterator.next();
 495  0
                         newFrame = new ID3v24Frame(field);
 496  0
                         frameMap.put(newFrame.getIdentifier(), newFrame);
 497  
                     }
 498  0
                     catch (InvalidTagException ex)
 499  
                     {
 500  0
                         logger.warning("Unable to convert Lyrics3 to v24 Frame:Frame Identifier");
 501  0
                     }
 502  
                 }
 503  
             }
 504  
         }
 505  239
     }
 506  
 
 507  
     /**
 508  
      * Creates a new ID3v2_4 datatype.
 509  
      *
 510  
      * @param buffer
 511  
      * @param loggingFilename
 512  
      * @throws TagException
 513  
      */
 514  
     public ID3v24Tag(ByteBuffer buffer, String loggingFilename) throws TagException
 515  302
     {
 516  302
         frameMap = new LinkedHashMap();
 517  302
         setLoggingFilename(loggingFilename);
 518  302
         this.read(buffer);
 519  136
     }
 520  
 
 521  
 
 522  
     /**
 523  
      * Creates a new ID3v2_4 datatype.
 524  
      *
 525  
      * @param buffer
 526  
      * @throws TagException
 527  
      * @deprecated use {@link #ID3v24Tag(ByteBuffer,String)} instead
 528  
      */
 529  
     public ID3v24Tag(ByteBuffer buffer) throws TagException
 530  
     {
 531  0
         this(buffer, "");
 532  0
     }
 533  
 
 534  
     /**
 535  
      * @return identifier
 536  
      */
 537  
     public String getIdentifier()
 538  
     {
 539  171
         return "ID3v2.40";
 540  
     }
 541  
 
 542  
     /**
 543  
      * Return tag size based upon the sizes of the frames rather than the physical
 544  
      * no of bytes between start of ID3Tag and start of Audio Data.
 545  
      *
 546  
      * @return size
 547  
      */
 548  
     public int getSize()
 549  
     {
 550  0
         int size = TAG_HEADER_LENGTH;
 551  0
         if (extended)
 552  
         {
 553  0
             size += this.TAG_EXT_HEADER_LENGTH;
 554  0
             if (updateTag)
 555  
             {
 556  0
                 size += this.TAG_EXT_HEADER_UPDATE_LENGTH;
 557  
             }
 558  0
             if (crcDataFlag)
 559  
             {
 560  0
                 size += this.TAG_EXT_HEADER_CRC_LENGTH;
 561  
             }
 562  0
             if (tagRestriction)
 563  
             {
 564  0
                 size += this.TAG_EXT_HEADER_RESTRICTION_LENGTH;
 565  
             }
 566  
         }
 567  0
         size += super.getSize();
 568  0
         logger.finer("Tag Size is" + size);
 569  0
         return size;
 570  
     }
 571  
 
 572  
     /**
 573  
      * @param obj
 574  
      * @return equality
 575  
      */
 576  
     public boolean equals(Object obj)
 577  
     {
 578  0
         if ((obj instanceof ID3v24Tag) == false)
 579  
         {
 580  0
             return false;
 581  
         }
 582  0
         ID3v24Tag object = (ID3v24Tag) obj;
 583  0
         if (this.footer != object.footer)
 584  
         {
 585  0
             return false;
 586  
         }
 587  0
         if (this.imageEncodingRestriction != object.imageEncodingRestriction)
 588  
         {
 589  0
             return false;
 590  
         }
 591  0
         if (this.imageSizeRestriction != object.imageSizeRestriction)
 592  
         {
 593  0
             return false;
 594  
         }
 595  0
         if (this.tagRestriction != object.tagRestriction)
 596  
         {
 597  0
             return false;
 598  
         }
 599  0
         if (this.tagSizeRestriction != object.tagSizeRestriction)
 600  
         {
 601  0
             return false;
 602  
         }
 603  0
         if (this.textEncodingRestriction != object.textEncodingRestriction)
 604  
         {
 605  0
             return false;
 606  
         }
 607  0
         if (this.textFieldSizeRestriction != object.textFieldSizeRestriction)
 608  
         {
 609  0
             return false;
 610  
         }
 611  0
         if (this.updateTag != object.updateTag)
 612  
         {
 613  0
             return false;
 614  
         }
 615  0
         return super.equals(obj);
 616  
     }
 617  
 
 618  
     /**
 619  
      * Read the size of a tag, based on  the value written in the tag header
 620  
      *
 621  
      * @param buffer
 622  
      * @return
 623  
      * @throws TagException
 624  
      */
 625  
     public int readSize(ByteBuffer buffer)
 626  
     {
 627  
 
 628  
         //Skip over flags
 629  0
         byte flags = buffer.get();
 630  
 
 631  
         // Read the size, this is size of tag not including  the tag header
 632  0
         int size = ID3SyncSafeInteger.bufferToValue(buffer);
 633  
 
 634  
         //Return the exact size of tag as set in the tag header
 635  0
         return size + TAG_HEADER_LENGTH;
 636  
     }
 637  
 
 638  
     /**
 639  
      * Read Tag from Specified file.
 640  
      * Read tag header, delegate reading of frames to readFrames()
 641  
      *
 642  
      * @param byteBuffer to read the tag from
 643  
      * @throws TagException
 644  
      * @throws TagNotFoundException
 645  
      * @throws InvalidTagException
 646  
      */
 647  
     public void read(ByteBuffer byteBuffer) throws TagException
 648  
     {
 649  
         int size;
 650  
         byte[] buffer;
 651  302
         if (seek(byteBuffer) == false)
 652  
         {
 653  166
             throw new TagNotFoundException(getLoggingFilename() + ":" + getIdentifier() + " tag not found");
 654  
         }
 655  
         //Flags
 656  136
         byte flags = byteBuffer.get();
 657  136
         unsynchronization = (flags & MASK_V24_UNSYNCHRONIZATION) != 0;
 658  136
         extended = (flags & MASK_V24_EXTENDED_HEADER) != 0;
 659  136
         experimental = (flags & MASK_V24_EXPERIMENTAL) != 0;
 660  136
         footer = (flags & MASK_V24_FOOTER_PRESENT) != 0;
 661  
 
 662  136
         if (isUnsynchronization())
 663  
         {
 664  2
             logger.info(getLoggingFilename() + ":" + "ID3v24 Tag is unsynchronized");
 665  
         }
 666  
 
 667  136
         if (extended)
 668  
         {
 669  0
             logger.warning(getLoggingFilename() + ":" + "ID3v24 Tag is extended");
 670  
         }
 671  
 
 672  136
         if (experimental)
 673  
         {
 674  0
             logger.warning(getLoggingFilename() + ":" + "ID3v24 Tag is experimental");
 675  
         }
 676  
 
 677  136
         if (footer)
 678  
         {
 679  0
             logger.warning(getLoggingFilename() + ":" + "ID3v24 Tag has footer");
 680  
         }
 681  
 
 682  
         // Read the size, this is size of tag apart from tag header
 683  136
         size = ID3SyncSafeInteger.bufferToValue(byteBuffer);
 684  136
         logger.info(getLoggingFilename() + ":" + "Reading tag from file size set in header is" + size);
 685  136
         if (extended)
 686  
         {
 687  
             // int is 4 bytes.
 688  0
             int extendedHeaderSize = byteBuffer.getInt();
 689  
             // the extended header must be atleast 6 bytes
 690  0
             if (extendedHeaderSize <= TAG_EXT_HEADER_LENGTH)
 691  
             {
 692  0
                 throw new InvalidTagException(getLoggingFilename() + ":" + "Invalid Extended Header Size.");
 693  
             }
 694  
             //Number of bytes
 695  0
             byteBuffer.get();
 696  
             // Read the extended flag bytes
 697  0
             byte extFlag = byteBuffer.get();
 698  0
             updateTag = (extFlag & MASK_V24_TAG_UPDATE) != 0;
 699  0
             crcDataFlag = (extFlag & MASK_V24_CRC_DATA_PRESENT) != 0;
 700  0
             tagRestriction = (extFlag & MASK_V24_TAG_RESTRICTIONS) != 0;
 701  
             // read the length byte if the flag is set
 702  
             // this tag should always be zero but just in case
 703  
             // read this information.
 704  0
             if (updateTag)
 705  
             {
 706  0
                 byteBuffer.get();
 707  
             }
 708  0
             if (crcDataFlag)
 709  
             {
 710  
                 // the CRC has a variable length
 711  0
                 byteBuffer.get();
 712  0
                 buffer = new byte[TAG_EXT_HEADER_CRC_DATA_LENGTH];
 713  0
                 byteBuffer.get(buffer, 0, TAG_EXT_HEADER_CRC_DATA_LENGTH);
 714  0
                 crcData = 0;
 715  0
                 for (int i = 0; i < TAG_EXT_HEADER_CRC_DATA_LENGTH; i++)
 716  
                 {
 717  0
                     crcData <<= 8;
 718  0
                     crcData += buffer[i];
 719  
                 }
 720  
             }
 721  0
             if (tagRestriction)
 722  
             {
 723  0
                 byteBuffer.get();
 724  0
                 buffer = new byte[1];
 725  0
                 byteBuffer.get(buffer, 0, 1);
 726  0
                 tagSizeRestriction = (byte) ((buffer[0] & MASK_V24_TAG_SIZE_RESTRICTIONS) >> 6);
 727  0
                 textEncodingRestriction = (byte) ((buffer[0] & MASK_V24_TEXT_ENCODING_RESTRICTIONS) >> 5);
 728  0
                 textFieldSizeRestriction = (byte) ((buffer[0] & MASK_V24_TEXT_FIELD_SIZE_RESTRICTIONS) >> 3);
 729  0
                 imageEncodingRestriction = (byte) ((buffer[0] & MASK_V24_IMAGE_ENCODING) >> 2);
 730  0
                 imageSizeRestriction = (byte) (buffer[0] & MASK_V24_IMAGE_SIZE_RESTRICTIONS);
 731  
             }
 732  
         }
 733  
         //Note if there was an extended header the size value has padding taken
 734  
         //off so we dont search it.
 735  136
         readFrames(byteBuffer, size);
 736  
 
 737  136
     }
 738  
 
 739  
     /**
 740  
      * Read frames from tag
 741  
      */
 742  
     protected void readFrames(ByteBuffer byteBuffer, int size)
 743  
     {
 744  136
         logger.finest(getLoggingFilename() + ":" + "Start of frame body at" + byteBuffer.position());
 745  
         //Now start looking for frames
 746  
         ID3v24Frame next;
 747  136
         frameMap = new LinkedHashMap();
 748  
         //Read the size from the Tag Header
 749  136
         this.fileReadSize = size;
 750  
         // Read the frames until got to upto the size as specified in header
 751  136
         logger.finest(getLoggingFilename() + ":" + "Start of frame body at:" + byteBuffer.position() + ",frames data size is:" + size);
 752  1005
         while (byteBuffer.position() <= size)
 753  
         {
 754  
             String id;
 755  
             try
 756  
             {
 757  
                 //Read Frame
 758  998
                 logger.finest(getLoggingFilename() + ":" + "looking for next frame at:" + byteBuffer.position());
 759  998
                 next = new ID3v24Frame(byteBuffer, getLoggingFilename());
 760  868
                 id = next.getIdentifier();
 761  868
                 loadFrameIntoMap(id, next);
 762  
             }
 763  
             //Found Empty Frame
 764  1
             catch (EmptyFrameException ex)
 765  
             {
 766  1
                 logger.warning(getLoggingFilename() + ":" + "Empty Frame:" + ex.getMessage());
 767  1
                 this.emptyFrameBytes += TAG_HEADER_LENGTH;
 768  
             }
 769  129
             catch (InvalidFrameIdentifierException ifie)
 770  
             {
 771  129
                 logger.info(getLoggingFilename() + ":" + "Invalid Frame Identifier:" + ifie.getMessage());
 772  129
                 this.invalidFrameBytes++;
 773  
                 //Dont try and find any more frames
 774  129
                 break;
 775  
             }
 776  
             //Problem trying to find frame
 777  0
             catch (InvalidFrameException ife)
 778  
             {
 779  0
                 logger.warning(getLoggingFilename() + ":" + "Invalid Frame:" + ife.getMessage());
 780  0
                 this.invalidFrameBytes++;
 781  
                 //Dont try and find any more frames
 782  0
                 break;
 783  869
             }
 784  869
         }
 785  136
     }
 786  
 
 787  
     /**
 788  
      * Write the ID3 header to the ByteBuffer.
 789  
      * <p/>
 790  
      * TODO Calculate the CYC Data Check
 791  
      * TODO Reintroduce Extended Header
 792  
      *
 793  
      * @param padding is the size of the padding
 794  
      * @param size    is the size of the body data
 795  
      * @return ByteBuffer
 796  
      * @throws IOException
 797  
      */
 798  
     private ByteBuffer writeHeaderToBuffer(int padding, int size) throws IOException
 799  
     {
 800  
         //This would only be set if every frame in tag has been unsynchronized, I only unsychronize frames
 801  
         //that need it, in any case I have been advised not to set it even then.
 802  111
         unsynchronization = false;
 803  
 
 804  
         // Flags,currently we never calculate the CRC
 805  
         // and if we dont calculate them cant keep orig values. Tags are not
 806  
         // experimental and we never create extended header to keep things simple.
 807  111
         extended = false;
 808  111
         experimental = false;
 809  111
         footer = false;
 810  
 
 811  
         // Create Header Buffer,allocate maximum possible size for the header
 812  111
         ByteBuffer headerBuffer = ByteBuffer.allocate(TAG_HEADER_LENGTH);
 813  
         //TAGID
 814  111
         headerBuffer.put(TAG_ID);
 815  
 
 816  
         //Major Version
 817  111
         headerBuffer.put(getMajorVersion());
 818  
 
 819  
         //Minor Version
 820  111
         headerBuffer.put(getRevision());
 821  
 
 822  
         //Flags
 823  111
         byte flagsByte = 0;
 824  111
         if (isUnsynchronization())
 825  
         {
 826  0
             flagsByte |= MASK_V24_UNSYNCHRONIZATION;
 827  
         }
 828  111
         if (extended)
 829  
         {
 830  0
             flagsByte |= MASK_V24_EXTENDED_HEADER;
 831  
         }
 832  111
         if (experimental)
 833  
         {
 834  0
             flagsByte |= MASK_V24_EXPERIMENTAL;
 835  
         }
 836  111
         if (footer)
 837  
         {
 838  0
             flagsByte |= MASK_V24_FOOTER_PRESENT;
 839  
         }
 840  111
         headerBuffer.put(flagsByte);
 841  
 
 842  
         //Size As Recorded in Header, don't include the main header length
 843  
         //Additional Header Size,(for completeness we never actually write the extended header, or footer)
 844  111
         int additionalHeaderSize = 0;
 845  111
         if (extended)
 846  
         {
 847  0
             additionalHeaderSize += this.TAG_EXT_HEADER_LENGTH;
 848  0
             if (updateTag)
 849  
             {
 850  0
                 additionalHeaderSize += this.TAG_EXT_HEADER_UPDATE_LENGTH;
 851  
             }
 852  0
             if (crcDataFlag)
 853  
             {
 854  0
                 additionalHeaderSize += this.TAG_EXT_HEADER_CRC_LENGTH;
 855  
             }
 856  0
             if (tagRestriction)
 857  
             {
 858  0
                 additionalHeaderSize += this.TAG_EXT_HEADER_RESTRICTION_LENGTH;
 859  
             }
 860  
         }
 861  
 
 862  
         //Size As Recorded in Header, don't include the main header length
 863  111
         headerBuffer.put(ID3SyncSafeInteger.valueToBuffer(padding + size + additionalHeaderSize));
 864  
 
 865  
         //Write Extended Header
 866  111
         ByteBuffer extHeaderBuffer = null;
 867  111
         if (extended)
 868  
         {
 869  
             //Write Extended Header Size
 870  0
             int extendedSize = TAG_EXT_HEADER_LENGTH;
 871  0
             if (updateTag)
 872  
             {
 873  0
                 extendedSize += TAG_EXT_HEADER_UPDATE_LENGTH;
 874  
             }
 875  0
             if (crcDataFlag)
 876  
             {
 877  0
                 extendedSize += TAG_EXT_HEADER_CRC_LENGTH;
 878  
             }
 879  0
             if (tagRestriction)
 880  
             {
 881  0
                 extendedSize += TAG_EXT_HEADER_RESTRICTION_LENGTH;
 882  
             }
 883  0
             extHeaderBuffer = ByteBuffer.allocate(extendedSize);
 884  0
             extHeaderBuffer.putInt(extendedSize);
 885  
             //Write Number of flags Byte
 886  0
             extHeaderBuffer.put((byte) TAG_EXT_NUMBER_BYTES_DATA_LENGTH);
 887  
             //Write Extended Flags
 888  0
             byte extFlag = 0;
 889  0
             if (updateTag)
 890  
             {
 891  0
                 extFlag |= MASK_V24_TAG_UPDATE;
 892  
             }
 893  0
             if (crcDataFlag)
 894  
             {
 895  0
                 extFlag |= MASK_V24_CRC_DATA_PRESENT;
 896  
             }
 897  0
             if (tagRestriction)
 898  
             {
 899  0
                 extFlag |= MASK_V24_TAG_RESTRICTIONS;
 900  
             }
 901  0
             extHeaderBuffer.put(extFlag);
 902  
             //Write Update Data
 903  0
             if (updateTag)
 904  
             {
 905  0
                 extHeaderBuffer.put((byte) 0);
 906  
             }
 907  
             //Write CRC Data
 908  0
             if (crcDataFlag)
 909  
             {
 910  0
                 extHeaderBuffer.put((byte) TAG_EXT_HEADER_CRC_DATA_LENGTH);
 911  0
                 extHeaderBuffer.put((byte) 0);
 912  0
                 extHeaderBuffer.putInt(crcData);
 913  
             }
 914  
             //Write Tag Restriction
 915  0
             if (tagRestriction)
 916  
             {
 917  0
                 extHeaderBuffer.put((byte) TAG_EXT_HEADER_RESTRICTION_DATA_LENGTH);
 918  
                 //todo not currently setting restrictions
 919  0
                 extHeaderBuffer.put((byte) 0);
 920  
             }
 921  
         }
 922  
 
 923  111
         if (extHeaderBuffer != null)
 924  
         {
 925  0
             extHeaderBuffer.flip();
 926  0
             headerBuffer.put(extHeaderBuffer);
 927  
         }
 928  
 
 929  111
         headerBuffer.flip();
 930  111
         return headerBuffer;
 931  
     }
 932  
 
 933  
     /**
 934  
      * Write this tag to file.
 935  
      *
 936  
      * @param file
 937  
      * @throws IOException
 938  
      */
 939  
     public void write(File file, long audioStartLocation) throws IOException
 940  
     {
 941  111
         logger.info("Writing tag to file");
 942  
 
 943  
         //Write Body Buffer
 944  111
         byte[] bodyByteBuffer = writeFramesToBuffer().toByteArray();
 945  
 
 946  
         //Calculate Tag Size including Padding
 947  111
         int sizeIncPadding = calculateTagSize(bodyByteBuffer.length + TAG_HEADER_LENGTH, (int) audioStartLocation);
 948  
 
 949  
         //Calculate padding bytes required
 950  111
         int padding = sizeIncPadding - (bodyByteBuffer.length + TAG_HEADER_LENGTH);
 951  
 
 952  111
         ByteBuffer headerBuffer = writeHeaderToBuffer(padding, bodyByteBuffer.length);
 953  111
         writeBufferToFile(file,headerBuffer, bodyByteBuffer,padding,sizeIncPadding,audioStartLocation);       
 954  111
     }
 955  
 
 956  
     /**
 957  
      * Write tag to channel
 958  
      *
 959  
      * @param channel
 960  
      * @throws IOException
 961  
      */
 962  
     public void write(WritableByteChannel channel) throws IOException
 963  
     {
 964  0
         logger.info("Writing tag to channel");
 965  
 
 966  0
         byte[] bodyByteBuffer = writeFramesToBuffer().toByteArray();
 967  0
         ByteBuffer headerBuffer = writeHeaderToBuffer(0, bodyByteBuffer.length);
 968  
 
 969  0
         channel.write(headerBuffer);
 970  0
         channel.write(ByteBuffer.wrap(bodyByteBuffer));
 971  0
     }
 972  
 
 973  
     /**
 974  
      * Display the tag in an XMLFormat
 975  
      */
 976  
     public void createStructure()
 977  
     {
 978  5
         MP3File.getStructureFormatter().openHeadingElement(TYPE_TAG, getIdentifier());
 979  
 
 980  5
         super.createStructureHeader();
 981  
 
 982  
         //Header
 983  5
         MP3File.getStructureFormatter().openHeadingElement(TYPE_HEADER, "");
 984  5
         MP3File.getStructureFormatter().addElement(TYPE_UNSYNCHRONISATION, this.isUnsynchronization());
 985  5
         MP3File.getStructureFormatter().addElement(TYPE_CRCDATA, this.crcData);
 986  5
         MP3File.getStructureFormatter().addElement(TYPE_EXPERIMENTAL, this.experimental);
 987  5
         MP3File.getStructureFormatter().addElement(TYPE_EXTENDED, this.extended);
 988  5
         MP3File.getStructureFormatter().addElement(TYPE_PADDINGSIZE, this.paddingSize);
 989  5
         MP3File.getStructureFormatter().addElement(TYPE_FOOTER, this.footer);
 990  5
         MP3File.getStructureFormatter().addElement(TYPE_IMAGEENCODINGRESTRICTION, this.paddingSize);
 991  5
         MP3File.getStructureFormatter().addElement(TYPE_IMAGESIZERESTRICTION, this.imageSizeRestriction);
 992  5
         MP3File.getStructureFormatter().addElement(TYPE_TAGRESTRICTION, this.tagRestriction);
 993  5
         MP3File.getStructureFormatter().addElement(TYPE_TAGSIZERESTRICTION, this.tagSizeRestriction);
 994  5
         MP3File.getStructureFormatter().addElement(TYPE_TEXTFIELDSIZERESTRICTION, this.textFieldSizeRestriction);
 995  5
         MP3File.getStructureFormatter().addElement(TYPE_TEXTENCODINGRESTRICTION, this.textEncodingRestriction);
 996  5
         MP3File.getStructureFormatter().addElement(TYPE_UPDATETAG, this.updateTag);
 997  5
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_HEADER);
 998  
 
 999  
         //Body
 1000  5
         super.createStructureBody();
 1001  
 
 1002  5
         MP3File.getStructureFormatter().closeHeadingElement(TYPE_TAG);
 1003  5
     }
 1004  
 
 1005  
     /**
 1006  
      * Are all frame swithin this tag unsynchronized
 1007  
      * <p/>
 1008  
      * <p>Because synchronization occurs at the frame level it is not normally desirable to unsynchronize all frames
 1009  
      * and hence this flag is not normally set.
 1010  
      *
 1011  
      * @return are all frames within the tag unsynchronized
 1012  
      */
 1013  
     public boolean isUnsynchronization()
 1014  
     {
 1015  257
         return unsynchronization;
 1016  
     }
 1017  
 
 1018  
     protected String getArtistId()
 1019  
     {
 1020  1
         return ID3v24Frames.FRAME_ID_ARTIST;
 1021  
     }
 1022  
 
 1023  
     protected String getAlbumId()
 1024  
     {
 1025  11
         return ID3v24Frames.FRAME_ID_ALBUM;
 1026  
     }
 1027  
 
 1028  
     protected String getTitleId()
 1029  
     {
 1030  5
         return ID3v24Frames.FRAME_ID_TITLE;
 1031  
     }
 1032  
 
 1033  
     protected String getTrackId()
 1034  
     {
 1035  3
         return ID3v24Frames.FRAME_ID_TRACK;
 1036  
     }
 1037  
 
 1038  
     protected String getYearId()
 1039  
     {
 1040  3
         return ID3v24Frames.FRAME_ID_YEAR;
 1041  
     }
 1042  
 
 1043  
     protected String getCommentId()
 1044  
     {
 1045  22
         return ID3v24Frames.FRAME_ID_COMMENT;
 1046  
     }
 1047  
 
 1048  
     protected String getGenreId()
 1049  
     {
 1050  3
         return ID3v24Frames.FRAME_ID_GENRE;
 1051  
     }
 1052  
 
 1053  
     /**
 1054  
      * Create a new frame with the specified frameid
 1055  
      *
 1056  
      * @param id
 1057  
      * @return
 1058  
      */
 1059  
     public ID3v24Frame createFrame(String id)
 1060  
     {
 1061  51
         return new ID3v24Frame(id);
 1062  
     }
 1063  
 
 1064  
 
 1065  
     /**
 1066  
      * Create Frame for Id3 Key
 1067  
      * <p/>
 1068  
      * Only textual data supported at the moment, should only be used with frames that
 1069  
      * support a simple string argument.
 1070  
      *
 1071  
      * @param id3Key
 1072  
      * @param value
 1073  
      * @return
 1074  
      * @throws KeyNotFoundException
 1075  
      * @throws FieldDataInvalidException
 1076  
      */
 1077  
     public TagField createTagField(ID3v24FieldKey id3Key, String value) throws KeyNotFoundException, FieldDataInvalidException
 1078  
     {
 1079  0
         if (id3Key == null)
 1080  
         {
 1081  0
             throw new KeyNotFoundException();
 1082  
         }
 1083  0
         return super.doCreateTagField(new FrameAndSubId(id3Key.getFrameId(), id3Key.getSubId()), value);
 1084  
     }
 1085  
 
 1086  
     /**
 1087  
      * Retrieve the first value that exists for this id3v24key
 1088  
      *
 1089  
      * @param id3v24FieldKey
 1090  
      * @return
 1091  
      */
 1092  
     public String getFirst(ID3v24FieldKey id3v24FieldKey) throws KeyNotFoundException
 1093  
     {
 1094  12
         if (id3v24FieldKey == null)
 1095  
         {
 1096  0
             throw new KeyNotFoundException();
 1097  
         }
 1098  12
         return super.doGetFirst(new FrameAndSubId(id3v24FieldKey.getFrameId(), id3v24FieldKey.getSubId()));
 1099  
     }
 1100  
 
 1101  
 
 1102  
     /**
 1103  
      * Delete fields with this id3v24FieldKey
 1104  
      *
 1105  
      * @param id3v24FieldKey
 1106  
      */
 1107  
     public void deleteTagField(ID3v24FieldKey id3v24FieldKey) throws KeyNotFoundException
 1108  
     {
 1109  0
         if (id3v24FieldKey == null)
 1110  
         {
 1111  0
             throw new KeyNotFoundException();
 1112  
         }
 1113  0
         super.doDeleteTagField(new FrameAndSubId(id3v24FieldKey.getFrameId(), id3v24FieldKey.getSubId()));
 1114  0
     }
 1115  
 
 1116  
 
 1117  
     protected FrameAndSubId getFrameAndSubIdFromGenericKey(TagFieldKey genericKey)
 1118  
     {
 1119  123
         ID3v24FieldKey id3v24FieldKey = ID3v24Frames.getInstanceOf().getId3KeyFromGenericKey(genericKey);
 1120  123
         if (id3v24FieldKey == null)
 1121  
         {
 1122  0
             throw new KeyNotFoundException();
 1123  
         }
 1124  123
         return new FrameAndSubId(id3v24FieldKey.getFrameId(), id3v24FieldKey.getSubId());
 1125  
     }
 1126  
 
 1127  
     protected ID3Frames getID3Frames()
 1128  
     {
 1129  31
         return ID3v24Frames.getInstanceOf();
 1130  
     }
 1131  
 
 1132  
      /**
 1133  
      *
 1134  
      * @return comparator used to order frames in preferred order for writing to file
 1135  
      * so that most important frames are written first.
 1136  
      */
 1137  
     public Comparator getPreferredFrameOrderComparator()
 1138  
     {
 1139  111
         return ID3v24PreferredFrameOrderComparator.getInstanceof();
 1140  
     }
 1141  
 
 1142  
      public List<Artwork> getArtworkList()
 1143  
     {
 1144  4
         List<TagField> coverartList = get(TagFieldKey.COVER_ART);
 1145  4
         List<Artwork> artworkList   = new ArrayList<Artwork>(coverartList.size());
 1146  
 
 1147  4
         for(TagField next:coverartList)
 1148  
         {
 1149  3
             FrameBodyAPIC coverArt = (FrameBodyAPIC) ((AbstractID3v2Frame) next).getBody();
 1150  3
             Artwork artwork = new Artwork();
 1151  3
             artwork.setMimeType(coverArt.getMimeType());
 1152  3
             artwork.setPictureType(coverArt.getPictureType());
 1153  3
             if(coverArt.isImageUrl())
 1154  
             {
 1155  0
                 artwork.setLinked(true);
 1156  0
                 artwork.setImageUrl(coverArt.getImageUrl());
 1157  
             }
 1158  
             else
 1159  
             {
 1160  3
                 artwork.setBinaryData(coverArt.getImageData());
 1161  
             }
 1162  3
             artworkList.add(artwork);
 1163  3
         }
 1164  4
         return artworkList;
 1165  
     }
 1166  
 
 1167  
      public TagField createArtworkField(Artwork artwork) throws FieldDataInvalidException
 1168  
     {
 1169  1
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(TagFieldKey.COVER_ART).getFrameId());
 1170  1
         FrameBodyAPIC body = (FrameBodyAPIC) frame.getBody();
 1171  1
         body.setObjectValue(DataTypes.OBJ_PICTURE_DATA,artwork.getBinaryData());
 1172  1
         body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, artwork.getPictureType());
 1173  1
         body.setObjectValue(DataTypes.OBJ_MIME_TYPE, artwork.getMimeType());
 1174  1
         body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 1175  1
         return frame;
 1176  
     }
 1177  
 
 1178  
     /**
 1179  
      * Create Artwork
 1180  
      *
 1181  
      * @param data
 1182  
      * @param mimeType of the image
 1183  
      * @see PictureTypes
 1184  
      */
 1185  
     public TagField createArtworkField(byte[] data, String mimeType)
 1186  
     {
 1187  2
         AbstractID3v2Frame frame = createFrame(getFrameAndSubIdFromGenericKey(TagFieldKey.COVER_ART).getFrameId());
 1188  2
         FrameBodyAPIC body = (FrameBodyAPIC) frame.getBody();
 1189  2
         body.setObjectValue(DataTypes.OBJ_PICTURE_DATA, data);
 1190  2
         body.setObjectValue(DataTypes.OBJ_PICTURE_TYPE, PictureTypes.DEFAULT_ID);
 1191  2
         body.setObjectValue(DataTypes.OBJ_MIME_TYPE, mimeType);
 1192  2
         body.setObjectValue(DataTypes.OBJ_DESCRIPTION, "");
 1193  2
         return frame;
 1194  
     }
 1195  
 }