diff options
Diffstat (limited to 'src/de/danoeh/antennapod')
21 files changed, 697 insertions, 81 deletions
diff --git a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java index 3587775a5..c469e70f9 100644 --- a/src/de/danoeh/antennapod/activity/AudioplayerActivity.java +++ b/src/de/danoeh/antennapod/activity/AudioplayerActivity.java @@ -16,7 +16,8 @@ import com.viewpagerindicator.TabPageIndicator; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.R; -import de.danoeh.antennapod.adapter.SCListAdapter; +import de.danoeh.antennapod.adapter.ChapterListAdapter; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.fragment.CoverFragment; @@ -62,7 +63,7 @@ public class AudioplayerActivity extends MediaplayerActivity { FeedMedia media = controller.getMedia(); int tabcount = 2; - if (media != null && media.getItem().getSimpleChapters() != null) { + if (media != null && media.getItem().getChapters() != null) { tabcount = 3; } pagerAdapter = new MediaPlayerPagerAdapter(getSupportFragmentManager(), @@ -85,8 +86,7 @@ public class AudioplayerActivity extends MediaplayerActivity { } } - public class MediaPlayerPagerAdapter extends - FragmentStatePagerAdapter { + public class MediaPlayerPagerAdapter extends FragmentStatePagerAdapter { private int numItems; private AudioplayerActivity activity; @@ -109,8 +109,8 @@ public class AudioplayerActivity extends MediaplayerActivity { if (media != null) { switch (position) { case POS_COVER: - activity.coverFragment = CoverFragment - .newInstance(media.getItem()); + activity.coverFragment = CoverFragment.newInstance(media + .getItem()); return activity.coverFragment; case POS_DESCR: activity.descriptionFragment = ItemDescriptionFragment @@ -123,16 +123,15 @@ public class AudioplayerActivity extends MediaplayerActivity { public void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); - SimpleChapter chapter = (SimpleChapter) this - .getListAdapter().getItem(position); + Chapter chapter = (Chapter) this.getListAdapter().getItem( + position); controller.seekToChapter(chapter); } }; - sCChapterFragment.setListAdapter(new SCListAdapter( - activity, 0, media.getItem() - .getSimpleChapters())); + sCChapterFragment.setListAdapter(new ChapterListAdapter( + activity, 0, media.getItem().getChapters())); return sCChapterFragment; default: diff --git a/src/de/danoeh/antennapod/adapter/SCListAdapter.java b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java index 0174b4848..e13b930eb 100644 --- a/src/de/danoeh/antennapod/adapter/SCListAdapter.java +++ b/src/de/danoeh/antennapod/adapter/ChapterListAdapter.java @@ -20,15 +20,16 @@ import android.view.ViewGroup; import android.widget.ArrayAdapter; import android.widget.TextView; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.util.Converter; -public class SCListAdapter extends ArrayAdapter<SimpleChapter> { +public class ChapterListAdapter extends ArrayAdapter<Chapter> { - private static final String TAG = "SCListAdapter"; + private static final String TAG = "ChapterListAdapter"; - public SCListAdapter(Context context, int textViewResourceId, - List<SimpleChapter> objects) { + public ChapterListAdapter(Context context, int textViewResourceId, + List<Chapter> objects) { super(context, textViewResourceId, objects); } @@ -36,7 +37,7 @@ public class SCListAdapter extends ArrayAdapter<SimpleChapter> { public View getView(int position, View convertView, ViewGroup parent) { Holder holder; - SimpleChapter sc = getItem(position); + Chapter sc = getItem(position); // Inflate Layout if (convertView == null) { @@ -114,7 +115,7 @@ public class SCListAdapter extends ArrayAdapter<SimpleChapter> { } }); - SimpleChapter current = sc.getItem().getCurrentChapter(); + Chapter current = sc.getItem().getCurrentChapter(); if (current != null) { if (current == sc) { holder.title.setTextColor(convertView.getResources().getColor( diff --git a/src/de/danoeh/antennapod/feed/Chapter.java b/src/de/danoeh/antennapod/feed/Chapter.java new file mode 100644 index 000000000..f50053f93 --- /dev/null +++ b/src/de/danoeh/antennapod/feed/Chapter.java @@ -0,0 +1,58 @@ +package de.danoeh.antennapod.feed; + +public abstract class Chapter extends FeedComponent { + + /** Defines starting point in milliseconds. */ + protected long start; + protected String title; + protected FeedItem item; + protected String link; + + public Chapter(long start) { + super(); + this.start = start; + } + + public Chapter(long start, String title, FeedItem item, String link) { + super(); + this.start = start; + this.title = title; + this.item = item; + this.link = link; + } + + public abstract int getChapterType(); + + public long getStart() { + return start; + } + + public String getTitle() { + return title; + } + + public FeedItem getItem() { + return item; + } + + public String getLink() { + return link; + } + + public void setStart(long start) { + this.start = start; + } + + public void setTitle(String title) { + this.title = title; + } + + public void setItem(FeedItem item) { + this.item = item; + } + + public void setLink(String link) { + this.link = link; + } + +} diff --git a/src/de/danoeh/antennapod/feed/FeedItem.java b/src/de/danoeh/antennapod/feed/FeedItem.java index 209426b5e..3fa9a66b8 100644 --- a/src/de/danoeh/antennapod/feed/FeedItem.java +++ b/src/de/danoeh/antennapod/feed/FeedItem.java @@ -2,6 +2,7 @@ package de.danoeh.antennapod.feed; import java.util.ArrayList; import java.util.Date; +import java.util.List; /** * Data Object for a XML message @@ -22,7 +23,7 @@ public class FeedItem extends FeedComponent { private Feed feed; protected boolean read; private String paymentLink; - private ArrayList<SimpleChapter> simpleChapters; + private List<Chapter> chapters; public FeedItem() { this.read = true; @@ -41,11 +42,11 @@ public class FeedItem extends FeedComponent { } /** Get the chapter that fits the position. */ - public SimpleChapter getCurrentChapter(int position) { - SimpleChapter current = null; - if (simpleChapters != null) { - current = simpleChapters.get(0); - for (SimpleChapter sc : simpleChapters) { + public Chapter getCurrentChapter(int position) { + Chapter current = null; + if (chapters != null) { + current = chapters.get(0); + for (Chapter sc : chapters) { if (sc.getStart() > position) { break; } else { @@ -57,7 +58,7 @@ public class FeedItem extends FeedComponent { } /** Calls getCurrentChapter with current position. */ - public SimpleChapter getCurrentChapter() { + public Chapter getCurrentChapter() { return getCurrentChapter(media.getPosition()); } @@ -144,12 +145,12 @@ public class FeedItem extends FeedComponent { this.paymentLink = paymentLink; } - public ArrayList<SimpleChapter> getSimpleChapters() { - return simpleChapters; + public List<Chapter> getChapters() { + return chapters; } - public void setSimpleChapters(ArrayList<SimpleChapter> simpleChapters) { - this.simpleChapters = simpleChapters; + public void setChapters(List<Chapter> chapters) { + this.chapters = chapters; } public String getItemIdentifier() { diff --git a/src/de/danoeh/antennapod/feed/FeedManager.java b/src/de/danoeh/antennapod/feed/FeedManager.java index a3ab901f9..1c6d7036f 100644 --- a/src/de/danoeh/antennapod/feed/FeedManager.java +++ b/src/de/danoeh/antennapod/feed/FeedManager.java @@ -374,13 +374,13 @@ public class FeedManager { downloadFeedItem(context, queue.toArray(new FeedItem[queue.size()])); } } - + public void downloadFeedItem(final Context context, FeedItem... items) { boolean autoQueue = PreferenceManager.getDefaultSharedPreferences( context.getApplicationContext()).getBoolean( PodcastApp.PREF_AUTO_QUEUE, true); List<FeedItem> addToQueue = new ArrayList<FeedItem>(); - + for (FeedItem item : items) { if (item.getMedia() != null && !requester.isDownloadingFile(item.getMedia()) @@ -390,7 +390,8 @@ public class FeedManager { } } if (autoQueue) { - addQueueItem(context, addToQueue.toArray(new FeedItem[addToQueue.size()])); + addQueueItem(context, + addToQueue.toArray(new FeedItem[addToQueue.size()])); } } @@ -919,19 +920,31 @@ public class FeedManager { Cursor chapterCursor = adapter .getSimpleChaptersOfFeedItemCursor(item); if (chapterCursor.moveToFirst()) { - item.setSimpleChapters(new ArrayList<SimpleChapter>()); + item.setChapters(new ArrayList<Chapter>()); do { - SimpleChapter chapter = new SimpleChapter( - item, - chapterCursor - .getLong(PodDBAdapter.KEY_SC_START_INDEX), - chapterCursor - .getString(PodDBAdapter.KEY_TITLE_INDEX), - chapterCursor - .getString(PodDBAdapter.KEY_SC_LINK_INDEX)); + int chapterType = chapterCursor + .getInt(PodDBAdapter.KEY_CHAPTER_TYPE_INDEX); + Chapter chapter = null; + long start = chapterCursor + .getLong(PodDBAdapter.KEY_CHAPTER_START_INDEX); + String title = chapterCursor + .getString(PodDBAdapter.KEY_TITLE_INDEX); + String link = chapterCursor + .getString(PodDBAdapter.KEY_CHAPTER_LINK_INDEX); + + switch (chapterType) { + case SimpleChapter.CHAPTERTYPE_SIMPLECHAPTER: + chapter = new SimpleChapter(start, title, item, + link); + break; + case ID3Chapter.CHAPTERTYPE_ID3CHAPTER: + chapter = new ID3Chapter(start, title, item, + link); + break; + } chapter.setId(chapterCursor .getLong(PodDBAdapter.KEY_ID_INDEX)); - item.getSimpleChapters().add(chapter); + item.getChapters().add(chapter); } while (chapterCursor.moveToNext()); } chapterCursor.close(); diff --git a/src/de/danoeh/antennapod/feed/FeedSearcher.java b/src/de/danoeh/antennapod/feed/FeedSearcher.java index cb98e3bee..0948af71b 100644 --- a/src/de/danoeh/antennapod/feed/FeedSearcher.java +++ b/src/de/danoeh/antennapod/feed/FeedSearcher.java @@ -114,8 +114,8 @@ public class FeedSearcher { private static void searchFeedItemChaptersSingleFeed(String query, ArrayList<SearchResult> destination, Feed feed) { for (FeedItem item : feed.getItems()) { - if (item.getSimpleChapters() != null) { - for (SimpleChapter sc : item.getSimpleChapters()) { + if (item.getChapters() != null) { + for (Chapter sc : item.getChapters()) { SearchResult result = createSearchResult(item, query, sc .getTitle().toLowerCase(), VALUE_ITEM_CHAPTER); if (result != null) { diff --git a/src/de/danoeh/antennapod/feed/ID3Chapter.java b/src/de/danoeh/antennapod/feed/ID3Chapter.java new file mode 100644 index 000000000..6dde7854e --- /dev/null +++ b/src/de/danoeh/antennapod/feed/ID3Chapter.java @@ -0,0 +1,36 @@ +package de.danoeh.antennapod.feed; + +public class ID3Chapter extends Chapter { + public static final int CHAPTERTYPE_ID3CHAPTER = 2; + + /** + * Identifies the chapter in its ID3 tag. This attribute does not have to be + * store in the DB and is only used for parsing. + */ + private String id3ID; + + public ID3Chapter(String id3ID, long start) { + super(start); + this.id3ID = id3ID; + } + + public ID3Chapter(long start, String title, FeedItem item, String link) { + super(start, title, item, link); + } + + @Override + public String toString() { + return "ID3Chapter [id3ID=" + id3ID + ", title=" + title + ", start=" + + start + ", url=" + link + "]"; + } + + @Override + public int getChapterType() { + return CHAPTERTYPE_ID3CHAPTER; + } + + public String getId3ID() { + return id3ID; + } + +} diff --git a/src/de/danoeh/antennapod/feed/SimpleChapter.java b/src/de/danoeh/antennapod/feed/SimpleChapter.java index 96731d6e0..2462497ef 100644 --- a/src/de/danoeh/antennapod/feed/SimpleChapter.java +++ b/src/de/danoeh/antennapod/feed/SimpleChapter.java @@ -1,18 +1,10 @@ package de.danoeh.antennapod.feed; -public class SimpleChapter extends FeedComponent { - /** Defines starting point in milliseconds. */ - private long start; - private String title; - private FeedItem item; - private String link; - - public SimpleChapter(FeedItem item, long start, String title, String link) { - super(); - this.item = item; - this.start = start; - this.title = title; - this.link = link; +public class SimpleChapter extends Chapter { + public static final int CHAPTERTYPE_SIMPLECHAPTER = 0; + + public SimpleChapter(long start, String title, FeedItem item, String link) { + super(start, title, item, link); } public String getTitle() { @@ -35,4 +27,9 @@ public class SimpleChapter extends FeedComponent { return link; } + @Override + public int getChapterType() { + return CHAPTERTYPE_SIMPLECHAPTER; + } + } diff --git a/src/de/danoeh/antennapod/service/PlaybackService.java b/src/de/danoeh/antennapod/service/PlaybackService.java index e59f237c9..2a425e9fe 100644 --- a/src/de/danoeh/antennapod/service/PlaybackService.java +++ b/src/de/danoeh/antennapod/service/PlaybackService.java @@ -38,6 +38,7 @@ import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; import de.danoeh.antennapod.activity.AudioplayerActivity; import de.danoeh.antennapod.activity.VideoplayerActivity; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedItem; import de.danoeh.antennapod.feed.FeedManager; @@ -761,7 +762,7 @@ public class PlaybackService extends Service { saveCurrentPosition(); } - public void seekToChapter(SimpleChapter chapter) { + public void seekToChapter(Chapter chapter) { seek((int) chapter.getStart()); } diff --git a/src/de/danoeh/antennapod/service/download/DownloadService.java b/src/de/danoeh/antennapod/service/download/DownloadService.java index 25cc1ee5a..5341c54aa 100644 --- a/src/de/danoeh/antennapod/service/download/DownloadService.java +++ b/src/de/danoeh/antennapod/service/download/DownloadService.java @@ -56,6 +56,7 @@ import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.storage.DownloadRequester; import de.danoeh.antennapod.syndication.handler.FeedHandler; import de.danoeh.antennapod.syndication.handler.UnsupportedFeedtypeException; +import de.danoeh.antennapod.util.ChapterUtils; import de.danoeh.antennapod.util.DownloadError; import de.danoeh.antennapod.util.InvalidFeedException; @@ -427,7 +428,7 @@ public class DownloadService extends Service { createReport = true; } successfulDownloads++; - } else if (!status.isCancelled()){ + } else if (!status.isCancelled()) { if (status.getFeedFile().getClass() != FeedImage.class) { createReport = true; } @@ -675,6 +676,8 @@ public class DownloadService extends Service { @Override public void run() { + boolean chaptersRead = false; + media.setDownloaded(true); // Get duration MediaPlayer mediaplayer = new MediaPlayer(); @@ -691,10 +694,21 @@ public class DownloadService extends Service { mediaplayer.release(); } + if (media.getItem().getChapters() == null) { + ChapterUtils.readID3ChaptersFromFeedItem(media.getItem()); + if (media.getItem().getChapters() != null) { + chaptersRead = true; + } + } + saveDownloadStatus(status); sendDownloadHandledIntent(DOWNLOAD_TYPE_MEDIA); - manager.setFeedMedia(DownloadService.this, media); - + if (chaptersRead) { + manager.setFeedItem(DownloadService.this, media.getItem()); + } else { + manager.setFeedMedia(DownloadService.this, media); + } + downloadsBeingHandled -= 1; queryDownloads(); } diff --git a/src/de/danoeh/antennapod/storage/PodDBAdapter.java b/src/de/danoeh/antennapod/storage/PodDBAdapter.java index 3ce4a94aa..6cb8e0940 100644 --- a/src/de/danoeh/antennapod/storage/PodDBAdapter.java +++ b/src/de/danoeh/antennapod/storage/PodDBAdapter.java @@ -14,6 +14,7 @@ import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.asynctask.DownloadStatus; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.Feed; import de.danoeh.antennapod.feed.FeedImage; import de.danoeh.antennapod.feed.FeedItem; @@ -25,7 +26,7 @@ import de.danoeh.antennapod.feed.SimpleChapter; * */ public class PodDBAdapter { private static final String TAG = "PodDBAdapter"; - private static final int DATABASE_VERSION = 6; + private static final int DATABASE_VERSION = 7; private static final String DATABASE_NAME = "Antennapod.db"; /** Maximum number of arguments for IN-operator. */ @@ -72,10 +73,11 @@ public class PodDBAdapter { // --------- Queue indices public static final int KEY_FEEDITEM_INDEX = 1; public static final int KEY_QUEUE_FEED_INDEX = 2; - // --------- Simplechapters indices - public static final int KEY_SC_START_INDEX = 2; - public static final int KEY_SC_FEEDITEM_INDEX = 3; - public static final int KEY_SC_LINK_INDEX = 4; + // --------- Chapters indices + public static final int KEY_CHAPTER_START_INDEX = 2; + public static final int KEY_CHAPTER_FEEDITEM_INDEX = 3; + public static final int KEY_CHAPTER_LINK_INDEX = 4; + public static final int KEY_CHAPTER_TYPE_INDEX = 5; // Key-constants public static final String KEY_ID = "id"; @@ -107,12 +109,13 @@ public class PodDBAdapter { public static final String KEY_START = "start"; public static final String KEY_LANGUAGE = "language"; public static final String KEY_AUTHOR = "author"; - public static final String KEY_HAS_SIMPLECHAPTERS = "has_simple_chapters"; + public static final String KEY_HAS_CHAPTERS = "has_simple_chapters"; public static final String KEY_TYPE = "type"; public static final String KEY_ITEM_IDENTIFIER = "item_identifier"; public static final String KEY_FEED_IDENTIFIER = "feed_identifier"; public static final String KEY_REASON_DETAILED = "reason_detailed"; public static final String KEY_DOWNLOADSTATUS_TITLE = "title"; + public static final String KEY_CHAPTER_TYPE = "type"; // Table names public static final String TABLE_NAME_FEEDS = "Feeds"; @@ -142,7 +145,7 @@ public class PodDBAdapter { + " INTEGER," + KEY_READ + " INTEGER," + KEY_LINK + " TEXT," + KEY_DESCRIPTION + " TEXT," + KEY_PAYMENT_LINK + " TEXT," + KEY_MEDIA + " INTEGER," + KEY_FEED + " INTEGER," - + KEY_HAS_SIMPLECHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + + KEY_HAS_CHAPTERS + " INTEGER," + KEY_ITEM_IDENTIFIER + " TEXT)"; private static final String CREATE_TABLE_FEED_IMAGES = "CREATE TABLE " @@ -170,7 +173,7 @@ public class PodDBAdapter { private static final String CREATE_TABLE_SIMPLECHAPTERS = "CREATE TABLE " + TABLE_NAME_SIMPLECHAPTERS + " (" + TABLE_PRIMARY_KEY + KEY_TITLE + " TEXT," + KEY_START + " INTEGER," + KEY_FEEDITEM + " INTEGER," - + KEY_LINK + " TEXT)"; + + KEY_LINK + " TEXT," + KEY_CHAPTER_TYPE + " INTEGER)"; private SQLiteDatabase db; private final Context context; @@ -330,7 +333,7 @@ public class PodDBAdapter { } values.put(KEY_FEED, item.getFeed().getId()); values.put(KEY_READ, item.isRead()); - values.put(KEY_HAS_SIMPLECHAPTERS, item.getSimpleChapters() != null); + values.put(KEY_HAS_CHAPTERS, item.getChapters() != null); values.put(KEY_ITEM_IDENTIFIER, item.getItemIdentifier()); if (item.getId() == 0) { item.setId(db.insert(TABLE_NAME_FEED_ITEMS, null, values)); @@ -338,19 +341,20 @@ public class PodDBAdapter { db.update(TABLE_NAME_FEED_ITEMS, values, KEY_ID + "=?", new String[] { String.valueOf(item.getId()) }); } - if (item.getSimpleChapters() != null) { - setSimpleChapters(item); + if (item.getChapters() != null) { + setChapters(item); } return item.getId(); } - public void setSimpleChapters(FeedItem item) { + public void setChapters(FeedItem item) { ContentValues values = new ContentValues(); - for (SimpleChapter chapter : item.getSimpleChapters()) { + for (Chapter chapter : item.getChapters()) { values.put(KEY_TITLE, chapter.getTitle()); values.put(KEY_START, chapter.getStart()); values.put(KEY_FEEDITEM, item.getId()); values.put(KEY_LINK, chapter.getLink()); + values.put(KEY_CHAPTER_TYPE, chapter.getChapterType()); if (chapter.getId() == 0) { chapter.setId(db .insert(TABLE_NAME_SIMPLECHAPTERS, null, values)); @@ -670,6 +674,10 @@ public class PodDBAdapter { db.execSQL("ALTER TABLE " + TABLE_NAME_DOWNLOAD_LOG + " ADD COLUMN " + KEY_DOWNLOADSTATUS_TITLE + " TEXT"); } + if (oldVersion <= 6) { + db.execSQL("ALTER TABLE " + TABLE_NAME_SIMPLECHAPTERS + + " ADD COLUMN " + KEY_CHAPTER_TYPE + " INTEGER"); + } } } diff --git a/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java b/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java index f1fecf513..f6918231c 100644 --- a/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java +++ b/src/de/danoeh/antennapod/syndication/namespace/simplechapters/NSSimpleChapters.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import org.xml.sax.Attributes; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.syndication.handler.HandlerState; import de.danoeh.antennapod.syndication.namespace.Namespace; @@ -24,15 +25,14 @@ public class NSSimpleChapters extends Namespace { public SyndElement handleElementStart(String localName, HandlerState state, Attributes attributes) { if (localName.equals(CHAPTERS)) { - state.getCurrentItem().setSimpleChapters( - new ArrayList<SimpleChapter>()); + state.getCurrentItem().setChapters(new ArrayList<Chapter>()); } else if (localName.equals(CHAPTER)) { state.getCurrentItem() - .getSimpleChapters() - .add(new SimpleChapter(state.getCurrentItem(), - SyndDateUtils.parseTimeString(attributes - .getValue(START)), attributes - .getValue(TITLE), attributes.getValue(HREF))); + .getChapters() + .add(new SimpleChapter(SyndDateUtils + .parseTimeString(attributes.getValue(START)), + attributes.getValue(TITLE), state.getCurrentItem(), + attributes.getValue(HREF))); } return new SyndElement(localName, this); diff --git a/src/de/danoeh/antennapod/util/ChapterUtils.java b/src/de/danoeh/antennapod/util/ChapterUtils.java new file mode 100644 index 000000000..dd9dbce5a --- /dev/null +++ b/src/de/danoeh/antennapod/util/ChapterUtils.java @@ -0,0 +1,97 @@ +package de.danoeh.antennapod.util; + +import java.io.BufferedInputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import android.util.Log; +import de.danoeh.antennapod.AppConfig; +import de.danoeh.antennapod.feed.Chapter; +import de.danoeh.antennapod.feed.FeedItem; +import de.danoeh.antennapod.feed.FeedMedia; +import de.danoeh.antennapod.util.comparator.ChapterStartTimeComparator; +import de.danoeh.antennapod.util.id3reader.ChapterReader; +import de.danoeh.antennapod.util.id3reader.ID3ReaderException; + +/** Utility class for getting chapter data from media files. */ +public class ChapterUtils { + private static final String TAG = "ChapterUtils"; + + private ChapterUtils() { + } + + public static void readID3ChaptersFromFeedItem(FeedItem item) { + if (AppConfig.DEBUG) + Log.d(TAG, "Reading id3 chapters from item " + item.getTitle()); + final FeedMedia media = item.getMedia(); + if (media != null && media.isDownloaded() + && media.getFile_url() != null) { + File source = new File(media.getFile_url()); + if (source.exists()) { + ChapterReader reader = new ChapterReader(); + InputStream in = null; + + try { + in = new BufferedInputStream(new FileInputStream(source)); + reader.readInputStream(in); + List<Chapter> chapters = reader.getChapters(); + + if (chapters != null) { + Collections.sort(chapters, new ChapterStartTimeComparator()); + processChapters(chapters, item); + if (chaptersValid(chapters)) { + item.setChapters(chapters); + } else { + Log.e(TAG, "Chapter data was invalid"); + } + } else { + Log.i(TAG, "ChapterReader could not find any ID3 chapters"); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (ID3ReaderException e) { + e.printStackTrace(); + } finally { + if (in != null) { + try { + in.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + } else { + Log.e(TAG, "Unable to read id3 chapters: Source doesn't exist"); + } + } + } + + /** Makes sure that chapter does a title and an item attribute. */ + private static void processChapters(List<Chapter> chapters, FeedItem item) { + for (int i = 0; i < chapters.size(); i++) { + Chapter c = chapters.get(i); + if (c.getTitle() == null) { + c.setTitle(Integer.toString(i)); + } + c.setItem(item); + } + } + + private static boolean chaptersValid(List<Chapter> chapters) { + for (Chapter c : chapters) { + if (c.getTitle() == null) { + return false; + } + if (c.getStart() < 0) { + return false; + } + } + return true; + } + +} diff --git a/src/de/danoeh/antennapod/util/PlaybackController.java b/src/de/danoeh/antennapod/util/PlaybackController.java index 38b5201e7..3e0ffb2ad 100644 --- a/src/de/danoeh/antennapod/util/PlaybackController.java +++ b/src/de/danoeh/antennapod/util/PlaybackController.java @@ -26,6 +26,7 @@ import android.widget.TextView; import de.danoeh.antennapod.AppConfig; import de.danoeh.antennapod.PodcastApp; import de.danoeh.antennapod.R; +import de.danoeh.antennapod.feed.Chapter; import de.danoeh.antennapod.feed.FeedMedia; import de.danoeh.antennapod.feed.SimpleChapter; import de.danoeh.antennapod.service.PlaybackService; @@ -552,7 +553,7 @@ public abstract class PlaybackController { } } - public void seekToChapter(SimpleChapter chapter) { + public void seekToChapter(Chapter chapter) { if (playbackService != null) { playbackService.seekToChapter(chapter); } diff --git a/src/de/danoeh/antennapod/util/comparator/ChapterStartTimeComparator.java b/src/de/danoeh/antennapod/util/comparator/ChapterStartTimeComparator.java new file mode 100644 index 000000000..7cc6fa458 --- /dev/null +++ b/src/de/danoeh/antennapod/util/comparator/ChapterStartTimeComparator.java @@ -0,0 +1,20 @@ +package de.danoeh.antennapod.util.comparator; + +import java.util.Comparator; + +import de.danoeh.antennapod.feed.Chapter; + +public class ChapterStartTimeComparator implements Comparator<Chapter> { + + @Override + public int compare(Chapter lhs, Chapter rhs) { + if (lhs.getStart() == rhs.getStart()) { + return 0; + } else if (lhs.getStart() < rhs.getStart()) { + return -1; + } else { + return 1; + } + } + +} diff --git a/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java new file mode 100644 index 000000000..9a86de15d --- /dev/null +++ b/src/de/danoeh/antennapod/util/id3reader/ChapterReader.java @@ -0,0 +1,95 @@ +package de.danoeh.antennapod.util.id3reader; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +import de.danoeh.antennapod.feed.Chapter; +import de.danoeh.antennapod.feed.ID3Chapter; +import de.danoeh.antennapod.util.id3reader.model.FrameHeader; +import de.danoeh.antennapod.util.id3reader.model.TagHeader; + +public class ChapterReader extends ID3Reader { + + private static final String FRAME_ID_CHAPTER = "CHAP"; + private static final String FRAME_ID_TITLE = "TIT2"; + + private List<Chapter> chapters; + private ID3Chapter currentChapter; + + @Override + public int onStartTagHeader(TagHeader header) { + chapters = new ArrayList<Chapter>(); + System.out.println(header.toString()); + return ID3Reader.ACTION_DONT_SKIP; + } + + @Override + public int onStartFrameHeader(FrameHeader header, InputStream input) + throws IOException, ID3ReaderException { + System.out.println(header.toString()); + if (header.getId().equals(FRAME_ID_CHAPTER)) { + if (currentChapter != null) { + if (!hasId3Chapter(currentChapter)) { + chapters.add(currentChapter); + currentChapter = null; + } + } + System.out.println("Found chapter"); + String elementId = readString(input, Integer.MAX_VALUE); + char[] startTimeSource = readBytes(input, 4); + long startTime = ((int) startTimeSource[0] << 24) + | ((int) startTimeSource[1] << 16) + | ((int) startTimeSource[2] << 8) | startTimeSource[3]; + currentChapter = new ID3Chapter(elementId, startTime); + skipBytes(input, 12); + return ID3Reader.ACTION_DONT_SKIP; + } else if (header.getId().equals(FRAME_ID_TITLE)) { + if (currentChapter != null && currentChapter.getTitle() == null) { + System.out.println("Found title"); + skipBytes(input, 1); + currentChapter + .setTitle(readString(input, header.getSize() - 1)); + return ID3Reader.ACTION_DONT_SKIP; + } + } + + return super.onStartFrameHeader(header, input); + } + + private boolean hasId3Chapter(ID3Chapter chapter) { + for (Chapter c : chapters) { + if (((ID3Chapter) c).getId3ID().equals(chapter.getId3ID())) { + return true; + } + } + return false; + } + + @Override + public void onEndTag() { + if (currentChapter != null) { + if (!hasId3Chapter(currentChapter)) { + chapters.add(currentChapter); + } + } + System.out.println("Reached end of tag"); + if (chapters != null) { + for (Chapter c : chapters) { + System.out.println(c.toString()); + } + } + } + + @Override + public void onNoTagHeaderFound() { + System.out.println("No tag header found"); + super.onNoTagHeaderFound(); + } + + public List<Chapter> getChapters() { + return chapters; + } + +} diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java new file mode 100644 index 000000000..8164170bf --- /dev/null +++ b/src/de/danoeh/antennapod/util/id3reader/ID3Reader.java @@ -0,0 +1,182 @@ +package de.danoeh.antennapod.util.id3reader; + +import java.io.IOException; +import java.io.InputStream; +import java.io.PushbackInputStream; + +import de.danoeh.antennapod.util.id3reader.model.FrameHeader; +import de.danoeh.antennapod.util.id3reader.model.TagHeader; + +/** + * Reads the ID3 Tag of a given file. In order to use this class, you should + * create a subclass of it and overwrite the onStart* - or onEnd* - methods. + */ +public class ID3Reader { + private static final int HEADER_LENGTH = 10; + private static final int ID3_LENGTH = 3; + private static final int FRAME_ID_LENGTH = 4; + + protected static final int ACTION_SKIP = 1; + protected static final int ACTION_DONT_SKIP = 2; + + protected int readerPosition; + + private static final char[] LITTLE_ENDIAN_BOM = { 0xff, 0xfe }; + private static final char[] BIG_ENDIAN_BOM = { 0xfe, 0xff }; + + public ID3Reader() { + } + + public final void readInputStream(InputStream input) throws IOException, + ID3ReaderException { + int rc; + readerPosition = 0; + char[] tagHeaderSource = readBytes(input, HEADER_LENGTH); + TagHeader tagHeader = createTagHeader(tagHeaderSource); + if (tagHeader == null) { + onNoTagHeaderFound(); + } else { + rc = onStartTagHeader(tagHeader); + if (rc == ACTION_SKIP) { + onEndTag(); + } else { + while (readerPosition < tagHeader.getSize()) { + FrameHeader frameHeader = createFrameHeader(readBytes( + input, HEADER_LENGTH)); + rc = onStartFrameHeader(frameHeader, input); + if (rc == ACTION_SKIP) { + skipBytes(input, frameHeader.getSize()); + } + } + onEndTag(); + } + } + } + + /** + * Read a certain number of bytes from the given input stream. This method + * changes the readerPosition-attribute. + */ + protected char[] readBytes(InputStream input, int number) + throws IOException, ID3ReaderException { + char[] header = new char[number]; + for (int i = 0; i < number; i++) { + int b = input.read(); + readerPosition++; + if (b != -1) { + header[i] = (char) b; + } else { + throw new ID3ReaderException("Unexpected end of stream"); + } + } + return header; + } + + /** + * Skip a certain number of bytes on the given input stream. This method + * changes the readerPosition-attribute. + */ + protected void skipBytes(InputStream input, int number) throws IOException { + int skipped = 0; + while (skipped < number) { + skipped += input.skip(number - skipped); + System.out.println("Skipped = " + skipped); + } + + readerPosition += number; + } + + private TagHeader createTagHeader(char[] source) throws ID3ReaderException { + boolean hasTag = (source[0] == 0x49) && (source[1] == 0x44) + && (source[2] == 0x33); + if (source.length != HEADER_LENGTH) { + throw new ID3ReaderException("Length of header must be " + + HEADER_LENGTH); + } + if (hasTag) { + String id = null; + id = new String(source, 0, ID3_LENGTH); + char version = (char) ((source[3] << 8) | source[4]); + byte flags = (byte) source[5]; + int size = (source[6] << 24) | (source[7] << 16) | (source[8] << 8) + | source[9]; + return new TagHeader(id, size, version, flags); + } else { + return null; + } + } + + private FrameHeader createFrameHeader(char[] source) + throws ID3ReaderException { + if (source.length != HEADER_LENGTH) { + throw new ID3ReaderException("Length of header must be " + + HEADER_LENGTH); + } + String id = null; + id = new String(source, 0, FRAME_ID_LENGTH); + int size = (((int) source[4]) << 24) | (((int) source[5]) << 16) + | (((int) source[6]) << 8) | source[7]; + char flags = (char) ((source[8] << 8) | source[9]); + return new FrameHeader(id, size, flags); + } + + protected String readString(InputStream input, int max) throws IOException, + ID3ReaderException { + char[] bom = readBytes(input, 2); + if (bom == LITTLE_ENDIAN_BOM || bom == BIG_ENDIAN_BOM) { + return readUnicodeString(input, bom, max); + } else { + PushbackInputStream pi = new PushbackInputStream(input, 2); + pi.unread(bom[1]); + pi.unread(bom[0]); + return readISOString(pi, max); + } + } + + private String readISOString(InputStream input, int max) + throws IOException, ID3ReaderException { + int bytesRead = 0; + StringBuilder builder = new StringBuilder(); + char c; + while (++bytesRead <= max && (c = (char) input.read()) > 0) { + builder.append(c); + } + return builder.toString(); + } + + private String readUnicodeString(InputStream input, char[] bom, int max) + throws IOException, ID3ReaderException { + StringBuffer builder = new StringBuffer(); + char c1 = (char) input.read(); + char c2 = (char) input.read(); + int bytesRead = 2; + while ((c1 > 0 && c2 > 0) && ++bytesRead <= max) { + + builder.append(c1); + c1 = c2; + c2 = (char) input.read(); + } + if (bom == LITTLE_ENDIAN_BOM) { + builder.reverse(); + } + return builder.toString(); + } + + public int onStartTagHeader(TagHeader header) { + return ACTION_SKIP; + } + + public int onStartFrameHeader(FrameHeader header, InputStream input) + throws IOException, ID3ReaderException { + return ACTION_SKIP; + } + + public void onEndTag() { + + } + + public void onNoTagHeaderFound() { + + } + +} diff --git a/src/de/danoeh/antennapod/util/id3reader/ID3ReaderException.java b/src/de/danoeh/antennapod/util/id3reader/ID3ReaderException.java new file mode 100644 index 000000000..c458540ee --- /dev/null +++ b/src/de/danoeh/antennapod/util/id3reader/ID3ReaderException.java @@ -0,0 +1,20 @@ +package de.danoeh.antennapod.util.id3reader; + +public class ID3ReaderException extends Exception { + + public ID3ReaderException() { + } + + public ID3ReaderException(String arg0) { + super(arg0); + } + + public ID3ReaderException(Throwable arg0) { + super(arg0); + } + + public ID3ReaderException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + +} diff --git a/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java new file mode 100644 index 000000000..2c0d8e5ba --- /dev/null +++ b/src/de/danoeh/antennapod/util/id3reader/model/FrameHeader.java @@ -0,0 +1,18 @@ +package de.danoeh.antennapod.util.id3reader.model; + +public class FrameHeader extends Header { + + protected char flags; + + public FrameHeader(String id, int size, char flags) { + super(id, size); + this.flags = flags; + } + + @Override + public String toString() { + return "FrameHeader [flags=" + Integer.toString(flags) + ", id=" + id + ", size=" + size + + "]"; + } + +} diff --git a/src/de/danoeh/antennapod/util/id3reader/model/Header.java b/src/de/danoeh/antennapod/util/id3reader/model/Header.java new file mode 100644 index 000000000..22d5b6376 --- /dev/null +++ b/src/de/danoeh/antennapod/util/id3reader/model/Header.java @@ -0,0 +1,29 @@ +package de.danoeh.antennapod.util.id3reader.model; + +public abstract class Header { + + protected String id; + protected int size; + + public 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 + "]"; + } + + + +} diff --git a/src/de/danoeh/antennapod/util/id3reader/model/TagHeader.java b/src/de/danoeh/antennapod/util/id3reader/model/TagHeader.java new file mode 100644 index 000000000..ec99ef14e --- /dev/null +++ b/src/de/danoeh/antennapod/util/id3reader/model/TagHeader.java @@ -0,0 +1,26 @@ +package de.danoeh.antennapod.util.id3reader.model; + +public class TagHeader extends Header { + + protected char version; + protected byte flags; + + public TagHeader(String id, int size, char version, byte flags) { + super(id, size); + this.version = version; + this.flags = flags; + } + + @Override + public String toString() { + return "TagHeader [version=" + version + ", flags=" + flags + ", id=" + + id + ", size=" + size + "]"; + } + + public char getVersion() { + return version; + } + + + +} |