diff --git a/python/podcast/data_migration.py b/python/podcast/data_migration.py index 84aae247e39055c476f628bdb8594d5f87f019ae..13de0da0e10292987e320e6f6d4a94018201662f 100644 --- a/python/podcast/data_migration.py +++ b/python/podcast/data_migration.py @@ -24,7 +24,7 @@ from podcast.util import chunks logger = logging.getLogger(__name__) -target_data_version = 5 +target_data_version = 6 def needs_migration(): @@ -125,6 +125,7 @@ def run_migrations(strict=True): raise set_versionnumber(3) if current_version < 4 and start_version > 1: + # duration is now in ms db.execute_sql("UPDATE podpost set duration = duration * 1000;") set_versionnumber(4) if current_version < 5 and start_version > 1: @@ -141,6 +142,12 @@ def run_migrations(strict=True): queue.sanitize() queue.save() set_versionnumber(5) + if current_version < 6 and start_version > 1: + # the fields were filled with the podcast logo path, we need that for the episode logo now + logger.info("clearing podpost logo data") + db.execute_sql("UPDATE podpost set logo_url = null;") + db.execute_sql("UPDATE podpost set logo_path = null;") + set_versionnumber(6) db.execute_sql('VACUUM "main"') pyotherside.send("migrationDone") @@ -344,10 +351,6 @@ def migrate_all_podposts_from_v0(post_hash_to_id): def migrate_podcast_v0_v1(podcast_dict: dict, strict=True) -> Tuple[Podcast, Dict[str, Podpost]]: logger.info("migrating podcast '%s' to new format version 1 strictmode: %s", podcast_dict['title'], strict) try: - if podcast_dict['logo_path']: - image = podcast_dict['logo_path'] - else: - image = podcast_dict['logo_url'] podcast: Podcast = Podcast(**podcast_dict) PodcastFactory().persist(podcast) entry_ids_new_to_old = [] @@ -360,7 +363,7 @@ def migrate_podcast_v0_v1(podcast_dict: dict, strict=True) -> Tuple[Podcast, Dic logger.info("Found %d entries in old format", old_entry_count) for entry in feed.entries: try: - post = Podpost.from_feed_entry(podcast, entry, podcast_dict['url'], image) + post = Podpost.from_feed_entry(podcast, entry, podcast_dict['url']) except Exception as e: logger.exception("could not load post from podcast %s", podcast_dict['url']) if strict: diff --git a/python/podcast/feedutils.py b/python/podcast/feedutils.py index 89624420e8c508cf2b3927474c4df6c9d7e2ee82..1f553236288c80826e42953a8afb09cc0aa7255b 100644 --- a/python/podcast/feedutils.py +++ b/python/podcast/feedutils.py @@ -31,15 +31,15 @@ def fetch_feed(published, url) -> FeedParserDict: else: agent = util.user_agent feed: FeedParserDict = feedparser.parse(url, agent=agent, modified=time.gmtime(published)) - if feed.status == 304: - raise NotModified() if feed.bozo != 0: exc: Exception = feed.bozo_exception if type(exc) != feedparser.CharacterEncodingOverride: logger.exception( - "Podcast init: error in parsing feed %s", str(type(exc)), exc_info=exc + "Podcast init: error in parsing feed '%s' %s", url, str(type(exc)), exc_info=exc ) raise FeedFetchingError(exc.message if hasattr(exc, 'message') else "message_missing") + if feed.status == 304: + raise NotModified() if "itunes_new-feed-url" in feed.feed and feed.feed["itunes_new-feed-url"] != url: raise NewFeedUrlError(feed.feed["itunes_new-feed-url"]) logger.info( diff --git a/python/podcast/podcast.py b/python/podcast/podcast.py index 56cf1ce249ffc20d25b96b3523280b8cbe63fe48..ae573fdfc08d660f9084dcbf9c2de491c78a54e5 100644 --- a/python/podcast/podcast.py +++ b/python/podcast/podcast.py @@ -5,6 +5,7 @@ Podcast import sys from typing import List, Dict, Optional, Iterator, Tuple +from feedparser import FeedParserDict from peewee import CharField, TextField, BooleanField, IntegerField, ModelSelect, DoesNotExist, FloatField, \ IntegrityError from podcast.persistent_log import persist_log, LogType @@ -61,7 +62,7 @@ class Podcast(BaseModel): super().__init__(*args, **kwargs) @classmethod - def create_from_url(cls, url, preview=False, num_preview_items=3) -> Optional[Tuple['Podcast',List['Podpost']]]: + def create_from_url(cls, url, preview=False, num_preview_items=3) -> Optional[Tuple['Podcast', List['Podpost']]]: """ @rtype: Tuple[Podcast,List[Podpost]] @@ -75,6 +76,7 @@ class Podcast(BaseModel): podcast.subscribed = not preview logger.info("initializing podcast %s", url) podcast.url = url + feed: FeedParserDict if feedCache.exists(url): feed = feedCache.get(url) else: @@ -98,7 +100,7 @@ class Podcast(BaseModel): return @classmethod - def podcast_from_feed(cls, feed, podcast, preview, url, num_preview_items=3) -> List['Podpost']: + def podcast_from_feed(cls, feed: FeedParserDict, podcast: 'Podcast', preview: bool, url: str, num_preview_items: int = 3) -> List['Podpost']: try: podcast.title = feed.feed.title except: @@ -112,14 +114,6 @@ class Podcast(BaseModel): podcast.description = feed.feed.description except: podcast.description = "" - try: - podcast.logo_url = feed.feed.image.href - logger.debug("podcast logo_url: %s", podcast.logo_url) - except: - podcast.logo_url = "../../images/podcast.png" - logger.exception("podcast logo_url error") - podcast.logo_path = None - podcast.download_icon() podcast.__set_data_from_feed(feed) if preview: episodes = podcast.__process_episodes(feed, limit=num_preview_items, supress_limit_notification=True, @@ -129,6 +123,24 @@ class Podcast(BaseModel): episodes = podcast.__process_episodes(feed) return episodes + def __set_logo_from_feed(self, feed: FeedParserDict): + try: + if not feed or "image" not in feed.feed or not feed.feed.image.href: + return #not there + if self.logo_path and os.path.exists(self.logo_path) and feed.feed.image.href == self.logo_url: + return #already downloaded + self.logo_url = feed.feed.image.href + logger.debug("podcast new logo_url: %s", self.logo_url) + old_path = self.logo_path + if self.logo_path: + self.logo_path = None + self.__download_icon() + if old_path: + util.delete_file(old_path) + except: + self.logo_url = "../../images/podcast.png" + logger.exception("podcast logo_url error") + def __process_episodes(self, feed, limit=0, supress_limit_notification=False, do_not_store=False, break_on_first_existing_episode=True) -> List['Podpost']: """ @@ -138,8 +150,6 @@ class Podcast(BaseModel): logger.debug("fetching episodes for %s full=%s limit=%d", self.title, str(not break_on_first_existing_episode), limit) - image = self.logo() - known_guids = set() known_hrefs = set() from podcast.podpost import Podpost @@ -169,7 +179,7 @@ class Podcast(BaseModel): else: continue try: - post = Podpost.from_feed_entry(self, entry, self.url, image) + post = Podpost.from_feed_entry(self, entry, self.url) episode_count += 1 new_posts.append(post) except Exception as e: @@ -191,7 +201,7 @@ class Podcast(BaseModel): from podcast.podpost import Podpost return [post.id for post in self.episodes.select(Podpost.id).order_by(Podpost.published)] - def logo(self): + def logo(self) -> str: if self.logo_path: image = self.logo_path else: @@ -200,6 +210,7 @@ class Podcast(BaseModel): def __set_data_from_feed(self, feed): logger.debug("setting instance values from feed") + self.__set_logo_from_feed(feed) self.published = self.__get_times_from_feeds(feed) self._set_alt_feeds(feed) @@ -212,7 +223,7 @@ class Podcast(BaseModel): else: return 0 - def feedinfo(self) -> Dict[str,object]: + def feedinfo(self) -> Dict[str, object]: """ return feed information """ @@ -280,17 +291,11 @@ class Podcast(BaseModel): else: return None - # todo handle download icon during init - # TODO unify with podpost download icon - def download_icon(self): + def __download_icon(self): """ Download icon """ - if self.logo_path: - if os.path.exists(self.logo_path): - return - iconpath = Constants().iconpath rdot = self.logo_url.rfind(".") extexcl = self.logo_url.rfind("?") @@ -315,8 +320,7 @@ class Podcast(BaseModel): else: self.logo_path = "" - - def refresh(self, moveto, limit=0, full_refresh=False) -> Iterator[Tuple[int,'Podpost']]: + def refresh(self, moveto, limit=0, full_refresh=False) -> Iterator[Tuple[int, 'Podpost']]: """ Refresh podcast and return list of new entries @type moveto: int the page the podcast should be moved to (see FeedParser#movePost) @@ -380,7 +384,7 @@ class Podcast(BaseModel): self.playrate = params["playrate"] PodcastFactory().persist(self) - def get_params(self) -> Dict[str,object]: + def get_params(self) -> Dict[str, object]: """ Return a set of parameters """ @@ -412,8 +416,6 @@ class Podcast(BaseModel): return self.episodes.count() - - class PodcastFactory(BaseFactory): """ Helping Factory diff --git a/python/podcast/podpost.py b/python/podcast/podpost.py index 72cb6fdec79b91494fa0fdbac800c9af0e74d191..cc47b9fd33ec94b225f32c49fcab6d2de5d8e658 100644 --- a/python/podcast/podpost.py +++ b/python/podcast/podpost.py @@ -22,7 +22,7 @@ from urllib.parse import urlparse import html2text from email.utils import mktime_tz, parsedate_tz from calendar import timegm -from typing import List, Iterator, Optional, Dict +from typing import List, Iterator, Optional, Dict, Union from podcast.factory import BaseFactory import pyotherside @@ -42,37 +42,37 @@ class Podpost(BaseModel): One podcast post/episode. Most fields are persisted in the database """ - guid: str = CharField(index=True) - id: int = AutoField(primary_key=True) # - author: str = CharField(default="") - duration: int = IntegerField(null=True, help_text="in ms") - favorite: bool = BooleanField(default=False) - file_path: str = TextField(null=True) + guid: Union[str,TextField] = CharField(index=True) + id: Union[int,AutoField] = AutoField(primary_key=True) # + author: Union[str,TextField] = CharField(default="") + duration: Union[int,IntegerField] = IntegerField(null=True, help_text="in ms") + favorite: Union[bool,BooleanField] = BooleanField(default=False) + file_path: Union[str,TextField] = TextField(null=True) # podcast file url - href: TextField = TextField(default="", index=True) - htmlpart: str = TextField(null=True) + href: Union[str,TextField] = TextField(default="", index=True) + htmlpart: Union[str,TextField] = TextField(null=True) # when we entered this post into our db insert_date = FloatField(default=lambda: datetime.datetime.now().timestamp()) # if the post comes from the external folder isaudio: bool = BooleanField(default=False, help_text="Is episode from external folder source") # filesize - length: int = IntegerField(default=0) + length: Union[int,IntegerField] = IntegerField(default=0) listened: bool = BooleanField(default=False, null=False, help_text="Has been played already") - link: str = TextField(default="") - logo_path: str = TextField(null=True) - logo_url: str = TextField(null=True) + link: Union[str,TextField] = TextField(default="") + logo_path: Union[str,TextField] = TextField(null=True) + logo_url: Union[str,TextField] = TextField(null=True) # download percentage percentage: float = FloatField(default=0) plainpart: TextField = TextField(default="") - position: int = IntegerField(default=0, help_text="in ms") - podcast = ForeignKeyField(Podcast, null=True, backref='episodes', lazy_load=True, on_delete='CASCADE') + position: Union[int,IntegerField] = IntegerField(default=0, help_text="in ms") + podcast: Union[Podcast,ForeignKeyField] = ForeignKeyField(Podcast, null=True, backref='episodes', lazy_load=True, on_delete='CASCADE') # when the post was published according to feed published = DateTimeField() # play_state - state: int = IntegerField(default=STOP) - title: TextField = TextField() + state: Union[int,IntegerField] = IntegerField(default=STOP) + title: Union[str,TextField] = TextField() # mimetype - type: str = TextField(null=True) + type: Union[str,TextField] = TextField(null=True) def __init__(self, *args, **kwargs): """ @@ -85,20 +85,19 @@ class Podpost(BaseModel): super().__init__(*args, **kwargs) @classmethod - def from_feed_entry(cls, podcast, entry, podurl, logo_url) -> 'Podpost': + def from_feed_entry(cls, podcast, entry, podurl) -> 'Podpost': """ Creates a podpost from a feed entry @rtype: Podpost """ assert podurl - assert logo_url post = cls() post.podcast = podcast - post.logo_url = logo_url - if logo_url and len(logo_url) > 0 and logo_url[0] == "/": - post.logo_path = logo_url - else: - post.logo_path = None + if 'image' in entry.keys(): + image_url: str = entry["image"].href + if image_url and isinstance(image_url,str) and image_url.startswith("http") and image_url != podcast.logo_url: + post.logo_url = image_url + if 'published_parsed' in entry.keys(): post.published = timegm(entry['published_parsed']) else: @@ -213,8 +212,6 @@ class Podpost(BaseModel): inqueue = QueueFactory().get_queue().is_in_queue(self) - image = self.get_image_descriptor() - section = util.format_full_date(self.published) date = self.published asection = util.format_full_date(self.insert_date) @@ -235,7 +232,8 @@ class Podpost(BaseModel): "title": self.title, "podcast_url": self.podcast.url if not self.isaudio else "", "audio_url": self.href, - "logo_url": image, + "podcast_logo": self.podcast.logo() if not self.isaudio else "", + "episode_logo": self.get_episode_logo_path_or_url(), "link": self.link, "description": self.plainpart, "detail": self.htmlpart, @@ -256,14 +254,22 @@ class Podpost(BaseModel): "podcastTitle": self.podcast.title if not self.isaudio else "" } - def get_image_descriptor(self): + def get_episode_logo_path_or_url(self): if self.logo_path != None: image = self.logo_path else: image = self.logo_url return image - def download_audio(self): + def download(self): + yield from self.__download_audio() + try: + self.__download_icon() + except: + logger.exception("exception during downloading logo") + PodpostFactory().persist(self) + + def __download_audio(self) -> Iterator[int]: """ Download the audio stuff """ @@ -313,10 +319,8 @@ class Podpost(BaseModel): persist_log(LogType.Exception, what="episode download", title=self.title, exception=e) self.file_path = file_path - PodpostFactory().persist(self) - # TODO unify with podcasts download icon - def download_icon(self): + def __download_icon(self): """ Download icon """ @@ -336,8 +340,6 @@ class Podpost(BaseModel): logo_path = os.path.join(iconpath, name) try: util.dl_from_url(self.logo_url, logo_path) - # for i in dl_from_url_progress(self.logo_url, logo_path): - # yield i except: logo_path = "" @@ -453,6 +455,9 @@ class Podpost(BaseModel): util.delete_file(self.file_path) self.percentage = 0 self.file_path = None + if self.logo_path: + util.delete_file(self.logo_path) + self.logo_path = None PodpostFactory().persist(self) def toggle_fav(self, do_download=False): @@ -481,7 +486,7 @@ class Podpost(BaseModel): if self.favorite and do_download: logger.info("need to download file") - for perc in self.download_audio(): + for perc in self.download(): pyotherside.send("downloading", self.id, perc) if self.file_path: diff --git a/python/podcast/queue.py b/python/podcast/queue.py index 3335156b49947d493548df0a4b6d508198c843be..077619766c92a38e297dd5df8a722053d9419ab3 100644 --- a/python/podcast/queue.py +++ b/python/podcast/queue.py @@ -210,10 +210,10 @@ class Queue: if podpost in self.podposts: post = PodpostFactory().get_podpost(podpost) - for perc in post.download_audio(): + for perc in post.download(): yield perc if podpost == self.podposts[0] and post.file_path: - pyotherside.send("firstDownloaded", post.file_path) + pyotherside.send("firstDownloaded", post.file_path, post.logo_path) def download_all(self): """ diff --git a/qml/components/EpisodeImage.qml b/qml/components/EpisodeImage.qml index 926dbf310bb7df9d19a9e30c271f6606ea3ef451..f24ce90e279dfac44f6f4bc046e580cb803412c4 100644 --- a/qml/components/EpisodeImage.qml +++ b/qml/components/EpisodeImage.qml @@ -15,12 +15,36 @@ Thumbnail { MouseArea { anchors.fill: parent onClicked: { - if (model.id === playerHandler.firstid) { - playerHandler.playpause() - } else { - queuehandler.queueInsertTop(model.id, function () { - playerHandler.play() - }) + switch (episodeImageClickActionConf.value) { + case 0: + queuehandler.playAndSetPositionInQueue(model.id) + break + case 1: + pageStack.push(Qt.resolvedUrl("../pages/PodpostList.qml"), { + "url": model.podcast_url + }) + break + case 2: + pageStack.push(Qt.resolvedUrl("../pages/PostDescription.qml"), { + "title": model.title, + "detail": model.detail, + "length": model.length, + "date": model.date, + "duration": model.duration, + "href": model.link, + "episode_logo": model.episode_logo + }) + break + case 3: + queuehandler.queueInsertNext(model.id) + break + case 4: + queuehandler.queueInsertBottom(model.id) + break + default: + console.error( + "unexpected config value for episodeImageClickActionConf: " + + episodeImageClickActionConf.value) } } } @@ -28,7 +52,7 @@ Thumbnail { height: Theme.iconSizeLarge sourceSize.width: Theme.iconSizeLarge sourceSize.height: Theme.iconSizeLarge - source: logo_url == "" ? "../../images/podcast.png" : logo_url + source: model.podcast_logo === "" ? "../../images/podcast.png" : model.podcast_logo Rectangle { id: dlstatus visible: showDownladingState diff --git a/qml/components/PlayDockedPanel.qml b/qml/components/PlayDockedPanel.qml index e3365702602c03300925ed4c0f689358c56981ba..d9427ce102e00ca4b56eb822a970d7b789c36b2c 100644 --- a/qml/components/PlayDockedPanel.qml +++ b/qml/components/PlayDockedPanel.qml @@ -43,8 +43,7 @@ DockedPanel { } IconButton { id: playpanelicon - icon.source: playerHandler.playicon - === "" ? "../../images/podcast.png" : playerHandler.playicon + icon.source: playerHandler.podcast_logo_or_fallback icon.width: Theme.iconSizeMedium icon.height: Theme.iconSizeMedium icon.color: undefined @@ -61,7 +60,8 @@ DockedPanel { "length": data.length, "date": data.date, "duration": data.duration, - "href": data.link + "href": data.link, + "episode_logo": data.episode_logo }) } } diff --git a/qml/components/PlayerHandler.qml b/qml/components/PlayerHandler.qml index 578e185f82d6baa35411cd38a26652f8eee2d8c6..6ce8a9a903947ab812b07308a207a13409a781df 100644 --- a/qml/components/PlayerHandler.qml +++ b/qml/components/PlayerHandler.qml @@ -14,7 +14,10 @@ Python { property real positionFromDb: 0 property bool isPlaying: false - property string playicon: "../../images/podcast.png" + property string podcast_logo: "../../images/podcast.png" + property string podcast_logo_or_fallback: podcast_logo ? podcast_logo : "../../images/podcast.png" + property string episode_logo: "" + property string episode_logo_or_fallback: episode_logo ? episode_logo : podcast_logo property string playtext: "" property string firstid: "" property string lastPlayedId: "" @@ -55,7 +58,9 @@ Python { } onPlaying: { - console.info("Playing audio_url: " + audio_url + " @ rate " + playrate) + console.info("Playing audio_url: " + audio_url + " @ rate " + playrate + + "@ position " + position) + positionFromDb = position if (audio_url != "") { if (mediaplayer.source != audio_url) { mediaplayer.source = audio_url @@ -83,13 +88,13 @@ Python { if (mediaplayer.position > pauseRewindTime) positionToSave = mediaplayer.position - pauseRewindTime isPlaying = false - queuehandler.updatePlayingPosition(firstid, positionToSave) + queuehandler.updatePlayingPosition(lastPlayedId, positionToSave) } onStopping: { mediaplayer.pause() isPlaying = false - queuehandler.updatePlayingPosition(firstid, mediaplayer.position) + queuehandler.updatePlayingPosition(lastPlayedId, mediaplayer.position) } onDownloading: { @@ -103,7 +108,11 @@ Python { firsttitle = data.title firstPodcastTitle = data.podcastTitle chapters = chapterlist - playicon = data.logo_url + podcast_logo = data.podcast_logo + if (data.episode_logo) + episode_logo = data.episode_logo + else + episode_logo = "" playtext = data.title durationFromDb = data.duration positionFromDb = data.position @@ -218,4 +227,10 @@ Python { } seek(posi) } + + function interval_task() { + if (lastPlayedId) + queuehandler.updatePlayingPosition(lastPlayedId, + mediaplayer.position) + } } diff --git a/qml/components/PostListItem.qml b/qml/components/PostListItem.qml index fd763c18b73389e039f8471e5e93ae0074021925..8881ac8764a4bb83be72cca8187ff7b5e5451154 100644 --- a/qml/components/PostListItem.qml +++ b/qml/components/PostListItem.qml @@ -11,7 +11,7 @@ ListItem { property double duration: model.duration property var postId: model.id property string link: model.link - property string logo_url: model.logo_url + property string logo_url: model.podcast_logo property double position: model.position property string description: model.description property bool favorite: model.favorite @@ -30,7 +30,8 @@ ListItem { "length": length, "date": date, "duration": duration, - "href": link + "href": link, + "episode_logo": model.episode_logo }) clip: true property bool isfavorite: favorite diff --git a/qml/components/QueueHandlerPython.qml b/qml/components/QueueHandlerPython.qml index bdbd3cfcc0b0640b94926d365ef6fd403ada75bc..8f7cdba6b19d11a23189ae2472a7344998674220 100644 --- a/qml/components/QueueHandlerPython.qml +++ b/qml/components/QueueHandlerPython.qml @@ -15,7 +15,7 @@ Python { signal createList(var data, string moved) signal downloading(string dlid, int percent) signal needDownload(string podpost) - signal firstDownloaded(string filepath) + signal firstDownloaded(string filepath, string episode_logo_path) signal episodeChapters(var chapters) Component.onCompleted: { @@ -73,12 +73,6 @@ Python { call("QueueHandler.instance.get_first_entry", function () {}) } function updatePlayingPosition(id, position) { - if (id === undefined) { - id = playerHandler.firstid - } - if (position === undefined) { - position = playerHandler.position - } call("QueueHandler.instance.update_position", [id, position], function () { if (id === playerHandler.firstid) @@ -107,6 +101,16 @@ Python { function () {}) } + function playAndSetPositionInQueue(episodeid) { + if (episodeid === playerHandler.firstid && !playerHandler.isPlaying) { + playerHandler.play() + } else { + queueInsertTop(episodeid, function () { + playerHandler.play() + }) + } + } + onError: { console.log('python error: ' + traceback) } @@ -139,6 +143,7 @@ Python { + filepath + "/" + playerHandler.seekPos) //magic needed to replace the source and seek to same position mediaplayer.source = "file://" + filepath + playerHandler.episode_logo = episode_logo_path mediaplayer.seek(position) mediaplayer.play() if (!mediaplayer.seekable) { diff --git a/qml/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 5f92308f1f9fd71194b707c60d81b9429a45c78b..1464d71079aa26b43a7c078ddf72d6aab1973245 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -4,7 +4,7 @@ import Sailfish.Silica 1.0 CoverBackground { Image { id: coverImage - source: playerHandler.playicon === "" ? "../../images/podcast.png" : playerHandler.playicon + source: playerHandler.isPlaying ? playerHandler.episode_logo_or_fallback : playerHandler.podcast_logo_or_fallback width: parent.height height: parent.height anchors.horizontalCenter: parent.horizontalCenter @@ -70,5 +70,11 @@ CoverBackground { playerHandler.playpause() } } + CoverAction { + iconSource: "image://theme/icon-m-media-forward" + onTriggered: { + playerHandler.fast_forward() + } + } } } diff --git a/qml/harbour-podqast.qml b/qml/harbour-podqast.qml index 851b2b4b35ecfd5feb5d547cdaba3da074133028..12424d7287d532c2d0c22cacd56da1a4b0be7b69 100644 --- a/qml/harbour-podqast.qml +++ b/qml/harbour-podqast.qml @@ -71,7 +71,7 @@ ApplicationWindow { repeat: true onTriggered: { console.log("Minuteman") - queuehandler.updatePlayingPosition() + playerHandler.interval_task() } } @@ -265,6 +265,12 @@ ApplicationWindow { defaultValue: true } + ConfigurationValue { + id: episodeImageClickActionConf + key: "/apps/ControlPanel/podqast/episodeImageClickAction" + defaultValue: 0 + } + function refresh() { connmanWifi.getProperties() } @@ -317,7 +323,7 @@ ApplicationWindow { feedparserhandler.refreshPodcasts() } - getProperties() + connmanWifi.getProperties() } DBusAdaptor { @@ -450,9 +456,6 @@ ApplicationWindow { id: loghandler } - // PodqastAudioPlayer { - // id: mediaplayer - // } PlayerHandler { id: playerHandler onAudioNotExist: { @@ -519,10 +522,10 @@ ApplicationWindow { duration: playerHandler.duration title: playerHandler.firsttitle contributingArtist: playerHandler.firstPodcastTitle - artUrl: playerHandler.playicon + artUrl: playerHandler.episode_logo_or_fallback } - loopStatus: Mpris.None + loopStatus: Mpris.LoopNone shuffle: false onPauseRequested: playerHandler.pause() onPlayPauseRequested: playerHandler.playpause() diff --git a/qml/pages/Player.qml b/qml/pages/Player.qml index c2ff9b592cd0a69c93b5c96cdbb9934b93b2cfef..61df1b8c13e802487973f1f50a2e1739bb7595b6 100644 --- a/qml/pages/Player.qml +++ b/qml/pages/Player.qml @@ -1,5 +1,6 @@ import QtQuick 2.0 import Sailfish.Silica 1.0 +import Nemo.Thumbnailer 1.0 import "../lib/hints" import "../components/hints" import "../components" @@ -133,8 +134,9 @@ Page { id: podimage width: parent.width height: parent.width - source: playerHandler.playicon - === "" ? "../../images/podcast.png" : playerHandler.playicon + source: playerHandler.episode_logo_or_fallback + sourceSize.width: width + sourceSize.height: height MouseArea { anchors.fill: parent onClicked: { @@ -272,7 +274,7 @@ Page { height: Theme.itemSizeLarge IconButton { - icon.source: "image://theme/icon-m-previous" + icon.source: "image://theme/icon-m-media-rewind" onClicked: { playerHandler.fast_backward() } @@ -285,7 +287,7 @@ Page { } } IconButton { - icon.source: "image://theme/icon-m-next" + icon.source: "image://theme/icon-m-media-forward" onClicked: { playerHandler.fast_forward() diff --git a/qml/pages/PostDescription.qml b/qml/pages/PostDescription.qml index ad06a990f575bdf8e89f96fc6bf14b5eb8832e42..64cd470194cb6c95deb1a6271361a9851738eca2 100644 --- a/qml/pages/PostDescription.qml +++ b/qml/pages/PostDescription.qml @@ -11,6 +11,7 @@ Page { property string length property string duration property string date + property string episode_logo // The effective value will be restricted by ApplicationWindow.allowedOrientations allowedOrientations: Orientation.All @@ -73,6 +74,16 @@ Page { font.pixelSize: Theme.fontSizeSmall color: Theme.highlightColor } + Image { + id: episode_image + anchors.horizontalCenter: parent.horizontalCenter + visible: episode_logo + width: Math.min(sourceSize.width, parent.width) + height: width + source: episode_logo + asynchronous: true + } + Label { id: detailcolumn width: parent.width diff --git a/qml/pages/Queue.qml b/qml/pages/Queue.qml index 6c64c22a89da0cb4eca1a03fa5897d9816a522ab..907d01a8fc929d57cb13d096cb9f9d019c736481 100644 --- a/qml/pages/Queue.qml +++ b/qml/pages/Queue.qml @@ -18,12 +18,10 @@ Item { queuePostModel.clear() for (var i = 0; i < data.length; i++) { queuePostModel.append(data[i]) - if (i === 0) { - playerHandler.playicon = data[i].logo_url - } } if (i > 0) { playeropen = true + playerHandler.podcast_logo = data[0].podcast_logo } else { playeropen = false } diff --git a/qml/pages/Settings.qml b/qml/pages/Settings.qml index 555b361860c8650688f6855ffb4df0de0ed1d65f..e69456984d4a8660cab0366c04013ed4ff588988 100644 --- a/qml/pages/Settings.qml +++ b/qml/pages/Settings.qml @@ -34,6 +34,34 @@ Dialog { color: Theme.highlightColor } + ComboBox { + anchors { + left: parent.left + right: parent.right + margins: Theme.paddingMedium + } + id: imageClickAction + width: parent.width + label: qsTr("When touching episode image...") + menu: ContextMenu { + MenuItem { + text: qsTr("Play Episode") + } + MenuItem { + text: qsTr("Show Podcast Episodes") + } + MenuItem { + text: qsTr("Show Episode Info") + } + MenuItem { + text: qsTr("Add To Queue Top") + } + MenuItem { + text: qsTr("Add To Queue Bottom") + } + } + } + ComboBox { anchors { left: parent.left @@ -365,6 +393,7 @@ Dialog { autoPlayQueue.checked = autoPlayNextInQueue.value // downLimit.value = downLimitConf.value // refreshTime = refreshTimeConf.value + imageClickAction.currentIndex = episodeImageClickActionConf.value } onAccepted: { // useGpodderConf.value = useGpodder.checked @@ -388,5 +417,6 @@ Dialog { markListenedEndThresholdConf.value = listenedThreshold.value // downLimitConf.value = downLimit.value // refreshTimeConf.value = refreshTime + episodeImageClickActionConf.value = imageClickAction.currentIndex } } diff --git a/test/__init__.py b/test/__init__.py index 3d7db0dacefcc7cae1abf6d223188c712f123763..fa52cfb88516e852168a10637512ffb61fcc0e0f 100644 --- a/test/__init__.py +++ b/test/__init__.py @@ -13,6 +13,8 @@ from test.conftest import cleanup_podcast logger = logging.getLogger(__name__) +HTTPretty.allow_net_connect = False + def mock_pyotherside(): module_name = "pyotherside" module = types.ModuleType(module_name) @@ -40,7 +42,7 @@ def setup_inbox_with_2_posts(): inbox.insert(entry2.id) return entry1, entry2 -@httprettified +@httprettified(allow_net_connect=False) def setup_and_get_2_posts(): logger.info("setup with dummy feed") HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/', diff --git a/test/test_favorites.py b/test/test_favorites.py index c20e39943f6112a6a0419f9c7989391895dd62f7..ae9e30b19df4edfa76fd5b61bcaf047d77aeea86 100644 --- a/test/test_favorites.py +++ b/test/test_favorites.py @@ -6,7 +6,7 @@ from podcast.util import ilen from . import xml_headers, read_testdata -@httprettified +@httprettified(allow_net_connect=False) def test_queue_favorites(): """ Test listing of podposts diff --git a/test/test_feedutils.py b/test/test_feedutils.py index ac4293d77985f5fb64454132cc71c94cb2098625..3def30917008e10b3c0ecf56f1c55db1c33a4438 100644 --- a/test/test_feedutils.py +++ b/test/test_feedutils.py @@ -4,7 +4,7 @@ from podcast.feedutils import fetch_feed, NewFeedUrlError from test import read_testdata, xml_headers -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_new_feed_url(): url_f = "https://fakefeed.com/" httpretty.HTTPretty.register_uri(httpretty.HTTPretty.GET, url_f, body=read_testdata('testdata/new-feed-url-feed.rss'), diff --git a/test/test_podcast.py b/test/test_podcast.py index fe5dd70d2c931bbcca02ee62ca639f0fe65f3986..9531750172ca112727e140252a030a86dd41e011 100644 --- a/test/test_podcast.py +++ b/test/test_podcast.py @@ -95,11 +95,12 @@ def test_feed_entry(): assert data != None assert type(data) == dict assert 'id' in data.keys() - assert 'logo_url' in data.keys() + assert 'podcast_logo' in data.keys() + assert 'episode_logo' in data.keys() assert 'description' in data.keys() assert type(data['id']) == POST_ID_TYPE - assert len(data['logo_url']) > 0 or data['logo_url'] == "" - assert type(data['logo_url']) == str + assert len(data['podcast_logo']) > 0 or data['podcast_logo'] == "" + assert type(data['podcast_logo']) == str assert podcast.get_entry(data['id']) != None assert data == podcast.get_entry(data['id']).get_data() @@ -137,7 +138,7 @@ def test_import_nohref(): Podcast.create_from_url(url_99) -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_preview(): invoked = 0 @@ -159,7 +160,7 @@ def test_preview(): assert invoked == 1 -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_feed_no_changes(): global invoked invoked = 0 @@ -248,7 +249,7 @@ def refreshable_podcast_fixture(request) -> Tuple[Podcast, List[Podpost]]: -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_pagination_stops(): url_f1 = "https://fakefeed.com/" url_f2 = "https://fakefeed.com/page2" @@ -276,7 +277,7 @@ def test_pagination_stops(): assert 1 == len(episodes) -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_pagination(): url_f = "https://fakefeed.com/page" HTTPretty.register_uri(HTTPretty.GET, url_f, body=read_testdata('testdata/fakefeed2.xml'), @@ -289,7 +290,7 @@ def test_pagination(): assert 2 == podcast.count_episodes() -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_new_feed_url(): url_f = "https://fakefeed.com/" HTTPretty.register_uri(HTTPretty.GET, url_f, body=read_testdata('testdata/new-feed-url-feed.rss'), diff --git a/test/test_podpost.py b/test/test_podpost.py index 3b8f89fda42cd0b2e6356e9d12baa8a67d95f465..a7968284047062cabeaf1e34ac9c3fb80087c49b 100644 --- a/test/test_podpost.py +++ b/test/test_podpost.py @@ -17,7 +17,7 @@ from podcast.podpost import Podpost, PodpostFactory logger = logging.getLogger(__name__) -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_podpost_save(): """ Test podpost saving @@ -39,7 +39,7 @@ def test_podpost_save(): assert n.get_data()["listened"] -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_podpost_save2(): """ Test podpost saving diff --git a/test/test_queue.py b/test/test_queue.py index c1961109adf06484a7eee327398e8eebdca834cb..f69d529d1587a0d5e140118358f7d0bcb7c941d2 100644 --- a/test/test_queue.py +++ b/test/test_queue.py @@ -44,7 +44,7 @@ def test_queue_save(): -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_insert_top(): HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/', body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers) @@ -72,7 +72,7 @@ def test_insert_top(): q.save() -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_insert_next(): HTTPretty.register_uri(HTTPretty.GET, 'https://freakshow.fm/feed/opus/', body=read_testdata('testdata/freakshow.rss'), adding_headers=xml_headers) @@ -100,7 +100,7 @@ def test_insert_next(): q.save() assert QueueData.get_queue() == [e.id, e2.id] -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_get_podposts(): """ Test listing of podposts @@ -129,7 +129,7 @@ def test_get_podposts(): assert os.path.exists(refreshed_post.file_path) assert refreshed_post.percentage == 100 -@httpretty.activate +@httpretty.activate(allow_net_connect=False) def test_queue_favorites(): """ Test listing of podposts diff --git a/translations/harbour-podqast-de.ts b/translations/harbour-podqast-de.ts index d5c97f5014f57b0c7a91477099b04433a35fbc0e..c0e4996ee1edd893afbb559e9d8aaf538183cefa 100644 --- a/translations/harbour-podqast-de.ts +++ b/translations/harbour-podqast-de.ts @@ -509,32 +509,32 @@ Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Abspielgeschwindigkeit - + Sleep timer Schlummer-Uhr - + running... läuft... - + chapters Kapitel @@ -680,7 +680,7 @@ PostDescription - + Open in browser Im Browser öffnen @@ -688,17 +688,17 @@ PostListItem - + playing - + listened - + remaining @@ -729,12 +729,12 @@ Queue - + No posts - + Pull down to Discover new podcasts or get posts from Inbox or Library @@ -761,154 +761,184 @@ + When touching episode image... + Beim tippen auf ein Episodenbild... + + + + Show Podcast Episodes + Zeige andere Episoden des Podcasts + + + Move new post to Neuen Beitrag - - + + Inbox in den Eingang - - + + Archive in das Archiv - + Automatic post limit Höchstgrenze automatisierter Beiträge - + Refresh time Aktualisierungs-Intervall - + Audio playrate Abspielgeschwindigkeit - + off aus - + Time left to ignore for listened-mark - + Download/Streaming Laden/Stream - + Development Entwicklung - + Experimental features Experimentelle Funktionen - - + + Top of Playlist an den Anfang der Playlist + + + Play Episode + Spiele die Episode + - + Show Episode Info + Zeige die Episodenbeschreibung + + + + Add To Queue Top + An den Anfang der Playlist + + + + Add To Queue Bottom + An das Ende der Playlist + + + + Bottom of Playlist ans Ende der Playlist - + All - + Alle - + s - + Sleep timer Schlummer-Uhr - + min min - + External Audio Fremde Audiodateien - + Allow external audio Erlaube fremde Audiodateien - + Will take data from Hole Daten von - + Move external audio to Externe Audiodateien - + Download Playlist Posts Herunterladen von Playlist-Beiträgen - + Download on Mobile Bei Mobilfunk laden - + Keep Favorites downloaded Favoriten speichern - + Will save data in Speicher Daten in - + System System - + Audio viewable in system Audio im System sichbar - + Auto-play next episode in queue - + Force refresh of old episodes @@ -1021,50 +1051,50 @@ harbour-podqast - + New posts available Neue Beiträge verfügbar - + Click to view updates Klicken um Aktualisierungen anzusehen - + New Posts are available. Click to view. Neue Beiträge verfügbar. Klicke zum Betrachten. - - + + PodQast message PodQast-Nachricht - + New PodQast message Neue PodQast-Nachricht - + Podcasts imported Podcasts importiert - - + + Podcasts imported from OPML Podcasts vom OPML importiert - + Error Fehler - - + + Audio File not existing Audiodatei existiert nicht diff --git a/translations/harbour-podqast-es.ts b/translations/harbour-podqast-es.ts index 1837e03ced911de695c60db55dacc3a4aca244d7..c80c63371246b2841c182391cf9e4f9de12a87bc 100644 --- a/translations/harbour-podqast-es.ts +++ b/translations/harbour-podqast-es.ts @@ -509,32 +509,32 @@ Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Velocidad de reproducción - + Sleep timer - + running... - + chapters capítulos @@ -680,7 +680,7 @@ PostDescription - + Open in browser Abrir en el navegador @@ -688,17 +688,17 @@ PostListItem - + playing - + listened - + remaining @@ -729,12 +729,12 @@ Queue - + No posts - + Pull down to Discover new podcasts or get posts from Inbox or Library @@ -761,152 +761,182 @@ + When touching episode image... + + + + + Show Podcast Episodes + + + + Move new post to Mover nueva publicación a - - + + Inbox Entradas - - + + Archive Almacén - + Automatic post limit Límite de publicaciones automáticas - + Refresh time Tiempo de actualización - + Audio playrate Velocidad de reproducción - + off apagado - + Time left to ignore for listened-mark - + Download/Streaming Descarga/Transmisión - + Development Desarrollo - + Experimental features Funciones experimentales - - + + Top of Playlist Al principio de la lista + + + Play Episode + + - + Show Episode Info + + + + + Add To Queue Top + + + + + Add To Queue Bottom + + + + + Bottom of Playlist Al final de la lista - + All - + s - + Sleep timer - + min - + External Audio Audio externo - + Allow external audio Permitir audio externo - + Will take data from Cogerá datos de - + Move external audio to Mover audio externo a - + Download Playlist Posts Descargar publicaciones de la lista - + Download on Mobile Descargar en el móvil - + Keep Favorites downloaded Conservar favoritos descargados - + Will save data in Guardará los datos en - + System Sistema - + Audio viewable in system Audio visible en el sistema - + Auto-play next episode in queue - + Force refresh of old episodes @@ -1019,50 +1049,50 @@ harbour-podqast - + New posts available Nuevas publicaciones disponibles - + Click to view updates Haz clic para ver actualizaciones - + New Posts are available. Click to view. Hay nuevas publicaciones. Haz clic para verlas. - - + + PodQast message Mensaje de PodQast - + New PodQast message Nuevo mensaje de PodQast - + Podcasts imported podcasts importados - - + + Podcasts imported from OPML podcasts importados desde OPML - + Error Error - - + + Audio File not existing El archivo de audio no existe diff --git a/translations/harbour-podqast-fr.ts b/translations/harbour-podqast-fr.ts index 92810dca8664a37efb61148316c159e90e19c9d5..0f66804e061f2d9d11cde4366a085144f73d40a7 100644 --- a/translations/harbour-podqast-fr.ts +++ b/translations/harbour-podqast-fr.ts @@ -510,32 +510,32 @@ Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Vitesse de lecture - + Sleep timer Minuterie avant coupure - + running... activée... - + chapters chapitres @@ -681,7 +681,7 @@ PostDescription - + Open in browser Ouvrir dans le navigateur @@ -689,17 +689,17 @@ PostListItem - + playing - + listened - + remaining @@ -730,12 +730,12 @@ Queue - + No posts - + Pull down to Discover new podcasts or get posts from Inbox or Library @@ -762,154 +762,184 @@ + When touching episode image... + + + + + Show Podcast Episodes + + + + Move new post to Transférer les nouveaux articles - - + + Inbox Boîte aux lettres - - + + Archive Archives - + Automatic post limit Limite automatique du nombre d'articles - + Refresh time Temps de rafraîchissement - + Audio playrate Vitesse de lecture - + off Désactivé - + Time left to ignore for listened-mark - + Download/Streaming Téléchargement/Streaming - + Development Développement - + Experimental features Fonctionnalités expérimentales - - + + Top of Playlist En début de Playlist + + + Play Episode + + - + Show Episode Info + + + + + Add To Queue Top + + + + + Add To Queue Bottom + + + + + Bottom of Playlist En fin de la Playlist - + All - + s - + Sleep timer Minuterie avant coupure - + min - + External Audio Fichiers Audios Externes - + Allow external audio Autoriser les fichiers externes - + Will take data from Prendra les données depuis - + Move external audio to Transférer les fichiers externes - + Download Playlist Posts Télécharger les Playlists d'Articles - + Download on Mobile Télécharger hors Wifi - + Keep Favorites downloaded Conserver les favoris déjà téléchargés - + Will save data in Sauvegardera les données dans - + System Système - + Audio viewable in system Audio visible dans le système - + Auto-play next episode in queue - + Force refresh of old episodes @@ -1022,50 +1052,50 @@ harbour-podqast - + New posts available Nouveaux articles disponibles - + Click to view updates Cliquez pour voir les mises à jour - + New Posts are available. Click to view. De nouveaux articles sont diponibles. Cliquez pour voir. - - + + PodQast message Message PodQast - + New PodQast message Nouveau message PodQast - + Podcasts imported Podcasts importés - - + + Podcasts imported from OPML Podcasts importés depuis OPML - + Error Erreur - - + + Audio File not existing Le fichier audio n'existe pas diff --git a/translations/harbour-podqast-sv.ts b/translations/harbour-podqast-sv.ts index 36e2a4846231c4ce33f5cff1e4f22c13367073f4..7b652c33e05038fa4aa19f1e11b3862e88482813 100644 --- a/translations/harbour-podqast-sv.ts +++ b/translations/harbour-podqast-sv.ts @@ -509,32 +509,32 @@ Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Spelningsfrekvens - + Sleep timer Insomningsur - + running... körs... - + chapters avsnitt @@ -680,7 +680,7 @@ PostDescription - + Open in browser Öppna i webbläsaren @@ -688,17 +688,17 @@ PostListItem - + playing - + listened - + remaining @@ -729,12 +729,12 @@ Queue - + No posts - + Pull down to Discover new podcasts or get posts from Inbox or Library @@ -761,154 +761,184 @@ + When touching episode image... + + + + + Show Podcast Episodes + + + + Move new post to Flytta ny post till - - + + Inbox Inkorg - - + + Archive Arkiv - + Automatic post limit Automatisk postbegränsning - + Refresh time Uppdateringstid - + Audio playrate Spelningsfrekvens - + off av - + Time left to ignore for listened-mark - + Download/Streaming Nerladdning/Strömmning - + Development Utveckling - + Experimental features Experimentella funktioner - - + + Top of Playlist Först i spelningslistan + + + Play Episode + + - + Show Episode Info + + + + + Add To Queue Top + + + + + Add To Queue Bottom + + + + + Bottom of Playlist Sist i spelningslistan - + All - + s - + Sleep timer Insomningsur - + min min - + External Audio Externt ljud - + Allow external audio Tillåt externt ljud - + Will take data from Hämtar data från - + Move external audio to Flytta externt ljud till - + Download Playlist Posts Ladda ner spelningslistans poster - + Download on Mobile Ladda ner på mobilanslutning - + Keep Favorites downloaded Behåll favoriter nerladdade - + Will save data in Sparar data i - + System System - + Audio viewable in system Ljud som visas i systemet - + Auto-play next episode in queue - + Force refresh of old episodes @@ -1021,50 +1051,50 @@ harbour-podqast - + New posts available Nya poster tillgängliga - + Click to view updates Tryck för att visa uppdateringar - + New Posts are available. Click to view. Det finns nya poster. Tryck för att visa. - - + + PodQast message PodQast-meddelande - + New PodQast message Nytt PodQast-meddelande - + Podcasts imported poddar importerade - - + + Podcasts imported from OPML poddar importerade från OPML - + Error Fel - - + + Audio File not existing Ljudfilen finns inte diff --git a/translations/harbour-podqast-zh_CN.ts b/translations/harbour-podqast-zh_CN.ts index 0f9ece323eaceda3d97c28db263c55593e93c89c..5b494c2195f5dc1c0f4c7ca8ef88655d9c294d8b 100644 --- a/translations/harbour-podqast-zh_CN.ts +++ b/translations/harbour-podqast-zh_CN.ts @@ -509,32 +509,32 @@ Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate 音频播放率 - + Sleep timer 睡眠计时器 - + running... 运行中…… - + chapters 章节 @@ -680,7 +680,7 @@ PostDescription - + Open in browser 用浏览器打开 @@ -688,17 +688,17 @@ PostListItem - + playing - + listened - + remaining @@ -729,12 +729,12 @@ Queue - + No posts - + Pull down to Discover new podcasts or get posts from Inbox or Library @@ -761,152 +761,182 @@ + When touching episode image... + + + + + Show Podcast Episodes + + + + Move new post to 移动新内容到 - - + + Inbox 收件箱 - - + + Archive 存档 - + Automatic post limit 自动限制内容 - + Refresh time 刷新时间 - + Audio playrate 音频播放比率 - + off 关闭 - + Time left to ignore for listened-mark - + Download/Streaming 下载流媒体 - + Development 开发 - + Experimental features 实验功能 - - + + Top of Playlist 播放列表顶部 + + + Play Episode + + - + Show Episode Info + + + + + Add To Queue Top + + + + + Add To Queue Bottom + + + + + Bottom of Playlist 播放列表底部 - + All - + s - + Sleep timer 睡眠计时器 - + min 分钟 - + External Audio 外部音频 - + Allow external audio 允许外部音频 - + Will take data from 将会获取数据自 - + Move external audio to 移动外部音频到 - + Download Playlist Posts 下载播放列表内容 - + Download on Mobile 通过移动网络下载 - + Keep Favorites downloaded 保留下载的收藏 - + Will save data in 将会保存数据到 - + System 系统 - + Audio viewable in system 音频在系统可见 - + Auto-play next episode in queue - + Force refresh of old episodes @@ -1019,50 +1049,50 @@ harbour-podqast - + New posts available 有可获取的新内容 - + Click to view updates 点击以查看更新 - + New Posts are available. Click to view. 有可用新内容,点击以查看。 - - + + PodQast message PodQast 消息 - + New PodQast message 新 PodQast 消息 - + Podcasts imported 播客已导入 - - + + Podcasts imported from OPML 已从 OPML 导入播客 - + Error 错误 - - + + Audio File not existing 音频文件不存在 diff --git a/translations/harbour-podqast.ts b/translations/harbour-podqast.ts index 4a59c7a82a3215b08b284aecec528875c2d3aa54..90be4447db064bc0084b71a483bd61bfdaee53ab 100644 --- a/translations/harbour-podqast.ts +++ b/translations/harbour-podqast.ts @@ -509,32 +509,32 @@ Player - + Auto-play next episode in queue - + Stop after each episode - + Audio playrate Audio playrate - + Sleep timer Sleep timer - + running... running... - + chapters chapters @@ -680,7 +680,7 @@ PostDescription - + Open in browser Open in browser @@ -688,17 +688,17 @@ PostListItem - + playing - + listened - + remaining @@ -729,12 +729,12 @@ Queue - + No posts - + Pull down to Discover new podcasts or get posts from Inbox or Library @@ -761,154 +761,184 @@ + When touching episode image... + + + + + Show Podcast Episodes + + + + Move new post to Move new post to - - + + Inbox Inbox - - + + Archive Archive - + Automatic post limit Automatic post limit - + Refresh time Refresh time - + Audio playrate Audio playrate - + off off - + Time left to ignore for listened-mark - + Download/Streaming Download/Streaming - + Development Development - + Experimental features Experimental features - - + + Top of Playlist Top of Playlist + + + Play Episode + + - + Show Episode Info + + + + + Add To Queue Top + + + + + Add To Queue Bottom + + + + + Bottom of Playlist Bottom of Playlist - + All - + s - + Sleep timer Sleep timer - + min min - + External Audio External Audio - + Allow external audio Allow external audio - + Will take data from Will take data from - + Move external audio to Move external audio to - + Download Playlist Posts Download Playlist Posts - + Download on Mobile Download on Mobile - + Keep Favorites downloaded Keep Favorites downloaded - + Will save data in Will save data in - + System System - + Audio viewable in system Audio viewable in system - + Auto-play next episode in queue - + Force refresh of old episodes @@ -1021,50 +1051,50 @@ harbour-podqast - + New posts available New posts available - + Click to view updates Click to view updates - + New Posts are available. Click to view. New Posts are available. Click to view. - - + + PodQast message PodQast message - + New PodQast message New PodQast message - + Podcasts imported Podcasts imported - - + + Podcasts imported from OPML Podcasts imported from OPML - + Error Error - - + + Audio File not existing Audio File not existing