Coverage Report - org.jaudiotagger.audio.flac.metadatablock.MetadataBlockDataPicture
 
Classes in this File Line Coverage Branch Coverage Complexity
MetadataBlockDataPicture
84%
61/72
66%
4/6
1.435
 
 1  
 package org.jaudiotagger.audio.flac.metadatablock;
 2  
 
 3  
 import org.jaudiotagger.audio.generic.Utils;
 4  
 import org.jaudiotagger.tag.FieldKey;
 5  
 import org.jaudiotagger.tag.InvalidFrameException;
 6  
 import org.jaudiotagger.tag.TagField;
 7  
 import org.jaudiotagger.tag.id3.valuepair.TextEncoding;
 8  
 import org.jaudiotagger.tag.reference.PictureTypes;
 9  
 
 10  
 import java.io.ByteArrayOutputStream;
 11  
 import java.io.IOException;
 12  
 import java.io.RandomAccessFile;
 13  
 import java.io.UnsupportedEncodingException;
 14  
 import java.nio.ByteBuffer;
 15  
 import java.util.logging.Logger;
 16  
 
 17  
 
 18  
 /**
 19  
  * Picture Block
 20  
  * <p/>
 21  
  * <p/>
 22  
  * <p>This block is for storing pictures associated with the file, most commonly cover art from CDs.
 23  
  * There may be more than one PICTURE block in a file. The picture format is similar to the APIC frame in ID3v2.
 24  
  * The PICTURE block has a type, MIME type, and UTF-8 description like ID3v2, and supports external linking via URL
 25  
  * (though this is discouraged). The differences are that there is no uniqueness constraint on the description field,
 26  
  * and the MIME type is mandatory. The FLAC PICTURE block also includes the resolution, color depth, and palette size
 27  
  * so that the client can search for a suitable picture without having to scan them all
 28  
  * <p/>
 29  
  * Format:
 30  
  * <Size in bits> Info
 31  
  * <32> The picture type according to the ID3v2 APIC frame: (There may only be one each of picture type 1 and 2 in a file)
 32  
  * <32>         The length of the MIME type string in bytes.
 33  
  * <n*8>         The MIME type string, in printable ASCII characters 0x20-0x7e. The MIME type may also be --> to signify that the data part is a URL of the picture instead of the picture data itself.
 34  
  * <32>         The length of the description string in bytes.
 35  
  * <n*8>         The description of the picture, in UTF-8.
 36  
  * <32>         The width of the picture in pixels.
 37  
  * <32>         The height of the picture in pixels.
 38  
  * <32>         The color depth of the picture in bits-per-pixel.
 39  
  * <32>         For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for non-indexed pictures.
 40  
  * <32>         The length of the picture data in bytes.
 41  
  * <n*8>         The binary picture data.
 42  
  */
 43  
 public class MetadataBlockDataPicture implements MetadataBlockData, TagField
 44  
 {
 45  
     public static final String IMAGE_IS_URL = "-->";
 46  
 
 47  
     private int pictureType;
 48  
     private String mimeType;
 49  
     private String description;
 50  
     private int width;
 51  
     private int height;
 52  
     private int colourDepth;
 53  
     private int indexedColouredCount;
 54  
     private byte[] imageData;
 55  
 
 56  
     // Logger Object
 57  4
     public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.flac.MetadataBlockDataPicture");
 58  
 
 59  
     /**
 60  
      * Construct picture block by reading from file
 61  
      * @param header
 62  
      * @param raf
 63  
      * @throws java.io.IOException
 64  
      * @throws org.jaudiotagger.tag.InvalidFrameException
 65  
      */
 66  
     //TODO check for buffer underflows see http://research.eeye.com/html/advisories/published/AD20071115.html
 67  
     public MetadataBlockDataPicture(MetadataBlockHeader header, RandomAccessFile raf) throws IOException, InvalidFrameException
 68  73
     {
 69  73
         ByteBuffer rawdata = ByteBuffer.allocate(header.getDataLength());
 70  73
         int bytesRead = raf.getChannel().read(rawdata);
 71  73
         if (bytesRead < header.getDataLength())
 72  
         {
 73  0
             throw new IOException("Unable to read required number of databytes read:" + bytesRead + ":required:" + header.getDataLength());
 74  
         }
 75  73
         rawdata.rewind();
 76  
 
 77  
         //Picture Type
 78  73
         pictureType = rawdata.getInt();
 79  73
         if (pictureType >= PictureTypes.getInstanceOf().getSize())
 80  
         {
 81  0
             throw new InvalidFrameException("PictureType was:" + pictureType + "but the maximum allowed is " + (PictureTypes.getInstanceOf().getSize() - 1));
 82  
         }
 83  
 
 84  
         //MimeType
 85  73
         int mimeTypeSize = rawdata.getInt();
 86  73
         mimeType = getString(rawdata, mimeTypeSize, "ISO-8859-1");
 87  
 
 88  
         //Description
 89  73
         int descriptionSize = rawdata.getInt();
 90  73
         description = getString(rawdata, descriptionSize, "UTF-8");
 91  
 
 92  
         //Image width
 93  73
         width = rawdata.getInt();
 94  
 
 95  
         //Image height
 96  73
         height = rawdata.getInt();
 97  
 
 98  
         //Colour Depth
 99  73
         colourDepth = rawdata.getInt();
 100  
 
 101  
         //Indexed Colour Count
 102  73
         indexedColouredCount = rawdata.getInt();
 103  
 
 104  
         //ImageData
 105  73
         int rawdataSize = rawdata.getInt();
 106  73
         imageData = new byte[rawdataSize];
 107  73
         rawdata.get(imageData);
 108  
 
 109  73
         logger.info("Read image:" + this.toString());
 110  
 
 111  73
     }
 112  
 
 113  
     /**
 114  
      * Construct new MetadataPicture block
 115  
      * @param imageData
 116  
      * @param pictureType
 117  
      * @param mimeType
 118  
      * @param description
 119  
      * @param width
 120  
      * @param height
 121  
      * @param colourDepth
 122  
      * @param indexedColouredCount
 123  
      */
 124  
     public MetadataBlockDataPicture(byte[] imageData, int pictureType, String mimeType, String description, int width, int height, int colourDepth, int indexedColouredCount)
 125  17
     {
 126  
         //Picture Type
 127  17
         this.pictureType = pictureType;
 128  
 
 129  
         //MimeType
 130  17
         this.mimeType = mimeType;
 131  
 
 132  
         //Description
 133  17
         this.description = description;
 134  
 
 135  17
         this.width = width;
 136  
 
 137  17
         this.height = height;
 138  
 
 139  17
         this.colourDepth = colourDepth;
 140  
 
 141  17
         this.indexedColouredCount = indexedColouredCount;
 142  
         //ImageData
 143  17
         this.imageData = imageData;
 144  17
     }
 145  
 
 146  
     private String getString(ByteBuffer rawdata, int length, String charset) throws IOException
 147  
     {
 148  146
         byte[] tempbuffer = new byte[length];
 149  146
         rawdata.get(tempbuffer);
 150  146
         return new String(tempbuffer, charset);
 151  
     }
 152  
 
 153  
     public byte[] getBytes()
 154  
     {
 155  
         try
 156  
         {
 157  246
             ByteArrayOutputStream baos = new ByteArrayOutputStream();
 158  246
             baos.write(Utils.getSizeBEInt32(pictureType));
 159  246
             baos.write(Utils.getSizeBEInt32(mimeType.length()));
 160  246
             baos.write(mimeType.getBytes("ISO-8859-1"));
 161  246
             baos.write(Utils.getSizeBEInt32(description.length()));
 162  246
             baos.write(description.getBytes("UTF-8"));
 163  246
             baos.write(Utils.getSizeBEInt32(width));
 164  246
             baos.write(Utils.getSizeBEInt32(height));
 165  246
             baos.write(Utils.getSizeBEInt32(colourDepth));
 166  246
             baos.write(Utils.getSizeBEInt32(indexedColouredCount));
 167  246
             baos.write(Utils.getSizeBEInt32(imageData.length));
 168  246
             baos.write(imageData);
 169  246
             return baos.toByteArray();
 170  
 
 171  
         }
 172  0
         catch (IOException ioe)
 173  
         {
 174  0
             throw new RuntimeException(ioe.getMessage());
 175  
         }
 176  
     }
 177  
 
 178  
     public int getLength()
 179  
     {
 180  82
         return getBytes().length;
 181  
     }
 182  
 
 183  
     public int getPictureType()
 184  
     {
 185  68
         return pictureType;
 186  
     }
 187  
 
 188  
     public String getMimeType()
 189  
     {
 190  192
         return mimeType;
 191  
     }
 192  
 
 193  
     public String getDescription()
 194  
     {
 195  60
         return description;
 196  
     }
 197  
 
 198  
     public int getWidth()
 199  
     {
 200  12
         return width;
 201  
     }
 202  
 
 203  
     public int getHeight()
 204  
     {
 205  12
         return height;
 206  
     }
 207  
 
 208  
     public int getColourDepth()
 209  
     {
 210  12
         return colourDepth;
 211  
     }
 212  
 
 213  
     public int getIndexedColourCount()
 214  
     {
 215  12
         return indexedColouredCount;
 216  
     }
 217  
 
 218  
     public byte[] getImageData()
 219  
     {
 220  124
         return imageData;
 221  
     }
 222  
 
 223  
     /**
 224  
      * @return true if imagedata  is held as a url rather than actually being imagedata
 225  
      */
 226  
     public boolean isImageUrl()
 227  
     {
 228  124
         return getMimeType().equals(IMAGE_IS_URL);
 229  
     }
 230  
 
 231  
     /**
 232  
      * @return the image url if there is otherwise return an empty String
 233  
      */
 234  
     public String getImageUrl()
 235  
     {
 236  40
         if (isImageUrl())
 237  
         {
 238  36
             return Utils.getString(getImageData(), 0, getImageData().length, TextEncoding.CHARSET_ISO_8859_1);
 239  
         }
 240  
         else
 241  
         {
 242  4
             return "";
 243  
         }
 244  
     }
 245  
 
 246  
     public String toString()
 247  
     {
 248  73
         return PictureTypes.getInstanceOf().getValueForId(pictureType) + ":" + mimeType + ":" + description + ":" + "width:" + width + ":height:" + height + ":colourdepth:" + colourDepth + ":indexedColourCount:" + indexedColouredCount + ":image size in bytes:" + imageData.length;
 249  
     }
 250  
 
 251  
     /**
 252  
      * This method copies the data of the given field to the current data.<br>
 253  
      *
 254  
      * @param field The field containing the data to be taken.
 255  
      */
 256  
     public void copyContent(TagField field)
 257  
     {
 258  0
         throw new UnsupportedOperationException();
 259  
     }
 260  
 
 261  
     /**
 262  
      * Returns the Id of the represented tag field.<br>
 263  
      * This value should uniquely identify a kind of tag data, like title.
 264  
      * {@link org.jaudiotagger.audio.generic.AbstractTag} will use the &quot;id&quot; to summarize multiple
 265  
      * fields.
 266  
      *
 267  
      * @return Unique identifier for the fields type. (title, artist...)
 268  
      */
 269  
     public String getId()
 270  
     {
 271  0
         return FieldKey.COVER_ART.name();
 272  
     }
 273  
 
 274  
     /**
 275  
      * This method delivers the binary representation of the fields data in
 276  
      * order to be directly written to the file.<br>
 277  
      *
 278  
      * @return Binary data representing the current tag field.<br>
 279  
      * @throws java.io.UnsupportedEncodingException
 280  
      *          Most tag data represents text. In some cases the underlying
 281  
      *          implementation will need to convert the text data in java to
 282  
      *          a specific charset encoding. In these cases an
 283  
      *          {@link java.io.UnsupportedEncodingException} may occur.
 284  
      */
 285  
     public byte[] getRawContent() throws UnsupportedEncodingException
 286  
     {
 287  0
         return getBytes();
 288  
     }
 289  
 
 290  
     /**
 291  
      * Determines whether the represented field contains (is made up of) binary
 292  
      * data, instead of text data.<br>
 293  
      * Software can identify fields to be displayed because they are human
 294  
      * readable if this method returns <code>false</code>.
 295  
      *
 296  
      * @return <code>true</code> if field represents binary data (not human
 297  
      *         readable).
 298  
      */
 299  
     public boolean isBinary()
 300  
     {
 301  0
         return true;
 302  
     }
 303  
 
 304  
     /**
 305  
      * This method will set the field to represent binary data.<br>
 306  
      * <p/>
 307  
      * Some implementations may support conversions.<br>
 308  
      * As of now (Octobre 2005) there is no implementation really using this
 309  
      * method to perform useful operations.
 310  
      *
 311  
      * @param b <code>true</code>, if the field contains binary data.
 312  
      * @deprecated As for now is of no use. Implementations should use another
 313  
      *             way of setting this property.
 314  
      */
 315  
     public void isBinary(boolean b)
 316  
     {
 317  
         //Do nothing, always true
 318  0
     }
 319  
 
 320  
     /**
 321  
      * Identifies a field to be of common use.<br>
 322  
      * <p/>
 323  
      * Some software may differ between common and not common fields. A common
 324  
      * one is for sure the title field. A web link may not be of common use for
 325  
      * tagging. However some file formats, or future development of users
 326  
      * expectations will make more fields common than now can be known.
 327  
      *
 328  
      * @return <code>true</code> if the field is of common use.
 329  
      */
 330  
     public boolean isCommon()
 331  
     {
 332  0
         return true;
 333  
     }
 334  
 
 335  
     /**
 336  
      * Determines whether the content of the field is empty.<br>
 337  
      *
 338  
      * @return <code>true</code> if no data is stored (or empty String).
 339  
      */
 340  
     public boolean isEmpty()
 341  
     {
 342  0
         return false;
 343  
     }
 344  
 
 345  
 
 346  
 }