/** * AbstractID3Body.java * * 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; import mobscrob.logging.Log; import mobscrob.logging.LogFactory; import mobscrob.mp3.MP3Stream; /** * @author Neill * */ public abstract class AbstractID3Body { private static final Log log = LogFactory.getLogger(AbstractID3Body.class); private final ID3Header header; /** The stream to read the body tags from **/ protected MP3Stream is; private boolean readComplete; public AbstractID3Body(ID3Header header, MP3Stream is) { this.header = header; this.is = is; this.readComplete = false; } public abstract Frame readNextFrame() throws IOException; public boolean readComplete() { if (is.currentPos() > header.bodyLength()) { readComplete = true; } return readComplete; } /** * @param frameLen * @return * @throws IOException */ protected byte[] readRawFrameBytes(int frameLen) throws IOException { final String methodName = "1"; byte[] rawBytes = new byte[frameLen]; int byteCount = is.read(rawBytes); if (byteCount < 0) { log.info(methodName, "End of stream while reading frame"); readComplete = true; } else if (byteCount != frameLen) { throw ID3Exception.UNEXPECTED_BYTE_READ_COUNT; } return rawBytes; } public static AbstractID3Body instance(ID3Header header, MP3Stream is) { final String methodName = "2"; // return the appropriate ID3Body implementation // for the version number if (header instanceof ID3v1Header) { return new ID3v1Body(header, is); } else { switch (header.majorVersion()) { case ID3v2Header.VERSION_2_2: log.info(methodName, "v2.2"); return new ID3v22Body(header, is); case ID3v2Header.VERSION_2_3: log.info(methodName, "v2.3"); return new ID3v23Body(header, is); case ID3v2Header.VERSION_2_4: log.info(methodName, "v2.4"); return new ID3v24Body(header, is); default: return null; } } } /** * A frame of the tag body * * @author Neill * */ public class Frame { private final String id; private final int length; protected final byte[] rawBytes; public Frame(String id, int length, byte[] raw) { this.id = id; this.length = length; this.rawBytes = raw; } public String getId() { return this.id; } public int getLength() { return this.length; } /** * Returns the raw bytes as a String, determining whether the content is * 8-bit or 16-bit little endian. Any other format is unsupported. * * @return */ public String getContentsAsString() { final String methodName = "3"; if(rawBytes.length < 1) { log.info(methodName, "No content for tag "+id); return "N/A"; } // check whether double byte if ((rawBytes[1] & 0xFF) == 0xFF && (rawBytes[2] & 0xFF) == 0xFE) { log.info(methodName, "Have little endian 16-bit frame"); // have little endian, so pull out the odd bytes int length = (rawBytes.length - 3) / 2; byte[] leBytes = new byte[length]; for (int i = 0; i < leBytes.length; i++) { int pos = (2 * i) + 3; if (pos >= rawBytes.length) { log.error(methodName, "Little endian double byte array index out of bounds: " + pos); } else { leBytes[i] = rawBytes[pos]; } } return new String(leBytes); } else { // need to ignore first byte for some reason... return new String(rawBytes, 1, rawBytes.length - 1); } } } // public class ID3v232Frame extends Frame { // // private boolean compressed; // private final int decompressedSize; // private final byte encType; // private final byte grouping; // // public ID3v232Frame(String frameId, int frameLen, byte[] raw, // boolean compressed, int decompressedSize, byte encType, // byte grouping) { // super(frameId, frameLen, raw); // this.compressed = compressed; // this.decompressedSize = decompressedSize; // this.encType = encType; // this.grouping = grouping; // } // // } }