package de.danoeh.antennapod.core.util.vorbiscommentreader; import org.apache.commons.io.EndianUtils; import org.apache.commons.io.IOUtils; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.nio.ByteBuffer; import java.nio.charset.Charset; import java.util.Arrays; public abstract class VorbisCommentReader { /** Length of first page in an ogg file in bytes. */ private static final int FIRST_PAGE_LENGTH = 58; private static final int SECOND_PAGE_MAX_LENGTH = 64 * 1024 * 1024; private static final int PACKET_TYPE_IDENTIFICATION = 1; private static final int PACKET_TYPE_COMMENT = 3; /** Called when Reader finds identification header. */ public abstract void onVorbisCommentFound(); public abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header); /** * Is called every time the Reader finds a content vector. The handler * should return true if it wants to handle the content vector. */ public abstract boolean onContentVectorKey(String content); /** * Is called if onContentVectorKey returned true for the key. * * @throws VorbisCommentReaderException */ public abstract void onContentVectorValue(String key, String value) throws VorbisCommentReaderException; public abstract void onNoVorbisCommentFound(); public abstract void onEndOfComment(); public abstract void onError(VorbisCommentReaderException exception); public void readInputStream(InputStream input) throws VorbisCommentReaderException { try { // look for identification header if (findIdentificationHeader(input)) { onVorbisCommentFound(); input = new OggInputStream(input); if (findCommentHeader(input)) { VorbisCommentHeader commentHeader = readCommentHeader(input); if (commentHeader != null) { onVorbisCommentHeaderFound(commentHeader); for (int i = 0; i < commentHeader .getUserCommentLength(); i++) { try { long vectorLength = EndianUtils .readSwappedUnsignedInteger(input); String key = readContentVectorKey(input, vectorLength).toLowerCase(); boolean readValue = onContentVectorKey(key); if (readValue) { String value = readUTF8String( input, (int) (vectorLength - key.length() - 1)); onContentVectorValue(key, value); } else { IOUtils.skipFully(input, vectorLength - key.length() - 1); } } catch (IOException e) { e.printStackTrace(); } } onEndOfComment(); } } else { onError(new VorbisCommentReaderException( "No comment header found")); } } else { onNoVorbisCommentFound(); } } catch (IOException e) { onError(new VorbisCommentReaderException(e)); } } private String readUTF8String(InputStream input, long length) throws IOException { byte[] buffer = new byte[(int) length]; IOUtils.readFully(input, buffer); Charset charset = Charset.forName("UTF-8"); return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString(); } /** * Looks for an identification header in the first page of the file. If an * identification header is found, it will be skipped completely and the * method will return true, otherwise false. * * @throws IOException */ private boolean findIdentificationHeader(InputStream input) throws IOException { byte[] buffer = new byte[FIRST_PAGE_LENGTH]; IOUtils.readFully(input, buffer); int i; for (i = 6; i < buffer.length; i++) { if (buffer[i - 5] == 'v' && buffer[i - 4] == 'o' && buffer[i - 3] == 'r' && buffer[i - 2] == 'b' && buffer[i - 1] == 'i' && buffer[i] == 's' && buffer[i - 6] == PACKET_TYPE_IDENTIFICATION) { return true; } } return false; } private boolean findCommentHeader(InputStream input) throws IOException { char[] buffer = new char["vorbis".length() + 1]; for (int bytesRead = 0; bytesRead < SECOND_PAGE_MAX_LENGTH; bytesRead++) { char c = (char) input.read(); int dest = -1; switch (c) { case PACKET_TYPE_COMMENT: dest = 0; break; case 'v': dest = 1; break; case 'o': dest = 2; break; case 'r': dest = 3; break; case 'b': dest = 4; break; case 'i': dest = 5; break; case 's': dest = 6; break; } if (dest >= 0) { buffer[dest] = c; if (buffer[1] == 'v' && buffer[2] == 'o' && buffer[3] == 'r' && buffer[4] == 'b' && buffer[5] == 'i' && buffer[6] == 's' && buffer[0] == PACKET_TYPE_COMMENT) { return true; } } else { Arrays.fill(buffer, (char) 0); } } return false; } private VorbisCommentHeader readCommentHeader(InputStream input) throws IOException, VorbisCommentReaderException { try { long vendorLength = EndianUtils.readSwappedUnsignedInteger(input); String vendorName = readUTF8String(input, vendorLength); long userCommentLength = EndianUtils .readSwappedUnsignedInteger(input); return new VorbisCommentHeader(vendorName, userCommentLength); } catch (UnsupportedEncodingException e) { throw new VorbisCommentReaderException(e); } } private String readContentVectorKey(InputStream input, long vectorLength) throws IOException { StringBuilder builder = new StringBuilder(); for (int i = 0; i < vectorLength; i++) { char c = (char) input.read(); if (c == '=') { return builder.toString(); } else { builder.append(c); } } return null; // no key found } }