// ID3v2Header.java // // $Id: ID3v2Header.java,v 1.5 2008/01/03 04:35:51 dmitriy Exp $ // // de.vdheide.mp3: Access MP3 properties, ID3 and ID3v2 tags // Copyright (C) 1999 Jens Vonderheide <jens@vdheide.de> // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Library General Public // License as published by the Free Software Foundation; either // version 2 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Library General Public License for more details. // // You should have received a copy of the GNU Library General Public // License along with this library; if not, write to the // Free Software Foundation, Inc., 59 Temple Place - Suite 330, // Boston, MA 02111-1307, USA. /** * This class contains an ID3v2 header */ package de.vdheide.mp3; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; public class ID3v2Header implements Serializable { /********** Constructors **********/ /** * Create a new (empty) header */ public ID3v2Header() { this(ID3v2.VERSION, ID3v2.REVISION, false, false, false, 0); } /** * Build a ID3v2 header * * @param version ID3v2 version * @param revision ID3v2 revision * @param unsynch Use unsynchronization scheme? * @param extended_header Use extended header? * @param experimental Is experimental? * @param length ID3v2 tag length */ public ID3v2Header (byte version, byte revision, boolean unsynch, boolean extended_header, boolean experimental, int length) { this.version = version; this.revision = revision; this.unsynch = unsynch; this.extended_header = extended_header; this.experimental = experimental; this.size = length; } /** * Creates an ID3v2 header from an input stream. * * @param in Stream to read from * @exception ID3v2IllegalVersionException If tag has a revision higher than * <code>ID3v2.VERSION</code>.<code>ID3v2.REVISION</code> * @exception NoID3v2HeaderException If file does not contain an ID3v2 header * @exception IOException If an I/O error occurs */ public ID3v2Header(IOAdapter in) throws ID3v2IllegalVersionException, NoID3v2HeaderException, IOException { readFromFile(in); } /********** Public methods **********/ /** * Reads header from stream <code>in</code> * Header must start at file position. * * @param in Stream to read from * @exception ID3v2IllegalVersionException If tag has a revision higher than * <code>ID3v2.VERSION</code>.<code>ID3v2.REVISION</code> * @exception NoID3v2HeaderException If file does not contain an ID3v2 header * @exception IOException If an I/O error occurs */ public void readFromFile(IOAdapter in) throws ID3v2IllegalVersionException, NoID3v2HeaderException, IOException { byte []head = new byte[10]; in.read(head); // check if header if (! isHeader(head)) { throw new NoID3v2HeaderException(); } // so we have a valid header // check version version = head[3]; revision = head[4]; if ((version & 0xff) > (ID3v2.MAX_COMPATIBLE_VERSION & 0xff) || (version == ID3v2.MAX_COMPATIBLE_VERSION && (revision & 0xff) > (ID3v2.MAX_COMPATIBLE_REVISION & 0xff))) { throw new ID3v2IllegalVersionException("Version "+version+'.'+revision+" is not supported."); } //System.err.println("Version "+version+'.'+revision); // read & parse flags unsynch = ((head[5] & 0xff) & FLAG_UNSYNCHRONIZATION) > 0; extended_header = (((head[5] & 0xff) & FLAG_EXTENDED_HEADER) > 0); experimental = ((head[5] & 0xff) & FLAG_EXPERIMENTAL) > 0; footer_present = ((head[5] & 0xff) & FLAG_FOOTER_PRESENT) >0; // Last, read size. Size is stored in 4 bits, which all have their highest // bit set to 0 (unsynchronization) size = (head[9] & 0xff) + ((head[8] & 0xff) << 7) + ((head[7] & 0xff) << 14) + ((head[6] & 0xff) << 21); } /** * Checks if bytes contain a correct header * * @param head Array of bytes to be checked * @return true if header is correct */ public static boolean isHeader(byte []head) { // head must be 10 bytes long if (head.length!=10) return false; // must start with ID3 if (head[0]!='I' || head[1]!='D' || head[2]!='3') return false; // next two bytes must be smaller than 255 if (head[3]==(byte)255 || head[4]==(byte)255) return false; // for safety's sake (who knows what future versions will bring), // the flags are not checked // last 4 bytes must be smaller than 128 (first bit set to 0) if ((head[6] & 0xff)>=128 || (head[7] & 0xff)>=128 || (head[8] & 0xff)>=128 || (head[9] & 0xff)>=128) return false; return true; } /** * Is unsynchronization bit set? */ public boolean getUnsynchronization() { return unsynch; } /** * Set / unset unsynchronization bit * * @param act True: Set unsynchronization bit */ public void setUnsynchronization(boolean act) { unsynch = act; } /** * Is extended header present? */ public boolean hasExtendedHeader() { return extended_header; } /** * Set / unset extended header present * * @param act True: Set extended header present bit */ public void setExtendedHeader(boolean act) { extended_header = act; } /** * Is experimental? */ public boolean getExperimental() { return experimental; } /** * Set / unset experimental * e * @param act True: Set experimental bit */ public void setExperimental(boolean act) { experimental = act; } /** * Get length of tag * * @return Length of tag without header (complete length - 10) */ public int getTagSize() { return size; } /** * Set length if tag */ public void setTagSize(int size) { this.size = size; } /** * Convert header to array of bytes * * @return Header as bytes, ready to write */ public byte []getBytes() { byte []work = new byte[10]; work[0]='I'; work[1]='D'; work[2]='3'; work[3] = version; work[4] = revision; byte flag = 0; if (unsynch==true) { flag += FLAG_UNSYNCHRONIZATION; } if (extended_header==true) { flag += FLAG_EXTENDED_HEADER; } if (experimental==true) { flag += FLAG_EXPERIMENTAL; } if (footer_present==true) { flag += FLAG_FOOTER_PRESENT; } work[5] = (byte)flag; /* byte []size_byte = (new Bytes(size, 4)).getBytes(); System.arraycopy(size_byte, 0, work, 6, 4);*/ // create length bytes manually ("unsynchronized") for (int i=0; i<4; i++) { work[i+6] = (byte)((size >> ((3 - i) * 7)) & 127); } return work; } /********** Private variables **********/ byte version = 0; byte revision = 0; boolean unsynch = false; boolean extended_header = false; boolean experimental = false; boolean footer_present ; int size = 0; private final static byte FLAG_UNSYNCHRONIZATION = (byte)(1 << 7); private final static byte FLAG_EXTENDED_HEADER = (byte)(1 << 6); private final static byte FLAG_EXPERIMENTAL = (byte)(1 << 5); private final static byte FLAG_FOOTER_PRESENT = (byte)(1 << 4); }