Reformatted vorbis reader for readability
This commit is contained in:
parent
9db5450243
commit
246a2e650c
|
@ -10,93 +10,91 @@ import de.danoeh.antennapod.core.feed.Chapter;
|
||||||
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
|
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
|
||||||
|
|
||||||
public class VorbisCommentChapterReader extends VorbisCommentReader {
|
public class VorbisCommentChapterReader extends VorbisCommentReader {
|
||||||
private static final String TAG = "VorbisCommentChptrReadr";
|
private static final String TAG = "VorbisCommentChptrReadr";
|
||||||
|
|
||||||
private static final String CHAPTER_KEY = "chapter\\d\\d\\d.*";
|
private static final String CHAPTER_KEY = "chapter\\d\\d\\d.*";
|
||||||
private static final String CHAPTER_ATTRIBUTE_TITLE = "name";
|
private static final String CHAPTER_ATTRIBUTE_TITLE = "name";
|
||||||
private static final String CHAPTER_ATTRIBUTE_LINK = "url";
|
private static final String CHAPTER_ATTRIBUTE_LINK = "url";
|
||||||
|
|
||||||
private List<Chapter> chapters;
|
private List<Chapter> chapters;
|
||||||
|
|
||||||
public VorbisCommentChapterReader() {
|
public VorbisCommentChapterReader() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVorbisCommentFound() {
|
public void onVorbisCommentFound() {
|
||||||
System.out.println("Vorbis comment found");
|
System.out.println("Vorbis comment found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVorbisCommentHeaderFound(VorbisCommentHeader header) {
|
public void onVorbisCommentHeaderFound(VorbisCommentHeader header) {
|
||||||
chapters = new ArrayList<>();
|
chapters = new ArrayList<>();
|
||||||
System.out.println(header.toString());
|
System.out.println(header.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onContentVectorKey(String content) {
|
public boolean onContentVectorKey(String content) {
|
||||||
return content.matches(CHAPTER_KEY);
|
return content.matches(CHAPTER_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContentVectorValue(String key, String value)
|
public void onContentVectorValue(String key, String value) throws VorbisCommentReaderException {
|
||||||
throws VorbisCommentReaderException {
|
if (BuildConfig.DEBUG) {
|
||||||
if (BuildConfig.DEBUG)
|
Log.d(TAG, "Key: " + key + ", value: " + value);
|
||||||
Log.d(TAG, "Key: " + key + ", value: " + value);
|
}
|
||||||
String attribute = VorbisCommentChapter.getAttributeTypeFromKey(key);
|
String attribute = VorbisCommentChapter.getAttributeTypeFromKey(key);
|
||||||
int id = VorbisCommentChapter.getIDFromKey(key);
|
int id = VorbisCommentChapter.getIDFromKey(key);
|
||||||
Chapter chapter = getChapterById(id);
|
Chapter chapter = getChapterById(id);
|
||||||
if (attribute == null) {
|
if (attribute == null) {
|
||||||
if (getChapterById(id) == null) {
|
if (getChapterById(id) == null) {
|
||||||
// new chapter
|
// new chapter
|
||||||
long start = VorbisCommentChapter.getStartTimeFromValue(value);
|
long start = VorbisCommentChapter.getStartTimeFromValue(value);
|
||||||
chapter = new VorbisCommentChapter(id);
|
chapter = new VorbisCommentChapter(id);
|
||||||
chapter.setStart(start);
|
chapter.setStart(start);
|
||||||
chapters.add(chapter);
|
chapters.add(chapter);
|
||||||
} else {
|
} else {
|
||||||
throw new VorbisCommentReaderException(
|
throw new VorbisCommentReaderException("Found chapter with duplicate ID (" + key + ", " + value + ")");
|
||||||
"Found chapter with duplicate ID (" + key + ", "
|
}
|
||||||
+ value + ")");
|
} else if (attribute.equals(CHAPTER_ATTRIBUTE_TITLE)) {
|
||||||
}
|
if (chapter != null) {
|
||||||
} else if (attribute.equals(CHAPTER_ATTRIBUTE_TITLE)) {
|
chapter.setTitle(value);
|
||||||
if (chapter != null) {
|
}
|
||||||
chapter.setTitle(value);
|
} else if (attribute.equals(CHAPTER_ATTRIBUTE_LINK)) {
|
||||||
}
|
if (chapter != null) {
|
||||||
} else if (attribute.equals(CHAPTER_ATTRIBUTE_LINK)) {
|
chapter.setLink(value);
|
||||||
if (chapter != null) {
|
}
|
||||||
chapter.setLink(value);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNoVorbisCommentFound() {
|
public void onNoVorbisCommentFound() {
|
||||||
System.out.println("No vorbis comment found");
|
System.out.println("No vorbis comment found");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEndOfComment() {
|
public void onEndOfComment() {
|
||||||
System.out.println("End of comment");
|
System.out.println("End of comment");
|
||||||
for (Chapter c : chapters) {
|
for (Chapter c : chapters) {
|
||||||
System.out.println(c.toString());
|
System.out.println(c.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onError(VorbisCommentReaderException exception) {
|
public void onError(VorbisCommentReaderException exception) {
|
||||||
exception.printStackTrace();
|
exception.printStackTrace();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Chapter getChapterById(long id) {
|
private Chapter getChapterById(long id) {
|
||||||
for (Chapter c : chapters) {
|
for (Chapter c : chapters) {
|
||||||
if (((VorbisCommentChapter) c).getVorbisCommentId() == id) {
|
if (((VorbisCommentChapter) c).getVorbisCommentId() == id) {
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Chapter> getChapters() {
|
public List<Chapter> getChapters() {
|
||||||
return chapters;
|
return chapters;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package de.danoeh.antennapod.core.util.vorbiscommentreader;
|
package de.danoeh.antennapod.core.util.vorbiscommentreader;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
import org.apache.commons.io.EndianUtils;
|
import org.apache.commons.io.EndianUtils;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
|
|
||||||
|
@ -10,185 +11,165 @@ import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
|
||||||
public abstract class VorbisCommentReader {
|
public abstract class VorbisCommentReader {
|
||||||
/** Length of first page in an ogg file in bytes. */
|
/** Length of first page in an ogg file in bytes. */
|
||||||
private static final int FIRST_PAGE_LENGTH = 58;
|
private static final int FIRST_PAGE_LENGTH = 58;
|
||||||
private static final int SECOND_PAGE_MAX_LENGTH = 64 * 1024 * 1024;
|
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_IDENTIFICATION = 1;
|
||||||
private static final int PACKET_TYPE_COMMENT = 3;
|
private static final int PACKET_TYPE_COMMENT = 3;
|
||||||
|
|
||||||
/** Called when Reader finds identification header. */
|
/** Called when Reader finds identification header. */
|
||||||
protected abstract void onVorbisCommentFound();
|
protected abstract void onVorbisCommentFound();
|
||||||
|
|
||||||
protected abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header);
|
protected abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is called every time the Reader finds a content vector. The handler
|
* Is called every time the Reader finds a content vector. The handler
|
||||||
* should return true if it wants to handle the content vector.
|
* should return true if it wants to handle the content vector.
|
||||||
*/
|
*/
|
||||||
protected abstract boolean onContentVectorKey(String content);
|
protected abstract boolean onContentVectorKey(String content);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is called if onContentVectorKey returned true for the key.
|
* Is called if onContentVectorKey returned true for the key.
|
||||||
*
|
*/
|
||||||
* @throws VorbisCommentReaderException
|
protected abstract void onContentVectorValue(String key, String value) throws VorbisCommentReaderException;
|
||||||
*/
|
|
||||||
protected abstract void onContentVectorValue(String key, String value)
|
|
||||||
throws VorbisCommentReaderException;
|
|
||||||
|
|
||||||
protected abstract void onNoVorbisCommentFound();
|
protected abstract void onNoVorbisCommentFound();
|
||||||
|
|
||||||
protected abstract void onEndOfComment();
|
protected abstract void onEndOfComment();
|
||||||
|
|
||||||
protected abstract void onError(VorbisCommentReaderException exception);
|
protected abstract void onError(VorbisCommentReaderException exception);
|
||||||
|
|
||||||
public void readInputStream(InputStream input)
|
public void readInputStream(InputStream input) throws VorbisCommentReaderException {
|
||||||
throws VorbisCommentReaderException {
|
try {
|
||||||
try {
|
// look for identification header
|
||||||
// look for identification header
|
if (findIdentificationHeader(input)) {
|
||||||
if (findIdentificationHeader(input)) {
|
onVorbisCommentFound();
|
||||||
|
input = new OggInputStream(input);
|
||||||
onVorbisCommentFound();
|
if (findCommentHeader(input)) {
|
||||||
input = new OggInputStream(input);
|
VorbisCommentHeader commentHeader = readCommentHeader(input);
|
||||||
if (findCommentHeader(input)) {
|
onVorbisCommentHeaderFound(commentHeader);
|
||||||
VorbisCommentHeader commentHeader = readCommentHeader(input);
|
for (int i = 0; i < commentHeader.getUserCommentLength(); i++) {
|
||||||
if (commentHeader != null) {
|
readUserComment(input);
|
||||||
onVorbisCommentHeaderFound(commentHeader);
|
}
|
||||||
for (int i = 0; i < commentHeader
|
onEndOfComment();
|
||||||
.getUserCommentLength(); i++) {
|
} else {
|
||||||
try {
|
onError(new VorbisCommentReaderException("No comment header found"));
|
||||||
long vectorLength = EndianUtils
|
}
|
||||||
.readSwappedUnsignedInteger(input);
|
} else {
|
||||||
String key = readContentVectorKey(input,
|
onNoVorbisCommentFound();
|
||||||
vectorLength).toLowerCase();
|
}
|
||||||
boolean readValue = onContentVectorKey(key);
|
} catch (IOException e) {
|
||||||
if (readValue) {
|
onError(new VorbisCommentReaderException(e));
|
||||||
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 {
|
private void readUserComment(InputStream input) throws VorbisCommentReaderException {
|
||||||
onError(new VorbisCommentReaderException(
|
try {
|
||||||
"No comment header found"));
|
long vectorLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||||
}
|
String key = readContentVectorKey(input, vectorLength).toLowerCase();
|
||||||
} else {
|
boolean readValue = onContentVectorKey(key);
|
||||||
onNoVorbisCommentFound();
|
if (readValue) {
|
||||||
}
|
String value = readUtf8String(input, (int) (vectorLength - key.length() - 1));
|
||||||
} catch (IOException e) {
|
onContentVectorValue(key, value);
|
||||||
onError(new VorbisCommentReaderException(e));
|
} else {
|
||||||
}
|
IOUtils.skipFully(input, vectorLength - key.length() - 1);
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private String readUTF8String(InputStream input, long length)
|
private String readUtf8String(InputStream input, long length) throws IOException {
|
||||||
throws IOException {
|
byte[] buffer = new byte[(int) length];
|
||||||
byte[] buffer = new byte[(int) length];
|
IOUtils.readFully(input, buffer);
|
||||||
|
Charset charset = Charset.forName("UTF-8");
|
||||||
IOUtils.readFully(input, buffer);
|
return charset.newDecoder().decode(ByteBuffer.wrap(buffer)).toString();
|
||||||
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
|
* 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
|
* identification header is found, it will be skipped completely and the
|
||||||
* method will return true, otherwise false.
|
* method will return true, otherwise false.
|
||||||
*
|
*/
|
||||||
* @throws IOException
|
private boolean findIdentificationHeader(InputStream input) throws IOException {
|
||||||
*/
|
byte[] buffer = new byte[FIRST_PAGE_LENGTH];
|
||||||
private boolean findIdentificationHeader(InputStream input)
|
IOUtils.readFully(input, buffer);
|
||||||
throws IOException {
|
for (int i = 6; i < buffer.length; i++) {
|
||||||
byte[] buffer = new byte[FIRST_PAGE_LENGTH];
|
if (buffer[i - 5] == 'v' && buffer[i - 4] == 'o'
|
||||||
IOUtils.readFully(input, buffer);
|
&& buffer[i - 3] == 'r' && buffer[i - 2] == 'b'
|
||||||
int i;
|
&& buffer[i - 1] == 'i' && buffer[i] == 's'
|
||||||
for (i = 6; i < buffer.length; i++) {
|
&& buffer[i - 6] == PACKET_TYPE_IDENTIFICATION) {
|
||||||
if (buffer[i - 5] == 'v' && buffer[i - 4] == 'o'
|
return true;
|
||||||
&& buffer[i - 3] == 'r' && buffer[i - 2] == 'b'
|
}
|
||||||
&& buffer[i - 1] == 'i' && buffer[i] == 's'
|
}
|
||||||
&& buffer[i - 6] == PACKET_TYPE_IDENTIFICATION) {
|
return false;
|
||||||
return true;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean findCommentHeader(InputStream input) throws IOException {
|
private boolean findCommentHeader(InputStream input) throws IOException {
|
||||||
char[] buffer = new char["vorbis".length() + 1];
|
char[] buffer = new char["vorbis".length() + 1];
|
||||||
for (int bytesRead = 0; bytesRead < SECOND_PAGE_MAX_LENGTH; bytesRead++) {
|
for (int bytesRead = 0; bytesRead < SECOND_PAGE_MAX_LENGTH; bytesRead++) {
|
||||||
char c = (char) input.read();
|
char c = (char) input.read();
|
||||||
int dest = -1;
|
int dest = -1;
|
||||||
switch (c) {
|
switch (c) {
|
||||||
case PACKET_TYPE_COMMENT:
|
case PACKET_TYPE_COMMENT:
|
||||||
dest = 0;
|
dest = 0;
|
||||||
break;
|
break;
|
||||||
case 'v':
|
case 'v':
|
||||||
dest = 1;
|
dest = 1;
|
||||||
break;
|
break;
|
||||||
case 'o':
|
case 'o':
|
||||||
dest = 2;
|
dest = 2;
|
||||||
break;
|
break;
|
||||||
case 'r':
|
case 'r':
|
||||||
dest = 3;
|
dest = 3;
|
||||||
break;
|
break;
|
||||||
case 'b':
|
case 'b':
|
||||||
dest = 4;
|
dest = 4;
|
||||||
break;
|
break;
|
||||||
case 'i':
|
case 'i':
|
||||||
dest = 5;
|
dest = 5;
|
||||||
break;
|
break;
|
||||||
case 's':
|
case 's':
|
||||||
dest = 6;
|
dest = 6;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (dest >= 0) {
|
if (dest >= 0) {
|
||||||
buffer[dest] = c;
|
buffer[dest] = c;
|
||||||
if (buffer[1] == 'v' && buffer[2] == 'o' && buffer[3] == 'r'
|
if (buffer[1] == 'v' && buffer[2] == 'o' && buffer[3] == 'r'
|
||||||
&& buffer[4] == 'b' && buffer[5] == 'i'
|
&& buffer[4] == 'b' && buffer[5] == 'i'
|
||||||
&& buffer[6] == 's' && buffer[0] == PACKET_TYPE_COMMENT) {
|
&& buffer[6] == 's' && buffer[0] == PACKET_TYPE_COMMENT) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Arrays.fill(buffer, (char) 0);
|
Arrays.fill(buffer, (char) 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private VorbisCommentHeader readCommentHeader(InputStream input)
|
@NonNull
|
||||||
throws IOException, VorbisCommentReaderException {
|
private VorbisCommentHeader readCommentHeader(InputStream input) throws IOException, VorbisCommentReaderException {
|
||||||
try {
|
try {
|
||||||
long vendorLength = EndianUtils.readSwappedUnsignedInteger(input);
|
long vendorLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||||
String vendorName = readUTF8String(input, vendorLength);
|
String vendorName = readUtf8String(input, vendorLength);
|
||||||
long userCommentLength = EndianUtils
|
long userCommentLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||||
.readSwappedUnsignedInteger(input);
|
return new VorbisCommentHeader(vendorName, userCommentLength);
|
||||||
return new VorbisCommentHeader(vendorName, userCommentLength);
|
} catch (UnsupportedEncodingException e) {
|
||||||
} catch (UnsupportedEncodingException e) {
|
throw new VorbisCommentReaderException(e);
|
||||||
throw new VorbisCommentReaderException(e);
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private String readContentVectorKey(InputStream input, long vectorLength)
|
private String readContentVectorKey(InputStream input, long vectorLength) throws IOException {
|
||||||
throws IOException {
|
StringBuilder builder = new StringBuilder();
|
||||||
StringBuilder builder = new StringBuilder();
|
for (int i = 0; i < vectorLength; i++) {
|
||||||
for (int i = 0; i < vectorLength; i++) {
|
char c = (char) input.read();
|
||||||
char c = (char) input.read();
|
if (c == '=') {
|
||||||
if (c == '=') {
|
return builder.toString();
|
||||||
return builder.toString();
|
} else {
|
||||||
} else {
|
builder.append(c);
|
||||||
builder.append(c);
|
}
|
||||||
}
|
}
|
||||||
}
|
return null; // no key found
|
||||||
return null; // no key found
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue