package com.limegroup.gnutella.metadata;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Locale;
import java.util.Set;
import com.limegroup.gnutella.ByteOrder;
import com.limegroup.gnutella.util.IOUtils;
/**
* this file parses comments from a flac file for general packet specs see:
* <url>http://flac.sourceforge.net</url>
*/
public class FLACMetaData extends AudioMetaData {
// a set of recommended headers by the spec:
// note we parse only those tags relevant to the Lime XML Audio schema
public static final String TITLE_TAG = "title";
public static final String TRACK_TAG = "tracknumber";
public static final String ALBUM_TAG = "album";
public static final String GENRE_TAG = "genre";
public static final String DATE_TAG = "date";
public static final String COMMENT_TAG = "comment";
public static final String ARTIST_TAG = "artist";
public static final String LICENSE_TAG = "license";
public FLACMetaData(File f) throws IOException {
super(f);
}
protected void parseFile(File file) throws IOException {
InputStream is = null;
try {
is = new FileInputStream(file);
DataInputStream dis = new DataInputStream(is);
if (!readHeader(dis))
return;
Set comments = searchAndReadMetaData(dis);
parseVorbisComment(comments);
} finally {
IOUtils.close(is);
}
}
private boolean readHeader(DataInputStream dis) throws IOException {
return dis.readByte() == 'f' && dis.readByte() == 'L'
&& dis.readByte() == 'a' && dis.readByte() == 'C';
}
private static final byte FIRST_BIT = (byte) (1 << 7);
private Set searchAndReadMetaData(DataInputStream dis)
throws IOException {
Set ret = new HashSet();
boolean shouldStop = false;
do {
byte[] blockHeader = new byte[4];
dis.readFully(blockHeader);
shouldStop = (blockHeader[0] & FIRST_BIT) != 0;
byte type = (byte) (blockHeader[0] & ~FIRST_BIT);
int size = ByteOrder.beb2int(blockHeader, 1, 3);
if (type == 4) {
readVorbisComments(dis, ret);
} else if (type == 0) {
readStreamInfo(dis);
} else {
IOUtils.ensureSkip(dis, size);
}
} while (!shouldStop);
return ret;
}
private void readStreamInfo(DataInputStream dis) throws IOException {
IOUtils.ensureSkip(dis, 10);
// next 8 bytes are 20 bits sample rate, 3bits (no. of channels -1), 5
// bits (bits per sample -1), 36 bits (total samples in stream)
byte[] info = new byte[8];
dis.readFully(info);
// md5 of audio data
IOUtils.ensureSkip(dis, 16);
int sampleRate = ByteOrder.beb2int(info, 0, 3) >> 4;
int numChannels = ((ByteOrder.beb2int(info, 2, 1) >> 1) & 7) + 1;
int bitsPerSample = ((ByteOrder.beb2int(info, 2, 2) >> 4) & 31) + 1;
// read the first 4 bit, than do some shifting
long totalSamples = ByteOrder.beb2int(info, 3, 1) & 15;
totalSamples = totalSamples << 32;
totalSamples = totalSamples | ByteOrder.beb2int(info, 4, 4);
setBitrate(bitsPerSample * sampleRate / 1024 * numChannels);
setLength((int) (totalSamples / sampleRate));
}
private void readVorbisComments(DataInputStream dis, Set comments)
throws IOException {
// read size of vendor string
byte[] dword = new byte[4];
dis.readFully(dword);
int vendorStringSize = ByteOrder.leb2int(dword, 0);
// read vendor string
byte[] vendorString = new byte[vendorStringSize];
dis.readFully(vendorString);
// read number of comments
dis.readFully(dword);
int numComments = ByteOrder.leb2int(dword, 0);
// read comments
for (int i = 0; i < numComments; i++) {
dis.readFully(dword);
int commentSize = ByteOrder.leb2int(dword, 0);
byte[] comment = new byte[commentSize];
dis.readFully(comment);
comments.add(new String(comment, "UTF-8"));
}
}
private void parseVorbisComment(Set comments) {
for (Iterator iter = comments.iterator(); iter.hasNext();) {
String str = iter.next().toString();
int index = str.indexOf('=');
String key = str.substring(0, index);
key = key.toLowerCase(Locale.US);
String value = str.substring(index + 1);
if (key.equals(TITLE_TAG))
setTitle(value);
else if (key.equals(ARTIST_TAG))
setArtist(value);
else if (key.equals(COMMENT_TAG))
setComment(value);
else if (key.equals(ALBUM_TAG))
setAlbum(value);
else if (key.equals(LICENSE_TAG))
setLicense(value);
else if (key.equals(DATE_TAG))
// flac store the year in yyyy-mm-dd format like vorbis
setYear(value.length() > 4 ? value.substring(0, 4) : value);
else if (key.equals(TRACK_TAG)) {
try {
short track = Short.parseShort(value);
setTrack(track);
} catch (NumberFormatException ignored) {
}
}
}
}
}