Merge branch 'master' into dev-adaptive

This commit is contained in:
Teemu Ikonen 2022-04-30 23:48:12 +03:00
commit 03a6aabf96
45 changed files with 13729 additions and 11307 deletions

View File

@ -6,7 +6,7 @@
Media aggregator and podcast client
___
Copyright 2005-2018 The gPodder Team
Copyright 2005-2022 The gPodder Team
## License

690
po/ca.po

File diff suppressed because it is too large Load Diff

709
po/cs.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

714
po/da.po

File diff suppressed because it is too large Load Diff

710
po/de.po

File diff suppressed because it is too large Load Diff

714
po/el.po

File diff suppressed because it is too large Load Diff

716
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

714
po/eu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

714
po/fi.po

File diff suppressed because it is too large Load Diff

710
po/fr.po

File diff suppressed because it is too large Load Diff

714
po/gl.po

File diff suppressed because it is too large Load Diff

714
po/he.po

File diff suppressed because it is too large Load Diff

714
po/hu.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

714
po/it.po

File diff suppressed because it is too large Load Diff

710
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

710
po/nb.po

File diff suppressed because it is too large Load Diff

710
po/nl.po

File diff suppressed because it is too large Load Diff

710
po/nn.po

File diff suppressed because it is too large Load Diff

718
po/pl.po

File diff suppressed because it is too large Load Diff

714
po/pt.po

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

707
po/ro.po

File diff suppressed because it is too large Load Diff

714
po/ru.po

File diff suppressed because it is too large Load Diff

714
po/sk.po

File diff suppressed because it is too large Load Diff

716
po/sv.po

File diff suppressed because it is too large Load Diff

854
po/tr.po

File diff suppressed because it is too large Load Diff

718
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

