/*
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-2003 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.util.logging.Logger;
/**
* My instances represent a APEv2 tail of the MP3 frames as
* described in http://wiki.hydrogenaudio.org/index.php?title=APEv2.
*
* @author Christian Pesch
* @version $Id: APEv2Tail.java 954 2009-02-2( 15:31:50Z cpesch $
*/
public class APETail implements ID3MetaData {
/**
* Logging output
*/
protected static final Logger log = Logger.getLogger(APETail.class.getName());
public static final String APEV2TAG = "APETAGEX";
public static final int VERSION_NUMBER_SIZE = 4;
public static final int TAG_SIZE_SIZE = 4;
public static final int ITEM_COUNT_SIZE = 4;
public static final int TAGS_FLAGS_SIZE = 4;
public static final int RESERVED_SIZE = 8;
/**
* Encoding to use when converting from bytes to Unicode (String).
*/
protected static final String ENCODING = "ISO8859_1";
/**
* Create a new (empty) tail.
*/
public APETail() {
valid = true;
}
// --- read/write object -----------------------------------
/**
* Reads the APEv2Tail tail from the InputStream.
*
* @param in the InputStream to read
* @param bufferSize the buffer on the tail
* @return if the read tail is valid
* @throws NoAPEv2TailException if no tail can be found or exists
* @throws IOException if an error occurs
*/
public boolean read(InputStream in, int bufferSize) throws NoAPEv2TailException, IOException {
valid = false;
readSize = 0;
log.fine("Reading APEv2 tail");
// search for a valid tail
boolean tailFound = false;
while (checkForAPEv2Tail(in, bufferSize)) {
tailFound = parse(in);
if (tailFound) {
valid = true;
break;
}
}
if (!tailFound)
throw new NoAPEv2TailException();
return valid;
}
/**
* Check if APEv2 tail is present
*
* @param in the InputStream to read
* @param bufferSize the buffer on the tail
* @return true if tail is present
* @throws IOException if an error occurs
*/
protected boolean checkForAPEv2Tail(InputStream in, int bufferSize) throws IOException {
byte tag[] = APEV2TAG.getBytes(ENCODING);
int readSize = 0;
while (true) {
// read through stream until first byte of the tag is read
int read = in.read(); readSize++;
while (read != tag[0] && read != -1 && readSize < bufferSize) {
read = in.read(); readSize++;
}
if (read == -1 || readSize >= bufferSize) {
// first tag byte not found but stream finished
return false;
}
// check if rest of the tag matches
int count = 1;
while (count < tag.length && readSize < bufferSize) {
read = in.read(); readSize++;
if (read == -1 || readSize >= bufferSize) {
// tag byte not found but stream finished
return false;
}
if (read == tag[count]) {
count++;
} else
break;
}
if (count == tag.length) {
// synchronized
return true;
}
}
}
protected boolean parse(InputStream in) throws NoAPEv2TailException, IOException {
byte[] buffer = new byte[VERSION_NUMBER_SIZE];
if (in.read(buffer, 0, VERSION_NUMBER_SIZE) != VERSION_NUMBER_SIZE)
return false;
int versionCode = BitConversion.extract4LittleEndian(buffer);
version = versionCode == 2000 ? "2" : (versionCode == 1000) ? "1" : "?";
buffer = new byte[TAG_SIZE_SIZE];
if (in.read(buffer, 0, TAG_SIZE_SIZE) != TAG_SIZE_SIZE)
return false;
tagSize = BitConversion.extract4LittleEndian(buffer);
buffer = new byte[ITEM_COUNT_SIZE];
if (in.read(buffer, 0, ITEM_COUNT_SIZE) != ITEM_COUNT_SIZE)
return false;
itemCount = BitConversion.extract4LittleEndian(buffer);
buffer = new byte[TAGS_FLAGS_SIZE];
if (in.read(buffer, 0, TAGS_FLAGS_SIZE) != TAGS_FLAGS_SIZE)
return false;
tagsFlags = BitConversion.extract4LittleEndian(buffer);
buffer = new byte[RESERVED_SIZE];
if (in.read(buffer, 0, RESERVED_SIZE) != RESERVED_SIZE)
return false;
byte[] tail = new byte[tagSize];
//noinspection RedundantIfStatement
if (in.read(tail, 0, tagSize) != tagSize)
return false;
return true;
}
// --- get object ------------------------------------------
public boolean isValid() {
return valid;
}
public long getReadSize() {
return readSize;
}
// --- MetaData get ----------------------------------------
public String getTrack() {
throw new UnsupportedOperationException();
}
public String getArtist() {
throw new UnsupportedOperationException();
}
public String getAlbum() {
throw new UnsupportedOperationException();
}
public int getYear() {
throw new UnsupportedOperationException();
}
public ID3Genre getGenre() {
throw new UnsupportedOperationException();
}
public String getComment() {
throw new UnsupportedOperationException();
}
public int getIndex() {
throw new UnsupportedOperationException();
}
public String getVersion() {
return version;
}
// --- set object ------------------------------------------
public void setValid(boolean valid) {
this.valid = valid;
}
// --- MetaData set ----------------------------------------
public void setTrack(String newTrack) {
throw new UnsupportedOperationException();
}
public void setArtist(String newArtist) {
throw new UnsupportedOperationException();
}
public void setAlbum(String newAlbum) {
throw new UnsupportedOperationException();
}
public void setYear(int newYear) {
throw new UnsupportedOperationException();
}
public void setGenre(ID3Genre newGenre) {
throw new UnsupportedOperationException();
}
public void setIndex(int newIndex) {
throw new UnsupportedOperationException();
}
public void setComment(String newComment) {
throw new UnsupportedOperationException();
}
// --- overwrites Object -----------------------------------
public String toString() {
StringBuffer buffer = new StringBuffer();
buffer.append("APETail[isValid=").append(isValid());
if (isValid()) {
buffer.append(", version=").append(getVersion());
buffer.append(", size=").append(tagSize);
buffer.append(", items=").append(itemCount);
}
buffer.append("]");
return buffer.toString();
}
// --- member variables ------------------------------------
/**
* file data
*/
protected boolean valid;
protected long readSize;
protected String version;
protected int tagSize, itemCount, tagsFlags;
}