Integrated id3 chapterreader in Antennapod
This commit is contained in:
parent
2ebef34408
commit
0eb841db4b
|
@ -8,6 +8,11 @@ public abstract class Chapter extends FeedComponent{
|
|||
protected FeedItem item;
|
||||
protected String link;
|
||||
|
||||
public Chapter(long start) {
|
||||
super();
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public Chapter(long start, String title, FeedItem item, String link) {
|
||||
super();
|
||||
this.start = start;
|
||||
|
@ -34,4 +39,20 @@ public abstract class Chapter extends FeedComponent{
|
|||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -390,7 +390,8 @@ public class FeedManager {
|
|||
}
|
||||
}
|
||||
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()) {
|
||||
item.setChapters(new ArrayList<Chapter>());
|
||||
do {
|
||||
int chapterType = chapterCursor.getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
|
||||
int chapterType = chapterCursor
|
||||
.getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX);
|
||||
Chapter chapter = null;
|
||||
long start = chapterCursor.getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
|
||||
String title = chapterCursor.getString(PodDBAdapter.KEY_TITLE_INDEX);
|
||||
String link = chapterCursor.getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
|
||||
long start = chapterCursor
|
||||
.getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX);
|
||||
String title = chapterCursor
|
||||
.getString(PodDBAdapter.KEY_TITLE_INDEX);
|
||||
String link = chapterCursor
|
||||
.getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX);
|
||||
|
||||
switch (chapterType) {
|
||||
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;
|
||||
}
|
||||
chapter.setId(chapterCursor
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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
|
||||
+ "]";
|
||||
}
|
||||
|
||||
}
|
|
@ -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 + "]";
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue