Read vorbis description of local files
This commit is contained in:
parent
36a36e4f85
commit
933fde839e
|
@ -38,6 +38,8 @@ import de.danoeh.antennapod.model.playback.MediaType;
|
|||
import de.danoeh.antennapod.parser.feed.util.MimeTypeUtils;
|
||||
import de.danoeh.antennapod.parser.media.id3.ID3ReaderException;
|
||||
import de.danoeh.antennapod.parser.media.id3.Id3MetadataReader;
|
||||
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentMetadataReader;
|
||||
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReaderException;
|
||||
import org.apache.commons.io.input.CountingInputStream;
|
||||
|
||||
public class LocalFeedUpdater {
|
||||
|
@ -209,8 +211,15 @@ public class LocalFeedUpdater {
|
|||
reader.readInputStream();
|
||||
item.setDescriptionIfLonger(reader.getComment());
|
||||
} catch (IOException | ID3ReaderException e) {
|
||||
// Do not flood Logcat with full stack traces
|
||||
Log.d(TAG, "Unable to parse ID3 of " + file.getUri() + ": " + e.getMessage());
|
||||
|
||||
try (InputStream inputStream = context.getContentResolver().openInputStream(file.getUri())) {
|
||||
VorbisCommentMetadataReader reader = new VorbisCommentMetadataReader(inputStream);
|
||||
reader.readInputStream();
|
||||
item.setDescriptionIfLonger(reader.getDescription());
|
||||
} catch (IOException | VorbisCommentReaderException e2) {
|
||||
Log.d(TAG, "Unable to parse vorbis comments of " + file.getUri() + ": " + e2.getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -171,8 +171,8 @@ public class ChapterUtils {
|
|||
|
||||
@NonNull
|
||||
private static List<Chapter> readOggChaptersFromInputStream(InputStream input) throws VorbisCommentReaderException {
|
||||
VorbisCommentChapterReader reader = new VorbisCommentChapterReader();
|
||||
reader.readInputStream(input);
|
||||
VorbisCommentChapterReader reader = new VorbisCommentChapterReader(input);
|
||||
reader.readInputStream();
|
||||
List<Chapter> chapters = reader.getChapters();
|
||||
if (chapters == null) {
|
||||
return Collections.emptyList();
|
||||
|
|
|
@ -2,6 +2,7 @@ package de.danoeh.antennapod.parser.media.vorbis;
|
|||
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
@ -17,25 +18,15 @@ public class VorbisCommentChapterReader extends VorbisCommentReader {
|
|||
private static final String CHAPTER_ATTRIBUTE_LINK = "url";
|
||||
private static final int CHAPTERXXX_LENGTH = "chapterxxx".length();
|
||||
|
||||
private List<Chapter> chapters;
|
||||
private final List<Chapter> chapters = new ArrayList<>();
|
||||
|
||||
public VorbisCommentChapterReader() {
|
||||
public VorbisCommentChapterReader(InputStream input) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVorbisCommentFound() {
|
||||
System.out.println("Vorbis comment found");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVorbisCommentHeaderFound(VorbisCommentHeader header) {
|
||||
chapters = new ArrayList<>();
|
||||
System.out.println(header.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onContentVectorKey(String content) {
|
||||
return content.matches(CHAPTER_KEY);
|
||||
public boolean handles(String key) {
|
||||
return key.matches(CHAPTER_KEY);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -68,19 +59,6 @@ public class VorbisCommentChapterReader extends VorbisCommentReader {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onEndOfComment() {
|
||||
System.out.println("End of comment");
|
||||
for (Chapter c : chapters) {
|
||||
System.out.println(c.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(VorbisCommentReaderException exception) {
|
||||
exception.printStackTrace();
|
||||
}
|
||||
|
||||
private Chapter getChapterById(long id) {
|
||||
for (Chapter c : chapters) {
|
||||
if (("" + id).equals(c.getChapterId())) {
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
package de.danoeh.antennapod.parser.media.vorbis;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
public class VorbisCommentMetadataReader extends VorbisCommentReader {
|
||||
private static final String KEY_DESCRIPTION = "description";
|
||||
private static final String KEY_COMMENT = "comment";
|
||||
|
||||
private String description = null;
|
||||
|
||||
public VorbisCommentMetadataReader(InputStream input) {
|
||||
super(input);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handles(String key) {
|
||||
return KEY_DESCRIPTION.equals(key) || KEY_COMMENT.equals(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onContentVectorValue(String key, String value) {
|
||||
if (KEY_DESCRIPTION.equals(key) || KEY_COMMENT.equals(key)) {
|
||||
if (description == null || value.length() > description.length()) {
|
||||
description = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
}
|
|
@ -3,6 +3,7 @@ package de.danoeh.antennapod.parser.media.vorbis;
|
|||
import androidx.annotation.NonNull;
|
||||
import org.apache.commons.io.EndianUtils;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import android.util.Log;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
@ -12,51 +13,35 @@ import java.nio.charset.Charset;
|
|||
import java.util.Locale;
|
||||
|
||||
public abstract class VorbisCommentReader {
|
||||
/** Length of first page in an ogg file in bytes. */
|
||||
private static final String TAG = "VorbisCommentReader";
|
||||
private static final int FIRST_OGG_PAGE_LENGTH = 58;
|
||||
private static final int FIRST_OPUS_PAGE_LENGTH = 47;
|
||||
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. */
|
||||
protected abstract void onVorbisCommentFound();
|
||||
private final InputStream input;
|
||||
|
||||
protected abstract void onVorbisCommentHeaderFound(VorbisCommentHeader header);
|
||||
VorbisCommentReader(InputStream input) {
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called every time the Reader finds a content vector. The handler
|
||||
* should return true if it wants to handle the content vector.
|
||||
*/
|
||||
protected abstract boolean onContentVectorKey(String content);
|
||||
|
||||
/**
|
||||
* Is called if onContentVectorKey returned true for the key.
|
||||
*/
|
||||
protected abstract void onContentVectorValue(String key, String value) throws VorbisCommentReaderException;
|
||||
|
||||
protected abstract void onEndOfComment();
|
||||
|
||||
protected abstract void onError(VorbisCommentReaderException exception);
|
||||
|
||||
public void readInputStream(InputStream input) throws VorbisCommentReaderException {
|
||||
public void readInputStream() throws VorbisCommentReaderException {
|
||||
try {
|
||||
findIdentificationHeader(input);
|
||||
onVorbisCommentFound();
|
||||
findOggPage(input);
|
||||
findCommentHeader(input);
|
||||
VorbisCommentHeader commentHeader = readCommentHeader(input);
|
||||
onVorbisCommentHeaderFound(commentHeader);
|
||||
findIdentificationHeader();
|
||||
findOggPage();
|
||||
findCommentHeader();
|
||||
VorbisCommentHeader commentHeader = readCommentHeader();
|
||||
Log.d(TAG, commentHeader.toString());
|
||||
for (int i = 0; i < commentHeader.getUserCommentLength(); i++) {
|
||||
readUserComment(input);
|
||||
readUserComment();
|
||||
}
|
||||
onEndOfComment();
|
||||
} catch (IOException e) {
|
||||
onError(new VorbisCommentReaderException(e));
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private void findOggPage(InputStream input) throws IOException {
|
||||
private void findOggPage() throws IOException {
|
||||
// find OggS
|
||||
byte[] buffer = new byte[4];
|
||||
final byte[] oggPageHeader = {'O', 'g', 'g', 'S'};
|
||||
|
@ -76,17 +61,19 @@ public abstract class VorbisCommentReader {
|
|||
IOUtils.skipFully(input, numSegments);
|
||||
}
|
||||
|
||||
private void readUserComment(InputStream input) throws VorbisCommentReaderException {
|
||||
private void readUserComment() throws VorbisCommentReaderException {
|
||||
try {
|
||||
long vectorLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||
if (vectorLength > 20 * 1024 * 1024) {
|
||||
// Avoid reading entire file if it is encoded incorrectly
|
||||
throw new VorbisCommentReaderException("User comment unrealistically long: " + vectorLength);
|
||||
String keyPart = readUtf8String(10);
|
||||
throw new VorbisCommentReaderException("User comment unrealistically long. "
|
||||
+ "key=" + keyPart + ", length=" + vectorLength);
|
||||
}
|
||||
String key = readContentVectorKey(input, vectorLength).toLowerCase(Locale.US);
|
||||
boolean readValue = onContentVectorKey(key);
|
||||
if (readValue) {
|
||||
String value = readUtf8String(input, vectorLength - key.length() - 1);
|
||||
String key = readContentVectorKey(vectorLength).toLowerCase(Locale.US);
|
||||
boolean shouldReadValue = handles(key);
|
||||
Log.d(TAG, "key=" + key + ", length=" + vectorLength + ", handles=" + shouldReadValue);
|
||||
if (shouldReadValue) {
|
||||
String value = readUtf8String(vectorLength - key.length() - 1);
|
||||
onContentVectorValue(key, value);
|
||||
} else {
|
||||
IOUtils.skipFully(input, vectorLength - key.length() - 1);
|
||||
|
@ -96,7 +83,7 @@ public abstract class VorbisCommentReader {
|
|||
}
|
||||
}
|
||||
|
||||
private String readUtf8String(InputStream input, long length) throws IOException {
|
||||
private String readUtf8String(long length) throws IOException {
|
||||
byte[] buffer = new byte[(int) length];
|
||||
IOUtils.readFully(input, buffer);
|
||||
Charset charset = Charset.forName("UTF-8");
|
||||
|
@ -107,7 +94,7 @@ public abstract class VorbisCommentReader {
|
|||
* Looks for an identification header in the first page of the file. If an
|
||||
* identification header is found, it will be skipped completely
|
||||
*/
|
||||
private void findIdentificationHeader(InputStream input) throws IOException {
|
||||
private void findIdentificationHeader() throws IOException {
|
||||
byte[] buffer = new byte[FIRST_OPUS_PAGE_LENGTH];
|
||||
IOUtils.readFully(input, buffer);
|
||||
final byte[] oggIdentificationHeader = new byte[]{ PACKET_TYPE_IDENTIFICATION, 'v', 'o', 'r', 'b', 'i', 's' };
|
||||
|
@ -122,7 +109,7 @@ public abstract class VorbisCommentReader {
|
|||
throw new IOException("No vorbis identification header found");
|
||||
}
|
||||
|
||||
private void findCommentHeader(InputStream input) throws IOException {
|
||||
private void findCommentHeader() throws IOException {
|
||||
byte[] buffer = new byte[64]; // Enough space for some bytes. Used circularly.
|
||||
final byte[] oggCommentHeader = new byte[]{ PACKET_TYPE_COMMENT, 'v', 'o', 'r', 'b', 'i', 's' };
|
||||
for (int bytesRead = 0; bytesRead < SECOND_PAGE_MAX_LENGTH; bytesRead++) {
|
||||
|
@ -155,10 +142,10 @@ public abstract class VorbisCommentReader {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
private VorbisCommentHeader readCommentHeader(InputStream input) throws IOException, VorbisCommentReaderException {
|
||||
private VorbisCommentHeader readCommentHeader() throws IOException, VorbisCommentReaderException {
|
||||
try {
|
||||
long vendorLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||
String vendorName = readUtf8String(input, vendorLength);
|
||||
String vendorName = readUtf8String(vendorLength);
|
||||
long userCommentLength = EndianUtils.readSwappedUnsignedInteger(input);
|
||||
return new VorbisCommentHeader(vendorName, userCommentLength);
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
|
@ -166,7 +153,7 @@ public abstract class VorbisCommentReader {
|
|||
}
|
||||
}
|
||||
|
||||
private String readContentVectorKey(InputStream input, long vectorLength) throws IOException {
|
||||
private String readContentVectorKey(long vectorLength) throws IOException {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (int i = 0; i < vectorLength; i++) {
|
||||
char c = (char) input.read();
|
||||
|
@ -178,4 +165,15 @@ public abstract class VorbisCommentReader {
|
|||
}
|
||||
return null; // no key found
|
||||
}
|
||||
|
||||
/**
|
||||
* Is called every time the Reader finds a content vector. The handler
|
||||
* should return true if it wants to handle the content vector.
|
||||
*/
|
||||
protected abstract boolean handles(String key);
|
||||
|
||||
/**
|
||||
* Is called if onContentVectorKey returned true for the key.
|
||||
*/
|
||||
protected abstract void onContentVectorValue(String key, String value) throws VorbisCommentReaderException;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package de.danoeh.antennapod.parser.media.id3;
|
||||
|
||||
import org.apache.commons.io.input.CountingInputStream;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class MetadataReaderTest {
|
||||
@Test
|
||||
public void testRealFileUltraschall() throws IOException, ID3ReaderException {
|
||||
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
|
||||
.getResource("ultraschall5.mp3").openStream());
|
||||
Id3MetadataReader reader = new Id3MetadataReader(inputStream);
|
||||
reader.readInputStream();
|
||||
assertEquals("Description", reader.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealFileAuphonic() throws IOException, ID3ReaderException {
|
||||
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
|
||||
.getResource("auphonic.mp3").openStream());
|
||||
Id3MetadataReader reader = new Id3MetadataReader(inputStream);
|
||||
reader.readInputStream();
|
||||
assertEquals("Summary", reader.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealFileHindenburgJournalistPro() throws IOException, ID3ReaderException {
|
||||
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
|
||||
.getResource("hindenburg-journalist-pro.mp3").openStream());
|
||||
Id3MetadataReader reader = new Id3MetadataReader(inputStream);
|
||||
reader.readInputStream();
|
||||
assertEquals("This is the summary of this podcast episode. This file was made with"
|
||||
+ " Hindenburg Journalist Pro version 1.85, build number 2360.", reader.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRealFileMp3chapsPy() throws IOException, ID3ReaderException {
|
||||
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
|
||||
.getResource("mp3chaps-py.mp3").openStream());
|
||||
Id3MetadataReader reader = new Id3MetadataReader(inputStream);
|
||||
reader.readInputStream();
|
||||
assertEquals("2021.08.13", reader.getComment());
|
||||
}
|
||||
}
|
|
@ -23,8 +23,8 @@ public class VorbisCommentChapterReaderTest {
|
|||
public void testRealFileAuphonic(String filename) throws IOException, VorbisCommentReaderException {
|
||||
InputStream inputStream = getClass().getClassLoader()
|
||||
.getResource(filename).openStream();
|
||||
VorbisCommentChapterReader reader = new VorbisCommentChapterReader();
|
||||
reader.readInputStream(inputStream);
|
||||
VorbisCommentChapterReader reader = new VorbisCommentChapterReader(inputStream);
|
||||
reader.readInputStream();
|
||||
List<Chapter> chapters = reader.getChapters();
|
||||
|
||||
assertEquals(4, chapters.size());
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
package de.danoeh.antennapod.parser.media.vorbis;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.RobolectricTestRunner;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
@RunWith(RobolectricTestRunner.class)
|
||||
public class VorbisCommentMetadataReaderTest {
|
||||
|
||||
@Test
|
||||
public void testRealFilesAuphonic() throws IOException, VorbisCommentReaderException {
|
||||
testRealFileAuphonic("auphonic.ogg");
|
||||
testRealFileAuphonic("auphonic.opus");
|
||||
}
|
||||
|
||||
public void testRealFileAuphonic(String filename) throws IOException, VorbisCommentReaderException {
|
||||
InputStream inputStream = getClass().getClassLoader()
|
||||
.getResource(filename).openStream();
|
||||
VorbisCommentMetadataReader reader = new VorbisCommentMetadataReader(inputStream);
|
||||
reader.readInputStream();
|
||||
assertEquals("Summary", reader.getDescription());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue