Internet radio browser GUI for music/video streams from various directory services.

⌈⌋ ⎇ branch:  streamtuner2


Check-in [8c7b4f2662]

Overview
Comment:Remove a few options from configuration dialog; now available in [feature] plugin tabs for favicon module. Favicon module exposes google_station_homepage, and retrieval methods, but no longer `conf.show_favicons` (which is decided on just by having the plugin enabled - or not.) Introduce conf.auto_save_stations for favicon/DND plugin.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 8c7b4f2662b5bbb2c6d805c11f358a794751e4ac
User & Date: mario on 2015-05-14 18:41:05
Other Links: manifest | tags
Context
2015-05-14
20:01
Add 0install pkg description wrapper for testing. check-in: 68786b48a4 user: mario tags: trunk
18:41
Remove a few options from configuration dialog; now available in [feature] plugin tabs for favicon module. Favicon module exposes google_station_homepage, and retrieval methods, but no longer `conf.show_favicons` (which is decided on just by having the plugin enabled - or not.) Introduce conf.auto_save_stations for favicon/DND plugin. check-in: 8c7b4f2662 user: mario tags: trunk
18:39
Default to encoding=utf-8 for jamendo API. check-in: 0a7528326f user: mario tags: trunk
Changes

Modified channels/__init__.py from [ef7bb71538] to [f444708642].

180
181
182
183
184
185
186
187




188
189
190
191
192
193
194
180
181
182
183
184
185
186

187
188
189
190
191
192
193
194
195
196
197







-
+
+
+
+







        
        # add to main menu
        uikit.add_menu([parent.channelmenuitems], self.meta["title"], lambda w: parent.channel_switch_by_name(self.module) or 1)


    # Just wraps uikit.columns() to retain liststore, rowmap and pix_entry
    def columns(self, entries=None):
        self.ls, self.rowmap, self.pix_entry = uikit.columns(self.gtk_list, self.datamap, entries, show_favicons=conf.show_favicons)
        self.ls, self.rowmap, self.pix_entry = uikit.columns(
            self.gtk_list, self.datamap, entries, show_favicons=True
        )
        # no longer using `conf.show_favicons`


    # Statusbar stub (defers to parent/main window, if in GUI mode)
    def status(self, *args, **kw):
        if self.parent: self.parent.status(*args, **kw)
        else: log.INFO("status():", *v)

Modified channels/favicon.py from [2be50aefaa] to [cef590a4be].

1
2
3
4
5

6
7
8

9
10
11
12
13
14




15
16
17
18



19
20
21


22
23
24
25
26
27






28
29
30
31
32
33
34
1
2
3
4
5
6
7
8

9

10
11
12
13
14
15
16
17
18
19



20
21
22
23


24
25

26




27
28
29
30
31
32
33
34
35
36
37
38
39





+


-
+
-





+
+
+
+

-
-
-
+
+
+

-
-
+
+
-

-
-
-
-
+
+
+
+
+
+







# encoding: utf-8
# api: streamtuner2
# title: Favicons
# description: Display station favicons/logos. Instantly download them when â–¸playing.
# config:
#    { name: load_favicon, type: bool, value: 1, description: "Load favicon instantly when â–¸playing a station.", color: yellow }
#    { name: favicon_google_first, type: bool, value: 1, description: "Prefer faster Google favicon to PNG conversion service." }
#    { name: favicon_delete_stub , type: bool, value: 1, description: "Don't accept any placeholder favicons." }
#    [ main-name: google_homepage ]
#    { name: google_homepage, type: bool, value: 0, description: "Google missing station homepages right away." }
#    [ main-name: load_favicon ]
# type: feature
# category: ui
# version: 1.9
# depends: streamtuner2 >= 2.1.9, python:pil
# priority: standard
# png:
#   iVBORw0KGgoAAAANSUhEUgAAABYAAAAWBAMAAAA2mnEIAAAAJ1BMVEUAAACwDw5oKh1RRU5OTSCOTxp0Um9zcyFUhSXsbwChdp/lgCNbrA7VFTQPAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHU
#   gAAAAJcEhZcwAACxMAAAsTAQCanBgAAAAHdElNRQffBQ4ENQJMtfdfAAAAmUlEQVQY02NgcAECYxBgYODeOXPmTKtVQLCAwXsmjL2YQRPINDNGsFclI7GXQdmzZ87MSoOyI0pnpgHVLAOy1c+c
#   mTkzeFWioBSUbQZkiy1mcPFpCXUxTksTFEtm8Ojp6OhQVDJWVFJi8DkDBIIgIARhKyKx3c8g2GfOBCKxFeHspg6EmiZFJDbEHB44W4CBwQNor5MSEDAAAGcoaQmD1t8TAAAAAElFTkSuQmCC
#
# This module fetches a favicon for each station, or a small banner
# or logo for some channel modules. It converts .ico image files and
# sanitizes .png or .jpeg images even prior display.
# This module fetches a favicon for each station. Some channels
# provide small logos/banners even. It sanitizes and converts
# any .ico/.png/.jpeg file prior display.
# 
# It prepares cache files in ~/.config/streamtuner2/icons/ in silent
# agreement with the station list display logic. Either uses station
# Cache files are kept in ~/.config/streamtuner2/icons/ where
# the station column display picks them up form.
# row["homepage"] or row["img"] URLs from any entry.
#
# While it can often discover favicons directly from station homepages,
# it's often speedier to use the Google PNG conversion service. Both
# depend on a recent Pillow2 python module (superseding the PIL module).
# Else may display images with fragments if converted from ICO files.
# While it can often discover favicons directly from station
# homepages, it's mostly speedier to use the PNG conversion
# service from Google.
# Both methods depend on a recent Pillow2 python module (that
# superseded the PIL module). Else icon display may have some
# transparency fragments.


import os, os.path
from io import BytesIO
import re
import channels
from config import *
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83

84
85



86
87
88
89
90
91
92
93
94
95
96
97
98

99
100
101
102
103
104

105
106
107
108
109
110



111
112
113
114
115
116
117

118
119
120
121
122

123
124
125
126
127
128
129
58
59
60
61
62
63
64








65
66
67
68
69
70
71
72
73
74
75
76
77
78
79

80
81
82
83
84
85
86
87
88
89
90
91
92


93



94
95
96
97
98
99

100
101
102
103
104
105

106
107
108
109
110
111
112
113
114

115
116
117
118
119

120
121
122
123
124
125
126
127







-
-
-
-
-
-
-
-















-
+


+
+
+







-
-

-
-
-
+





-
+





-
+
+
+






-
+




-
+







#    station list updates.
#
#  · uikit.columns() merely checks row["favicon"] for file existence
#    when redrawing a station list.
#
#  · main calls .update_playing() on hooks["play"],
#    or .update_all() per menu command
#
#  · urllib is no longer required. Using just ahttp/requests API now.
#
#  · Might need unhtml() utility from channels/__init__ later..
#
#  · Still need to consolidate config options → Move main favicon
#    options here?
#



# Hook up as feature plugin
#
class favicon(object):

    # plugin attributes
    module = "favicon"
    meta = plugin_meta()
    
    
    # Register with main
    def __init__(self, parent):
    
        # Reference main, and register hook
        # Reference main, and register station .play() hook for conf.load_favicon
        self.parent, self.main = parent, parent
        parent.hooks["play"].append(self.update_playing)

        # Register in channel/streams updating pipeline (to predefine row["favicon"] filename from `homepage` or `img`)
        channels.GenericChannel.prepare_filters.append(self.prepare_filter_favicon)

        # Prepare favicon cache directory
        conf.icon_dir = conf.dir + "/icons"
        if not os.path.exists(conf.icon_dir):
            os.mkdir(conf.icon_dir)
            open(icon_dir+"/.nobackup", "a").close()
            
        # Hook into channel/streams updating pipine
        channels.GenericChannel.prepare_filters.append(self.prepare_filter_favicon)



    # Main callback: update favicon cache for complete list of station rows
    # Main menu "Update favicons": update favicon cache for complete list of station rows
    def update_all(self, *args, **kwargs):
        #kwargs[pixstore] = self.parent.channel()._ls, ...
        self.parent.thread(self.update_rows, *args, **kwargs)


    # Main callback for a single play() event
    # Main [â–¸play] event for a single station
    def update_playing(self, row, pixstore=None, channel=None, **x):

        # Homepage search
        if conf.google_homepage and not len(row.get("homepage", "")):
            found = google_find_homepage(row)
            # could call channel.save() now to preserve found homepage URL
            # Save channel list right away to preserve found homepage URL
            if found and conf.auto_save_stations:
                channel.save()
        else:
            found = False

        # Favicon only for currently playing station
        if conf.load_favicon:
            if row.get("homepage") or row.get("img"):
                self.update_all([row], pixstore=pixstore, always_update=found)
                self.parent.thread(self.update_rows, [row], pixstore=pixstore, always_update=found)

      
    # Run through rows[] to update "favicon" from "homepage" or "img",
    # optionally display new image right away in ListStore
    def update_rows(self, entries, pixstore=None, always_update=False):
    def update_rows(self, entries, pixstore=None, always_update=False, **x):
        for i,row in enumerate(entries):
            ok = False

            # Try just once
            if row.get("homepage") in tried_urls:
                continue
            # Ignore existing ["favicon"] filename
193
194
195
196
197
198
199

200
201
202
203
204
205
206
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205







+







            except Exception as e:
                log.ERR("Update_pixstore image", fn, "error:", e)


    # Run after any channel .update_streams() to populate row["favicon"]
    # from `homepage` or `img` url.
    def prepare_filter_favicon(self, row):
        # if conf.show_favicons:  (do we still need that?)
        row["favicon"] = row_to_fn(row)




#--- somewhat unrelated ---
#

Modified config.py from [e3065a7831] to [a293720f07].

136
137
138
139
140
141
142

143
144
145
146
147
148
149
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150







+







        self.max_streams = "500"
        self.show_bookmarks = 1
        self.show_favicons = 1
        self.load_favicon = 1
        self.heuristic_bookmark_update = 0
        self.retain_deleted = 0
        self.auto_save_appstate = 1
        self.auto_save_stations = 0
        self.reuse_m3u = 1
        self.playlist_asis = 0
        self.google_homepage = 0
        self.windows = platform.system()=="Windows"
        self.pyquery = 1
        self.debug = 0

Modified gtk3.xml.gz from [c6569919cf] to [924e5121ad].

cannot compute difference between binary files