package org.jaudiotagger.audio.flac.metadatablock; import org.jaudiotagger.audio.generic.Utils; import org.jaudiotagger.tag.FieldKey; import org.jaudiotagger.tag.InvalidFrameException; import org.jaudiotagger.tag.TagField; import org.jaudiotagger.tag.id3.valuepair.TextEncoding; import org.jaudiotagger.tag.reference.PictureTypes; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.util.logging.Logger; /** * Picture Block * <p/> * <p/> * <p>This block is for storing pictures associated with the file, most commonly cover art from CDs. * There may be more than one PICTURE block in a file. The picture format is similar to the APIC frame in ID3v2. * The PICTURE block has a type, MIME type, and UTF-8 description like ID3v2, and supports external linking via URL * (though this is discouraged). The differences are that there is no uniqueness constraint on the description field, * and the MIME type is mandatory. The FLAC PICTURE block also includes the resolution, color depth, and palette size * so that the client can search for a suitable picture without having to scan them all * <p/> * Format: * <Size in bits> Info * <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> The length of the MIME type string in bytes. * <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. * <32> The length of the description string in bytes. * <n*8> The description of the picture, in UTF-8. * <32> The width of the picture in pixels. * <32> The height of the picture in pixels. * <32> The color depth of the picture in bits-per-pixel. * <32> For indexed-color pictures (e.g. GIF), the number of colors used, or 0 for non-indexed pictures. * <32> The length of the picture data in bytes. * <n*8> The binary picture data. */ public class MetadataBlockDataPicture implements MetadataBlockData, TagField { public static final String IMAGE_IS_URL = "-->"; private int pictureType; private String mimeType; private String description; private int width; private int height; private int colourDepth; private int indexedColouredCount; private byte[] imageData; // Logger Object public static Logger logger = Logger.getLogger("org.jaudiotagger.audio.flac.MetadataBlockDataPicture"); private void initFromByteBuffer(ByteBuffer rawdata) throws IOException, InvalidFrameException { //Picture Type pictureType = rawdata.getInt(); if (pictureType >= PictureTypes.getInstanceOf().getSize()) { throw new InvalidFrameException("PictureType was:" + pictureType + "but the maximum allowed is " + (PictureTypes.getInstanceOf().getSize() - 1)); } //MimeType int mimeTypeSize = rawdata.getInt(); mimeType = getString(rawdata, mimeTypeSize, "ISO-8859-1"); //Description int descriptionSize = rawdata.getInt(); description = getString(rawdata, descriptionSize, "UTF-8"); //Image width width = rawdata.getInt(); //Image height height = rawdata.getInt(); //Colour Depth colourDepth = rawdata.getInt(); //Indexed Colour Count indexedColouredCount = rawdata.getInt(); //ImageData int rawdataSize = rawdata.getInt(); imageData = new byte[rawdataSize]; rawdata.get(imageData); //logger.info("Read image:" + this.toString()); } /** * Initialize MetaBlockDataPicture from byteBuffer * * @param rawdata * @throws IOException * @throws InvalidFrameException */ public MetadataBlockDataPicture(ByteBuffer rawdata) throws IOException, InvalidFrameException { initFromByteBuffer(rawdata); } /** * Construct picture block by reading from file, the header informs us how many bytes we should be reading from * * @param header * @param raf * @throws java.io.IOException * @throws org.jaudiotagger.tag.InvalidFrameException * */ //TODO check for buffer underflows see http://research.eeye.com/html/advisories/published/AD20071115.html public MetadataBlockDataPicture(MetadataBlockHeader header, RandomAccessFile raf) throws IOException, InvalidFrameException { ByteBuffer rawdata = ByteBuffer.allocate(header.getDataLength()); int bytesRead = raf.getChannel().read(rawdata); if (bytesRead < header.getDataLength()) { throw new IOException("Unable to read required number of databytes read:" + bytesRead + ":required:" + header.getDataLength()); } rawdata.rewind(); initFromByteBuffer(rawdata); } /** * Construct new MetadataPicture block * * @param imageData * @param pictureType * @param mimeType * @param description * @param width * @param height * @param colourDepth * @param indexedColouredCount */ public MetadataBlockDataPicture(byte[] imageData, int pictureType, String mimeType, String description, int width, int height, int colourDepth, int indexedColouredCount) { //Picture Type this.pictureType = pictureType; //MimeType this.mimeType = mimeType; //Description this.description = description; this.width = width; this.height = height; this.colourDepth = colourDepth; this.indexedColouredCount = indexedColouredCount; //ImageData this.imageData = imageData; } private String getString(ByteBuffer rawdata, int length, String charset) throws IOException { byte[] tempbuffer = new byte[length]; rawdata.get(tempbuffer); return new String(tempbuffer, charset); } public byte[] getBytes() { try { ByteArrayOutputStream baos = new ByteArrayOutputStream(); baos.write(Utils.getSizeBEInt32(pictureType)); baos.write(Utils.getSizeBEInt32(mimeType.length())); baos.write(mimeType.getBytes("ISO-8859-1")); baos.write(Utils.getSizeBEInt32(description.length())); baos.write(description.getBytes("UTF-8")); baos.write(Utils.getSizeBEInt32(width)); baos.write(Utils.getSizeBEInt32(height)); baos.write(Utils.getSizeBEInt32(colourDepth)); baos.write(Utils.getSizeBEInt32(indexedColouredCount)); baos.write(Utils.getSizeBEInt32(imageData.length)); baos.write(imageData); return baos.toByteArray(); } catch (IOException ioe) { throw new RuntimeException(ioe.getMessage()); } } public int getLength() { return getBytes().length; } public int getPictureType() { return pictureType; } public String getMimeType() { return mimeType; } public String getDescription() { return description; } public int getWidth() { return width; } public int getHeight() { return height; } public int getColourDepth() { return colourDepth; } public int getIndexedColourCount() { return indexedColouredCount; } public byte[] getImageData() { return imageData; } /** * @return true if imagedata is held as a url rather than actually being imagedata */ public boolean isImageUrl() { return getMimeType().equals(IMAGE_IS_URL); } /** * @return the image url if there is otherwise return an empty String */ public String getImageUrl() { if (isImageUrl()) { return Utils.getString(getImageData(), 0, getImageData().length, TextEncoding.CHARSET_ISO_8859_1); } else { return ""; } } public String toString() { return PictureTypes.getInstanceOf().getValueForId(pictureType) + ":" + mimeType + ":" + description + ":" + "width:" + width + ":height:" + height + ":colourdepth:" + colourDepth + ":indexedColourCount:" + indexedColouredCount + ":image size in bytes:" + imageData.length; } /** * This method copies the data of the given field to the current data.<br> * * @param field The field containing the data to be taken. */ public void copyContent(TagField field) { throw new UnsupportedOperationException(); } /** * Returns the Id of the represented tag field.<br> * This value should uniquely identify a kind of tag data, like title. * {@link org.jaudiotagger.audio.generic.AbstractTag} will use the "id" to summarize multiple * fields. * * @return Unique identifier for the fields type. (title, artist...) */ public String getId() { return FieldKey.COVER_ART.name(); } /** * This method delivers the binary representation of the fields data in * order to be directly written to the file.<br> * * @return Binary data representing the current tag field.<br> * @throws java.io.UnsupportedEncodingException * Most tag data represents text. In some cases the underlying * implementation will need to convert the text data in java to * a specific charset encoding. In these cases an * {@link java.io.UnsupportedEncodingException} may occur. */ public byte[] getRawContent() throws UnsupportedEncodingException { return getBytes(); } /** * Determines whether the represented field contains (is made up of) binary * data, instead of text data.<br> * Software can identify fields to be displayed because they are human * readable if this method returns <code>false</code>. * * @return <code>true</code> if field represents binary data (not human * readable). */ public boolean isBinary() { return true; } /** * This method will set the field to represent binary data.<br> * <p/> * Some implementations may support conversions.<br> * As of now (Octobre 2005) there is no implementation really using this * method to perform useful operations. * * @param b <code>true</code>, if the field contains binary data. * @deprecated As for now is of no use. Implementations should use another * way of setting this property. */ public void isBinary(boolean b) { //Do nothing, always true } /** * Identifies a field to be of common use.<br> * <p/> * Some software may differ between common and not common fields. A common * one is for sure the title field. A web link may not be of common use for * tagging. However some file formats, or future development of users * expectations will make more fields common than now can be known. * * @return <code>true</code> if the field is of common use. */ public boolean isCommon() { return true; } /** * Determines whether the content of the field is empty.<br> * * @return <code>true</code> if no data is stored (or empty String). */ public boolean isEmpty() { return false; } }