Merge tag '3.11.0' into dev-adaptive

gPodder 3.11.0 release
This commit is contained in:
Teemu Ikonen 2022-08-01 22:04:06 +03:00
commit 27aed1fbea
51 changed files with 8416 additions and 8861 deletions

View File

@ -6,7 +6,7 @@ jobs:
xcode: "13.2.1"
shell: /bin/bash --login -o pipefail
environment:
- BUNDLE_TAG: 22.3.7
- BUNDLE_TAG: 22.7.28
steps:
- checkout
- run: >

486
po/ca.po

File diff suppressed because it is too large Load Diff

480
po/cs.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

480
po/da.po

File diff suppressed because it is too large Load Diff

640
po/de.po

File diff suppressed because it is too large Load Diff

480
po/el.po

File diff suppressed because it is too large Load Diff

480
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

480
po/eu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

480
po/fi.po

File diff suppressed because it is too large Load Diff

484
po/fr.po

File diff suppressed because it is too large Load Diff

480
po/gl.po

File diff suppressed because it is too large Load Diff

480
po/he.po

File diff suppressed because it is too large Load Diff

480
po/hu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

737
po/it.po

File diff suppressed because it is too large Load Diff

480
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

484
po/nb.po

File diff suppressed because it is too large Load Diff

571
po/nl.po

File diff suppressed because it is too large Load Diff

573
po/nn.po

File diff suppressed because it is too large Load Diff

480
po/pl.po

File diff suppressed because it is too large Load Diff

480
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

480
po/ro.po

File diff suppressed because it is too large Load Diff

484
po/ru.po

File diff suppressed because it is too large Load Diff

597
po/sk.po

File diff suppressed because it is too large Load Diff

480
po/sv.po

File diff suppressed because it is too large Load Diff

488
po/tr.po

File diff suppressed because it is too large Load Diff

480
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

