Moved media file parser to its own module

This commit is contained in:
ByteHamster 2021-08-28 09:52:45 +02:00
parent ddae5e2278
commit ca64739f36
40 changed files with 204 additions and 195 deletions

View File

@ -18,7 +18,7 @@ import de.danoeh.antennapod.R;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.util.Converter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.IntentUtils;
import de.danoeh.antennapod.ui.common.ThemeUtils;
import de.danoeh.antennapod.model.playback.Playable;

View File

@ -51,7 +51,7 @@ import de.danoeh.antennapod.core.feed.util.ImageResourceUtils;
import de.danoeh.antennapod.core.glide.ApGlideSettings;
import de.danoeh.antennapod.core.util.ChapterUtils;
import de.danoeh.antennapod.core.util.DateFormatter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.playback.PlaybackController;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.model.playback.Playable;

View File

@ -10,7 +10,7 @@
</Match>
<Match>
<Bug pattern="DM_DEFAULT_ENCODING"/>
<Class name="de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReader"/>
<Class name="de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReader"/>
</Match>
<Match>
<Bug pattern="HSC_HUGE_SHARED_STRING_CONSTANT"/>

View File

@ -25,6 +25,7 @@ dependencies {
implementation project(':net:sync:gpoddernet')
implementation project(':net:sync:model')
implementation project(':parser:feed')
implementation project(':parser:media')
implementation project(':ui:app-start-intent')
implementation project(':ui:common')
implementation project(':ui:png-icons')

View File

@ -14,7 +14,7 @@ import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory;
import com.bumptech.glide.load.model.StringLoader;
import com.bumptech.glide.module.AppGlideModule;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import java.io.InputStream;
import com.bumptech.glide.request.RequestOptions;

View File

@ -10,7 +10,7 @@ import com.bumptech.glide.load.model.ModelLoaderFactory;
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
import com.bumptech.glide.signature.ObjectKey;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;

View File

@ -3,10 +3,10 @@ package de.danoeh.antennapod.core.storage.mapper;
import android.database.Cursor;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.core.feed.ID3Chapter;
import de.danoeh.antennapod.parser.feed.element.SimpleChapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.core.storage.PodDBAdapter;
import de.danoeh.antennapod.parser.media.id3.ID3Chapter;
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentChapter;
/**
* Converts a {@link Cursor} to a {@link Chapter} object.

View File

@ -11,11 +11,11 @@ import de.danoeh.antennapod.model.feed.FeedMedia;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.storage.DBReader;
import de.danoeh.antennapod.core.util.comparator.ChapterStartTimeComparator;
import de.danoeh.antennapod.core.util.id3reader.ChapterReader;
import de.danoeh.antennapod.core.util.id3reader.ID3ReaderException;
import de.danoeh.antennapod.parser.media.id3.ChapterReader;
import de.danoeh.antennapod.parser.media.id3.ID3ReaderException;
import de.danoeh.antennapod.model.playback.Playable;
import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentChapterReader;
import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException;
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentChapterReader;
import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReaderException;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.input.CountingInputStream;

View File

@ -1,29 +0,0 @@
package de.danoeh.antennapod.core.util.id3reader.model;
public abstract class Header {
final String id;
final int size;
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 + "]";
}
}

View File

@ -1,81 +0,0 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
class OggInputStream extends InputStream {
private final InputStream input;
/** True if OggInputStream is currently inside an Ogg page. */
private boolean isInPage;
private long bytesLeft;
public OggInputStream(InputStream input) {
super();
isInPage = false;
this.input = input;
}
@Override
public int read() throws IOException {
if (!isInPage) {
readOggPage();
}
if (isInPage && bytesLeft > 0) {
int result = input.read();
bytesLeft -= 1;
if (bytesLeft == 0) {
isInPage = false;
}
return result;
}
return -1;
}
private void readOggPage() throws IOException {
// find OggS
int[] buffer = new int[4];
int c;
boolean isInOggS = false;
while ((c = input.read()) != -1) {
switch (c) {
case 'O':
isInOggS = true;
buffer[0] = c;
break;
case 'g':
if (buffer[1] != c) {
buffer[1] = c;
} else {
buffer[2] = c;
}
break;
case 'S':
buffer[3] = c;
break;
default:
if (isInOggS) {
Arrays.fill(buffer, 0);
isInOggS = false;
}
}
if (buffer[0] == 'O' && buffer[1] == 'g' && buffer[2] == 'g'
&& buffer[3] == 'S') {
break;
}
}
// read segments
IOUtils.skipFully(input, 22);
bytesLeft = 0;
int numSegments = input.read();
for (int i = 0; i < numSegments; i++) {
bytesLeft += input.read();
}
isInPage = true;
}
}

