Merge commit 'cd35e29fa1d627d206b556421d92907a64ed028c' into dev-adaptive

This commit is contained in:
Teemu Ikonen 2022-03-24 22:44:10 +02:00
commit d3fc65377f
56 changed files with 8526 additions and 8820 deletions

View File

@ -3,10 +3,10 @@ version: 2
jobs:
release-from-macos:
macos:
xcode: "11.4.1"
xcode: "13.2.1"
shell: /bin/bash --login -o pipefail
environment:
- BUNDLE_TAG: 21.4.27
- BUNDLE_TAG: 22.3.7
steps:
- checkout
- run: >

1
.gitignore vendored
View File

@ -12,3 +12,4 @@ share/applications/gpodder-url-handler.desktop
share/applications/gpodder.desktop
share/dbus-1/services/org.gpodder.service
share/locale/
venv/*

View File

@ -56,7 +56,7 @@ PyPI. With this, you get a self-contained gPodder CLI codebase.
- Clickable links in GTK UI show notes: html5lib
- HTML show notes: WebKit2 gobject bindings
(webkit2gtk, webkitgtk4 or gir1.2-webkit2-4.0 packages).
- Better Youtube support (> 15 entries in feeds, download audio-only): youtube_dl
- Better Youtube support (> 15 entries in feeds, download audio-only): youtube_dl or yt-dlp
### Build Dependencies

10
bin/gpo
View File

@ -955,6 +955,16 @@ class gPodderCli(object):
task.status = sync.SyncTask.DOWNLOADING
task.add_progress_callback(progress_updated)
task.run()
if task.notify_as_finished():
if self._config.device_sync.after_sync.mark_episodes_played:
logger.info('Marking as played on transfer: %s', task.episode.url)
task.episode.mark(is_played=True)
if self._config.device_sync.after_sync.delete_episodes:
logger.info('Removing episode after transfer: %s', task.episode.url)
task.episode.delete_from_disk()
task.recycle()
done_lock = threading.Lock()

484
po/ca.po

File diff suppressed because it is too large Load Diff

490
po/cs.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

488
po/da.po

File diff suppressed because it is too large Load Diff

497
po/de.po

File diff suppressed because it is too large Load Diff

488
po/el.po

File diff suppressed because it is too large Load Diff

494
po/es.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

488
po/eu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

488
po/fi.po

File diff suppressed because it is too large Load Diff

502
po/fr.po

File diff suppressed because it is too large Load Diff

488
po/gl.po

File diff suppressed because it is too large Load Diff

490
po/he.po

File diff suppressed because it is too large Load Diff

488
po/hu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

497
po/it.po

File diff suppressed because it is too large Load Diff

488
po/kk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

502
po/nb.po

File diff suppressed because it is too large Load Diff

674
po/nl.po

File diff suppressed because it is too large Load Diff

669
po/nn.po

File diff suppressed because it is too large Load Diff

497
po/pl.po

File diff suppressed because it is too large Load Diff

488
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

488
po/ro.po

File diff suppressed because it is too large Load Diff

497
po/ru.po

File diff suppressed because it is too large Load Diff

538
po/sk.po

File diff suppressed because it is too large Load Diff

488
po/sv.po

File diff suppressed because it is too large Load Diff

502
po/tr.po

File diff suppressed because it is too large Load Diff

488
po/uk.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -10,7 +10,10 @@ import re
import sys
import time
import youtube_dl
try:
import yt_dlp as youtube_dl
except:
import youtube_dl
from youtube_dl.utils import DownloadError, ExtractorError, sanitize_url
import gpodder
@ -25,13 +28,13 @@ logger = logging.getLogger(__name__)
__title__ = 'Youtube-dl'
__description__ = _('Manage Youtube subscriptions using youtube-dl (pip install youtube_dl)')
__description__ = _('Manage Youtube subscriptions using youtube-dl (pip install youtube_dl) or yt-dlp (pip install yt-dlp)')
__only_for__ = 'gtk, cli'
__authors__ = 'Eric Le Lay <elelay.fr:contact>'
__doc__ = 'https://gpodder.github.io/docs/extensions/youtubedl.html'
want_ytdl_version = '2021.02.04'
want_ytdl_version_msg = _('Your version of youtube-dl %(have_version)s has known issues, please upgrade to %(want_version)s or newer.')
want_ytdl_version_msg = _('Your version of youtube-dl/yt-dlp %(have_version)s has known issues, please upgrade to %(want_version)s or newer.')
DefaultConfig = {
# youtube-dl downloads and parses each video page to get informations about it, which is very slow.
@ -99,18 +102,16 @@ class YoutubeCustomDownload(download.CustomDownload):
# See #673 when merging multiple formats, the extension is appended to the tempname
# by YoutubeDL resulting in empty .partial file + .partial.mp4 exists
# and #796 .mkv is chosen by ytdl sometimes
tempstat = os.stat(tempname)
if not tempstat.st_size:
for try_ext in (dot_ext, ".mp4", ".m4a", ".webm", ".mkv"):
tempname_with_ext = tempname + try_ext
if os.path.isfile(tempname_with_ext):
logger.debug('Youtubedl downloaded to "%s" instead of "%s", moving',
os.path.basename(tempname_with_ext),
os.path.basename(tempname))
os.remove(tempname)
os.rename(tempname_with_ext, tempname)
dot_ext = try_ext
break
for try_ext in (dot_ext, ".mp4", ".m4a", ".webm", ".mkv"):
tempname_with_ext = tempname + try_ext
if os.path.isfile(tempname_with_ext):
logger.debug('Youtubedl downloaded to "%s" instead of "%s", moving',
os.path.basename(tempname_with_ext),
os.path.basename(tempname))
os.remove(tempname)
os.rename(tempname_with_ext, tempname)
dot_ext = try_ext
break
ext_filetype = mimetype_from_extension(dot_ext)
if ext_filetype:
# Youtube weba formats have a webm extension and get a video/webm mime-type
@ -262,6 +263,7 @@ class gPodderYoutubeDL(download.CustomDownloader):
self._ydl_opts = {
'cachedir': cachedir,
'no_color': True, # prevent escape codes in desktop notifications on errors
'noprogress': True, # prevent progress bar from appearing in console
}
if gpodder.verbose:
self._ydl_opts['verbose'] = True
@ -406,7 +408,11 @@ class gPodderYoutubeDL(download.CustomDownloader):
self.regex_cache.insert(0, r)
return True
with youtube_dl.YoutubeDL(self._ydl_opts) as ydl:
for ie in ydl._ies:
# youtube-dl returns a list, yt-dlp returns a dict
ies = ydl._ies
if type(ydl._ies) == dict:
ies = ydl._ies.values()
for ie in ies:
if ie.suitable(url) and ie.ie_key() not in self.ie_blacklist:
self.regex_cache.insert(0, ie._VALID_URL_RE)
return True

View File

@ -27,13 +27,14 @@
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label" translatable="yes">Cancel</property>
<property name="label" translatable="yes">_Cancel</property>
<property name="use-action-appearance">False</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_btnCancel_clicked" swapped="no"/>
</object>
<packing>
@ -44,7 +45,7 @@
</child>
<child>
<object class="GtkButton" id="btnOK">
<property name="label" translatable="yes">OK</property>
<property name="label" translatable="yes">_OK</property>
<property name="use-action-appearance">False</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
@ -52,6 +53,7 @@
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_btnOK_clicked" swapped="no"/>
</object>
<packing>
@ -208,10 +210,11 @@
</child>
<child>
<object class="GtkButton" id="title_save_button">
<property name="label" translatable="yes">Save</property>
<property name="label" translatable="yes">_Save</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
<property name="valign">center</property>
<signal name="clicked" handler="on_title_save_button_clicked" swapped="no"/>
</object>

View File

@ -15,7 +15,7 @@
<property name="window-position">center-on-parent</property>
<property name="type-hint">dialog</property>
<property name="action">save</property>
<property name="do-overwrite-confirmation">True</property>
<property name="do-overwrite-confirmation">False</property>
<property name="extra-widget">allsamefolder</property>
<property name="preview-widget-active">False</property>
<property name="use-preview-label">False</property>
@ -29,36 +29,10 @@
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label" translatable="yes">_Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_btnCancel_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
<placeholder/>
</child>
<child>
<object class="GtkButton" id="btnOK">
<property name="label" translatable="yes">_Save</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">True</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_btnOK_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
<placeholder/>
</child>
</object>
<packing>
@ -69,9 +43,5 @@
</child>
</object>
</child>
<action-widgets>
<action-widget response="-6">btnCancel</action-widget>
<action-widget response="-3">btnOK</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -172,7 +172,7 @@
<property name="layout-style">start</property>
<child>
<object class="GtkButton" id="btnSelectAll">
<property name="label" translatable="yes">Select All</property>
<property name="label" translatable="yes">Select _all</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
@ -187,7 +187,7 @@
</child>
<child>
<object class="GtkButton" id="btnSelectNone">
<property name="label" translatable="yes">Select None</property>
<property name="label" translatable="yes">Select _none</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
@ -215,12 +215,13 @@
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="btnCancel">
<property name="label" translatable="yes">Cancel</property>
<property name="label" translatable="yes">_Cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="has-default">True</property>
<property name="receives-default">False</property>
<property name="use-underline">True</property>
<signal name="clicked" handler="on_btnCancel_clicked" swapped="no"/>
</object>
<packing>

View File

@ -40,6 +40,8 @@ def clean_up_downloads(delete_partial=False):
if delete_partial:
temporary_files += glob.glob('%s/*/*.partial' % gpodder.downloads)
# YoutubeDL creates .partial.* files for adaptive formats
temporary_files += glob.glob('%s/*/*.partial.*' % gpodder.downloads)
for tempfile in temporary_files:
util.delete_file(tempfile)
@ -53,7 +55,7 @@ def find_partial_downloads(channels, start_progress_callback, progress_callback,
progress_callback - A callback(title, progress) when an episode was found
finish_progress_callback - A callback(resumable_episodes) when finished
"""
# Look for partial file downloads
# Look for partial file downloads, ignoring .partial.* files created by YoutubeDL
partial_files = glob.glob(os.path.join(gpodder.downloads, '*', '*.partial'))
count = len(partial_files)
resumable_episodes = []

View File

@ -27,6 +27,7 @@
import collections
import email
import glob
import logging
import mimetypes
import os
@ -631,9 +632,17 @@ class DownloadTask(object):
elif self.status == self.DOWNLOADING:
self.status = self.CANCELLING
def delete_partial_files(self):
temporary_files = [self.tempname]
# YoutubeDL creates .partial.* files for adaptive formats
temporary_files += glob.glob('%s.*' % self.tempname)
for tempfile in temporary_files:
util.delete_file(tempfile)
def removed_from_list(self):
if self.status != self.DONE:
util.delete_file(self.tempname)
self.delete_partial_files()
def __init__(self, episode, config, downloader=None):
assert episode.download_task is None
@ -684,6 +693,11 @@ class DownloadTask(object):
# Store a reference to this task in the episode
episode.download_task = self
def reuse(self):
if not os.path.exists(self.tempname):
# partial file was deleted when cancelled, recreate it
open(self.tempname, 'w').close()
def notify_as_finished(self):
if self.status == DownloadTask.DONE:
if self._notification_shown:
@ -791,7 +805,7 @@ class DownloadTask(object):
with self:
if self.status == DownloadTask.CANCELLING:
self.status = DownloadTask.CANCELLED
util.delete_file(self.tempname)
self.delete_partial_files()
self.progress = 0.0
self.speed = 0.0
self.recycle()
@ -892,7 +906,7 @@ class DownloadTask(object):
except DownloadCancelledException:
logger.info('Download has been cancelled/paused: %s', self)
if self.status == DownloadTask.CANCELLING:
util.delete_file(self.tempname)
self.delete_partial_files()
self.progress = 0.0
self.speed = 0.0
result = DownloadTask.CANCELLED

View File

@ -110,6 +110,12 @@ class FeedAutodiscovery(HTMLParser):
self._resolved_url = url
class FetcherFeedData:
def __init__(self, text, content):
self.text = text
self.content = content
class Fetcher(object):
# Supported types, see http://feedvalidator.org/docs/warning/EncodingMismatch.html
FEED_TYPES = ('application/rss+xml',
@ -152,7 +158,7 @@ class Fetcher(object):
else:
raise UnknownStatusCode(status)
def parse_feed(self, url, data_stream, headers, status, **kwargs):
def parse_feed(self, url, feed_data, data_stream, headers, status, **kwargs):
"""
kwargs are passed from Fetcher.fetch
:param str url: real url
@ -169,7 +175,7 @@ class Fetcher(object):
if url.startswith('file://'):
url = url[len('file://'):]
stream = open(url)
return self.parse_feed(url, stream, {}, UPDATED_FEED, **kwargs)
return self.parse_feed(url, None, stream, {}, UPDATED_FEED, **kwargs)
# remote feed
headers = {}
@ -210,4 +216,5 @@ class Fetcher(object):
# xml documents specify the encoding inline so better pass encoded body.
# Especially since requests will use ISO-8859-1 for content-type 'text/xml'
# if the server doesn't specify a charset.
return self.parse_feed(url, BytesIO(stream.content), stream.headers, UPDATED_FEED, **kwargs)
return self.parse_feed(url, FetcherFeedData(stream.text, stream.content), BytesIO(stream.content), stream.headers,
UPDATED_FEED, **kwargs)

View File

@ -196,7 +196,7 @@ class gPodderChannel(BuilderWidget):
# Title editing callbacks
def on_title_edit_button_clicked(self, button):
self.title_save_button_saves = True
self.title_save_button.set_label(_("Save"))
self.title_save_button.set_label(_("_Save"))
self.title_stack.set_visible_child(self.title_edit_box)
self.title_entry.set_text(self.title_label.get_text())
self.title_entry.grab_focus()
@ -204,7 +204,7 @@ class gPodderChannel(BuilderWidget):
def on_title_entry_changed(self, entry):
if len(entry.get_text()) > 0:
self.title_save_button_saves = True
self.title_save_button.set_label(_("Save"))
self.title_save_button.set_label(_("_Save"))
else:
self.title_save_button_saves = False
self.title_save_button.set_label(_("Cancel"))

View File

@ -279,11 +279,11 @@ class gPodderEpisodeSelector(BuilderWidget):
menu.append(item)
menu.append(Gtk.SeparatorMenuItem())
item = Gtk.MenuItem(_('Select all'))
item = Gtk.MenuItem(_('Select _all'))
item.connect('activate', self.on_btnCheckAll_clicked)
menu.append(item)
item = Gtk.MenuItem(_('Select none'))
item = Gtk.MenuItem(_('Select _none'))
item.connect('activate', self.on_btnCheckNone_clicked)
menu.append(item)

View File

@ -32,17 +32,12 @@ class gPodderExportToLocalFolder(BuilderWidget):
""" Export to Local Folder UI: file dialog + checkbox to save all to same folder """
def new(self):
self.gPodderExportToLocalFolder.set_transient_for(self.parent_widget)
self.RES_CANCEL = -6
self.RES_SAVE = -3
self.gPodderExportToLocalFolder.add_buttons("_Cancel", self.RES_CANCEL,
"_Save", self.RES_SAVE)
self._config.connect_gtk_window(self.gPodderExportToLocalFolder,
'export_to_local_folder', True)
self._ok = False
self.gPodderExportToLocalFolder.hide()
def on_btnOK_clicked(self, widget):
self._ok = True
self.gPodderExportToLocalFolder.hide()
def on_btnCancel_clicked(self, widget):
self.gPodderExportToLocalFolder.hide()
def save_as(self, initial_directory, filename, remaining=0):
"""
@ -64,9 +59,9 @@ class gPodderExportToLocalFolder(BuilderWidget):
initial_directory = os.path.expanduser('~')
self.gPodderExportToLocalFolder.set_current_folder(initial_directory)
self.gPodderExportToLocalFolder.set_current_name(filename)
self._ok = False
self.gPodderExportToLocalFolder.run()
notCancelled = self._ok
res = self.gPodderExportToLocalFolder.run()
self.gPodderExportToLocalFolder.hide()
notCancelled = (res == self.RES_SAVE)
allRemainingDefault = self.allsamefolder.get_active()
if notCancelled:
folder = self.gPodderExportToLocalFolder.get_current_folder()

View File

@ -708,6 +708,13 @@ class gPodder(BuilderWidget, dbus.service.Object):
message = str(e)
if not message:
message = e.__class__.__name__
if message == 'NotFound':
message = _(
'Could not find your device.\n'
'\n'
'Check login is a username (not an email)\n'
'and that the device name matches one in your account.'
)
self.show_message(html.escape(message),
_('Error while uploading'),
important=True)
@ -2077,9 +2084,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
if os.path.exists(copy_to):
logger.warn(copy_from)
logger.warn(copy_to)
title = _('File already exist')
title = _('File already exists')
d = {'filename': os.path.basename(copy_to)}
message = _('A file named "%(filename)s" already exist. Do you want to replace it?') % d
message = _('A file named "%(filename)s" already exists. Do you want to replace it?') % d
if not self.show_confirmation(message, title):
return
try:
@ -3323,6 +3330,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
for task in self.download_tasks_seen:
if episode.url == task.url:
task_exists = True
task.reuse()
if task.status not in (task.DOWNLOADING, task.QUEUED):
if downloader:
# replace existing task's download with forced one

View File

@ -110,13 +110,13 @@ class PodcastParserFeed(Feed):
def get_link(self):
vid = youtube.get_youtube_id(self.feed['url'])
if vid is not None:
self.feed['link'] = youtube.get_channel_id_url(self.feed['url'])
self.feed['link'] = youtube.get_channel_id_url(self.feed['url'], self.fetcher.feed_data)
return self.feed.get('link')
def get_description(self):
vid = youtube.get_youtube_id(self.feed['url'])
if vid is not None:
self.feed['description'] = youtube.get_channel_desc(self.feed['url'])
self.feed['description'] = youtube.get_channel_desc(self.feed['url'], self.fetcher.feed_data)
return self.feed.get('description')
def get_cover_url(self):
@ -215,7 +215,8 @@ class gPodderFetcher(feedcore.Fetcher):
url = vimeo.get_real_channel_url(url)
return url
def parse_feed(self, url, data_stream, headers, status, max_episodes=0, **kwargs):
def parse_feed(self, url, feed_data, data_stream, headers, status, max_episodes=0, **kwargs):
self.feed_data = feed_data
try:
feed = podcastparser.parse(url, data_stream)
feed['url'] = url

View File

@ -26,6 +26,7 @@ import logging
import re
import urllib
import xml.etree.ElementTree
from functools import lru_cache
from html.parser import HTMLParser
from urllib.parse import parse_qs
@ -366,6 +367,7 @@ def get_real_download_url(url, allow_partial, preferred_fmt_ids=None):
return url, duration
@lru_cache(1)
def get_youtube_id(url):
r = re.compile(r'http[s]?://(?:[a-z]+\.)?youtube\.com/watch\?v=([^&]*)', re.IGNORECASE).match(url)
if r is not None:
@ -427,16 +429,22 @@ def get_real_channel_url(url):
return for_each_feed_pattern(return_user_feed, url, url)
def get_channel_id_url(url):
@lru_cache(1)
def get_channel_id_url(url, feed_data=None):
if 'youtube.com' in url:
try:
req = util.urlopen(url)
if feed_data is None:
r = util.urlopen(url)
if not r.ok:
raise YouTubeError('Youtube "%s": %d %s' % (url, r.status_code, r.reason))
else:
r = feed_data
# video page may contain corrupt HTML/XML, search for tag to avoid exception
m = re.search(r'<meta itemprop="channelId" content="([^"]+)">', req.text)
m = re.search(r'<meta itemprop="channelId" content="([^"]+)">', r.text)
if m:
channel_id = m.group(1)
else:
raw_xml_data = io.BytesIO(req.content)
raw_xml_data = io.BytesIO(r.content)
xml_data = xml.etree.ElementTree.parse(raw_xml_data)
channel_id = xml_data.find("{http://www.youtube.com/xml/schemas/2015}channelId").text
channel_url = 'https://www.youtube.com/channel/{}'.format(channel_id)
@ -445,8 +453,10 @@ def get_channel_id_url(url):
except Exception:
logger.warning('Could not retrieve youtube channel id.', exc_info=True)
raise Exception('Could not retrieve youtube channel id.')
def get_cover(url):
def get_cover(url, feed_data=None):
if 'youtube.com' in url:
class YouTubeHTMLCoverParser(HTMLParser):
@ -471,8 +481,11 @@ def get_cover(url):
self.url.append(attribute_dict['src'])
try:
channel_url = get_channel_id_url(url)
html_data = util.response_text(util.urlopen(channel_url))
channel_url = get_channel_id_url(url, feed_data)
r = util.urlopen(channel_url)
if not r.ok:
raise YouTubeError('Youtube "%s": %d %s' % (url, r.status_code, r.reason))
html_data = util.response_text(r)
parser = YouTubeHTMLCoverParser()
parser.feed(html_data)
if parser.url:
@ -523,7 +536,7 @@ def get_gdpr_consent_url(html_data):
raise YouTubeError('No acceptable GDPR consent URL')
def get_channel_desc(url):
def get_channel_desc(url, feed_data=None):
if 'youtube.com' in url:
class YouTubeHTMLDesc(HTMLParser):
@ -542,8 +555,11 @@ def get_channel_desc(url):
self.description = attribute_dict['content']
try:
channel_url = get_channel_id_url(url)
html_data = util.response_text(util.urlopen(channel_url))
channel_url = get_channel_id_url(url, feed_data)
r = util.urlopen(channel_url)
if not r.ok:
raise YouTubeError('Youtube "%s": %d %s' % (url, r.status_code, r.reason))
html_data = util.response_text(r)
parser = YouTubeHTMLDesc()
parser.feed(html_data)
if parser.description:

View File

@ -25,10 +25,11 @@ from gpodder.feedcore import Fetcher, Result, NEW_LOCATION, NOT_MODIFIED, UPDATE
class MyFetcher(Fetcher):
def parse_feed(self, url, data_stream, headers, status, **kwargs):
def parse_feed(self, url, feed_data, data_stream, headers, status, **kwargs):
return Result(status, {
'parse_feed': {
'url': url,
'feed_data': feed_data,
'data_stream': data_stream,
'headers': headers,
'extra_args': dict(**kwargs),
@ -112,4 +113,4 @@ def test_temporary_error_retry(httpserver):
assert res.status == UPDATED_FEED
args = res.feed['parse_feed']
assert args['headers']['content-type'] == 'text/xml'
assert args['url'] == httpserver.url_for('/feed')
assert args['url'] == httpserver.url_for('/feed')

View File

@ -104,7 +104,7 @@ os.environ['GI_TYPELIB_PATH'] = join(bundle_lib, 'girepository-1.0')
# for forked python
os.environ['PYTHONHOME'] = bundle_res
# Set $PYTHON to point inside the bundle
PYVER = 'python3.8'
PYVER = 'python3.9'
sys.path.append(bundle_res)
print('System Path:\n', '\n'.join(sys.path))

View File

@ -69,8 +69,8 @@ cp -a "$checkout"/tools/mac-osx/make_cert_pem.py "$resources"/bin
$run_pip install setuptools wheel
$run_pip install podcastparser==0.6.7 mygpoclient==1.8 requests[socks]==2.25.1
# install extension dependencies; no explicit version for youtube_dl
$run_pip install mutagen==1.45.1 html5lib==1.1 youtube_dl
# install extension dependencies; no explicit version for yt-dlp
$run_pip install mutagen==1.45.1 html5lib==1.1 yt-dlp
cd "$checkout"
touch share/applications/gpodder{,-url-handler}.desktop
@ -86,7 +86,7 @@ for po in po/*; do
done
# copy fake dbus
cp -r tools/fake-dbus-module/dbus $resources/lib/python3.8/site-packages/dbus
cp -r tools/fake-dbus-module/dbus $resources/lib/python3.9/site-packages/dbus
# install
"$run_python" setup.py install --root="$resources/" --prefix=. --optimize=0

View File

@ -8,6 +8,6 @@ urllib3==1.26.5
html5lib==1.1
mutagen==1.45.1
dbus-python
youtube_dl
yt-dlp
# eyed3 is optional and pulls in a lot of dependencies, so disable by default
# eyed3

View File

@ -91,7 +91,7 @@ html5lib==1.1
webencodings==0.5.1
certifi==2021.5.30
mutagen==1.45.1
youtube_dl
yt-dlp
requests==2.25.1
urllib3==1.26.5
chardet==4.0.0