@ -1,5 +1,5 @@
# -*- coding: utf-8 -*-
# Manage Youtube subscriptions using youtube-dl (https://github.com/ytdl-org/youtube-dl)
# Manage YouTube subscriptions using youtube-dl (https://github.com/ytdl-org/youtube-dl)
# Requirements: youtube-dl module (pip install youtube_dl)
# (c) 2019-08-17 Eric Le Lay <elelay.fr:contact>
# Released under the same license terms as gPodder itself.
@ -28,8 +28,8 @@ _ = gpodder.gettext
logger = logging.getLogger(__name__)
__title__ = 'Youtube-dl'
__description__ = _('Manage Youtube subscriptions using youtube-dl (pip install youtube_dl) or yt-dlp (pip install yt-dlp)')
__title__ = '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'
@ -56,7 +56,7 @@ PLAYLIST_RE = re.compile(r'''https://www.youtube.com/feeds/videos.xml\?playlist_
def youtube_parsedate(s):
"""Parse a string into a unix timestamp
Only strings provided by Youtube-dl API are
Only strings provided by youtube-dl API are
parsed with this function (20170920).
"""
if s:
@ -101,12 +101,12 @@ class YoutubeCustomDownload(download.CustomDownload):
if 'ext' in res:
dot_ext = '.{}'.format(res['ext'])
# See #673 when merging multiple formats, the extension is appended to the tempname
# by YoutubeDL resulting in empty .partial file + .partial.mp4 exists
# by youtube-dl resulting in empty .partial file + .partial.mp4 exists
# and #796 .mkv is chosen by ytdl sometimes
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',
logger.debug('youtube-dl downloaded to "%s" instead of "%s", moving',
os.path.basename(tempname_with_ext),
os.path.basename(tempname))
os.remove(tempname)
@ -115,7 +115,7 @@ class YoutubeCustomDownload(download.CustomDownload):
break
ext_filetype = util.mimetype_from_extension(dot_ext)
if ext_filetype:
# Youtube weba formats have a webm extension and get a video/webm mime-type
# YouTube weba formats have a webm extension and get a video/webm mime-type
# but audio content has no width or height, so change it to audio/webm for correct icon and player
if ext_filetype.startswith('video/') and ('height' not in res or res['height'] is None):
ext_filetype = ext_filetype.replace('video/', 'audio/')
@ -175,7 +175,7 @@ class YoutubeFeed(model.Feed):
return filtered_entries
def get_title(self):
return '{} (Youtube)'.format(self._ie_result.get('title') or self._ie_result.get('id') or self._url)
return '{} (YouTube)'.format(self._ie_result.get('title') or self._ie_result.get('id') or self._url)
def get_link(self):
return self._ie_result.get('webpage_url')
@ -277,7 +277,7 @@ class gPodderYoutubeDL(download.CustomDownloader):
# when adding podcasts.
# See https://docs.python.org/3/library/sys.html#sys.__stderr__ Note
if not sys.stdout:
logger.debug('no stdout, setting YoutubeDL logger')
logger.debug('no stdout, setting youtube-dl logger')
self._ydl_opts['logger'] = logger
def add_format(self, gpodder_config, opts, fallback=None):
@ -395,7 +395,7 @@ class gPodderYoutubeDL(download.CustomDownloader):
if m:
url = 'https://www.youtube.com/playlist?list={}'.format(m.group(1))
if url:
logger.info('Youtube-dl Handling %s => %s', channel.url, url)
logger.info('youtube-dl handling %s => %s', channel.url, url)
return self.refresh(url, channel.url, max_episodes)
return None
@ -443,7 +443,7 @@ class gPodderExtension:
registry.feed_handler.register(self.ytdl.fetch_channel)
registry.custom_downloader.register(self.ytdl.custom_downloader)
logger.debug('Youtube-DL %s' % youtube_dl.version.__version__)
logger.debug('youtube-dl %s' % youtube_dl.version.__version__)
if youtube_dl.utils.version_tuple(youtube_dl.version.__version__) < youtube_dl.utils.version_tuple(want_ytdl_version):
logger.error(want_ytdl_version_msg
@ -468,11 +468,11 @@ class gPodderExtension:
if youtube_dl.utils.version_tuple(youtube_dl.version.__version__) < youtube_dl.utils.version_tuple(want_ytdl_version):
ui_object.notification(want_ytdl_version_msg %
{'have_version': youtube_dl.version.__version__, 'want_version': want_ytdl_version},
_('Old Youtube-DL'), important=True, widget=ui_object.main_window)
_('Old youtube-dl'), important=True, widget=ui_object.main_window)
def on_episodes_context_menu(self, episodes):
if not self.container.config.manage_downloads and any(e.can_download() for e in episodes):
return [(_("Download with Youtube-DL"), self.download_episodes)]
return [(_("Download with youtube-dl"), self.download_episodes)]
def download_episodes(self, episodes):
episodes = [e for e in episodes if e.can_download()]
@ -491,22 +491,22 @@ class gPodderExtension:
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10)
box.set_border_width(10)
checkbox = Gtk.CheckButton(_('Parse Youtube channel feeds with Youtube-DL to access more than 15 episodes'))
checkbox = Gtk.CheckButton(_('Parse YouTube channel feeds with youtube-dl to access more than 15 episodes'))
checkbox.set_active(self.container.config.manage_channel)
checkbox.connect('toggled', self.toggle_manage_channel)
box.pack_start(checkbox, False, False, 0)
box.pack_start(Gtk.HSeparator(), False, False, 0)
checkbox = Gtk.CheckButton(_('Download all supported episodes with Youtube-DL'))
checkbox = Gtk.CheckButton(_('Download all supported episodes with youtube-dl'))
checkbox.set_active(self.container.config.manage_downloads)
checkbox.connect('toggled', self.toggle_manage_downloads)
box.pack_start(checkbox, False, False, 0)
note = Gtk.Label(use_markup=True, wrap=True, label=_(
'Youtube-DL provides access to additional Youtube formats and DRM content.'
' Episodes from non-Youtube channels, that have Youtube-DL support, will <b>fail</b> to download unless you manually'
'youtube-dl provides access to additional YouTube formats and DRM content.'
' Episodes from non-YouTube channels, that have youtube-dl support, will <b>fail</b> to download unless you manually'
' <a href="https://gpodder.github.io/docs/youtube.html#formats">add custom formats</a> for each site.'
' <b>Download with Youtube-DL</b> appears in the episode menu when this option is disabled,'
' <b>Download with youtube-dl</b> appears in the episode menu when this option is disabled,'
' and can be used to manually download from supported sites.'))
note.connect('activate-link', lambda label, url: util.open_website(url))
note.set_property('xalign', 0.0)
@ -516,4 +516,4 @@ class gPodderExtension:
return box
def on_preferences(self):
return [(_('Youtube-DL'), self.show_preferences)]
return [(_('youtube-dl'), self.show_preferences)]

View File

@ -119,7 +119,6 @@
<property name="margin-start">16</property>
<property name="margin-end">16</property>
<property name="pixel-size">80</property>
<property name="icon-name">face-smile-big-symbolic</property>
<property name="icon_size">6</property>
</object>
</child>
@ -182,7 +181,6 @@
</object>
<packing>
<property name="name">page0</property>
<property name="title" translatable="yes">page0</property>
</packing>
</child>
<child>
@ -227,7 +225,6 @@
</object>
<packing>
<property name="name">page1</property>
<property name="title" translatable="yes">page1</property>
<property name="position">1</property>
</packing>
</child>

View File

@ -546,7 +546,7 @@
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="halign">center</property>
<property name="text" translatable="yes">200</property>
<property name="text">200</property>
<property name="adjustment">adjustment_episode_limit</property>
<property name="value">200</property>
</object>
@ -1127,9 +1127,9 @@
<object class="GtkBox" id="vbox_extensions">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="border-width">0</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<property name="spacing">0</property>
<child>
<object class="GtkTreeView" id="treeviewExtensions">
<property name="visible">True</property>

View File

@ -1,4 +1,4 @@
.TH GPO "1" "July 2021" "gpodder 3.10.21" "User Commands"
.TH GPO "1" "July 2022" "gpodder 3.11.0" "User Commands"
.SH NAME
gpo \- Text mode interface of gPodder
.SH SYNOPSIS

View File

@ -1,5 +1,5 @@
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.3.
.TH GPODDER "1" "July 2021" "gpodder 3.10.21" "User Commands"
.\" DO NOT MODIFY THIS FILE! It was generated by help2man 1.48.5.
.TH GPODDER "1" "July 2022" "gpodder 3.11.0" "User Commands"
.SH NAME
gpodder \- Media aggregator and podcast client
.SH SYNOPSIS
@ -17,12 +17,21 @@ show program's version number and exit
.TP
\fB\-h\fR, \fB\-\-help\fR
show this help message and exit
.IP
Subscriptions:
.TP
\fB\-s\fR URL, \fB\-\-subscribe\fR=\fI\,URL\/\fR
subscribe to the feed at URL
.IP
Logging:
.TP
\fB\-v\fR, \fB\-\-verbose\fR
print logging output on the console
.TP
\fB\-q\fR, \fB\-\-quiet\fR
reduce warnings on the console
.IP
Advanced:
.TP
\fB\-s\fR URL, \fB\-\-subscribe\fR=\fI\,URL\/\fR
subscribe to the feed at URL
\fB\-\-close\-after\-startup\fR
exit once started up (for profiling)

View File

@ -20,9 +20,9 @@
# This metadata block gets parsed by setup.py - use single quotes only
__tagline__ = 'Media aggregator and podcast client'
__author__ = 'Thomas Perl <thp@gpodder.org>'
__version__ = '3.10.21+1'
__date__ = '2021-08-04'
__copyright__ = '© 2005-2021 The gPodder Team'
__version__ = '3.11.0'
__date__ = '2022-07-30'
__copyright__ = '© 2005-2022 The gPodder Team'
__license__ = 'GNU General Public License, version 3 or later'
__url__ = 'http://gpodder.org/'

View File

@ -40,7 +40,7 @@ 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
# youtube-dl creates .partial.* files for adaptive formats
temporary_files += glob.glob('%s/*/*.partial.*' % gpodder.downloads)
for tempfile in temporary_files:
@ -55,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, ignoring .partial.* files created by YoutubeDL
# Look for partial file downloads, ignoring .partial.* files created by youtube-dl
partial_files = glob.glob(os.path.join(gpodder.downloads, '*', '*.partial'))
count = len(partial_files)
resumable_episodes = []

View File

@ -661,7 +661,7 @@ class DownloadTask(object):
def delete_partial_files(self):
temporary_files = [self.tempname]
# YoutubeDL creates .partial.* files for adaptive formats
# youtube-dl creates .partial.* files for adaptive formats
temporary_files += glob.glob('%s.*' % self.tempname)
for tempfile in temporary_files:

View File

@ -332,21 +332,35 @@ class gPodderPreferences(BuilderWidget):
gpodder.user_extensions.on_ui_object_available('preferences-gtk', self)
self.inject_extensions_preferences(init=True)
self.prefs_stack.foreach(self._wrap_checkbox_labels)
def _wrap_checkbox_labels(self, w, *args):
if w.get_name().startswith("no_label_wrap"):
return
elif isinstance(w, Gtk.CheckButton):
label = w.get_child()
label.set_line_wrap(True)
elif isinstance(w, Gtk.Container):
w.foreach(self._wrap_checkbox_labels)
def inject_extensions_preferences(self, init=False):
if not init:
# remove preferences buttons for all extensions
for child in self.prefs_stack.get_children():
if child.get_name().startswith("extension."):
self.prefs_stack.remove(child)
# add preferences buttons for all extensions
result = gpodder.user_extensions.on_preferences()
if result:
for label, callback in result:
self.prefs_stack.add_titled(callback(), label, label)
def _wrap_checkbox_labels(w, *args):
if w.get_name().startswith("no_label_wrap"):
return
elif isinstance(w, Gtk.CheckButton):
label = w.get_child()
label.set_line_wrap(True)
elif isinstance(w, Gtk.Container):
w.foreach(_wrap_checkbox_labels)
self.prefs_stack.foreach(_wrap_checkbox_labels)
page = callback()
name = "extension." + label
page.set_name(name)
page.foreach(self._wrap_checkbox_labels)
self.prefs_stack.add_titled(page, name, label)
self.background_color = get_background_color()
self.prefs_sidebar_bg.override_background_color(Gtk.StateFlags.NORMAL, self.background_color)
@ -481,6 +495,7 @@ class gPodderPreferences(BuilderWidget):
self.on_extension_enabled(container.module)
else:
self.on_extension_disabled(container.module)
self.inject_extensions_preferences()
elif container.error is not None:
if hasattr(container.error, 'message'):
error_msg = container.error.message

View File

@ -485,7 +485,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
def offer_resuming():
if resumable_episodes:
self.download_episode_list_paused(resumable_episodes)
resume_all = Gtk.Button(_('Resume all'))
def on_resume_all(button):
selection = self.treeDownloads.get_selection()
@ -494,6 +493,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
selection.unselect_all()
self._for_each_task_set_status(selected_tasks, download.DownloadTask.QUEUED)
self.message_area.hide()
resume_all = Gtk.Button(_('Resume all'))
resume_all.connect('clicked', on_resume_all)
self.message_area = SimpleMessageArea(

View File

@ -57,16 +57,21 @@ libglib.g_strdup.restype = ctypes.c_void_p
libglib.g_free.argtypes = (ctypes.c_void_p,)
libglib.g_free.restype = None
# See also: https://github.com/python/cpython/issues/92869
if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_int64):
time_t = ctypes.c_int64
# ctypes.c_time_t will be available in Python 3.12 onwards
# See also: https://github.com/python/cpython/pull/92870
if hasattr(ctypes, 'c_time_t'):
time_t = ctypes.c_time_t
else:
# On 32-bit systems, time_t is historically 32-bit, but due to Y2K38
# there have been efforts to establish 64-bit time_t on 32-bit Linux:
# https://linux.slashdot.org/story/20/02/15/0247201/linux-is-ready-for-the-end-of-time
# https://www.gnu.org/software/libc/manual/html_node/64_002dbit-time-symbol-handling.html
logger.info('libgpod may cause issues if time_t is 64-bit on your 32-bit system.')
time_t = ctypes.c_int32
# See also: https://github.com/python/cpython/issues/92869
if ctypes.sizeof(ctypes.c_void_p) == ctypes.sizeof(ctypes.c_int64):
time_t = ctypes.c_int64
else:
# On 32-bit systems, time_t is historically 32-bit, but due to Y2K38
# there have been efforts to establish 64-bit time_t on 32-bit Linux:
# https://linux.slashdot.org/story/20/02/15/0247201/linux-is-ready-for-the-end-of-time
# https://www.gnu.org/software/libc/manual/html_node/64_002dbit-time-symbol-handling.html
logger.info('libgpod may cause issues if time_t is 64-bit on your 32-bit system.')
time_t = ctypes.c_int32
# glib/glist.h: struct _GList

View File

@ -50,7 +50,7 @@ gpod_available = True
try:
from gpodder import libgpod_ctypes
except:
logger.info('iPod sync not available', exc_info=True)
logger.info('iPod sync not available')
gpod_available = False
mplayer_available = True if util.find_command('mplayer') is not None else False
@ -59,7 +59,7 @@ eyed3mp3_available = True
try:
import eyed3.mp3
except:
logger.info('eyeD3 MP3 not available', exc_info=True)
logger.info('eyeD3 MP3 not available')
eyed3mp3_available = False

View File

@ -1521,6 +1521,7 @@ def open_website(url):
make sure your system is set up correctly.
"""
run_in_background(lambda: webbrowser.open(url))
return True
def copy_text_to_clipboard(text):

View File

@ -67,10 +67,13 @@ cp -a "$checkout"/tools/mac-osx/make_cert_pem.py "$resources"/bin
# install gPodder hard dependencies
$run_pip install setuptools wheel
$run_pip install podcastparser==0.6.8 mygpoclient==1.9 requests[socks]==2.25.1
$run_pip install mygpoclient==1.9 podcastparser==0.6.8 requests[socks]==2.28.1
# install brotli and pycryptodomex (build from source)
$run_pip debug -v
$run_pip install -v brotli
$run_pip install -v pycryptodomex
# install extension dependencies; no explicit version for yt-dlp
$run_pip install mutagen==1.45.1 html5lib==1.1 yt-dlp
$run_pip install html5lib==1.1 mutagen==1.45.1 yt-dlp
cd "$checkout"
touch share/applications/gpodder{,-url-handler}.desktop

View File

@ -1,13 +1,13 @@
# PyPI / pip requirements for Linux
# For the benefit of e.g. flatpak-pip-generator.
#
mygpoclient==1.9
podcastparser==0.6.8
requests[socks]==2.25.1
urllib3==1.26.5
dbus-python
html5lib==1.1
mutagen==1.45.1
dbus-python
mygpoclient==1.9
podcastparser==0.6.8
requests[socks]==2.28.1
urllib3==1.26.10
yt-dlp
# eyed3 is optional and pulls in a lot of dependencies, so disable by default
# eyed3

View File

@ -84,20 +84,20 @@ function extract_installer {
}
PIP_REQUIREMENTS="\
podcastparser==0.6.8
mygpoclient==1.9
certifi==2022.6.15
chardet==4.0.0
comtypes==1.1.11
git+https://github.com/jaraco/pywin32-ctypes.git@f27d6a0
html5lib==1.1
webencodings==0.5.1
certifi==2021.5.30
idna==3.3
mutagen==1.45.1
yt-dlp
requests==2.25.1
urllib3==1.26.5
chardet==4.0.0
idna==3.2
mygpoclient==1.9
podcastparser==0.6.8
PySocks==1.7.1
comtypes==1.1.11
requests==2.28.1
urllib3==1.26.10
webencodings==0.5.1
yt-dlp
"
function install_deps {