registry.custom_downloader + registry.download_url
for extensions to be able to register custom downloaders, not only resolving episode to a real download url. refactor {escapist,youtube,vimeo}.get_real_download_url to use registry.
This commit is contained in:
parent
c847988531
commit
a05bd85d76
|
@ -3,6 +3,7 @@
|
|||
# Requirements: gPodder 3.x (or "tres" branch newer than 2011-06-08)
|
||||
# (c) 2011-06-08 Thomas Perl <thp.io/about>
|
||||
# Released under the same license terms as gPodder itself.
|
||||
import functools
|
||||
import logging
|
||||
import subprocess
|
||||
|
||||
|
@ -42,8 +43,8 @@ class Player(object):
|
|||
def open_files(self, filenames):
|
||||
raise NotImplemented('Must be implemented by subclass')
|
||||
|
||||
def enqueue_episodes(self, episodes):
|
||||
filenames = [episode.get_playback_url() for episode in episodes]
|
||||
def enqueue_episodes(self, episodes, config=None):
|
||||
filenames = [episode.get_playback_url(config=config) for episode in episodes]
|
||||
|
||||
self.open_files(filenames)
|
||||
|
||||
|
@ -104,8 +105,8 @@ class MPRISResumer(FreeDesktopPlayer):
|
|||
return False
|
||||
return util.find_command(self.command[0]) is not None
|
||||
|
||||
def enqueue_episodes(self, episodes):
|
||||
self.do_enqueue(episodes[0].get_playback_url(),
|
||||
def enqueue_episodes(self, episodes, config=None):
|
||||
self.do_enqueue(episodes[0].get_playback_url(config=config),
|
||||
episodes[0].current_position)
|
||||
|
||||
for episode in episodes:
|
||||
|
@ -259,6 +260,7 @@ class gPodderExtension:
|
|||
def __init__(self, container):
|
||||
self.container = container
|
||||
self.config = container.config
|
||||
self.gpodder_config = self.container.manager.core.config
|
||||
|
||||
# Only display media players that can be found at extension load time
|
||||
self.players = [player for player in PLAYERS if player.is_installed()]
|
||||
|
@ -273,13 +275,15 @@ class gPodderExtension:
|
|||
if not any(e.file_exists() for e in episodes):
|
||||
return None
|
||||
|
||||
ret = [(p.title, p.enqueue_episodes) for p in self.players]
|
||||
ret = [(p.title, functools.partial(p.enqueue_episodes, config=self.gpodder_config))
|
||||
for p in self.players]
|
||||
|
||||
# needs dbus, doesn't handle more than 1 episode
|
||||
# and no point in using DBus when episode is not played.
|
||||
if not hasattr(gpodder.dbus_session_bus, 'fake') and \
|
||||
len(episodes) == 1 and episodes[0].current_position > 0:
|
||||
ret.extend([(p.title, p.enqueue_episodes) for p in self.resumers])
|
||||
ret.extend([(p.title, functools.partial(p.enqueue_episodes, config=self.gpodder_config))
|
||||
for p in self.resumers])
|
||||
|
||||
return ret
|
||||
|
||||
|
|
|
@ -41,13 +41,43 @@ import urllib.request
|
|||
from email.header import decode_header
|
||||
|
||||
import gpodder
|
||||
from gpodder import escapist_videos, util, vimeo, youtube
|
||||
from gpodder import registry, util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_ = gpodder.gettext
|
||||
|
||||
|
||||
class CustomDownload:
|
||||
""" abstract class for custom downloads. DownloadTask call retrieve_resume() on it """
|
||||
|
||||
def retrieve_resume(self, unused_tempname, reporthook):
|
||||
"""
|
||||
:param str tempname: temporary filename for the download
|
||||
:param func(number, number, number) reporthook: callback for download progress (count, blockSize, totalSize)
|
||||
:return dict(str, str), str: (headers, real_url)
|
||||
"""
|
||||
return {}, None
|
||||
|
||||
|
||||
class CustomDownloader:
|
||||
"""
|
||||
abstract class for custom downloaders.
|
||||
|
||||
DownloadTask calls custom_downloader to get a CustomDownload
|
||||
"""
|
||||
|
||||
def custom_downloader(self, config, episode):
|
||||
"""
|
||||
if this custom downloader has a custom download method (e.g. youtube-dl),
|
||||
return a CustomDownload. Else return None
|
||||
:param config: gpodder config (e.g. to get preferred video format)
|
||||
:param model.PodcastEpisode episode: episode to download
|
||||
:return CustomDownload: object used to download the episode
|
||||
"""
|
||||
return None
|
||||
|
||||
|
||||
def get_header_param(headers, param, header_name):
|
||||
"""Extract a HTTP header parameter from a dict
|
||||
|
||||
|
@ -746,53 +776,59 @@ class DownloadTask(object):
|
|||
self.episode.download_task = self
|
||||
|
||||
try:
|
||||
# Resolve URL and start downloading the episode
|
||||
fmt_ids = youtube.get_fmt_ids(self._config.youtube)
|
||||
|
||||
custom_downloader = registry.custom_downloader.resolve(self._config, None, self.episode)
|
||||
|
||||
url = self.__episode.url
|
||||
url = youtube.get_real_download_url(self.__episode.url, fmt_ids)
|
||||
url = vimeo.get_real_download_url(url, self._config.vimeo.fileformat)
|
||||
url = escapist_videos.get_real_download_url(url)
|
||||
if custom_downloader:
|
||||
logger.info('Downloading %s with %s', url, custom_downloader)
|
||||
headers, real_url = custom_downloader.retrieve_resume(
|
||||
self.tempname, reporthook=self.status_updated)
|
||||
else:
|
||||
# Resolve URL and start downloading the episode
|
||||
res = registry.download_url.resolve(self._config, None, self.episode)
|
||||
if res:
|
||||
url = res
|
||||
if url == self.__episode.url:
|
||||
# don't modify custom urls (#635 - vimeo breaks if * is unescaped)
|
||||
url = url.strip()
|
||||
url = util.iri_to_url(url)
|
||||
|
||||
if url == self.__episode.url:
|
||||
# don't modify custom urls (#635 - vimeo breaks if * is unescaped)
|
||||
url = url.strip()
|
||||
url = util.iri_to_url(url)
|
||||
logger.info("Downloading %s", url)
|
||||
downloader = DownloadURLOpener(self.__episode.channel)
|
||||
|
||||
logger.info("Downloading %s", url)
|
||||
downloader = DownloadURLOpener(self.__episode.channel)
|
||||
# HTTP Status codes for which we retry the download
|
||||
retry_codes = (408, 418, 504, 598, 599)
|
||||
max_retries = max(0, self._config.auto.retries)
|
||||
|
||||
# HTTP Status codes for which we retry the download
|
||||
retry_codes = (408, 418, 504, 598, 599)
|
||||
max_retries = max(0, self._config.auto.retries)
|
||||
# Retry the download on timeout (bug 1013)
|
||||
for retry in range(max_retries + 1):
|
||||
if retry > 0:
|
||||
logger.info('Retrying download of %s (%d)', url, retry)
|
||||
time.sleep(1)
|
||||
|
||||
# Retry the download on timeout (bug 1013)
|
||||
for retry in range(max_retries + 1):
|
||||
if retry > 0:
|
||||
logger.info('Retrying download of %s (%d)', url, retry)
|
||||
time.sleep(1)
|
||||
|
||||
try:
|
||||
headers, real_url = downloader.retrieve_resume(url,
|
||||
self.tempname, reporthook=self.status_updated)
|
||||
# If we arrive here, the download was successful
|
||||
break
|
||||
except urllib.error.ContentTooShortError as ctse:
|
||||
if retry < max_retries:
|
||||
logger.info('Content too short: %s - will retry.',
|
||||
url)
|
||||
continue
|
||||
raise
|
||||
except socket.timeout as tmout:
|
||||
if retry < max_retries:
|
||||
logger.info('Socket timeout: %s - will retry.', url)
|
||||
continue
|
||||
raise
|
||||
except gPodderDownloadHTTPError as http:
|
||||
if retry < max_retries and http.error_code in retry_codes:
|
||||
logger.info('HTTP error %d: %s - will retry.',
|
||||
http.error_code, url)
|
||||
continue
|
||||
raise
|
||||
try:
|
||||
headers, real_url = downloader.retrieve_resume(url,
|
||||
self.tempname, reporthook=self.status_updated)
|
||||
# If we arrive here, the download was successful
|
||||
break
|
||||
except urllib.error.ContentTooShortError as ctse:
|
||||
if retry < max_retries:
|
||||
logger.info('Content too short: %s - will retry.',
|
||||
url)
|
||||
continue
|
||||
raise
|
||||
except socket.timeout as tmout:
|
||||
if retry < max_retries:
|
||||
logger.info('Socket timeout: %s - will retry.', url)
|
||||
continue
|
||||
raise
|
||||
except gPodderDownloadHTTPError as http:
|
||||
if retry < max_retries and http.error_code in retry_codes:
|
||||
logger.info('HTTP error %d: %s - will retry.',
|
||||
http.error_code, url)
|
||||
continue
|
||||
raise
|
||||
|
||||
new_mimetype = headers.get('content-type', self.__episode.mime_type)
|
||||
old_mimetype = self.__episode.mime_type
|
||||
|
|
|
@ -31,7 +31,7 @@ import urllib.parse
|
|||
import urllib.request
|
||||
|
||||
import gpodder
|
||||
from gpodder import util
|
||||
from gpodder import registry, util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -51,6 +51,12 @@ DATA_COVERART_RE = re.compile(r'<url>(http:.+\.jpg)</url>')
|
|||
class EscapistError(BaseException): pass
|
||||
|
||||
|
||||
@registry.download_url.register
|
||||
def escapist_real_download_url(unused_config, episode):
|
||||
res = get_real_download_url(episode.url)
|
||||
return None if res == episode.url else res
|
||||
|
||||
|
||||
def get_real_download_url(url):
|
||||
video_id = get_escapist_id(url)
|
||||
if video_id is None:
|
||||
|
|
|
@ -2011,11 +2011,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
episode.playback_mark()
|
||||
self.mygpo_client.on_playback([episode])
|
||||
|
||||
fmt_ids = youtube.get_fmt_ids(self.config.youtube)
|
||||
vimeo_fmt = self.config.vimeo.fileformat
|
||||
|
||||
allow_partial = (player != 'default')
|
||||
filename = episode.get_playback_url(fmt_ids, vimeo_fmt, allow_partial)
|
||||
filename = episode.get_playback_url(self.config, allow_partial)
|
||||
|
||||
# Determine the playback resume position - if the file
|
||||
# was played 100%, we simply start from the beginning
|
||||
|
|
|
@ -37,8 +37,8 @@ import time
|
|||
|
||||
import gpodder
|
||||
import podcastparser
|
||||
from gpodder import (coverart, escapist_videos, feedcore, schema, util, vimeo,
|
||||
youtube)
|
||||
from gpodder import (coverart, escapist_videos, feedcore, registry, schema,
|
||||
util, vimeo, youtube)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -494,7 +494,7 @@ class PodcastEpisode(PodcastModelObject):
|
|||
|
||||
self.set_state(gpodder.STATE_DELETED)
|
||||
|
||||
def get_playback_url(self, fmt_ids=None, vimeo_fmt=None, allow_partial=False):
|
||||
def get_playback_url(self, config=None, allow_partial=False):
|
||||
"""Local (or remote) playback/streaming filename/URL
|
||||
|
||||
Returns either the local filename or a streaming URL that
|
||||
|
@ -510,11 +510,8 @@ class PodcastEpisode(PodcastModelObject):
|
|||
return url + '.partial'
|
||||
|
||||
if url is None or not os.path.exists(url):
|
||||
url = self.url
|
||||
url = youtube.get_real_download_url(url, fmt_ids)
|
||||
url = vimeo.get_real_download_url(url, vimeo_fmt)
|
||||
url = escapist_videos.get_real_download_url(url)
|
||||
|
||||
# FIXME: may custom downloaders provide the real url ?
|
||||
url = registry.download_url.resolve(config, self.url, self)
|
||||
return url
|
||||
|
||||
def find_unique_file_name(self, filename, extension):
|
||||
|
|
|
@ -28,7 +28,7 @@ import logging
|
|||
import re
|
||||
|
||||
import gpodder
|
||||
from gpodder import util
|
||||
from gpodder import registry, util
|
||||
|
||||
_ = gpodder.gettext
|
||||
|
||||
|
@ -49,6 +49,13 @@ FORMATS = tuple((x, x) for x in FILEFORMAT_RANKING)
|
|||
class VimeoError(BaseException): pass
|
||||
|
||||
|
||||
@registry.download_url.register
|
||||
def vimeo_real_download_url(config, episode):
|
||||
fmt = config.vimeo.fileformat if config else None
|
||||
res = get_real_download_url(episode.url, preferred_fileformat=fmt)
|
||||
return None if res == episode.url else res
|
||||
|
||||
|
||||
def get_real_download_url(url, preferred_fileformat=None):
|
||||
video_id = get_vimeo_id(url)
|
||||
|
||||
|
|
|
@ -28,7 +28,7 @@ import xml.etree.ElementTree
|
|||
from html.parser import HTMLParser
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
from gpodder import util
|
||||
from gpodder import registry, util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
@ -109,6 +109,13 @@ def get_fmt_ids(youtube_config):
|
|||
return fmt_ids
|
||||
|
||||
|
||||
@registry.download_url.register
|
||||
def youtube_real_download_url(config, episode):
|
||||
fmt_ids = get_fmt_ids(config.youtube) if config else None
|
||||
res = get_real_download_url(episode.url, fmt_ids)
|
||||
return None if res == episode.url else res
|
||||
|
||||
|
||||
def get_real_download_url(url, preferred_fmt_ids=None):
|
||||
if not preferred_fmt_ids:
|
||||
preferred_fmt_ids, _, _ = formats_dict[22] # MP4 720p
|
||||
|
|
Loading…
Reference in New Issue