Merge pull request #4936 from ByteHamster/chapter-reader

Simplified chapter reader
This commit is contained in:
ByteHamster 2021-02-15 15:21:49 +01:00 committed by GitHub
commit 42ce907aaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 67 additions and 173 deletions

View File

@ -407,14 +407,7 @@ public class FeedMedia extends FeedFile implements Playable {
if (item.hasChapters()) { if (item.hasChapters()) {
chaptersFromDatabase = DBReader.loadChaptersOfFeedItem(item); chaptersFromDatabase = DBReader.loadChaptersOfFeedItem(item);
} }
List<Chapter> chaptersFromMediaFile = ChapterUtils.loadChaptersFromMediaFile(this, context);
List<Chapter> chaptersFromMediaFile;
if (localFileAvailable()) {
chaptersFromMediaFile = ChapterUtils.loadChaptersFromFileUrl(this);
} else {
chaptersFromMediaFile = ChapterUtils.loadChaptersFromStreamUrl(this, context);
}
return ChapterMerger.merge(chaptersFromDatabase, chaptersFromMediaFile); return ChapterMerger.merge(chaptersFromDatabase, chaptersFromMediaFile);
} }

View File

@ -56,7 +56,7 @@ public class MediaDownloadedHandler implements Runnable {
// check if file has chapters // check if file has chapters
if (media.getItem() != null && !media.getItem().hasChapters()) { if (media.getItem() != null && !media.getItem().hasChapters()) {
media.setChapters(ChapterUtils.loadChaptersFromFileUrl(media)); media.setChapters(ChapterUtils.loadChaptersFromMediaFile(media, context));
} }
// Get duration // Get duration

View File

@ -3,32 +3,27 @@ package de.danoeh.antennapod.core.util;
import android.content.ContentResolver; import android.content.ContentResolver;
import android.content.Context; import android.content.Context;
import android.net.Uri; import android.net.Uri;
import androidx.annotation.NonNull;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import java.net.URLConnection;
import de.danoeh.antennapod.core.ClientConfig;
import org.apache.commons.io.IOUtils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import de.danoeh.antennapod.core.feed.Chapter; import de.danoeh.antennapod.core.feed.Chapter;
import de.danoeh.antennapod.core.service.download.AntennapodHttpClient;
import de.danoeh.antennapod.core.util.comparator.ChapterStartTimeComparator; import de.danoeh.antennapod.core.util.comparator.ChapterStartTimeComparator;
import de.danoeh.antennapod.core.util.id3reader.ChapterReader; import de.danoeh.antennapod.core.util.id3reader.ChapterReader;
import de.danoeh.antennapod.core.util.id3reader.ID3ReaderException; import de.danoeh.antennapod.core.util.id3reader.ID3ReaderException;
import de.danoeh.antennapod.core.util.playback.Playable; import de.danoeh.antennapod.core.util.playback.Playable;
import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentChapterReader; import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentChapterReader;
import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException; import de.danoeh.antennapod.core.util.vorbiscommentreader.VorbisCommentReaderException;
import okhttp3.Request;
import okhttp3.Response;
import org.apache.commons.io.input.CountingInputStream; import org.apache.commons.io.input.CountingInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collections;
import java.util.List;
/** /**
* Utility class for getting chapter data from media files. * Utility class for getting chapter data from media files.
*/ */
@ -52,93 +47,54 @@ public class ChapterUtils {
return chapters.size() - 1; return chapters.size() - 1;
} }
public static List<Chapter> loadChaptersFromStreamUrl(Playable media, Context context) { public static List<Chapter> loadChaptersFromMediaFile(Playable playable, Context context) {
List<Chapter> chapters = ChapterUtils.readID3ChaptersFromPlayableStreamUrl(media, context); try (CountingInputStream in = openStream(playable, context)) {
if (chapters == null) { List<Chapter> chapters = readId3ChaptersFrom(in);
chapters = ChapterUtils.readOggChaptersFromPlayableStreamUrl(media, context);
}
return chapters;
}
public static List<Chapter> loadChaptersFromFileUrl(Playable media) {
if (!media.localFileAvailable()) {
Log.e(TAG, "Could not load chapters from file url: local file not available");
return null;
}
List<Chapter> chapters = ChapterUtils.readID3ChaptersFromPlayableFileUrl(media);
if (chapters == null) {
chapters = ChapterUtils.readOggChaptersFromPlayableFileUrl(media);
}
return chapters;
}
/**
* Uses the download URL of a media object of a feeditem to read its ID3
* chapters.
*/
private static List<Chapter> readID3ChaptersFromPlayableStreamUrl(Playable p, Context context) {
if (p == null || p.getStreamUrl() == null) {
Log.e(TAG, "Unable to read ID3 chapters: media or download URL was null");
return null;
}
Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
CountingInputStream in = null;
try {
if (p.getStreamUrl().startsWith(ContentResolver.SCHEME_CONTENT)) {
Uri uri = Uri.parse(p.getStreamUrl());
in = new CountingInputStream(context.getContentResolver().openInputStream(uri));
} else {
URL url = new URL(p.getStreamUrl());
URLConnection urlConnection = url.openConnection();
urlConnection.setRequestProperty("User-Agent", ClientConfig.USER_AGENT);
in = new CountingInputStream(urlConnection.getInputStream());
}
List<Chapter> chapters = readChaptersFrom(in);
if (!chapters.isEmpty()) { if (!chapters.isEmpty()) {
Log.i(TAG, "Chapters loaded");
return chapters; return chapters;
} }
Log.i(TAG, "Chapters loaded");
} catch (IOException | ID3ReaderException | IllegalArgumentException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
IOUtils.closeQuietly(in);
}
return null;
}
/**
* Uses the file URL of a media object of a feeditem to read its ID3
* chapters.
*/
private static List<Chapter> readID3ChaptersFromPlayableFileUrl(Playable p) {
if (p == null || !p.localFileAvailable() || p.getLocalMediaUrl() == null) {
return null;
}
Log.d(TAG, "Reading id3 chapters from item " + p.getEpisodeTitle());
File source = new File(p.getLocalMediaUrl());
if (!source.exists()) {
Log.e(TAG, "Unable to read id3 chapters: Source doesn't exist");
return null;
}
CountingInputStream in = null;
try {
in = new CountingInputStream(new BufferedInputStream(new FileInputStream(source)));
List<Chapter> chapters = readChaptersFrom(in);
if (!chapters.isEmpty()) {
return chapters;
}
Log.i(TAG, "Chapters loaded");
} catch (IOException | ID3ReaderException e) { } catch (IOException | ID3ReaderException e) {
Log.e(TAG, Log.getStackTraceString(e)); Log.e(TAG, "Unable to load ID3 chapters: " + e.getMessage());
} finally { }
IOUtils.closeQuietly(in);
try (CountingInputStream in = openStream(playable, context)) {
List<Chapter> chapters = readOggChaptersFromInputStream(in);
if (!chapters.isEmpty()) {
Log.i(TAG, "Chapters loaded");
return chapters;
}
} catch (IOException | VorbisCommentReaderException e) {
Log.e(TAG, "Unable to load vorbis chapters: " + e.getMessage());
} }
return null; return null;
} }
private static CountingInputStream openStream(Playable playable, Context context) throws IOException {
if (playable.localFileAvailable()) {
if (playable.getLocalMediaUrl() == null) {
throw new IOException("No local url");
}
File source = new File(playable.getLocalMediaUrl());
if (!source.exists()) {
throw new IOException("Local file does not exist");
}
return new CountingInputStream(new FileInputStream(source));
} else if (playable.getStreamUrl().startsWith(ContentResolver.SCHEME_CONTENT)) {
Uri uri = Uri.parse(playable.getStreamUrl());
return new CountingInputStream(context.getContentResolver().openInputStream(uri));
} else {
Request request = new Request.Builder().url(playable.getStreamUrl()).build();
Response response = AntennapodHttpClient.getHttpClient().newCall(request).execute();
if (response.body() == null) {
throw new IOException("Body is null");
}
return new CountingInputStream(response.body().byteStream());
}
}
@NonNull @NonNull
private static List<Chapter> readChaptersFrom(CountingInputStream in) throws IOException, ID3ReaderException { private static List<Chapter> readId3ChaptersFrom(CountingInputStream in) throws IOException, ID3ReaderException {
ChapterReader reader = new ChapterReader(); ChapterReader reader = new ChapterReader();
reader.readInputStream(in); reader.readInputStream(in);
List<Chapter> chapters = reader.getChapters(); List<Chapter> chapters = reader.getChapters();
@ -156,73 +112,21 @@ public class ChapterUtils {
return chapters; return chapters;
} }
private static List<Chapter> readOggChaptersFromPlayableStreamUrl(Playable media, Context context) { @NonNull
if (media == null || !media.streamAvailable()) { private static List<Chapter> readOggChaptersFromInputStream(InputStream input) throws VorbisCommentReaderException {
return null;
}
InputStream input = null;
try {
if (media.getStreamUrl().startsWith(ContentResolver.SCHEME_CONTENT)) {
Uri uri = Uri.parse(media.getStreamUrl());
input = context.getContentResolver().openInputStream(uri);
} else {
URL url = new URL(media.getStreamUrl());
URLConnection urlConnection = url.openConnection();
urlConnection.setRequestProperty("User-Agent", ClientConfig.USER_AGENT);
input = urlConnection.getInputStream();
}
if (input != null) {
return readOggChaptersFromInputStream(media, input);
}
} catch (IOException | IllegalArgumentException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
IOUtils.closeQuietly(input);
}
return null;
}
private static List<Chapter> readOggChaptersFromPlayableFileUrl(Playable media) {
if (media == null || media.getLocalMediaUrl() == null) {
return null;
}
File source = new File(media.getLocalMediaUrl());
if (source.exists()) {
InputStream input = null;
try {
input = new BufferedInputStream(new FileInputStream(source));
return readOggChaptersFromInputStream(media, input);
} catch (FileNotFoundException e) {
Log.e(TAG, Log.getStackTraceString(e));
} finally {
IOUtils.closeQuietly(input);
}
}
return null;
}
private static List<Chapter> readOggChaptersFromInputStream(Playable p, InputStream input) {
Log.d(TAG, "Trying to read chapters from item with title " + p.getEpisodeTitle());
try {
VorbisCommentChapterReader reader = new VorbisCommentChapterReader(); VorbisCommentChapterReader reader = new VorbisCommentChapterReader();
reader.readInputStream(input); reader.readInputStream(input);
List<Chapter> chapters = reader.getChapters(); List<Chapter> chapters = reader.getChapters();
if (chapters == null) { if (chapters == null) {
Log.i(TAG, "ChapterReader could not find any Ogg vorbis chapters"); Log.i(TAG, "ChapterReader could not find any Ogg vorbis chapters");
return null; return Collections.emptyList();
} }
Collections.sort(chapters, new ChapterStartTimeComparator()); Collections.sort(chapters, new ChapterStartTimeComparator());
enumerateEmptyChapterTitles(chapters); enumerateEmptyChapterTitles(chapters);
if (chaptersValid(chapters)) { if (chaptersValid(chapters)) {
Log.i(TAG, "Chapters loaded");
return chapters; return chapters;
} else {
Log.e(TAG, "Chapter data was invalid");
} }
} catch (VorbisCommentReaderException e) { return Collections.emptyList();
e.printStackTrace();
}
return null;
} }
/** /**
@ -248,5 +152,4 @@ public class ChapterUtils {
} }
return true; return true;
} }
} }

View File

@ -118,13 +118,11 @@ public class ExternalMedia implements Playable {
} else { } else {
pubDate = null; pubDate = null;
} }
setChapters(ChapterUtils.loadChaptersFromFileUrl(this));
} }
@Override @Override
public void loadChapterMarks(Context context) { public void loadChapterMarks(Context context) {
setChapters(ChapterUtils.loadChaptersFromMediaFile(this, context));
} }
@Override @Override

View File

@ -130,7 +130,7 @@ public class RemoteMedia implements Playable {
@Override @Override
public void loadChapterMarks(Context context) { public void loadChapterMarks(Context context) {
setChapters(ChapterUtils.loadChaptersFromStreamUrl(this, context)); setChapters(ChapterUtils.loadChaptersFromMediaFile(this, context));
} }
@Override @Override