diff options
author | Tony Tam <149837+tonytamsf@users.noreply.github.com> | 2022-03-06 07:09:09 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-03-06 16:09:09 +0100 |
commit | 1a1bf02e8a73a3f7a05ced5c850c23fceb2629f0 (patch) | |
tree | 9d8fe03ef4432ead9874f696a3e9ca89e2cd80f2 | |
parent | dad4e405d48c13ec3c3ab90700b3969795778d3f (diff) | |
download | AntennaPod-1a1bf02e8a73a3f7a05ced5c850c23fceb2629f0.zip |
Support for podcast 2.0 chapters (#5630)
8 files changed, 103 insertions, 8 deletions
diff --git a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java index 90254c1f3..4aa76f453 100644 --- a/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java +++ b/core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java @@ -60,6 +60,9 @@ public class MediaDownloadedHandler implements Runnable { media.setChapters(ChapterUtils.loadChaptersFromMediaFile(media, context)); } + if (media.getItem() != null && media.getItem().getPodcastIndexChapterUrl() != null) { + ChapterUtils.loadChaptersFromUrl(media.getItem().getPodcastIndexChapterUrl()); + } // Get duration MediaMetadataRetriever mmr = new MediaMetadataRetriever(); String durationStr = null; 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 4092087f4..e9f812c7f 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 @@ -3,6 +3,7 @@ package de.danoeh.antennapod.core.util; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; +import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import de.danoeh.antennapod.model.feed.Chapter; @@ -11,11 +12,13 @@ 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.parser.feed.PodcastIndexChapterParser; 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.parser.media.vorbis.VorbisCommentChapterReader; import de.danoeh.antennapod.parser.media.vorbis.VorbisCommentReaderException; +import okhttp3.CacheControl; import okhttp3.Request; import okhttp3.Response; import org.apache.commons.io.input.CountingInputStream; @@ -57,6 +60,7 @@ public class ChapterUtils { } List<Chapter> chaptersFromDatabase = null; + List<Chapter> chaptersFromPodcastIndex = null; if (playable instanceof FeedMedia) { FeedMedia feedMedia = (FeedMedia) playable; if (feedMedia.getItem() == null) { @@ -65,10 +69,17 @@ public class ChapterUtils { if (feedMedia.getItem().hasChapters()) { chaptersFromDatabase = DBReader.loadChaptersOfFeedItem(feedMedia.getItem()); } + + if (!TextUtils.isEmpty(feedMedia.getItem().getPodcastIndexChapterUrl())) { + chaptersFromPodcastIndex = ChapterUtils.loadChaptersFromUrl( + feedMedia.getItem().getPodcastIndexChapterUrl()); + } + } List<Chapter> chaptersFromMediaFile = ChapterUtils.loadChaptersFromMediaFile(playable, context); - List<Chapter> chapters = ChapterMerger.merge(chaptersFromDatabase, chaptersFromMediaFile); + List<Chapter> chaptersMergePhase1 = ChapterMerger.merge(chaptersFromDatabase, chaptersFromMediaFile); + List<Chapter> chapters = ChapterMerger.merge(chaptersMergePhase1, chaptersFromPodcastIndex); if (chapters == null) { // Do not try loading again. There are no chapters. playable.setChapters(Collections.emptyList()); @@ -123,6 +134,27 @@ public class ChapterUtils { } } + public static List<Chapter> loadChaptersFromUrl(String url) { + try { + Request request = new Request.Builder().url(url).cacheControl(CacheControl.FORCE_CACHE).build(); + Response response = AntennapodHttpClient.getHttpClient().newCall(request).execute(); + if (response.isSuccessful() && response.body() != null) { + List<Chapter> chapters = PodcastIndexChapterParser.parse(response.body().string()); + if (chapters != null && !chapters.isEmpty()) { + return chapters; + } + } + request = new Request.Builder().url(url).build(); + response = AntennapodHttpClient.getHttpClient().newCall(request).execute(); + if (response.isSuccessful() && response.body() != null) { + return PodcastIndexChapterParser.parse(response.body().string()); + } + } catch (IOException e) { + e.printStackTrace(); + } + return null; + } + @NonNull private static List<Chapter> readId3ChaptersFrom(CountingInputStream in) throws IOException, ID3ReaderException { ChapterReader reader = new ChapterReader(in); diff --git a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java index 08f79252a..a8570ea4e 100644 --- a/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java +++ b/model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java @@ -41,6 +41,7 @@ public class FeedItem extends FeedComponent implements Serializable { private transient Feed feed; private long feedId; + private String podcastIndexChapterUrl; private int state; public static final int NEW = -1; @@ -81,7 +82,7 @@ public class FeedItem extends FeedComponent implements Serializable { * */ public FeedItem(long id, String title, String link, Date pubDate, String paymentLink, long feedId, boolean hasChapters, String imageUrl, int state, - String itemIdentifier, long autoDownload) { + String itemIdentifier, long autoDownload, String podcastIndexChapterUrl) { this.id = id; this.title = title; this.link = link; @@ -93,6 +94,7 @@ public class FeedItem extends FeedComponent implements Serializable { this.state = state; this.itemIdentifier = itemIdentifier; this.autoDownload = autoDownload; + this.podcastIndexChapterUrl = podcastIndexChapterUrl; } /** @@ -157,6 +159,9 @@ public class FeedItem extends FeedComponent implements Serializable { chapters = other.chapters; } } + if (other.podcastIndexChapterUrl != null) { + podcastIndexChapterUrl = other.podcastIndexChapterUrl; + } } /** @@ -427,6 +432,14 @@ public class FeedItem extends FeedComponent implements Serializable { tags.remove(tag); } + public String getPodcastIndexChapterUrl() { + return podcastIndexChapterUrl; + } + + public void setPodcastIndexChapterUrl(String url) { + podcastIndexChapterUrl = url; + } + @NonNull @Override public String toString() { diff --git a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/PodcastIndexChapterParser.java b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/PodcastIndexChapterParser.java new file mode 100644 index 000000000..5dcc18b14 --- /dev/null +++ b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/PodcastIndexChapterParser.java @@ -0,0 +1,31 @@ +package de.danoeh.antennapod.parser.feed; + +import de.danoeh.antennapod.model.feed.Chapter; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.util.ArrayList; +import java.util.List; + +public class PodcastIndexChapterParser { + public static List<Chapter> parse(String jsonStr) { + try { + List<Chapter> chapters = new ArrayList<>(); + JSONObject obj = new JSONObject(jsonStr); + JSONArray objChapters = obj.getJSONArray("chapters"); + for (int i = 0; i < objChapters.length(); i++) { + JSONObject jsonObject = objChapters.getJSONObject(i); + int startTime = jsonObject.optInt("startTime", 0); + String title = jsonObject.optString("title"); + String link = jsonObject.optString("url"); + String img = jsonObject.optString("img"); + chapters.add(new Chapter(startTime * 1000L, title, link, img)); + } + return chapters; + } catch (JSONException e) { + e.printStackTrace(); + } + return null; + } +} diff --git a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/PodcastIndex.java b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/PodcastIndex.java index 1d4a91192..1f543a5ae 100644 --- a/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/PodcastIndex.java +++ b/parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/PodcastIndex.java @@ -1,8 +1,8 @@ package de.danoeh.antennapod.parser.feed.namespace; +import android.text.TextUtils; import de.danoeh.antennapod.parser.feed.HandlerState; import de.danoeh.antennapod.parser.feed.element.SyndElement; -import org.jsoup.helper.StringUtil; import org.xml.sax.Attributes; import de.danoeh.antennapod.model.feed.FeedFunding; @@ -13,6 +13,7 @@ public class PodcastIndex extends Namespace { public static final String NSURI2 = "https://podcastindex.org/namespace/1.0"; private static final String URL = "url"; private static final String FUNDING = "funding"; + private static final String CHAPTERS = "chapters"; @Override public SyndElement handleElementStart(String localName, HandlerState state, @@ -22,6 +23,11 @@ public class PodcastIndex extends Namespace { FeedFunding funding = new FeedFunding(href, ""); state.setCurrentFunding(funding); state.getFeed().addPayment(state.getCurrentFunding()); + } else if (CHAPTERS.equals(localName)) { + String href = attributes.getValue(URL); + if (!TextUtils.isEmpty(href)) { + state.getCurrentItem().setPodcastIndexChapterUrl(href); + } } return new SyndElement(localName, this); } @@ -32,7 +38,7 @@ public class PodcastIndex extends Namespace { return; } String content = state.getContentBuf().toString(); - if (FUNDING.equals(localName) && state.getCurrentFunding() != null && !StringUtil.isBlank(content)) { + if (FUNDING.equals(localName) && state.getCurrentFunding() != null && !TextUtils.isEmpty(content)) { state.getCurrentFunding().setContent(content); } } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java index 1954a5652..78eaf6964 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java @@ -326,6 +326,10 @@ class DBUpgrader { db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEEDS + " ADD COLUMN " + PodDBAdapter.KEY_MINIMAL_DURATION_FILTER + " INTEGER DEFAULT -1"); } + if (oldVersion < 2060000) { + db.execSQL("ALTER TABLE " + PodDBAdapter.TABLE_NAME_FEED_ITEMS + + " ADD COLUMN " + PodDBAdapter.KEY_PODCASTINDEX_CHAPTER_URL + " TEXT"); + } } } diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java index 7994861e8..f66c385b7 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java @@ -50,7 +50,7 @@ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; public static final String DATABASE_NAME = "Antennapod.db"; - public static final int VERSION = 2050000; + public static final int VERSION = 2060000; /** * Maximum number of arguments for IN-operator. @@ -116,6 +116,7 @@ public class PodDBAdapter { public static final String KEY_FEED_SKIP_ENDING = "feed_skip_ending"; public static final String KEY_FEED_TAGS = "tags"; public static final String KEY_EPISODE_NOTIFICATION = "episode_notification"; + public static final String KEY_PODCASTINDEX_CHAPTER_URL = "podcastindex_chapter_url"; // Table names public static final String TABLE_NAME_FEEDS = "Feeds"; @@ -166,7 +167,8 @@ public class PodDBAdapter { + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT," + KEY_IMAGE_URL + " TEXT," - + KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER)"; + + KEY_AUTO_DOWNLOAD_ATTEMPTS + " INTEGER," + + KEY_PODCASTINDEX_CHAPTER_URL + " TEXT)"; private static final String CREATE_TABLE_FEED_MEDIA = "CREATE TABLE " + TABLE_NAME_FEED_MEDIA + " (" + TABLE_PRIMARY_KEY + KEY_DURATION @@ -292,7 +294,8 @@ public class PodDBAdapter { + TABLE_NAME_FEED_ITEMS + "." + KEY_HAS_CHAPTERS + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_ITEM_IDENTIFIER + ", " + TABLE_NAME_FEED_ITEMS + "." + KEY_IMAGE_URL + ", " - + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ATTEMPTS; + + TABLE_NAME_FEED_ITEMS + "." + KEY_AUTO_DOWNLOAD_ATTEMPTS + ", " + + TABLE_NAME_FEED_ITEMS + "." + KEY_PODCASTINDEX_CHAPTER_URL; private static final String KEYS_FEED_MEDIA = TABLE_NAME_FEED_MEDIA + "." + KEY_ID + " AS " + SELECT_KEY_MEDIA_ID + ", " @@ -648,6 +651,7 @@ public class PodDBAdapter { values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); values.put(KEY_AUTO_DOWNLOAD_ATTEMPTS, item.getAutoDownloadAttemptsAndTime()); values.put(KEY_IMAGE_URL, item.getImageUrl()); + values.put(KEY_PODCASTINDEX_CHAPTER_URL, item.getPodcastIndexChapterUrl()); if (item.getId() == 0) { item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); diff --git a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java index 799ca5dde..fcf51e31e 100644 --- a/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java +++ b/storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java @@ -27,6 +27,7 @@ public abstract class FeedItemCursorMapper { int indexItemIdentifier = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_ITEM_IDENTIFIER); int indexAutoDownload = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_AUTO_DOWNLOAD_ATTEMPTS); int indexImageUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_IMAGE_URL); + int indexPodcastIndexChapterUrl = cursor.getColumnIndexOrThrow(PodDBAdapter.KEY_PODCASTINDEX_CHAPTER_URL); long id = cursor.getInt(indexId); String title = cursor.getString(indexTitle); @@ -39,8 +40,9 @@ public abstract class FeedItemCursorMapper { String itemIdentifier = cursor.getString(indexItemIdentifier); long autoDownload = cursor.getLong(indexAutoDownload); String imageUrl = cursor.getString(indexImageUrl); + String podcastIndexChapterUrl = cursor.getString(indexPodcastIndexChapterUrl); return new FeedItem(id, title, link, pubDate, paymentLink, feedId, - hasChapters, imageUrl, state, itemIdentifier, autoDownload); + hasChapters, imageUrl, state, itemIdentifier, autoDownload, podcastIndexChapterUrl); } } |