diff options
author | ByteHamster <info@bytehamster.com> | 2022-03-11 19:58:41 +0100 |
---|---|---|
committer | ByteHamster <info@bytehamster.com> | 2022-03-11 20:57:24 +0100 |
commit | 933fde839e9d5a1c538de6ed4f740f9ac285da5a (patch) | |
tree | c52338f284901db9fea8fbd411a0d42d81ce4028 | |
parent | 36a36e4f853259f34fc3608ed1b21d575129e274 (diff) | |
download | AntennaPod-933fde839e9d5a1c538de6ed4f740f9ac285da5a.zip |
Read vorbis description of local files
8 files changed, 171 insertions, 76 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java index a63b9d41c..988b7c015 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java +++ b/core/src/main/java/de/danoeh/antennapod/core/feed/LocalFeedUpdater.java @@ -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()); + } } } diff --git a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java index e9f812c7f..723ea1d47 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java +++ b/core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java @@ -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(); diff --git a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReader.java b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReader.java index e3b91a0e7..8290a547a 100644 --- a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReader.java +++ b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReader.java @@ -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())) { diff --git a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentMetadataReader.java b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentMetadataReader.java new file mode 100644 index 000000000..158a0d8f7 --- /dev/null +++ b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentMetadataReader.java @@ -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; + } +} diff --git a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentReader.java b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentReader.java index 3d5f29f17..13126a73d 100644 --- a/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentReader.java +++ b/parser/media/src/main/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentReader.java @@ -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); - - /** - * 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); + VorbisCommentReader(InputStream input) { + this.input = input; + } - 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; } diff --git a/parser/media/src/test/java/de/danoeh/antennapod/parser/media/id3/MetadataReaderTest.java b/parser/media/src/test/java/de/danoeh/antennapod/parser/media/id3/MetadataReaderTest.java new file mode 100644 index 000000000..5b6b06ea7 --- /dev/null +++ b/parser/media/src/test/java/de/danoeh/antennapod/parser/media/id3/MetadataReaderTest.java @@ -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()); + } +} diff --git a/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReaderTest.java b/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReaderTest.java index 6ebe875d9..b19be324d 100644 --- a/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReaderTest.java +++ b/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentChapterReaderTest.java @@ -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()); diff --git a/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentMetadataReaderTest.java b/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentMetadataReaderTest.java new file mode 100644 index 000000000..7f71c8fa9 --- /dev/null +++ b/parser/media/src/test/java/de/danoeh/antennapod/parser/media/vorbis/VorbisCommentMetadataReaderTest.java @@ -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()); + } +} |