/** * ID3v2Header * @date 30 Sep 2008 * * This program is distributed under the terms of the GNU General Public * License * Copyright 2008 NJ Pearman * * This file is part of MobScrob. * * MobScrob is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * MobScrob 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with MobScrob. If not, see <http://www.gnu.org/licenses/>. */ package mobscrob.id3; import java.io.IOException; /** * Parses and represents an ID3v2 header. Note that this header only conforms to * ID3v2.2 and ID3v2.3 and NOT ID3v2.4 * * @author Neill */ public class ID3v2Header implements ID3Header { public static final int HEADER_LEN = 10; public static final int VERSION_2_2 = 2; public static final int VERSION_2_3 = 3; public static final int VERSION_2_4 = 4; private static final int FLAG_UNSYNCHRONIZATION = 1 << 7; private static final int FLAG_EXTENDED_HEADER = 1 << 6; private static final int FLAG_EXPERIMENTAL_HEADER = 1 << 5; private static final int FLAG_FOOTER = 1 << 4; private static final int INVALID_FLAGS = 7; private byte[] raw; private int majorVersion; private int revision; private long bodyLength; private boolean hasUnchronization; private boolean hasExtendedHeader; /** * Constructs an ID3Header with the specified raw header data * * @param raw */ public ID3v2Header(byte[] raw) throws IOException { if (raw.length != 10) { throw new IOException("Header data must be ten bytes long"); } this.raw = raw; } /* * (non-Javadoc) * * @see mobscrob.id3.IID3Header#parse() */ public void parse() throws IOException { verifyIsID3(); readVersion(); parseFlags(); bodyLength = determineTagLength(); } /* * (non-Javadoc) * * @see mobscrob.id3.IID3Header#bodyLength() */ public long bodyLength() { return bodyLength; } /* * (non-Javadoc) * * @see mobscrob.id3.IID3Header#majorVersion() */ public int majorVersion() { return this.majorVersion; } /** * Verifies that the header starts with the bytes I, D and 3 * * @throws IOException */ protected void verifyIsID3() throws IOException { if (raw[0] != 0x49) { // I throw new IOException("Invalid ID3 header, not got I: " + (char) raw[0]); } else if (raw[1] != 0x44) { // D throw new IOException("Invalid ID3 header, not got D: " + (char) raw[1]); } else if (raw[2] != 0x33) { // 3 throw new IOException("Invalid ID3 header, not got 3: " + (char) raw[2]); } } /** * Reads the major version and revision from the header bytes. Major * versions supported are 2 and 3. Revision number should always be 0 (zero) * * @throws IOException */ protected void readVersion() throws IOException { majorVersion = raw[3]; revision = raw[4]; if (majorVersion < 2 || majorVersion > 4) { throw new IOException("Unsupported major version " + majorVersion); } else if (revision != 0) { throw new IOException("Revision number not zero"); } } /** * Parses the flags in the header * * @throws IOException */ protected void parseFlags() throws IOException { long flags = raw[5]; if ((flags & FLAG_UNSYNCHRONIZATION) > 0) { hasUnchronization = true; flags ^= FLAG_UNSYNCHRONIZATION; } if (majorVersion > 2 && (flags & FLAG_EXTENDED_HEADER) > 0) { hasExtendedHeader = true; flags ^= FLAG_EXTENDED_HEADER; } if (majorVersion > 3 && (flags & FLAG_EXPERIMENTAL_HEADER) > 0) { flags ^= FLAG_EXPERIMENTAL_HEADER; } if (majorVersion > 3 && (flags & FLAG_FOOTER) > 0) { flags ^= FLAG_FOOTER; } if ((flags & INVALID_FLAGS) > 0) { throw new IOException("Unexpected flags in header"); } } /** * Determines the length of the tag from the header bytes and returns this * as a long. The maximum value is (2^7)-1 If the header contains invalid * values for the tag length and IOException is thrown. * * @return * @throws IOException */ protected long determineTagLength() throws IOException { return SynchsafeInteger.valueOf(new byte[] { raw[6], raw[7], raw[8], raw[9] }); } /** * Need a protected setter for unit testing * * @param value */ protected void setMajorVersion(int value) { this.majorVersion = value; } /* * (non-Javadoc) * * @see mobscrob.id3.IID3Header#hasUnchronization() */ public boolean hasUnchronization() { return hasUnchronization; } /* * (non-Javadoc) * * @see mobscrob.id3.IID3Header#hasExtendedHeader() */ public boolean hasExtendedHeader() { return hasExtendedHeader; } }