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

⌈⌋ ⎇ branch:  streamtuner2


Diff

Differences From Artifact [7cf3ceafbb]:

To Artifact [bdefdbfe53]:


26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

51
52
53
54


55
56
57
58
59


60
61
62
63
64
65
66
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50

51
52
53


54
55
56
57
58
59

60
61
62
63
64
65
66
67
68







+

















-
+


-
-
+
+




-
+
+







# depend on a recent Pillow2 python module (superseding the PIL module).
# Else may display images with fragments if converted from ICO files.


import os, os.path
from io import BytesIO
import re
import channels
from config import *
import ahttp
from PIL import Image
from uikit import gtk
#import traceback


# Ensure that we don't try to download a single favicon twice per session.
# If it's not available the first time, we won't get it after switching
# stations back and forth either. So URLs are skipped simply.
tried_urls = []



# Has recently been rewritten, is somewhat less entangled with other
# modules now:
#
#  · GenericChannel presets row["favicon"] with cache image filename
#  · GenericChannel presets row["favicon"] with cache image filenames
#    in any case. It uses row["homepage"] or row["img"] as template.
#
#  · The url-to-filename shortening functionality is therefore shared.
#    GenericChannel.prepare() duplicates all row_to_fn() logic.
#  · The url-to-filename shortening functionality in GenChan.prepare()
#    is identical to that in row_to_fn() here.
#
#  · uikit.columns() merely checks row["favicon"] for file existence
#    when redrawing a station list.
#
#  · main.play() only calls .update_playing() or .update_all()
#  · main only calls .update_playing() via hooks["play"], and the menu
#    invokes .update_all()
#
#  · 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?
85
86
87
88
89
90
91



92
93
94
95
96
97
98
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103







+
+
+







        parent.hooks["play"].append(self.update_playing)

        # 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
    def update_all(self, *args, **kwargs):
        #kwargs[pixstore] = self.parent.channel()._ls, ...
        self.parent.thread(self.update_rows, *args, **kwargs)
178
179
180
181
182
183
184



185
186
187
188
189
190
191
192
193
194

195
196
197
198
199
200
201
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201

202
203
204
205
206
207
208
209







+
+
+









-
+







            try:
                p = gtk.gdk.pixbuf_new_from_file(fn)
                ls[i][pix_entry] = p
            except Exception as e:
                log.ERR("Update_pixstore image", fn, "error:", e)


    # Run after any channel .update_streams() to populate "favicon"
    def prepare_filter_favicon(self, row):
        row["favicon"] = row_to_fn(row)


#--- somewhat unrelated ---
#
# Should become a distinct feature plugin. - It just depends on correct
# invocation order for both plugins to interact.
# Googling is often blocked anyway, because this is clearly a bot request.
# And requests are tagged with ?client=streamtuner2 still purposefully.
# 
def google_find_homepage(self, row):
def google_find_homepage(row):
    """ Searches for missing homepage URL via Google. """
    if row.get("url") not in tried_urls:
        tried_urls.append(row.get("url"))

    if row.get("title"):
        rx_t = re.compile('^(([^-:]+.?){1,2})')
        rx_u = re.compile(r'/url\?q=(https?://[^"&/]+)')
214
215
216
217
218
219
220


221
222
223
224
225
226


227
228
229
230
231
232
233
222
223
224
225
226
227
228
229
230
231
232
233
234


235
236
237
238
239
240
241
242
243







+
+




-
-
+
+







    pass
#-----------------




# Convert row["img"] or row["homepage"] into local favicon cache filename
rx_strip_proto = re.compile("^\w+://|/$")
rx_non_wordchr = re.compile("[^\w._-]")
def row_to_fn(row):
    url = row.get("img") or row.get("homepage") or None
    if url:
         url = url.lower()
         url = re.sub("^\w+://|/$", "", url)   # strip proto:// and trailing /
         url = re.sub("[^\w._-]", "_", url)    # remove any non-word characters
         url = rx_strip_proto.sub("", url)     # strip proto:// and trailing /
         url = rx_non_wordchr.sub("_", url)    # remove any non-word characters
         url = "{}/{}.png".format(conf.icon_dir, url)
    return url


    
# Copy banner row["img"] into icons/ directory
def banner_localcopy(url, fn):