Cover art: Unified download manager (bug 1288)
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 2.8 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.5 KiB |
Before Width: | Height: | Size: 2.0 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 6.9 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 7.2 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 6.6 KiB |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 6.2 KiB |
|
@ -1,646 +1,454 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<interface>
|
||||
<!-- interface-requires gtk+ 2.12 -->
|
||||
<!-- interface-naming-policy toplevel-contextual -->
|
||||
<object class="GtkDialog" id="gPodderChannel">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">10</property>
|
||||
<property name="visible">False</property>
|
||||
<property name="has_separator">False</property>
|
||||
<property name="title" translatable="yes">gPodder Podcast Editor</property>
|
||||
<property name="window_position">GTK_WIN_POS_CENTER_ON_PARENT</property>
|
||||
<property name="modal">True</property>
|
||||
<property name="window_position">center-on-parent</property>
|
||||
<property name="default_width">500</property>
|
||||
<property name="destroy_with_parent">False</property>
|
||||
<property name="skip_taskbar_hint">False</property>
|
||||
<property name="skip_pager_hint">False</property>
|
||||
<property name="type_hint">GDK_WINDOW_TYPE_HINT_DIALOG</property>
|
||||
<property name="focus_on_map">True</property>
|
||||
<property name="urgency_hint">False</property>
|
||||
<signal handler="on_gPodderChannel_destroy" name="destroy"/>
|
||||
<property name="default_height">400</property>
|
||||
<property name="type_hint">dialog</property>
|
||||
<signal name="destroy" handler="on_gPodderChannel_destroy" swapped="no"/>
|
||||
<child internal-child="vbox">
|
||||
<object class="GtkVBox" id="vboxChannelEditorMain">
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkHButtonBox" id="hboxButtons">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="layout_style">end</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnOK">
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_stock">True</property>
|
||||
<signal name="clicked" handler="on_btnOK_clicked" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="pack_type">end</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkNotebook" id="notebookChannelEditor">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="show_tabs">True</property>
|
||||
<property name="show_border">True</property>
|
||||
<property name="tab_pos">GTK_POS_TOP</property>
|
||||
<property name="scrollable">False</property>
|
||||
<property name="enable_popup">False</property>
|
||||
<child>
|
||||
<object class="GtkTable" id="table_settings">
|
||||
<property name="border_width">10</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">10</property>
|
||||
<property name="n_rows">5</property>
|
||||
<property name="n_columns">2</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="row_spacing">10</property>
|
||||
<property name="n_columns">3</property>
|
||||
<property name="column_spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label11">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Feed URL:</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<property name="row_spacing">10</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="labelURL">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="xalign">0.0</property>
|
||||
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="labelTitle">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Title:</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="justify">GTK_JUSTIFY_RIGHT</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="xalign">1</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="bottom_attach">1</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="entryTitle">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="max_length">0</property>
|
||||
<property name="has_frame">True</property>
|
||||
<property name="invisible_char">*</property>
|
||||
<property name="activates_default">False</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">True</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="bottom_attach">1</property>
|
||||
<property name="y_options"/>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label_section">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Section:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxEntry" id="combo_section">
|
||||
<property name="visible">True</property>
|
||||
<property name="text-column">0</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkComboBoxEntry" id="combo_section">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="text_column">0</property>
|
||||
<child internal-child="entry">
|
||||
<object class="GtkEntry" id="comboboxentry-entry2">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">True</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">3</property>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow" id="scrolledwindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="hscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="vscrollbar_policy">GTK_POLICY_AUTOMATIC</property>
|
||||
<property name="shadow_type">GTK_SHADOW_IN</property>
|
||||
<property name="window_placement">GTK_CORNER_TOP_LEFT</property>
|
||||
<property name="hscrollbar_policy">automatic</property>
|
||||
<property name="vscrollbar_policy">automatic</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<child>
|
||||
<object class="GtkTextView" id="channel_description">
|
||||
<property name="visible">True</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="overwrite">False</property>
|
||||
<property name="accepts_tab">True</property>
|
||||
<property name="wrap_mode">GTK_WRAP_WORD</property>
|
||||
<property name="cursor_visible">True</property>
|
||||
<property name="pixels_above_lines">0</property>
|
||||
<property name="pixels_below_lines">0</property>
|
||||
<property name="pixels_inside_wrap">0</property>
|
||||
<property name="left_margin">0</property>
|
||||
<property name="right_margin">0</property>
|
||||
<property name="editable">False</property>
|
||||
<property name="wrap_mode">word</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">4</property>
|
||||
<property name="bottom_attach">5</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkCheckButton" id="cbSkipFeedUpdate">
|
||||
<property name="label" translatable="yes">Disable feed updates (pause subscription)</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">Disable feed updates (pause subscription)</property>
|
||||
<property name="receives_default">False</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<property name="active">False</property>
|
||||
<property name="inconsistent">False</property>
|
||||
<property name="draw_indicator">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">2</property>
|
||||
<property name="bottom_attach">3</property>
|
||||
<property name="y_options">fill</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEventBox" id="imgCoverEventBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="imgCover">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="bottom_attach">4</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_expand">False</property>
|
||||
<property name="tab_fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label19">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">General</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vboxiPodProperties">
|
||||
<property name="border_width">10</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="border_width">10</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label96">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes"><b>HTTP/FTP Authentication</b></property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes"><b>HTTP/FTP Authentication</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTable" id="table10">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="n_rows">2</property>
|
||||
<property name="n_columns">2</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label93">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Username:</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes">Username:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="bottom_attach">1</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label94">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Password:</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes">Password:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="FeedUsername">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="max_length">0</property>
|
||||
<property name="has_frame">True</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="activates_default">False</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">True</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="bottom_attach">1</property>
|
||||
<property name="y_options"/>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="FeedPassword">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="max_length">0</property>
|
||||
<property name="has_frame">True</property>
|
||||
<property name="invisible_char">●</property>
|
||||
<property name="activates_default">False</property>
|
||||
<property name="primary_icon_activatable">False</property>
|
||||
<property name="secondary_icon_activatable">False</property>
|
||||
<property name="primary_icon_sensitive">True</property>
|
||||
<property name="secondary_icon_sensitive">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="y_options"/>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHSeparator" id="hseparator13">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label97">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes"><b>Locations</b></property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">True</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes"><b>Locations</b></property>
|
||||
<property name="use_markup">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="position">3</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTable" id="table3">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="n_rows">2</property>
|
||||
<property name="n_columns">2</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<property name="n_columns">3</property>
|
||||
<property name="column_spacing">6</property>
|
||||
<property name="row_spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label29">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Download to:</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes">Download to:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="bottom_attach">1</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="LabelDownloadTo">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label">download to label</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="ellipsize">PANGO_ELLIPSIZE_START</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label">download to label</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="ellipsize">start</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">0</property>
|
||||
<property name="bottom_attach">1</property>
|
||||
<property name="y_options"/>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label54">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Website:</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes">Website:</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">0</property>
|
||||
<property name="right_attach">1</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="x_options">fill</property>
|
||||
<property name="y_options"/>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="LabelWebsite">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label" translatable="yes">website label</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="xalign">0</property>
|
||||
<property name="ellipsize">PANGO_ELLIPSIZE_END</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
<property name="label" translatable="yes">website label</property>
|
||||
<property name="selectable">True</property>
|
||||
<property name="ellipsize">end</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">1</property>
|
||||
<property name="right_attach">2</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="y_options"/>
|
||||
<property name="y_options"></property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_website">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="use_action_appearance">False</property>
|
||||
<signal name="clicked" handler="on_btn_website_clicked" swapped="no"/>
|
||||
<child>
|
||||
<object class="GtkImage" id="image3167">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="icon_name">web-browser</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="left_attach">2</property>
|
||||
<property name="right_attach">3</property>
|
||||
<property name="top_attach">1</property>
|
||||
<property name="bottom_attach">2</property>
|
||||
<property name="x_options">GTK_FILL</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">4</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_expand">False</property>
|
||||
<property name="tab_fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label37">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="label" translatable="yes">Advanced</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkVBox" id="vboxCoverEditor">
|
||||
<property name="border_width">10</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="imgCover">
|
||||
<property name="visible">True</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkHButtonBox" id="hbuttonbox1">
|
||||
<property name="visible">True</property>
|
||||
<property name="layout_style">GTK_BUTTONBOX_END</property>
|
||||
<property name="spacing">6</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnDownloadCover">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="label">gtk-open</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<signal handler="on_btnDownloadCover_clicked" name="clicked"/>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnClearCover">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="label">gtk-refresh</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<signal handler="on_btnClearCover_clicked" name="clicked"/>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="tab_expand">False</property>
|
||||
<property name="tab_fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child type="tab">
|
||||
<object class="GtkLabel" id="label31">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Cover</property>
|
||||
<property name="use_underline">False</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child internal-child="action_area">
|
||||
<object class="GtkHBox" id="hboxButtons">
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">5</property>
|
||||
<child>
|
||||
<object class="GtkButton" id="btnOK">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_default">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<property name="label">gtk-close</property>
|
||||
<property name="use_stock">True</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<signal handler="on_btnOK_clicked" name="clicked"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
<property name="pack_type">GTK_PACK_END</property>
|
||||
<property name="position">1</property>
|
||||
<property name="tab_fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<placeholder/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="btn_website">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="focus_on_click">True</property>
|
||||
<signal handler="on_btn_website_clicked" name="clicked"/>
|
||||
<child>
|
||||
<object class="GtkAlignment" id="alignment26">
|
||||
<property name="visible">True</property>
|
||||
<property name="xscale">0</property>
|
||||
<property name="yscale">0</property>
|
||||
<property name="top_padding">0</property>
|
||||
<property name="bottom_padding">0</property>
|
||||
<property name="left_padding">0</property>
|
||||
<property name="right_padding">0</property>
|
||||
<child>
|
||||
<object class="GtkHBox" id="hbox40">
|
||||
<property name="visible">True</property>
|
||||
<property name="homogeneous">False</property>
|
||||
<property name="spacing">2</property>
|
||||
<child>
|
||||
<object class="GtkImage" id="image3167">
|
||||
<property name="visible">True</property>
|
||||
<property name="icon_size">4</property>
|
||||
<property name="icon_name">web-browser</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkLabel" id="label123">
|
||||
<property name="visible">True</property>
|
||||
<property name="label" translatable="yes">Go to website</property>
|
||||
<property name="use_underline">True</property>
|
||||
<property name="use_markup">False</property>
|
||||
<property name="wrap">False</property>
|
||||
<property name="selectable">False</property>
|
||||
<property name="width_chars">-1</property>
|
||||
<property name="single_line_mode">False</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">False</property>
|
||||
</packing>
|
||||
<child type="tab">
|
||||
<placeholder/>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="padding">0</property>
|
||||
<property name="expand">False</property>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<action-widgets>
|
||||
<action-widget response="0">btnOK</action-widget>
|
||||
</action-widgets>
|
||||
</object>
|
||||
</interface>
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# gPodder - A media aggregator and podcast client
|
||||
# Copyright (c) 2005-2012 Thomas Perl and the gPodder Team
|
||||
#
|
||||
# gPodder is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation; either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# gPodder is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
|
||||
|
||||
#
|
||||
# gpodder.coverart - Unified cover art downloading module (2012-03-04)
|
||||
#
|
||||
|
||||
|
||||
import gpodder
|
||||
_ = gpodder.gettext
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
from gpodder import util
|
||||
from gpodder import youtube
|
||||
|
||||
import os
|
||||
|
||||
class CoverDownloader(object):
|
||||
# File name extension dict, lists supported cover art extensions
|
||||
# Values: functions that check if some data is of that file type
|
||||
SUPPORTED_EXTENSIONS = {
|
||||
'.png': lambda d: d.startswith('\x89PNG\r\n\x1a\n\x00'),
|
||||
'.jpg': lambda d: d.startswith('\xff\xd8'),
|
||||
}
|
||||
|
||||
EXTENSIONS = SUPPORTED_EXTENSIONS.keys()
|
||||
ALL_EPISODES_ID = ':gpodder:all-episodes:'
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def get_cover_all_episodes(self):
|
||||
return self._default_filename('podcast-all.png')
|
||||
|
||||
def get_cover(self, filename, cover_url, feed_url, title,
|
||||
username=None, password=None, download=False):
|
||||
# Detection of "all episodes" podcast
|
||||
if filename == self.ALL_EPISODES_ID:
|
||||
return self.get_cover_all_episodes()
|
||||
|
||||
# Return already existing files
|
||||
for extension in self.EXTENSIONS:
|
||||
if os.path.exists(filename + extension):
|
||||
return filename + extension
|
||||
|
||||
# If allowed to download files, do so here
|
||||
if download:
|
||||
# YouTube-specific cover art image resolver
|
||||
youtube_cover_url = youtube.get_real_cover(feed_url)
|
||||
if youtube_cover_url is not None:
|
||||
cover_url = youtube_cover_url
|
||||
|
||||
if not cover_url:
|
||||
return self._fallback_filename(title)
|
||||
|
||||
# We have to add username/password, because password-protected
|
||||
# feeds might keep their cover art also protected (bug 1521)
|
||||
if username is not None and password is not None:
|
||||
cover_url = util.url_add_authentication(cover_url,
|
||||
username, password)
|
||||
|
||||
try:
|
||||
logger.info('Downloading cover art: %s', cover_url)
|
||||
data = util.urlopen(cover_url).read()
|
||||
except Exception, e:
|
||||
logger.warn('Cover art download failed: %s', e)
|
||||
return self._fallback_filename(title)
|
||||
|
||||
try:
|
||||
extension = None
|
||||
|
||||
for filetype, check in self.SUPPORTED_EXTENSIONS.items():
|
||||
if check(data):
|
||||
extension = filetype
|
||||
break
|
||||
|
||||
if extension is None:
|
||||
msg = 'Unknown file type: %s (%r)' % (cover_url, data[:6])
|
||||
raise ValueError(msg)
|
||||
|
||||
# Successfully downloaded the cover art - save it!
|
||||
fp = open(filename + extension, 'wb')
|
||||
fp.write(data)
|
||||
fp.close()
|
||||
|
||||
return filename + extension
|
||||
except Exception, e:
|
||||
logger.warn('Cannot save cover art', exc_info=True)
|
||||
|
||||
# Fallback to cover art based on the podcast title
|
||||
return self._fallback_filename(title)
|
||||
|
||||
def _default_filename(self, basename):
|
||||
return os.path.join(gpodder.images_folder, basename)
|
||||
|
||||
def _fallback_filename(self, title):
|
||||
return self._default_filename('podcast-%d.png' % (hash(title)%5))
|
||||
|
|
@ -81,7 +81,7 @@ class DBusPodcastsProxy(dbus.service.Object):
|
|||
title = safe_str(podcast.title)
|
||||
url = safe_str(podcast.url)
|
||||
description = safe_first_line(podcast.description)
|
||||
cover_file = safe_str(podcast.cover_file)
|
||||
cover_file = ''
|
||||
|
||||
return (title, url, description, cover_file)
|
||||
|
||||
|
|
|
@ -29,6 +29,8 @@ from gpodder.gtkui.interface.common import BuilderWidget
|
|||
|
||||
|
||||
class gPodderChannel(BuilderWidget):
|
||||
MAX_SIZE = 120
|
||||
|
||||
def new(self):
|
||||
self.gPodderChannel.set_title( self.channel.title)
|
||||
self.entryTitle.set_text( self.channel.title)
|
||||
|
@ -62,10 +64,31 @@ class gPodderChannel(BuilderWidget):
|
|||
|
||||
#Add Drag and Drop Support
|
||||
flags = gtk.DEST_DEFAULT_ALL
|
||||
targets = [ ('text/uri-list', 0, 2), ('text/plain', 0, 4) ]
|
||||
targets = [('text/uri-list', 0, 2), ('text/plain', 0, 4)]
|
||||
actions = gtk.gdk.ACTION_DEFAULT | gtk.gdk.ACTION_COPY
|
||||
self.vboxCoverEditor.drag_dest_set( flags, targets, actions)
|
||||
self.vboxCoverEditor.connect( 'drag_data_received', self.drag_data_received)
|
||||
self.imgCover.drag_dest_set(flags, targets, actions)
|
||||
self.imgCover.connect('drag_data_received', self.drag_data_received)
|
||||
border = 6
|
||||
self.imgCover.set_size_request(*((self.MAX_SIZE+border*2,)*2))
|
||||
self.imgCoverEventBox.connect('button-press-event',
|
||||
self.on_cover_popup_menu)
|
||||
|
||||
def on_cover_popup_menu(self, widget, event):
|
||||
if event.button != 3:
|
||||
return
|
||||
|
||||
menu = gtk.Menu()
|
||||
|
||||
item = gtk.ImageMenuItem(gtk.STOCK_OPEN)
|
||||
item.connect('activate', self.on_btnDownloadCover_clicked)
|
||||
menu.append(item)
|
||||
|
||||
item = gtk.ImageMenuItem(gtk.STOCK_REFRESH)
|
||||
item.connect('activate', self.on_btnClearCover_clicked)
|
||||
menu.append(item)
|
||||
|
||||
menu.show_all()
|
||||
menu.popup(None, None, None, event.button, event.time, None)
|
||||
|
||||
def on_btn_website_clicked(self, widget):
|
||||
util.open_website(self.channel.link)
|
||||
|
@ -77,17 +100,21 @@ class gPodderChannel(BuilderWidget):
|
|||
|
||||
if dlg.run() == gtk.RESPONSE_OK:
|
||||
url = dlg.get_uri()
|
||||
self.cover_downloader.replace_cover(self.channel, url)
|
||||
self.clear_cover_cache(self.channel.url)
|
||||
self.cover_downloader.replace_cover(self.channel, custom_url=url)
|
||||
|
||||
dlg.destroy()
|
||||
|
||||
def on_btnClearCover_clicked(self, widget):
|
||||
self.cover_downloader.replace_cover(self.channel)
|
||||
self.clear_cover_cache(self.channel.url)
|
||||
self.cover_downloader.replace_cover(self.channel, custom_url=False)
|
||||
|
||||
def cover_download_finished(self, channel_url, pixbuf):
|
||||
if pixbuf is not None:
|
||||
self.imgCover.set_from_pixbuf(pixbuf)
|
||||
self.gPodderChannel.show()
|
||||
def cover_download_finished(self, channel, pixbuf):
|
||||
def set_cover(channel, pixbuf):
|
||||
self.imgCover.set_from_pixbuf(self.scale_pixbuf(pixbuf))
|
||||
self.gPodderChannel.show()
|
||||
|
||||
util.idle_add(set_cover, channel, pixbuf)
|
||||
|
||||
def drag_data_received( self, widget, content, x, y, sel, ttype, time):
|
||||
files = sel.data.strip().split('\n')
|
||||
|
@ -98,7 +125,8 @@ class gPodderChannel(BuilderWidget):
|
|||
file = files[0]
|
||||
|
||||
if file.startswith('file://') or file.startswith('http://'):
|
||||
self.cover_downloader.replace_cover(self.channel, file)
|
||||
self.clear_cover_cache(self.channel.url)
|
||||
self.cover_downloader.replace_cover(self.channel, custom_url=file)
|
||||
return
|
||||
|
||||
self.show_message( _('You can only drop local files and http:// URLs here.'), _('Drag and drop'))
|
||||
|
@ -106,12 +134,31 @@ class gPodderChannel(BuilderWidget):
|
|||
def on_gPodderChannel_destroy(self, widget, *args):
|
||||
self.cover_downloader.unregister('cover-available', self.cover_download_finished)
|
||||
|
||||
def scale_pixbuf(self, pixbuf):
|
||||
|
||||
# Resize if width is too large
|
||||
if pixbuf.get_width() > self.MAX_SIZE:
|
||||
f = float(self.MAX_SIZE)/pixbuf.get_width()
|
||||
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
|
||||
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
|
||||
|
||||
# Resize if height is too large
|
||||
if pixbuf.get_height() > self.MAX_SIZE:
|
||||
f = float(self.MAX_SIZE)/pixbuf.get_height()
|
||||
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
|
||||
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
|
||||
|
||||
return pixbuf
|
||||
|
||||
def on_btnOK_clicked(self, widget, *args):
|
||||
self.channel.pause_subscription = self.cbSkipFeedUpdate.get_active()
|
||||
self.channel.rename(self.entryTitle.get_text())
|
||||
self.channel.auth_username = self.FeedUsername.get_text().strip()
|
||||
self.channel.auth_password = self.FeedPassword.get_text()
|
||||
|
||||
self.clear_cover_cache(self.channel.url)
|
||||
self.cover_downloader.request_cover(self.channel)
|
||||
|
||||
new_section = self.combo_section.child.get_text().strip()
|
||||
if self.channel.section != new_section:
|
||||
self.channel.section = new_section
|
||||
|
@ -121,8 +168,6 @@ class gPodderChannel(BuilderWidget):
|
|||
|
||||
self.channel.save()
|
||||
|
||||
self.cover_downloader.reload_cover_from_disk(self.channel)
|
||||
|
||||
self.gPodderChannel.destroy()
|
||||
|
||||
self.update_podcast_list_model(selected=True,
|
||||
|
|
|
@ -183,7 +183,6 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
self.podcast_list_model = PodcastListModel(self.cover_downloader)
|
||||
|
||||
self.cover_downloader.register('cover-available', self.cover_download_finished)
|
||||
self.cover_downloader.register('cover-removed', self.cover_file_removed)
|
||||
|
||||
# Source IDs for timeouts for search-as-you-type
|
||||
self._podcast_list_search_timeout = None
|
||||
|
@ -1533,7 +1532,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
|
||||
menu.append( gtk.SeparatorMenuItem())
|
||||
|
||||
item = gtk.ImageMenuItem(_('Podcast details'))
|
||||
item = gtk.ImageMenuItem(_('Podcast settings'))
|
||||
item.set_image(gtk.image_new_from_stock(gtk.STOCK_INFO, gtk.ICON_SIZE_MENU))
|
||||
item.connect('activate', self.on_itemEditChannel_activate)
|
||||
menu.append(item)
|
||||
|
@ -1552,21 +1551,14 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
|
||||
return True
|
||||
|
||||
def cover_file_removed(self, channel_url):
|
||||
"""
|
||||
The Cover Downloader calls this when a previously-
|
||||
available cover has been removed from the disk. We
|
||||
have to update our model to reflect this change.
|
||||
"""
|
||||
self.podcast_list_model.delete_cover_by_url(channel_url)
|
||||
|
||||
def cover_download_finished(self, channel, pixbuf):
|
||||
"""
|
||||
The Cover Downloader calls this when it has finished
|
||||
downloading (or registering, if already downloaded)
|
||||
a new channel cover, which is ready for displaying.
|
||||
"""
|
||||
self.podcast_list_model.add_cover_by_channel(channel, pixbuf)
|
||||
util.idle_add(self.podcast_list_model.add_cover_by_channel,
|
||||
channel, pixbuf)
|
||||
|
||||
def save_episodes_as_file(self, episodes):
|
||||
for episode in episodes:
|
||||
|
@ -2284,7 +2276,7 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
self.db.commit()
|
||||
|
||||
def _update_cover(self, channel):
|
||||
if channel is not None and not os.path.exists(channel.cover_file) and channel.image:
|
||||
if channel is not None:
|
||||
self.cover_downloader.request_cover(channel)
|
||||
|
||||
def show_update_feeds_buttons(self):
|
||||
|
@ -2877,11 +2869,12 @@ class gPodder(BuilderWidget, dbus.service.Object):
|
|||
self.show_message( message, title, widget=self.treeChannels)
|
||||
return
|
||||
|
||||
gPodderChannel(self.main_window, \
|
||||
channel=self.active_channel, \
|
||||
update_podcast_list_model=self.update_podcast_list_model, \
|
||||
cover_downloader=self.cover_downloader, \
|
||||
sections=set(c.section for c in self.channels))
|
||||
gPodderChannel(self.main_window,
|
||||
channel=self.active_channel,
|
||||
update_podcast_list_model=self.update_podcast_list_model,
|
||||
cover_downloader=self.cover_downloader,
|
||||
sections=set(c.section for c in self.channels),
|
||||
clear_cover_cache=self.podcast_list_model.clear_cover_cache)
|
||||
|
||||
def on_itemMassUnsubscribe_activate(self, item=None):
|
||||
columns = (
|
||||
|
|
|
@ -30,6 +30,7 @@ _ = gpodder.gettext
|
|||
from gpodder import util
|
||||
from gpodder import model
|
||||
from gpodder import query
|
||||
from gpodder import coverart
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -429,7 +430,10 @@ class PodcastChannelProxy(object):
|
|||
self.url = ''
|
||||
self.section = ''
|
||||
self.id = None
|
||||
self.cover_file = util.podcast_image_filename('podcast-all.png')
|
||||
self.cover_file = coverart.CoverDownloader.ALL_EPISODES_ID
|
||||
self.cover_url = None
|
||||
self.auth_username = None
|
||||
self.auth_password = None
|
||||
self.pause_subscription = False
|
||||
self.auto_archive_episodes = False
|
||||
|
||||
|
@ -779,6 +783,11 @@ class PodcastListModel(gtk.ListStore):
|
|||
self.C_HAS_EPISODES, total > 0, \
|
||||
self.C_DOWNLOADS, downloaded)
|
||||
|
||||
def clear_cover_cache(self, podcast_url):
|
||||
if podcast_url in self._cover_cache:
|
||||
logger.info('Clearing cover from cache: %s', podcast_url)
|
||||
del self._cover_cache[podcast_url]
|
||||
|
||||
def add_cover_by_channel(self, channel, pixbuf):
|
||||
# Resize and add the new cover image
|
||||
pixbuf = self._resize_pixbuf(channel.url, pixbuf)
|
||||
|
@ -791,14 +800,3 @@ class PodcastListModel(gtk.ListStore):
|
|||
row[self.C_COVER] = pixbuf
|
||||
break
|
||||
|
||||
def delete_cover_by_url(self, url):
|
||||
# Remove the cover from the model
|
||||
for row in self:
|
||||
if row[self.C_URL] == url:
|
||||
row[self.C_COVER] = None
|
||||
break
|
||||
|
||||
# Remove the cover from the cache
|
||||
if url in self._cover_cache:
|
||||
del self._cover_cache[url]
|
||||
|
||||
|
|
|
@ -32,11 +32,9 @@ import logging
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
from gpodder import util
|
||||
from gpodder import youtube
|
||||
from gpodder import coverart
|
||||
|
||||
import gtk
|
||||
import os
|
||||
import urlparse
|
||||
import threading
|
||||
|
||||
|
||||
|
@ -50,10 +48,8 @@ class CoverDownloader(ObservableService):
|
|||
'cover-available' message (via the ObservableService).
|
||||
"""
|
||||
|
||||
# Maximum width/height of the cover in pixels
|
||||
MAX_SIZE = 360
|
||||
|
||||
def __init__(self):
|
||||
self.downloader = coverart.CoverDownloader()
|
||||
signal_names = ['cover-available', 'cover-removed']
|
||||
ObservableService.__init__(self, signal_names)
|
||||
|
||||
|
@ -97,85 +93,39 @@ class CoverDownloader(ObservableService):
|
|||
(url, pixbuf) = self.__get_cover(channel, custom_url, False, avoid_downloading)
|
||||
return pixbuf
|
||||
|
||||
def remove_cover(self, channel):
|
||||
"""
|
||||
Removes the current cover for the channel
|
||||
so that a new one is downloaded the next
|
||||
time we request the channel cover.
|
||||
"""
|
||||
util.delete_file(channel.cover_file)
|
||||
self.notify('cover-removed', channel.url)
|
||||
|
||||
def replace_cover(self, channel, custom_url=None):
|
||||
"""
|
||||
This is a convenience function that deletes
|
||||
the current cover file and requests a new
|
||||
cover from the URL specified.
|
||||
"""
|
||||
self.remove_cover(channel)
|
||||
self.request_cover(channel, custom_url)
|
||||
|
||||
def reload_cover_from_disk(self, channel):
|
||||
self.notify('cover-removed', channel.url)
|
||||
self.request_cover(channel, None, True)
|
||||
|
||||
def get_default_cover(self, channel):
|
||||
filename = util.cover_fallback_filename(channel.title)
|
||||
return gtk.gdk.pixbuf_new_from_file(filename)
|
||||
|
||||
def __get_cover(self, channel, url, async=False, avoid_downloading=False):
|
||||
if not async and avoid_downloading and not os.path.exists(channel.cover_file):
|
||||
return (channel.url, self.get_default_cover(channel))
|
||||
def get_filename():
|
||||
return self.downloader.get_cover(channel.cover_file,
|
||||
url or channel.cover_url, channel.url, channel.title,
|
||||
channel.auth_username, channel.auth_password,
|
||||
not avoid_downloading)
|
||||
|
||||
if not os.path.exists(channel.cover_file):
|
||||
if url is None and channel.cover_url is not None:
|
||||
# We have to use authenticate_url, because password-protected
|
||||
# feeds might keep their cover art also protected (bug 1521)
|
||||
url = channel.authenticate_url(channel.cover_url)
|
||||
|
||||
new_url = youtube.get_real_cover(channel.url)
|
||||
if new_url is not None:
|
||||
url = new_url
|
||||
|
||||
if url is not None:
|
||||
image_data = None
|
||||
try:
|
||||
logger.debug('Trying to download: %s', url)
|
||||
|
||||
image_data = util.urlopen(url).read()
|
||||
except:
|
||||
logger.warn('Cannot get image from %s', url, exc_info=True)
|
||||
|
||||
if image_data is not None:
|
||||
logger.debug('Saving image data to %s', channel.cover_file)
|
||||
try:
|
||||
fp = open(channel.cover_file, 'wb')
|
||||
fp.write(image_data)
|
||||
fp.close()
|
||||
except IOError, ioe:
|
||||
logger.error('Cannot save image due to I/O error', exc_info=True)
|
||||
if url is not None:
|
||||
filename = get_filename()
|
||||
if filename.startswith(channel.cover_file):
|
||||
logger.info('Replacing cover: %s', filename)
|
||||
util.delete_file(filename)
|
||||
|
||||
filename = get_filename()
|
||||
pixbuf = None
|
||||
if os.path.exists(channel.cover_file):
|
||||
try:
|
||||
pixbuf = gtk.gdk.pixbuf_new_from_file(channel.cover_file.decode(util.encoding, 'ignore'))
|
||||
except:
|
||||
logger.error('Data error while loading %s', channel.cover_file)
|
||||
|
||||
if pixbuf is None:
|
||||
pixbuf = self.get_default_cover(channel)
|
||||
|
||||
# Resize if width is too large
|
||||
if pixbuf.get_width() > self.MAX_SIZE:
|
||||
f = float(self.MAX_SIZE)/pixbuf.get_width()
|
||||
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
|
||||
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
|
||||
|
||||
# Resize if height is too large
|
||||
if pixbuf.get_height() > self.MAX_SIZE:
|
||||
f = float(self.MAX_SIZE)/pixbuf.get_height()
|
||||
(width, height) = (int(pixbuf.get_width()*f), int(pixbuf.get_height()*f))
|
||||
pixbuf = pixbuf.scale_simple(width, height, gtk.gdk.INTERP_BILINEAR)
|
||||
try:
|
||||
pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
|
||||
except Exception, e:
|
||||
logger.warn('Cannot load cover art', exc_info=True)
|
||||
if filename.startswith(channel.cover_file):
|
||||
logger.info('Deleting broken cover: %s', filename)
|
||||
util.delete_file(filename)
|
||||
filename = get_filename()
|
||||
pixbuf = gtk.gdk.pixbuf_new_from_file(filename)
|
||||
|
||||
if async:
|
||||
self.notify('cover-available', channel, pixbuf)
|
||||
|
|
|
@ -30,6 +30,7 @@ from gpodder import feedcore
|
|||
from gpodder import youtube
|
||||
from gpodder import vimeo
|
||||
from gpodder import schema
|
||||
from gpodder import coverart
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -834,9 +835,13 @@ class PodcastChannel(PodcastModelObject):
|
|||
existing_files = set(filename for filename in \
|
||||
glob.glob(os.path.join(self.save_dir, '*')) \
|
||||
if not filename.endswith('.partial'))
|
||||
external_files = existing_files.difference(list(known_files) + \
|
||||
[os.path.join(self.save_dir, x) \
|
||||
for x in ('folder.jpg', 'Unknown')])
|
||||
|
||||
ignore_files = ['folder'+ext for ext in
|
||||
coverart.CoverDownloader.EXTENSIONS] + ['Unknown']
|
||||
|
||||
external_files = existing_files.difference(list(known_files) +
|
||||
[os.path.join(self.save_dir, ignore_file)
|
||||
for ignore_file in ignore_files])
|
||||
if not external_files:
|
||||
return
|
||||
|
||||
|
@ -1344,7 +1349,7 @@ class PodcastChannel(PodcastModelObject):
|
|||
|
||||
@property
|
||||
def cover_file(self):
|
||||
return os.path.join(self.save_dir, 'folder.jpg')
|
||||
return os.path.join(self.save_dir, 'folder')
|
||||
|
||||
|
||||
class Model(object):
|
||||
|
|
|
@ -23,8 +23,8 @@ from PySide.QtDeclarative import QDeclarativeImageProvider
|
|||
|
||||
import gpodder
|
||||
|
||||
from gpodder import youtube
|
||||
from gpodder import util
|
||||
from gpodder import coverart
|
||||
|
||||
import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
@ -37,6 +37,7 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
|
|||
|
||||
def __init__(self):
|
||||
QDeclarativeImageProvider.__init__(self, self.IMAGE_TYPE)
|
||||
self.downloader = coverart.CoverDownloader()
|
||||
self._cache = {}
|
||||
|
||||
def requestImage(self, id, size, requestedSize):
|
||||
|
@ -44,49 +45,26 @@ class LocalCachedImageProvider(QDeclarativeImageProvider):
|
|||
if key in self._cache:
|
||||
return self._cache[key]
|
||||
|
||||
filename, cover_url, url, title = (urllib.unquote(x) for x in id.split('|'))
|
||||
cover_file, cover_url, podcast_url, podcast_title = (urllib.unquote(x)
|
||||
for x in id.split('|'))
|
||||
|
||||
if filename == 'gpodder:episode-subset-view':
|
||||
filename = util.podcast_image_filename('podcast-all.png', big=True)
|
||||
def get_filename():
|
||||
return self.downloader.get_cover(cover_file, cover_url,
|
||||
podcast_url, podcast_title, None, None, True)
|
||||
|
||||
if '' in (filename, cover_url, url):
|
||||
if filename == '' or not os.path.exists(filename):
|
||||
filename = util.cover_fallback_filename(title, big=True)
|
||||
|
||||
data = None
|
||||
|
||||
if os.path.exists(filename):
|
||||
data = open(filename, 'rb').read()
|
||||
if data == '':
|
||||
util.delete_file(filename)
|
||||
filename = util.cover_fallback_filename(title, big=True)
|
||||
data = open(filename, 'rb').read()
|
||||
|
||||
write_to_disk = True
|
||||
if data is None or data == '' and cover_url:
|
||||
try:
|
||||
yt_url = youtube.get_real_cover(url)
|
||||
if yt_url is not None:
|
||||
cover_url = yt_url
|
||||
data = util.urlopen(cover_url).read()
|
||||
except Exception, e:
|
||||
logger.error('Error downloading cover: %s', e, exc_info=True)
|
||||
filename = util.cover_fallback_filename(title, big=True)
|
||||
data = open(filename, 'rb').read()
|
||||
write_to_disk = False
|
||||
|
||||
if write_to_disk:
|
||||
fp = open(filename, 'wb')
|
||||
fp.write(data)
|
||||
fp.close()
|
||||
filename = get_filename()
|
||||
|
||||
image = QImage()
|
||||
image.loadFromData(data)
|
||||
if image.isNull():
|
||||
return image
|
||||
else:
|
||||
self._cache[key] = image.scaled(requestedSize, \
|
||||
Qt.KeepAspectRatioByExpanding, \
|
||||
Qt.SmoothTransformation)
|
||||
return self._cache[key]
|
||||
if not image.load(filename):
|
||||
if filename.startswith(cover_file):
|
||||
logger.info('Deleting broken cover art: %s', filename)
|
||||
util.delete_file(filename)
|
||||
image.load(get_filename())
|
||||
|
||||
if not image.isNull():
|
||||
self._cache[key] = image.scaled(requestedSize,
|
||||
Qt.KeepAspectRatioByExpanding,
|
||||
Qt.SmoothTransformation)
|
||||
|
||||
return self._cache[key]
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ from gpodder import youtube
|
|||
from gpodder import download
|
||||
from gpodder import query
|
||||
from gpodder import model
|
||||
from gpodder import coverart
|
||||
|
||||
import threading
|
||||
import os
|
||||
|
@ -399,7 +400,7 @@ class EpisodeSubsetView(QObject):
|
|||
return convert('')
|
||||
|
||||
def _return_cover(self):
|
||||
return convert('gpodder:episode-subset-view')
|
||||
return convert(coverart.CoverDownloader.ALL_EPISODES_ID)
|
||||
|
||||
qupdating = Property(bool, _return_false, notify=changed)
|
||||
qurl = Property(unicode, _return_empty, notify=changed)
|
||||
|
|
|
@ -1575,16 +1575,6 @@ def rename_episode_file(self, episode, filename):
|
|||
episode.db.commit()
|
||||
|
||||
|
||||
def podcast_image_filename(basename, big=False):
|
||||
if big:
|
||||
return os.path.join(gpodder.images_folder, 'big', basename)
|
||||
|
||||
return os.path.join(gpodder.images_folder, basename)
|
||||
|
||||
def cover_fallback_filename(title, big=False):
|
||||
return podcast_image_filename('podcast-%d.png' % (hash(title)%5), big)
|
||||
|
||||
|
||||
def get_update_info(url='http://gpodder.org/downloads'):
|
||||
"""
|
||||
Get up to date release information from gpodder.org.
|
||||
|
|