View File

@ -1,26 +0,0 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
class VorbisCommentHeader {
private final String vendorString;
private final long userCommentLength;
public VorbisCommentHeader(String vendorString, long userCommentLength) {
super();
this.vendorString = vendorString;
this.userCommentLength = userCommentLength;
}
@Override
public String toString() {
return "VorbisCommentHeader [vendorString=" + vendorString
+ ", userCommentLength=" + userCommentLength + "]";
}
public String getVendorString() {
return vendorString;
}
public long getUserCommentLength() {
return userCommentLength;
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util;
package de.danoeh.antennapod.model.feed;
import android.text.TextUtils;
import de.danoeh.antennapod.model.playback.Playable;

3
parser/media/README.md Normal file
View File

@ -0,0 +1,3 @@
# :parser:media
This module provides the tag parser for media files. This includes id3 or ogg/vorbis.

12
parser/media/build.gradle Normal file
View File

@ -0,0 +1,12 @@
apply plugin: "com.android.library"
apply from: "../../common.gradle"
dependencies {
implementation project(':model')
annotationProcessor "androidx.annotation:annotation:$annotationVersion"
implementation "commons-io:commons-io:$commonsioVersion"
testImplementation 'junit:junit:4.13'
}

View File

@ -0,0 +1 @@
<manifest package="de.danoeh.antennapod.parser.media" />

View File

@ -1,12 +1,11 @@
package de.danoeh.antennapod.core.util.id3reader;
package de.danoeh.antennapod.parser.media.id3;
import android.text.TextUtils;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.core.feed.ID3Chapter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import de.danoeh.antennapod.parser.media.id3.model.FrameHeader;
import org.apache.commons.io.input.CountingInputStream;
import java.io.IOException;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.feed;
package de.danoeh.antennapod.parser.media.id3;
import de.danoeh.antennapod.model.feed.Chapter;

View File

@ -1,9 +1,9 @@
package de.danoeh.antennapod.core.util.id3reader;
package de.danoeh.antennapod.parser.media.id3;
import android.util.Log;
import androidx.annotation.NonNull;
import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.core.util.id3reader.model.TagHeader;
import de.danoeh.antennapod.parser.media.id3.model.FrameHeader;
import de.danoeh.antennapod.parser.media.id3.model.TagHeader;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.CountingInputStream;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.id3reader;
package de.danoeh.antennapod.parser.media.id3;
public class ID3ReaderException extends Exception {
private static final long serialVersionUID = 1L;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.id3reader.model;
package de.danoeh.antennapod.parser.media.id3.model;
import androidx.annotation.NonNull;
@ -15,5 +15,4 @@ public class FrameHeader extends Header {
public String toString() {
return String.format("FrameHeader [flags=%s, id=%s, size=%s]", Integer.toBinaryString(flags), id, size);
}
}

View File

@ -0,0 +1,26 @@
package de.danoeh.antennapod.parser.media.id3.model;
public abstract class Header {
final String id;
final int size;
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 + "]";
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.id3reader.model;
package de.danoeh.antennapod.parser.media.id3.model;
import androidx.annotation.NonNull;

View File

@ -0,0 +1,81 @@
package de.danoeh.antennapod.parser.media.vorbis;
import org.apache.commons.io.IOUtils;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
class OggInputStream extends InputStream {
private final InputStream input;
/** True if OggInputStream is currently inside an Ogg page. */
private boolean isInPage;
private long bytesLeft;
public OggInputStream(InputStream input) {
super();
isInPage = false;
this.input = input;
}
@Override
public int read() throws IOException {
if (!isInPage) {
readOggPage();
}
if (isInPage && bytesLeft > 0) {
int result = input.read();
bytesLeft -= 1;
if (bytesLeft == 0) {
isInPage = false;
}
return result;
}
return -1;
}
private void readOggPage() throws IOException {
// find OggS
int[] buffer = new int[4];
int c;
boolean isInOggS = false;
while ((c = input.read()) != -1) {
switch (c) {
case 'O':
isInOggS = true;
buffer[0] = c;
break;
case 'g':
if (buffer[1] != c) {
buffer[1] = c;
} else {
buffer[2] = c;
}
break;
case 'S':
buffer[3] = c;
break;
default:
if (isInOggS) {
Arrays.fill(buffer, 0);
isInOggS = false;
}
}
if (buffer[0] == 'O' && buffer[1] == 'g' && buffer[2] == 'g'
&& buffer[3] == 'S') {
break;
}
}
// read segments
IOUtils.skipFully(input, 22);
bytesLeft = 0;
int numSegments = input.read();
for (int i = 0; i < numSegments; i++) {
bytesLeft += input.read();
}
isInPage = true;
}
}

View File

@ -1,8 +1,7 @@
package de.danoeh.antennapod.core.feed;
package de.danoeh.antennapod.parser.media.vorbis;
import java.util.concurrent.TimeUnit;
import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException;
import de.danoeh.antennapod.model.feed.Chapter;
public class VorbisCommentChapter extends Chapter {

View File

@ -1,13 +1,12 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
package de.danoeh.antennapod.parser.media.vorbis;
import android.util.Log;
import java.util.ArrayList;
import java.util.List;
import de.danoeh.antennapod.core.BuildConfig;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.core.feed.VorbisCommentChapter;
import de.danoeh.antennapod.parser.media.BuildConfig;
public class VorbisCommentChapterReader extends VorbisCommentReader {
private static final String TAG = "VorbisCommentChptrReadr";

View File

@ -0,0 +1,27 @@
package de.danoeh.antennapod.parser.media.vorbis;
class VorbisCommentHeader {
private final String vendorString;
private final long userCommentLength;
public VorbisCommentHeader(String vendorString, long userCommentLength) {
super();
this.vendorString = vendorString;
this.userCommentLength = userCommentLength;
}
@Override
public String toString() {
return "VorbisCommentHeader [vendorString=" + vendorString
+ ", userCommentLength=" + userCommentLength + "]";
}
public String getVendorString() {
return vendorString;
}
public long getUserCommentLength() {
return userCommentLength;
}
}

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
package de.danoeh.antennapod.parser.media.vorbis;
import androidx.annotation.NonNull;
import org.apache.commons.io.EndianUtils;
@ -9,6 +9,7 @@ import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Locale;
public abstract class VorbisCommentReader {
/** Length of first page in an ogg file in bytes. */
@ -67,7 +68,7 @@ public abstract class VorbisCommentReader {
private void readUserComment(InputStream input) throws VorbisCommentReaderException {
try {
long vectorLength = EndianUtils.readSwappedUnsignedInteger(input);
String key = readContentVectorKey(input, vectorLength).toLowerCase();
String key = readContentVectorKey(input, vectorLength).toLowerCase(Locale.US);
boolean readValue = onContentVectorKey(key);
if (readValue) {
String value = readUtf8String(input, (int) (vectorLength - key.length() - 1));

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
package de.danoeh.antennapod.parser.media.vorbis;
public class VorbisCommentReaderException extends Exception {
private static final long serialVersionUID = 1L;

View File

@ -1,9 +1,8 @@
package de.danoeh.antennapod.core.util.id3reader;
package de.danoeh.antennapod.parser.media.id3;
import de.danoeh.antennapod.model.feed.Chapter;
import de.danoeh.antennapod.core.feed.ID3Chapter;
import de.danoeh.antennapod.core.util.EmbeddedChapterImage;
import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.model.feed.EmbeddedChapterImage;
import de.danoeh.antennapod.parser.media.id3.model.FrameHeader;
import org.apache.commons.io.input.CountingInputStream;
import org.junit.Test;
@ -11,9 +10,6 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import static de.danoeh.antennapod.core.util.id3reader.Id3ReaderTest.concat;
import static de.danoeh.antennapod.core.util.id3reader.Id3ReaderTest.generateFrameHeader;
import static de.danoeh.antennapod.core.util.id3reader.Id3ReaderTest.generateId3Header;
import static org.junit.Assert.assertEquals;
public class ChapterReaderTest {
@ -28,11 +24,11 @@ public class ChapterReaderTest {
@Test
public void testReadFullTagWithChapter() throws IOException, ID3ReaderException {
byte[] chapter = concat(
generateFrameHeader(ChapterReader.FRAME_ID_CHAPTER, CHAPTER_WITHOUT_SUBFRAME.length),
byte[] chapter = Id3ReaderTest.concat(
Id3ReaderTest.generateFrameHeader(ChapterReader.FRAME_ID_CHAPTER, CHAPTER_WITHOUT_SUBFRAME.length),
CHAPTER_WITHOUT_SUBFRAME);
byte[] data = concat(
generateId3Header(chapter.length),
byte[] data = Id3ReaderTest.concat(
Id3ReaderTest.generateId3Header(chapter.length),
chapter);
CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
ChapterReader reader = new ChapterReader(inputStream);
@ -43,11 +39,11 @@ public class ChapterReaderTest {
@Test
public void testReadFullTagWithMultipleChapters() throws IOException, ID3ReaderException {
byte[] chapter = concat(
generateFrameHeader(ChapterReader.FRAME_ID_CHAPTER, CHAPTER_WITHOUT_SUBFRAME.length),
byte[] chapter = Id3ReaderTest.concat(
Id3ReaderTest.generateFrameHeader(ChapterReader.FRAME_ID_CHAPTER, CHAPTER_WITHOUT_SUBFRAME.length),
CHAPTER_WITHOUT_SUBFRAME);
byte[] data = concat(
generateId3Header(2 * chapter.length),
byte[] data = Id3ReaderTest.concat(
Id3ReaderTest.generateId3Header(2 * chapter.length),
chapter,
chapter);
CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(data));
@ -74,9 +70,9 @@ public class ChapterReaderTest {
'H', 'e', 'l', 'l', 'o', // Title
0 // Null-terminated
};
byte[] chapterData = concat(
byte[] chapterData = Id3ReaderTest.concat(
CHAPTER_WITHOUT_SUBFRAME,
generateFrameHeader(ChapterReader.FRAME_ID_TITLE, title.length),
Id3ReaderTest.generateFrameHeader(ChapterReader.FRAME_ID_TITLE, title.length),
title);
FrameHeader header = new FrameHeader(ChapterReader.FRAME_ID_CHAPTER, chapterData.length, (short) 0);
CountingInputStream inputStream = new CountingInputStream(new ByteArrayInputStream(chapterData));
@ -108,7 +104,7 @@ public class ChapterReaderTest {
@Test
public void testRealFileUltraschall() throws IOException, ID3ReaderException {
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
.getResource("media-parser/ultraschall5.mp3").openStream());
.getResource("ultraschall5.mp3").openStream());
ChapterReader reader = new ChapterReader(inputStream);
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();
@ -135,7 +131,7 @@ public class ChapterReaderTest {
@Test
public void testRealFileAuphonic() throws IOException, ID3ReaderException {
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
.getResource("media-parser/auphonic.mp3").openStream());
.getResource("auphonic.mp3").openStream());
ChapterReader reader = new ChapterReader(inputStream);
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();
@ -166,7 +162,7 @@ public class ChapterReaderTest {
@Test
public void testRealFileHindenburgJournalistPro() throws IOException, ID3ReaderException {
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
.getResource("media-parser/hindenburg-journalist-pro.mp3").openStream());
.getResource("hindenburg-journalist-pro.mp3").openStream());
ChapterReader reader = new ChapterReader(inputStream);
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();
@ -189,7 +185,7 @@ public class ChapterReaderTest {
@Test
public void testRealFileMp3chapsPy() throws IOException, ID3ReaderException {
CountingInputStream inputStream = new CountingInputStream(getClass().getClassLoader()
.getResource("media-parser/mp3chaps-py.mp3").openStream());
.getResource("mp3chaps-py.mp3").openStream());
ChapterReader reader = new ChapterReader(inputStream);
reader.readInputStream();
List<Chapter> chapters = reader.getChapters();

View File

@ -1,7 +1,7 @@
package de.danoeh.antennapod.core.util.id3reader;
package de.danoeh.antennapod.parser.media.id3;
import de.danoeh.antennapod.core.util.id3reader.model.FrameHeader;
import de.danoeh.antennapod.core.util.id3reader.model.TagHeader;
import de.danoeh.antennapod.parser.media.id3.model.FrameHeader;
import de.danoeh.antennapod.parser.media.id3.model.TagHeader;
import org.apache.commons.io.input.CountingInputStream;
import org.junit.Test;

View File

@ -1,4 +1,4 @@
package de.danoeh.antennapod.core.util.vorbiscommentreader;
package de.danoeh.antennapod.parser.media.vorbis;
import de.danoeh.antennapod.model.feed.Chapter;
import org.junit.Test;
@ -13,8 +13,8 @@ public class VorbisCommentChapterReaderTest {
@Test
public void testRealFilesAuphonic() throws IOException, VorbisCommentReaderException {
testRealFileAuphonic("media-parser/auphonic.ogg");
testRealFileAuphonic("media-parser/auphonic.opus");
testRealFileAuphonic("auphonic.ogg");
testRealFileAuphonic("auphonic.opus");
}
public void testRealFileAuphonic(String filename) throws IOException, VorbisCommentReaderException {

View File

@ -7,6 +7,7 @@ include ':net:sync:gpoddernet'
include ':net:sync:model'
include ':parser:feed'
include ':parser:media'
include ':ui:app-start-intent'
include ':ui:common'