summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTony Tam <149837+tonytamsf@users.noreply.github.com>2022-03-06 07:09:09 -0800
committerGitHub <noreply@github.com>2022-03-06 16:09:09 +0100
commit1a1bf02e8a73a3f7a05ced5c850c23fceb2629f0 (patch)
tree9d8fe03ef4432ead9874f696a3e9ca89e2cd80f2
parentdad4e405d48c13ec3c3ab90700b3969795778d3f (diff)
downloadAntennaPod-1a1bf02e8a73a3f7a05ced5c850c23fceb2629f0.zip
Support for podcast 2.0 chapters (#5630)
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/service/download/handler/MediaDownloadedHandler.java3
-rw-r--r--core/src/main/java/de/danoeh/antennapod/core/util/ChapterUtils.java34
-rw-r--r--model/src/main/java/de/danoeh/antennapod/model/feed/FeedItem.java15
-rw-r--r--parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/PodcastIndexChapterParser.java31
-rw-r--r--parser/feed/src/main/java/de/danoeh/antennapod/parser/feed/namespace/PodcastIndex.java10
-rw-r--r--storage/database/src/main/java/de/danoeh/antennapod/storage/database/DBUpgrader.java4
-rw-r--r--storage/database/src/main/java/de/danoeh/antennapod/storage/database/PodDBAdapter.java10
-rw-r--r--storage/database/src/main/java/de/danoeh/antennapod/storage/database/mapper/FeedItemCursorMapper.java4
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);
}
}