/* You may freely copy, distribute, modify and use this class as long as the original author attribution remains intact. See message below. Copyright (C) 2001 Christian Pesch. All Rights Reserved. */ package slash.metamusic.mp3; import slash.metamusic.mp3.util.BitConversion; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.logging.Logger; /** * My instances represent a ID3v2 extended header of the MP3 frames as * described in http://www.id3.org/id3v2.3.0.html. * * @author Christian Pesch * @version $Id: ID3v2ExtendedHeader.java 159 2003-12-01 09:43:25Z cpesch $ */ public class ID3v2ExtendedHeader { /** * Logging output */ protected static final Logger log = Logger.getLogger(ID3v2ExtendedHeader.class.getName()); public static final int MINIMUM_EXTENDED_HEADER_SIZE = 6; public static final int CRC_CHECKSUM_SIZE = 5; public static final int UPDATE_FLAG = 0x0040; public static final int CRC_FLAG = 0x0020; public static final int MORE_FLAG = 0x0010; public static final int[] MAXIMUM_TAG_FRAMES = {128, 64, 32, 32}; public static final int[] MAXIMUM_TAG_SIZES = {8000000, 1024000, 320000, 32000}; public static final int[] MAXIMUM_TEXT_SIZES = {-1, 1024, 128, 30}; /** * Create a new (empty) extended header */ public ID3v2ExtendedHeader() { this.size = 0; this.flagSize = 0; this.update = false; this.crced = false; this.crc = new byte[CRC_CHECKSUM_SIZE]; this.maximumTagSize = -1; this.textEncoded = false; this.maximumTextSize = -1; this.imageEncoded = false; this.imageRestriction = -1; } /** * Reads the ID3v2 extended header from the input stream. * * @param in the InputStream to read from * @return if the read header is valid * @throws IOException if an error occurs */ public boolean read(InputStream in) throws IOException { byte[] buffer = new byte[4]; if (in.read(buffer) != buffer.length) throw new IOException("Read invalid extended header"); size = BitConversion.extract4BigEndian(buffer); if (size < MINIMUM_EXTENDED_HEADER_SIZE) { log.severe("The extended header size data is less than the minimum required size."); return false; } buffer = new byte[1]; if (in.read(buffer) != buffer.length) throw new IOException("Read invalid extended header flag count"); flagSize = (int) buffer[0]; buffer = new byte[flagSize + 1]; if (in.read(buffer) != buffer.length) throw new IOException("Read invalid extended header flags"); return readHeader(buffer); } protected boolean readHeader(byte[] data) { int bytesRead = 1; int flags = data[0]; if ((flags & UPDATE_FLAG) != 0) { update = true; bytesRead += 1; } if ((flags & CRC_FLAG) != 0) { crced = true; bytesRead += 1; for (int i = 0; i < crc.length; i++) { crc[i] = data[bytesRead++]; } } if ((flags & MORE_FLAG) != 0) { bytesRead += 1; maximumTagSize = BitConversion.shiftAnd(data[bytesRead], 6, 1); textEncoded = BitConversion.getBit(data[bytesRead], 5) == 1; maximumTextSize = BitConversion.shiftAnd(data[bytesRead], 3, 1); imageEncoded = BitConversion.getBit(data[bytesRead], 2) == 1; imageRestriction = BitConversion.shiftAnd(data[bytesRead], 0, 1); bytesRead += 1; } if (bytesRead != flagSize) { log.severe("The number of found flag bytes " + "in the extended header is not " + "equals to the number specified " + "in the extended header."); return false; } return true; } /** * Writes the ID3v2 extended header to the OutputStream. * * @throws IOException if an error occurs */ public void write(OutputStream out) throws IOException { out.write(getBytes()); } /** * Return an array of bytes representing this extended header in the * standard format to be written to a file. * * @return a binary represenation of this extended header */ public byte[] getBytes() { byte[] result = new byte[size]; int bytesCopied = 0; System.arraycopy(BitConversion.create4BigEndian(size), 0, result, bytesCopied, 4); bytesCopied += 4; result[bytesCopied++] = (byte) flagSize; System.arraycopy(getFlagBytes(), 0, result, bytesCopied, flagSize); bytesCopied += flagSize; return result; } /** * A helper function for the getBytes method that returns a byte array * representing the extended flags field of the extended header. * * @return the extended flags field of the extended header */ protected byte[] getFlagBytes() { byte[] b = new byte[flagSize]; int bytesCopied = 1; b[0] = 0; if (update) { b[0] = BitConversion.setBit(b[0], 7); b[bytesCopied++] = 0; } if (crced) { b[0] = BitConversion.setBit(b[0], 6); b[bytesCopied++] = (byte) crc.length; System.arraycopy(crc, 0, b, bytesCopied, crc.length); bytesCopied += crc.length; } if ((maximumTagSize != -1) || textEncoded || (maximumTextSize != -1) || imageEncoded || (imageRestriction != -1)) { b[0] = BitConversion.setBit(b[0], 5); b[bytesCopied++] = 0x01; byte restrict = 0; if (maximumTagSize != -1) { if (BitConversion.getBit((byte) maximumTagSize, 0) == 1) { restrict = BitConversion.setBit(restrict, 6); } if (BitConversion.getBit((byte) maximumTagSize, 1) == 1) { restrict = BitConversion.setBit(restrict, 7); } } if (textEncoded) { restrict = BitConversion.setBit(restrict, 5); } if (maximumTextSize != -1) { if (BitConversion.getBit((byte) maximumTextSize, 0) == 1) { restrict = BitConversion.setBit(restrict, 3); } if (BitConversion.getBit((byte) maximumTextSize, 1) == 1) { restrict = BitConversion.setBit(restrict, 4); } } if (imageEncoded) { restrict = BitConversion.setBit(restrict, 2); } if (imageRestriction != -1) { if (BitConversion.getBit((byte) imageRestriction, 0) == 1) { restrict = BitConversion.setBit(restrict, 0); } if (BitConversion.getBit((byte) imageRestriction, 1) == 1) { restrict = BitConversion.setBit(restrict, 1); } } b[bytesCopied++] = restrict; } return b; } /** * Returns the size of the extended header * * @return the size of the extended header */ public int getSize() { return size; } /** * Returns the number of extended flag bytes * * @return the number of extended flag bytes */ public int getFlagSize() { return flagSize; } /** * Returns the maximum number of frames if set. If unset, returns -1 * * @return the maximum number of frames or -1 if unset */ public int getMaximumFrames() { int retval = -1; if ((maximumTagSize >= 0) && (maximumTagSize < MAXIMUM_TAG_FRAMES.length)) { retval = MAXIMUM_TAG_FRAMES[maximumTagSize]; } return retval; } /** * Returns the maximum tag size or -1 if unset * * @return the maximum tag size or -1 if unset */ public int getMaximumTagSize() { int retval = -1; if ((maximumTagSize >= 0) && (maximumTagSize < MAXIMUM_TAG_SIZES.length)) { retval = MAXIMUM_TAG_SIZES[maximumTagSize]; } return retval; } /** * Returns true if the text encode flag is set * * @return true if the text encode flag is set */ public boolean getTextEncoded() { return textEncoded; } /** * Returns the maximum length of a string if set or -1 * * @return the maximum length of a string if set or -1 */ public int getMaximumTextSize() { int retval = -1; if ((maximumTextSize >= 0) && (maximumTextSize < MAXIMUM_TEXT_SIZES.length)) { retval = MAXIMUM_TEXT_SIZES[maximumTextSize]; } return retval; } /** * Returns true if the image encode flag is set * * @return true if the image encode flag is set */ public boolean getImageEncoded() { return imageEncoded; } /** * Returns the value of the image restriction field or -1 if not set * * @return the value of the image restriction field or -1 if not set */ public int getImageRestriction() { return imageRestriction; } /** * Returns true if this tag is an update of a previous tag * * @return true if this tag is an update of a previous tag */ public boolean getUpdate() { return update; } /** * Returns true if CRC information is provided for this tag * * @return true if CRC information is provided for this tag */ public boolean getCRCed() { return crced; } /** * If there is crc data in the extended header, then the attached 5 byte * crc will be returned. An empty array will be returned if this has * not been set. * * @return the attached crc data if there is any */ public byte[] getCRC() { return crc; } // --- overwrites Object ----------------------------------- public String toString() { return "ID3v2ExtendedHeader[size=" + getSize() + ", " + "flagSize=" + getFlagSize() + ", " + "update=" + getUpdate() + ", " + "crced=" + getCRCed() + ", " + "maximumFrames=" + getMaximumFrames() + ", " + "maximumTagSize=" + getMaximumTagSize() + ", " + "textEncoded=" + getTextEncoded() + ", " + "maximumTextSize=" + getMaximumTextSize() + ", " + "imageEncoded=" + getImageEncoded() + ", " + "imageRestriction=" + getImageRestriction() + "]"; } // --- member variables ------------------------------------ private int size; private int flagSize; private boolean update; private boolean crced; private byte[] crc; private int maximumTagSize; private boolean textEncoded; private int maximumTextSize; private boolean imageEncoded; private int imageRestriction; }