@ -128,7 +128,7 @@ class CurrentTrackTracker(object):
'calc: %r observed: %r', cur['pos'], kwargs['pos'])
self.notify_stop()
if ((kwargs['pos']) == 0 and
if ((kwargs['pos']) <= 0 and
self.pos is not None and
self.length is not None and
(self.length - USECS_IN_SEC) < self.pos and
@ -144,7 +144,8 @@ class CurrentTrackTracker(object):
logger.debug('pos=0 not end of stream (calculated pos: %f/%f [%f])',
self.pos / USECS_IN_SEC, self.length / USECS_IN_SEC,
(self.pos / USECS_IN_SEC) - (self.length / USECS_IN_SEC))
self.pos = kwargs.pop('pos')
newpos = kwargs.pop('pos')
self.pos = newpos if newpos >= 0 else 0
if 'status' in kwargs:
self.status = kwargs.pop('status')
@ -274,35 +275,28 @@ class MPRISDBusReceiver(object):
collected_info['rate'] = changed_properties['Rate']
# Fix #788 pos=0 when Stopped resulting in not saving position on VLC quit
if changed_properties.get('PlaybackStatus') != 'Stopped':
collected_info['pos'] = self.query_position(sender)
try:
collected_info['pos'] = self.query_property(sender, 'Position')
except dbus.exceptions.DBusException:
pass
if 'status' not in collected_info:
collected_info['status'] = str(self.query_status(sender))
logger.debug('collected info: %r', collected_info)
try:
collected_info['status'] = str(self.query_property(
sender, 'PlaybackStatus'))
except dbus.exceptions.DBusException:
pass
logger.debug('collected info: %r', collected_info)
self.cur.update(**collected_info)
def on_seeked(self, position):
logger.debug('seeked to pos: %f', position)
self.cur.update(pos=position)
def query_position(self, sender):
def query_property(self, sender, prop):
proxy = self.bus.get_object(sender, self.PATH_MPRIS)
props = dbus.Interface(proxy, self.INTERFACE_PROPS)
try:
pos = props.Get(self.INTERFACE_MPRIS, 'Position')
except:
pos = None
return pos
def query_status(self, sender):
proxy = self.bus.get_object(sender, self.PATH_MPRIS)
props = dbus.Interface(proxy, self.INTERFACE_PROPS)
try:
status = props.Get(self.INTERFACE_MPRIS, 'PlaybackStatus')
except:
status = None
return status
return props.Get(self.INTERFACE_MPRIS, prop)
class gPodderNotifier(dbus.service.Object):

View File

@ -18,6 +18,10 @@ except:
import gpodder
from gpodder import download, feedcore, model, registry, util, youtube
import gi # isort:skip
gi.require_version('Gtk', '3.0') # isort:skip
from gi.repository import Gtk # isort:skip
_ = gpodder.gettext
@ -477,3 +481,40 @@ class gPodderExtension:
# create a new gPodderYoutubeDL to force using it even if manage_downloads is False
downloader = gPodderYoutubeDL(self.container.manager.core.config, self.container.config, force=True)
self.gpodder.download_episode_list(episodes, downloader=downloader)
def toggle_manage_channel(self, widget):
self.container.config.manage_channel = widget.get_active()
def toggle_manage_downloads(self, widget):
self.container.config.manage_downloads = widget.get_active()
def show_preferences(self):
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.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.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'
' <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,'
' 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)
box.add(note)
box.show_all()
return box
def on_preferences(self):
return [(_('Youtube-DL'), self.show_preferences)]

View File

@ -26,8 +26,8 @@
<property name="title" translatable="yes">Preferences</property>
<property name="modal">True</property>
<property name="window-position">center-on-parent</property>
<property name="default-width">320</property>
<property name="default-height">260</property>
<property name="default-width">480</property>
<property name="default-height">340</property>
<property name="type-hint">dialog</property>
<signal name="destroy" handler="on_dialog_destroy" swapped="no"/>
<child internal-child="vbox">
@ -136,6 +136,7 @@
<property name="can-focus">False</property>
<child>
<object class="GtkStack" id="prefs_stack">
<property name="width-request">297</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hhomogeneous">False</property>
@ -146,7 +147,7 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<!-- n-columns=3 n-rows=2 -->
<object class="GtkGrid">
@ -298,13 +299,14 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<object class="GtkCheckButton" id="checkbutton_enable">
<property name="label" translatable="yes">Synchronize subscriptions and episode actions</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="margin-bottom">8</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_enabled_toggled" swapped="no"/>
</object>
@ -425,9 +427,22 @@
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Replace subscription list on server with local subscriptions:</property>
<property name="wrap">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkButton" id="button_overwrite">
<property name="label" translatable="yes">Replace list on server with local subscriptions</property>
<property name="label" translatable="yes">Upload local subscriptions</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
@ -436,7 +451,7 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">3</property>
</packing>
</child>
</object>
@ -452,17 +467,19 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="hbox_updating_interval">
<object class="GtkBox" id="vbox_updating_interval">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label_update_interval">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Update interval:</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
<property name="yalign">0.10000000149011612</property>
</object>
@ -506,46 +523,52 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox_episode_limit">
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkLabel" id="label_episode_limit">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Maximum number of episodes per podcast:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkSpinButton" id="spinbutton_episode_limit">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="text" translatable="yes">200</property>
<property name="adjustment">adjustment_episode_limit</property>
<property name="value">200</property>
<child>
<object class="GtkLabel" id="label_episode_limit">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Maximum number of episodes per podcast:</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkSpinButton" id="spinbutton_episode_limit">
<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="adjustment">adjustment_episode_limit</property>
<property name="value">200</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
<property name="position">2</property>
</packing>
</child>
<child>
@ -556,44 +579,49 @@
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkBox" id="hbox_auto_download">
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkLabel" id="label_auto_download">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">When new episodes are found:</property>
<property name="xalign">0</property>
<property name="can-focus">True</property>
<child>
<object class="GtkLabel" id="label_auto_download">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">When new episodes are found:</property>
<property name="wrap">True</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combo_auto_download">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combo_auto_download_changed" swapped="no"/>
<property name="can-focus">True</property>
<child>
<object class="GtkComboBox" id="combo_auto_download">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combo_auto_download_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">4</property>
</packing>
</child>
<child>
@ -613,6 +641,7 @@
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="draw-indicator">True</property>
</object>
<packing>
@ -634,11 +663,12 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<object class="GtkBox" id="hbox_expiration">
<object class="GtkBox" id="vbox_expiration">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label_expiration">
@ -685,6 +715,7 @@
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="draw-indicator">True</property>
</object>
<packing>
@ -699,6 +730,7 @@
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="draw-indicator">True</property>
</object>
<packing>
@ -720,62 +752,41 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<!-- n-columns=2 n-rows=2 -->
<object class="GtkGrid">
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkLabel" id="label_device_type">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Device type:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_device_type">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<signal name="changed" handler="on_combobox_device_type_changed" swapped="no"/>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_device_mount">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Mountpoint:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_filesystemMountpoint">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="hexpand">True</property>
<signal name="clicked" handler="on_btn_device_mountpoint_clicked" swapped="no"/>
<child>
<object class="GtkLabel" id="label_device_type">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Device type:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkComboBox" id="combobox_device_type">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<signal name="changed" handler="on_combobox_device_type_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
</object>
<packing>
@ -785,13 +796,41 @@
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_create_playlists">
<property name="label" translatable="yes">Create playlists on device</property>
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_checkbutton_create_playlists_toggled" swapped="no"/>
<property name="can-focus">False</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkLabel" id="label_device_mount">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Mountpoint:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkButton" id="btn_filesystemMountpoint">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="hexpand">True</property>
<signal name="clicked" handler="on_btn_device_mountpoint_clicked" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -800,36 +839,9 @@
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child>
<object class="GtkLabel" id="label_device_playlists">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Playlists Folder:</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="btn_playlistfolder">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<signal name="clicked" handler="on_btn_playlist_folder_clicked" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
@ -838,12 +850,14 @@
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_delete_using_playlists">
<property name="label" translatable="yes">Remove episodes deleted on device from gPodder</property>
<object class="GtkCheckButton" id="checkbutton_create_playlists">
<property name="label" translatable="yes">Create playlists on device</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="draw-indicator">True</property>
<signal name="toggled" handler="on_checkbutton_create_playlists_toggled" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
@ -852,34 +866,39 @@
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkLabel" id="label_on_sync">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">After syncing an episode:</property>
<property name="xalign">0</property>
<property name="can-focus">True</property>
<child>
<object class="GtkLabel" id="label_device_playlists">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Playlists Folder:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_on_sync">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combobox_on_sync_changed" swapped="no"/>
<property name="can-focus">True</property>
<child>
<object class="GtkButton" id="btn_playlistfolder">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<signal name="clicked" handler="on_btn_playlist_folder_clicked" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
@ -889,11 +908,13 @@
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_skip_played_episodes">
<property name="label" translatable="yes">Only sync unplayed episodes</property>
<object class="GtkCheckButton" id="checkbutton_delete_using_playlists">
<property name="label" translatable="yes">Remove episodes deleted on device from gPodder</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="margin-bottom">8</property>
<property name="draw-indicator">True</property>
</object>
<packing>
@ -903,12 +924,9 @@
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_delete_deleted_episodes">
<property name="label" translatable="yes">Remove episodes deleted in gPodder from device</property>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="draw-indicator">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
@ -916,6 +934,90 @@
<property name="position">6</property>
</packing>
</child>
<child>
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkLabel" id="label_on_sync">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">After syncing an episode:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
</child>
<child>
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">True</property>
<child>
<object class="GtkComboBox" id="combobox_on_sync">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combobox_on_sync_changed" swapped="no"/>
</object>
</child>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
</packing>
</child>
<child>
<object class="GtkSeparator">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_skip_played_episodes">
<property name="label" translatable="yes">Only sync unplayed episodes</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="margin-bottom">4</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
</packing>
</child>
<child>
<object class="GtkCheckButton" id="checkbutton_delete_deleted_episodes">
<property name="label" translatable="yes">Remove episodes deleted in gPodder from device</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">False</property>
<property name="halign">start</property>
<property name="margin-bottom">4</property>
<property name="draw-indicator">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">10</property>
</packing>
</child>
</object>
<packing>
<property name="name">devices</property>
@ -929,84 +1031,94 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<!-- n-columns=2 n-rows=3 -->
<object class="GtkGrid" id="table_video">
<object class="GtkFlowBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="row-spacing">6</property>
<property name="column-spacing">12</property>
<property name="min-children-per-line">1</property>
<property name="max-children-per-line">2</property>
<property name="selection-mode">none</property>
<child>
<object class="GtkLabel" id="label_preferred_youtube_format">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Preferred YouTube format:</property>
<property name="xalign">0</property>
<child>
<object class="GtkLabel" id="label_preferred_youtube_format">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Preferred YouTube format:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_preferred_youtube_format">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<signal name="changed" handler="on_combobox_preferred_youtube_format_changed" swapped="no"/>
<child>
<object class="GtkComboBox" id="combobox_preferred_youtube_format">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combobox_preferred_youtube_format_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_preferred_youtube_hls_format">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Preferred YouTube HLS format:</property>
<property name="xalign">0</property>
<child>
<object class="GtkLabel" id="label_preferred_youtube_hls_format">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Preferred YouTube HLS format:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_preferred_youtube_hls_format">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<signal name="changed" handler="on_combobox_preferred_youtube_hls_format_changed" swapped="no"/>
<child>
<object class="GtkComboBox" id="combobox_preferred_youtube_hls_format">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combobox_preferred_youtube_hls_format_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label_preferred_vimeo_format">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Preferred Vimeo format:</property>
<property name="xalign">0</property>
<child>
<object class="GtkLabel" id="label_preferred_vimeo_format">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Preferred Vimeo format:</property>
<property name="xalign">0</property>
</object>
</child>
</object>
<packing>
<property name="left-attach">0</property>
<property name="top-attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="combobox_preferred_vimeo_format">
<object class="GtkFlowBoxChild">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combobox_preferred_vimeo_format_changed" swapped="no"/>
<child>
<object class="GtkComboBox" id="combobox_preferred_vimeo_format">
<property name="visible">True</property>
<property name="can-focus">False</property>
<signal name="changed" handler="on_combobox_preferred_vimeo_format_changed" swapped="no"/>
</object>
</child>
</object>
<packing>
<property name="left-attach">1</property>
<property name="top-attach">2</property>
</packing>
</child>
</object>
<packing>
@ -1028,7 +1140,7 @@
<property name="can-focus">False</property>
<property name="border-width">12</property>
<property name="orientation">vertical</property>
<property name="spacing">6</property>
<property name="spacing">12</property>
<child>
<object class="GtkTreeView" id="treeviewExtensions">
<property name="visible">True</property>
@ -1037,6 +1149,9 @@
<property name="search-column">1</property>
<signal name="button-release-event" handler="on_treeview_extension_button_released" swapped="no"/>
<signal name="popup-menu" handler="on_treeview_extension_show_context_menu" swapped="no"/>
<child internal-child="selection">
<object class="GtkTreeSelection"/>
</child>
</object>
<packing>
<property name="expand">True</property>

View File

@ -209,6 +209,7 @@ class gPodderPreferences(BuilderWidget):
self.preferred_youtube_format_model = YouTubeVideoFormatListModel(self._config)
self.combobox_preferred_youtube_format.set_model(self.preferred_youtube_format_model)
cellrenderer = Gtk.CellRendererText()
cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
self.combobox_preferred_youtube_format.pack_start(cellrenderer, True)
self.combobox_preferred_youtube_format.add_attribute(cellrenderer, 'text', self.preferred_youtube_format_model.C_CAPTION)
self.combobox_preferred_youtube_format.set_active(self.preferred_youtube_format_model.get_index())
@ -216,6 +217,7 @@ class gPodderPreferences(BuilderWidget):
self.preferred_youtube_hls_format_model = YouTubeVideoHLSFormatListModel(self._config)
self.combobox_preferred_youtube_hls_format.set_model(self.preferred_youtube_hls_format_model)
cellrenderer = Gtk.CellRendererText()
cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
self.combobox_preferred_youtube_hls_format.pack_start(cellrenderer, True)
self.combobox_preferred_youtube_hls_format.add_attribute(cellrenderer, 'text', self.preferred_youtube_hls_format_model.C_CAPTION)
self.combobox_preferred_youtube_hls_format.set_active(self.preferred_youtube_hls_format_model.get_index())
@ -223,6 +225,7 @@ class gPodderPreferences(BuilderWidget):
self.preferred_vimeo_format_model = VimeoVideoFormatListModel(self._config)
self.combobox_preferred_vimeo_format.set_model(self.preferred_vimeo_format_model)
cellrenderer = Gtk.CellRendererText()
cellrenderer.set_property('ellipsize', Pango.EllipsizeMode.END)
self.combobox_preferred_vimeo_format.pack_start(cellrenderer, True)
self.combobox_preferred_vimeo_format.add_attribute(cellrenderer, 'text', self.preferred_vimeo_format_model.C_CAPTION)
self.combobox_preferred_vimeo_format.set_active(self.preferred_vimeo_format_model.get_index())
@ -323,6 +326,17 @@ class gPodderPreferences(BuilderWidget):
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)
def _extensions_select_function(self, selection, model, path, path_currently_selected):
return model.get_value(model.get_iter(path), self.C_SHOW_TOGGLE)
@ -623,7 +637,8 @@ class gPodderPreferences(BuilderWidget):
children = self.btn_playlistfolder.get_children()
if children:
label = children.pop()
label.set_alignment(0., .5)
label.set_ellipsize(Pango.EllipsizeMode.START)
label.set_xalign(0.0)
else:
self.btn_playlistfolder.set_sensitive(False)
self.btn_playlistfolder.set_label('')
@ -645,10 +660,6 @@ class gPodderPreferences(BuilderWidget):
self.btn_filesystemMountpoint.set_label(self._config.device_sync.device_folder or "")
self.btn_filesystemMountpoint.set_sensitive(True)
self.checkbutton_create_playlists.set_sensitive(True)
children = self.btn_filesystemMountpoint.get_children()
if children:
label = children.pop()
label.set_alignment(0., .5)
self.toggle_playlist_interface(self._config.device_sync.playlists.create)
self.combobox_on_sync.set_sensitive(True)
self.checkbutton_skip_played_episodes.set_sensitive(True)
@ -662,10 +673,11 @@ class gPodderPreferences(BuilderWidget):
self.combobox_on_sync.set_sensitive(False)
self.checkbutton_skip_played_episodes.set_sensitive(False)
children = self.btn_filesystemMountpoint.get_children()
if children:
label = children.pop()
label.set_alignment(0., .5)
children = self.btn_filesystemMountpoint.get_children()
if children:
label = children.pop()
label.set_ellipsize(Pango.EllipsizeMode.START)
label.set_xalign(0.0)
def on_btn_device_mountpoint_clicked(self, widget):
fs = Gtk.FileChooserDialog(title=_('Select folder for mount point'),
@ -709,7 +721,8 @@ class gPodderPreferences(BuilderWidget):
children = self.btn_playlistfolder.get_children()
if children:
label = children.pop()
label.set_alignment(0., .5)
label.set_ellipsize(Pango.EllipsizeMode.START)
label.set_xalign(0.0)
break
fs.destroy()

View File

@ -49,9 +49,10 @@ class gPodderAddPodcast(BuilderWidget):
clipboard = Gtk.Clipboard.get(Gdk.SELECTION_CLIPBOARD)
def receive_clipboard_text(clipboard, text, second_try):
# Heuristic: If there is a space in the clipboard
# text, assume it's some arbitrary text, and no URL
if text is not None and ' ' not in text:
# Heuristic: If space is present in clipboard text
# normalize_feed_url will either fix to valid url or
# return None if URL cannot be validated
if text is not None:
url = util.normalize_feed_url(text)
if url is not None:
self.entry_url.set_text(url)
@ -72,7 +73,7 @@ class gPodderAddPodcast(BuilderWidget):
def receive_clipboard_text(self, clipboard, text, data=None):
if text is not None:
self.entry_url.set_text(text)
self.entry_url.set_text(text).strip()
else:
self.show_message(_('Nothing to paste.'), _('Clipboard is empty'))

View File

@ -2207,7 +2207,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
any_new = any(e.is_new and e.state != gpodder.STATE_DELETED for e in episodes)
downloaded = all(e.was_downloaded(and_exists=True) for e in episodes)
downloading = any(e.downloading for e in episodes)
(open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete) = self.play_or_download()
(open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete, can_lock) = self.play_or_download()
menu = self.application.builder.get_object('episodes-context')
# Play
@ -2255,9 +2255,9 @@ class gPodder(BuilderWidget, dbus.service.Object):
return True
def set_episode_actions(self, open_instead_of_play=False, can_play=False, can_download=False, can_pause=False, can_cancel=False,
can_delete=False):
can_delete=False, can_lock=False, is_episode_selected=False):
# play icon and label
# if open_instead_of_play:
# if open_instead_of_play or not is_episode_selected:
# self.toolPlay.set_icon_name('document-open')
# self.toolPlay.set_label(_('Open'))
# else:
@ -2287,8 +2287,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.pause_action.set_enabled(can_pause)
self.episodes_cancel_action.set_enabled(can_cancel)
self.delete_action.set_enabled(can_delete)
# self.toggle_episode_new_action.set_enabled(can_play)
# self.toggle_episode_lock_action.set_enabled(can_play)
# self.toggle_episode_new_action.set_enabled(is_episode_selected)
# self.toggle_episode_lock_action.set_enabled(can_lock)
self.episode_new_action.set_enabled(can_play)
self.episode_lock_action.set_enabled(can_play)
@ -2435,7 +2435,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
# current_page = self.wNotebook.get_current_page()
# if current_page == 0:
if True:
(open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete) = (False,) * 6
(open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete, can_lock) = (False,) * 7
selection = self.treeAvailable.get_selection()
if selection.count_selected_rows() > 0:
@ -2451,16 +2451,20 @@ class gPodder(BuilderWidget, dbus.service.Object):
logger.error('Invalid episode at path %s', str(path))
continue
# These values should only ever be set, never unset them once set.
# Actions filter episodes using these methods.
open_instead_of_play = open_instead_of_play or episode.file_type() not in ('audio', 'video')
can_play = can_play or episode.can_play(self.config)
can_download = can_download or episode.can_download()
can_pause = can_pause or episode.can_pause()
can_cancel = can_cancel or episode.can_cancel()
can_delete = can_delete or episode.can_delete()
can_lock = can_lock or episode.can_lock()
self.set_episode_actions(open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete)
self.set_episode_actions(open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete, can_lock,
selection.count_selected_rows() > 0)
return (open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete)
return (open_instead_of_play, can_play, can_download, can_pause, can_cancel, can_delete, can_lock)
else:
(can_queue, can_pause, can_cancel, can_remove) = (False,) * 4
@ -2478,14 +2482,16 @@ class gPodder(BuilderWidget, dbus.service.Object):
logger.error('Invalid task at path %s', str(path))
continue
# These values should only ever be set, never unset them once set.
# Actions filter tasks using these methods.
can_queue = can_queue or task.can_queue()
can_pause = can_pause or task.can_pause()
can_cancel = can_cancel or task.can_cancel()
can_remove = can_remove or task.can_remove()
self.set_episode_actions(False, False, can_queue, can_pause, can_cancel, can_remove)
self.set_episode_actions(False, False, can_queue, can_pause, can_cancel, can_remove, False, False)
return (False, False, can_queue, can_pause, can_cancel, can_remove)
return (False, False, can_queue, can_pause, can_cancel, can_remove, False)
def on_cbMaxDownloads_toggled(self, widget, *args):
self.spinMaxDownloads.set_sensitive(self.cbMaxDownloads.get_active())
@ -3002,16 +3008,22 @@ class gPodder(BuilderWidget, dbus.service.Object):
# download older episodes first
episodes = list(Model.sort_episodes_by_pubdate(episodes))
if not episodes:
# Remove episodes without downloadable content
downloadable_episodes = [e for e in episodes if e.url]
if not downloadable_episodes:
# Nothing new here - but inform the user
self.pbFeedUpdate.set_fraction(1.0)
self.pbFeedUpdate.set_text(_('No new episodes'))
self.pbFeedUpdate.set_text(
_('No new episodes with downloadable content') if episodes else _('No new episodes'))
self.feed_cache_update_cancelled = True
self.btnCancelFeedUpdate.show()
self.btnCancelFeedUpdate.set_sensitive(True)
self.update_action.set_enabled(True)
self.btnCancelFeedUpdate.set_image(Gtk.Image.new_from_icon_name('edit-clear', Gtk.IconSize.BUTTON))
else:
episodes = downloadable_episodes
count = len(episodes)
# New episodes are available
self.pbFeedUpdate.set_fraction(1.0)
@ -3241,6 +3253,8 @@ class gPodder(BuilderWidget, dbus.service.Object):
self.update_episode_list_icons(selected=True)
self.db.commit()
self.play_or_download()
def mark_selected_episodes_new(self):
for episode in self.get_selected_episodes():
episode.mark(is_played=False)

View File

@ -194,6 +194,7 @@ class EpisodeListModel(Gtk.ListStore):
# Are we currently showing "all episodes"/section or a single channel?
self._section_view = False
self.ICON_WEB_BROWSER = 'web-browser'
self.ICON_AUDIO_FILE = 'audio-x-generic'
self.ICON_VIDEO_FILE = 'video-x-generic'
self.ICON_IMAGE_FILE = 'image-x-generic'
@ -468,6 +469,12 @@ class EpisodeListModel(Gtk.ListStore):
if episode.state == gpodder.STATE_NORMAL and episode.is_new:
view_show_downloaded = self._config.ui.gtk.episode_list.always_show_new
view_show_unplayed = True
elif not episode.url:
tooltip.append(_('No downloadable content'))
status_icon = self.ICON_WEB_BROWSER
if episode.state == gpodder.STATE_NORMAL and episode.is_new:
view_show_downloaded = self._config.ui.gtk.episode_list.always_show_new
view_show_unplayed = True
elif episode.state == gpodder.STATE_NORMAL and episode.is_new:
tooltip.append(_('New episode'))
view_show_downloaded = self._config.ui.gtk.episode_list.always_show_new

View File

@ -431,8 +431,9 @@ class gPodderShownotesHTML(gPodderShownotes):
if req.get_uri() in (self._base_uri, 'about:blank'):
decision.use()
else:
logger.debug("refusing to go to %s (base URI=%s)", req.get_uri(), self._base_uri)
# Avoid opening the page inside the WebView and open in the browser instead
decision.ignore()
util.open_website(req.get_uri())
return False
else:
decision.use()

View File

@ -351,7 +351,11 @@ class PodcastEpisode(PodcastModelObject):
if link_has_media:
return episode
return None
# The episode has no downloadable content.
# It is either a blog post or it links to a webpage with content accessible from shownotes title.
# Remove the URL so downloading will fail.
episode.url = ''
return episode
def __init__(self, channel):
self.parent = channel
@ -507,6 +511,13 @@ class PodcastEpisode(PodcastModelObject):
return self.state != gpodder.STATE_DELETED and not self.archive and (
not self.download_task or self.download_task.status == self.download_task.FAILED)
def can_lock(self):
"""
gPodder.on_item_toggle_lock_activate() unlocks deleted episodes and toggles all others.
Locked episodes can always be unlocked.
"""
return self.state != gpodder.STATE_DELETED or self.archive
def check_is_new(self):
return (self.state == gpodder.STATE_NORMAL and self.is_new and
not self.downloading)

View File

@ -73,14 +73,14 @@ logger = logging.getLogger(__name__)
try:
import html5lib
except ImportError:
logger.warn('html5lib not found, falling back to HTMLParser')
logger.warning("html5lib was not found, fall-back to HTMLParser")
html5lib = None
if gpodder.ui.win32:
try:
import gpodder.utilwin32ctypes as win32file
except ImportError:
logger.warn('Running on Win32 but utilwin32ctypes can\'t be loaded.')
logger.warning('Running on Win32: utilwin32ctypes cannot be loaded')
win32file = None
_ = gpodder.gettext
@ -248,6 +248,12 @@ def normalize_feed_url(url):
if not url or len(url) < 8:
return None
# Removes leading and/or trailing whitespaces - if url contains whitespaces
# in between after str.strip() -> conclude invalid url & return None
url = url.strip()
if ' ' in url:
return None
# This is a list of prefixes that you can use to minimize the amount of
# keystrokes that you have to use.
# Feel free to suggest other useful prefixes, and I'll add them here.