Integrated id3 chapterreader in Antennapod

This commit is contained in:
daniel oeh 2012-09-21 19:00:49 +02:00
parent 2ebef34408
commit 0eb841db4b
9 changed files with 442 additions and 10 deletions

View File

@ -8,6 +8,11 @@ public abstract class Chapter extends FeedComponent{
protected FeedItem item; protected FeedItem item;
protected String link; protected String link;
public Chapter(long start) {
super();
this.start = start;
}
public Chapter(long start, String title, FeedItem item, String link) { public Chapter(long start, String title, FeedItem item, String link) {
super(); super();
this.start = start; this.start = start;
@ -34,4 +39,20 @@ public abstract class Chapter extends FeedComponent{
return link; return link;
} }
public void setStart(long start) {
this.start = start;
}
public void setTitle(String title) {
this.title = title;
}
public void setItem(FeedItem item) {
this.item = item;
}
public void setLink(String link) {
this.link = link;
}
} }

View File

@ -390,7 +390,8 @@ public class FeedManager {
} }
} }
if (autoQueue) { if (autoQueue) {
addQueueItem(context, addToQueue.toArray(new FeedItem[addToQueue.size()])); addQueueItem(context,
addToQueue.toArray(new FeedItem[addToQueue.size()]));
} }
} }
@ -921,15 +922,24 @@ public class FeedManager {
if (chapterCursor.moveToFirst()) { if (chapterCursor.moveToFirst()) {
item.setChapters(new ArrayList<Chapter>()); item.setChapters(new ArrayList<Chapter>());
do { do {
int chapterType = chapterCursor.getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX); int chapterType = chapterCursor
.getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
Chapter chapter = null; Chapter chapter = null;
long start = chapterCursor.getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX); long start = chapterCursor
String title = chapterCursor.getString(PodDBAdapter.KEY_TITLE_INDEX); .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
String link = chapterCursor.getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX); String title = chapterCursor
.getString(PodDBAdapter.KEY_TITLE_INDEX);
String link = chapterCursor
.getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
switch (chapterType) { switch (chapterType) {
case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER: case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER:
chapter = new SimpleChapter(start, title, item, link); chapter = new SimpleChapter(start, title, item,
link);
break;
case ID3Chapter.CHAPTERTYPE_ID3CHAPTER:
chapter = new ID3Chapter(start, title, item,
link);
break; break;
} }
chapter.setId(chapterCursor chapter.setId(chapterCursor

View File

@ -0,0 +1,36 @@
package de.danoeh.antennapod.feed;
public class ID3Chapter extends Chapter {
public static final int CHAPTERTYPE_ID3CHAPTER = 2;
/**
* Identifies the chapter in its ID3 tag. This attribute does not have to be
* store in the DB and is only used for parsing.
*/
private String id3ID;
public ID3Chapter(String id3ID, long start) {
super(start);
this.id3ID = id3ID;
}
public ID3Chapter(long start, String title, FeedItem item, String link) {
super(start, title, item, link);
}
@Override
public String toString() {
return "ID3Chapter [id3ID=" + id3ID + ", title=" + title + ", start="
+ start + ", url=" + link + "]";
}
@Override
public int getChapterType() {
return CHAPTERTYPE_ID3CHAPTER;
}
public String getId3ID() {
return id3ID;
}
}

View File

@ -0,0 +1,90 @@
package de.danoeh.antennapod.util.id3reader;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.feed.ID3Chapter;
import de.danoeh.antennapod.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.util.id3reader.model.TagHeader;
public class ChapterReader extends ID3Reader {
private static final String FRAME_ID_CHAPTER = "CHAP";
private static final String FRAME_ID_TITLE = "TIT2";
private List<ID3Chapter> chapters;
private ID3Chapter currentChapter;
@Override
public int onStartTagHeader(TagHeader header) {
chapters = new ArrayList<ID3Chapter>();
System.out.println(header.toString());
return ID3Reader.ACTION_DONT_SKIP;
}
@Override
public int onStartFrameHeader(FrameHeader header, InputStream input)
throws IOException, ID3ReaderException {
System.out.println(header.toString());
if (header.getId().equals(FRAME_ID_CHAPTER)) {
if (currentChapter != null) {
if (!hasId3Chapter(currentChapter)) {
chapters.add(currentChapter);
currentChapter = null;
}
}
System.out.println("Found chapter");
String elementId = readString(input, Integer.MAX_VALUE);
char[] startTimeSource = readBytes(input, 4);
long startTime = ((int) startTimeSource[0] << 24)
| ((int) startTimeSource[1] << 16)
| ((int) startTimeSource[2] << 8) | startTimeSource[3];
currentChapter = new ID3Chapter(elementId, startTime);
skipBytes(input, 12);
return ID3Reader.ACTION_DONT_SKIP;
} else if (header.getId().equals(FRAME_ID_TITLE)) {
if (currentChapter != null && currentChapter.getTitle() == null) {
System.out.println("Found title");
skipBytes(input, 1);
currentChapter
.setTitle(readString(input, header.getSize() - 1));
return ID3Reader.ACTION_DONT_SKIP;
}
}
return super.onStartFrameHeader(header, input);
}
private boolean hasId3Chapter(ID3Chapter chapter) {
for (ID3Chapter c : chapters) {
if (c.getId3ID().equals(chapter.getId3ID())) {
return true;
}
}
return false;
}
@Override
public void onEndTag() {
if (currentChapter != null) {
if (!hasId3Chapter(currentChapter)) {
chapters.add(currentChapter);
}
}
System.out.println("Reached end of tag");
if (chapters != null) {
for (ID3Chapter c : chapters) {
System.out.println(c.toString());
}
}
}
@Override
public void onNoTagHeaderFound() {
System.out.println("No tag header found");
super.onNoTagHeaderFound();
}
}

View File

@ -0,0 +1,182 @@
package de.danoeh.antennapod.util.id3reader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PushbackInputStream;
import de.danoeh.antennapod.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.util.id3reader.model.TagHeader;
/**
* Reads the ID3 Tag of a given file. In order to use this class, you should
* create a subclass of it and overwrite the onStart* - or onEnd* - methods.
*/
public class ID3Reader {
private static final int HEADER_LENGTH = 10;
private static final int ID3_LENGTH = 3;
private static final int FRAME_ID_LENGTH = 4;
protected static final int ACTION_SKIP = 1;
protected static final int ACTION_DONT_SKIP = 2;
protected int readerPosition;
private static final char[] LITTLE_ENDIAN_BOM = { 0xff, 0xfe };
private static final char[] BIG_ENDIAN_BOM = { 0xfe, 0xff };
public ID3Reader() {
}
public final void readInputStream(InputStream input) throws IOException,
ID3ReaderException {
int rc;
readerPosition = 0;
char[] tagHeaderSource = readBytes(input, HEADER_LENGTH);
TagHeader tagHeader = createTagHeader(tagHeaderSource);
if (tagHeader == null) {
onNoTagHeaderFound();
} else {
rc = onStartTagHeader(tagHeader);
if (rc == ACTION_SKIP) {
onEndTag();
} else {
while (readerPosition < tagHeader.getSize()) {
FrameHeader frameHeader = createFrameHeader(readBytes(
input, HEADER_LENGTH));
rc = onStartFrameHeader(frameHeader, input);
if (rc == ACTION_SKIP) {
skipBytes(input, frameHeader.getSize());
}
}
onEndTag();
}
}
}
/**
* Read a certain number of bytes from the given input stream. This method
* changes the readerPosition-attribute.
*/
protected char[] readBytes(InputStream input, int number)
throws IOException, ID3ReaderException {
char[] header = new char[number];
for (int i = 0; i < number; i++) {
int b = input.read();
readerPosition++;
if (b != -1) {
header[i] = (char) b;
} else {
throw new ID3ReaderException("Unexpected end of stream");
}
}
return header;
}
/**
* Skip a certain number of bytes on the given input stream. This method
* changes the readerPosition-attribute.
*/
protected void skipBytes(InputStream input, int number) throws IOException {
int skipped = 0;
while (skipped < number) {
skipped += input.skip(number - skipped);
System.out.println("Skipped = " + skipped);
}
readerPosition += number;
}
private TagHeader createTagHeader(char[] source) throws ID3ReaderException {
boolean hasTag = (source[0] == 0x49) && (source[1] == 0x44)
&& (source[2] == 0x33);
if (source.length != HEADER_LENGTH) {
throw new ID3ReaderException("Length of header must be "
+ HEADER_LENGTH);
}
if (hasTag) {
String id = null;
id = new String(source, 0, ID3_LENGTH);
char version = (char) ((source[3] << 8) | source[4]);
byte flags = (byte) source[5];
int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8)
| source[9];
return new TagHeader(id, size, version, flags);
} else {
return null;
}
}
private FrameHeader createFrameHeader(char[] source)
throws ID3ReaderException {
if (source.length != HEADER_LENGTH) {
throw new ID3ReaderException("Length of header must be "
+ HEADER_LENGTH);
}
String id = null;
id = new String(source, 0, FRAME_ID_LENGTH);
int size = (((int) source[4]) << 24) | (((int) source[5]) << 16)
| (((int) source[6]) << 8) | source[7];
char flags = (char) ((source[8] << 8) | source[9]);
return new FrameHeader(id, size, flags);
}
protected String readString(InputStream input, int max) throws IOException,
ID3ReaderException {
char[] bom = readBytes(input, 2);
if (bom == LITTLE_ENDIAN_BOM || bom == BIG_ENDIAN_BOM) {
return readUnicodeString(input, bom, max);
} else {
PushbackInputStream pi = new PushbackInputStream(input, 2);
pi.unread(bom[1]);
pi.unread(bom[0]);
return readISOString(pi, max);
}
}
private String readISOString(InputStream input, int max)
throws IOException, ID3ReaderException {
int bytesRead = 0;
StringBuilder builder = new StringBuilder();
char c;
while (++bytesRead <= max && (c = (char) input.read()) > 0) {
builder.append(c);
}
return builder.toString();
}
private String readUnicodeString(InputStream input, char[] bom, int max)
throws IOException, ID3ReaderException {
StringBuffer builder = new StringBuffer();
char c1 = (char) input.read();
char c2 = (char) input.read();
int bytesRead = 2;
while ((c1 > 0 && c2 > 0) && ++bytesRead <= max) {
builder.append(c1);
c1 = c2;
c2 = (char) input.read();
}
if (bom == LITTLE_ENDIAN_BOM) {
builder.reverse();
}
return builder.toString();
}
public int onStartTagHeader(TagHeader header) {
return ACTION_SKIP;
}
public int onStartFrameHeader(FrameHeader header, InputStream input)
throws IOException, ID3ReaderException {
return ACTION_SKIP;
}
public void onEndTag() {
}
public void onNoTagHeaderFound() {
}
}

View File

@ -0,0 +1,20 @@
package de.danoeh.antennapod.util.id3reader;
public class ID3ReaderException extends Exception {
public ID3ReaderException() {
}
public ID3ReaderException(String arg0) {
super(arg0);
}
public ID3ReaderException(Throwable arg0) {
super(arg0);
}
public ID3ReaderException(String arg0, Throwable arg1) {
super(arg0, arg1);
}
}

View File

@ -0,0 +1,18 @@
package de.danoeh.antennapod.util.id3reader.model;
public class FrameHeader extends Header {
protected char flags;
public FrameHeader(String id, int size, char flags) {
super(id, size);
this.flags = flags;
}
@Override
public String toString() {
return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size
+ "]";
}
}

View File

@ -0,0 +1,29 @@
package de.danoeh.antennapod.util.id3reader.model;
public abstract class Header {
protected String id;
protected int size;
public Header(String id, int size) {
super();
this.id = id;
this.size = size;
}
public String getId() {
return id;
}
public int getSize() {
return size;
}
@Override
public String toString() {
return "Header [id=" + id + ", size=" + size + "]";
}
}

View File

@ -0,0 +1,26 @@
package de.danoeh.antennapod.util.id3reader.model;
public class TagHeader extends Header {
protected char version;
protected byte flags;
public TagHeader(String id, int size, char version, byte flags) {
super(id, size);
this.version = version;
this.flags = flags;
}
@Override
public String toString() {
return "TagHeader [version=" + version + ", flags=" + flags + ", id="
+ id + ", size=" + size + "]";
}
public char getVersion() {
return version;
}
}