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 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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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