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

βŒˆβŒ‹ βŽ‡ branch:  streamtuner2


Diff

Differences From Artifact [ac1734feec]:

To Artifact [9a39b12b72]:


46
47
48
49
50
51
52
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
46
47
48
49
50
51
52

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







-
+

-
-








-
-
+











-
-
-
-
+
+
+
+


-
-
-
-
-
-
+
+
+
+
+
+





-
+






+
+
+
+
+
+
+
+
+







]



# generic channel module                            ---------------------------------------
class GenericChannel(object):

    # desc
    # control attributes
    meta = { "config": [] }
    config = []
    homepage = "http://fossil.include-once.org/streamtuner2/"
    base_url = ""
    listformat = "pls"
    audioformat = "audio/mpeg" # fallback value
    has_search = False

    # categories
    categories = ["empty", ]
    catmap = {}
    current = None
    shown = None     # last selected entry in stream list, also indicator if notebook tab has been selected once / stream list of current category been displayed yet
    shown = None      # last selected entry in stream list, also indicator if notebook tab has been selected once / stream list of current category been displayed yet

    # gui + data
    streams = {}      # Station list dict, associates each genre to a list of stream rows
    gtk_list = None   # Gtk widget for station treeview
    gtk_cat = None    # Gtk widget for category columns

    # mapping of stream{} data into gtk treeview/treestore representation
    datamap = [
       # coltitle   width	[ datasrc key, type, renderer, attrs ]	[cellrenderer2], ...
       ["",		20,	["state",	str,  "pixbuf",	{}],	],
       ["Genre",	65,	['genre',	str,	"t",	{}],	],
       ["Station Title",275,["title",	str,    "text",	{"strikethrough":11, "cell-background":12, "cell-background-set":13}],  ["favicon", gtk.gdk.Pixbuf, "pixbuf", {}], ],
       ["Now Playing",185,	["playing",	str,	"text",	{"strikethrough":11}],	],                                                                             #{"width":20}
       ["Listeners", 45,	["listeners",	int,	"t",	{"strikethrough":11}],	],
      #["Max",	45,	["max",		int,	"t",	{}],	],
       ["Station Title",275,	["title",	str,    "text",	{"strikethrough":11, "cell-background":12, "cell-background-set":13}],  ["favicon", gtk.gdk.Pixbuf, "pixbuf", {}], ],
       ["Now Playing",	185,	["playing",	str,	"text",	{"strikethrough":11}],	],                                                                             #{"width":20}
       ["Listeners", 	45,	["listeners",	int,	"t",	{"strikethrough":11}],	],
      #["Max",		45,	["max",		int,	"t",	{}],	],
       ["Bitrate",	35,	["bitrate",	int,	"t",	{}],	],
       ["Homepage",	160,	["homepage",	str,	"t",	{"underline":10}],	],
       [False,	25,	["url",		str,	"t",	{"strikethrough":11}],	],
       [False,	0,	["format",	str,	None,	{}],	],
       [False,	0,	["favourite",	bool,	None,	{}],	],
       [False,	0,	["deleted",	bool,	None,	{}],	],
       [False,	0,	["search_col",	str,	None,	{}],	],
       [False,	0,	["search_set",	bool,	None,	{}],	],
       [False,		25,	["url",		str,	"t",	{"strikethrough":11}],	],
       ["Format",	20,	["format",	str,	None,	{}],	],
       [False,		0,	["favourite",	bool,	None,	{}],	],
       [False,		0,	["deleted",	bool,	None,	{}],	],
       [False,		0,	["search_col",	str,	None,	{}],	],
       [False,		0,	["search_set",	bool,	None,	{}],	],
    ]
    rowmap = []   # [state,genre,title,...] field enumeration still needed separately
    titles = {}   # for easier adapting of column titles in datamap

    # for empty grouping / categories
    placeholder = [dict(genre="./.", title="Subcategory placeholder", playing="./.", url="none:", listeners=0, bitrate=0, homepage="", state="gtk-folder")]
    placeholder = [dict(genre="./.", title="Subcategory placeholder", playing="./.", url="none:", listeners=0, bitrate=0, homepage="", state="gtkfolder")]
    empty_stub = [dict(genre="./.", title="No categories found (HTTP error)", playing="Try Channel→Reload Categories later..", url="none:", listeners=0, bitrate=0, homepage="", state="gtk-stop")]
    nothing_found = [dict(genre="./.", title="No contents found on directory server", playing="Notice", listeners=0, bitrate=0, state="gtk-info")]
    
    # regex            
    rx_www_url = re.compile("""(www(\.\w+[\w-]+){2,}|(\w+[\w-]+[ ]?\.)+(com|FM|net|org|de|PL|fr|uk))""", re.I)

    __current = None
    @property
    def current(self):
        return self.__current
    @current.setter
    def current(self, newcat):
        print "{}.current:={} ← from {}".format(self.module, newcat, [inspect.stack()[x][3] for x in range(1,4)])
        self.__current = newcat
        return self.__current


    #--------------------------- initialization --------------------------------


    # constructor
    def __init__(self, parent=None):
227
228
229
230
231
232
233
234





235
236

237
238
239
240
241
242
243
233
234
235
236
237
238
239
240
241
242
243
244
245
246

247
248
249
250
251
252
253
254








+
+
+
+
+

-
+







                row[0] = title


    # switch stream category,
    # load data,
    # update treeview content
    def load(self, category, force=False):

        # called to early
        if not category:
            print "load(None)"
            return

        # get data from cache or download
        if (force or not category in self.streams):
        if force or not category in self.streams:
            __print__(dbg.PROC, "load", "update_streams")
            self.status("Updating streams...")
            self.status(-0.1)
            if category == "empty":
                new_streams = self.empty_stub
            else:
                new_streams = self.update_streams(category)
405
406
407
408
409
410
411
412
413


414
415
416
417
418
419

420
421
422
423
424





425
426
427
428
429
430
431
416
417
418
419
420
421
422


423
424
425
426
427
428
429
430
431





432
433
434
435
436
437
438
439
440
441
442
443







-
-
+
+






+
-
-
-
-
-
+
+
+
+
+







                self.reload_categories()
            except:
                __print__(dbg.ERR, "HTTP error or extraction failure.")
                self.categories = ["empty"]
            self.display_categories()

        # Select first category
        self.current = self.str_from_struct(self.categories) or None
        __print__(dbg.STAT, self.module, "β†’ first_show(); use first category as current =", self.current)
        #self.current = self.str_from_struct(self.categories) or None
        #__print__(dbg.STAT, self.module, "β†’ first_show(); use first category as current =", self.current)
        try:
            self.load(self.current)
        except:
            pass
    
        # put selection/cursor on last position
        if self.shown:
        __print__(dbg.STAT, self.module+".first_show()", "select last known category treelist position =", self.shown)
        try:
            self.gtk_list.get_selection().select_path(self.shown)
        except:
            pass
            __print__(dbg.STAT, self.module+".first_show()", "select last known category treelist position =", self.shown)
            try:
                self.gtk_list.get_selection().select_path(self.shown)
            except:
                pass
            
        # Invoke only once
        self.shown = 55555


    # Retrieve first list value, or key from dict (-- used to get first category on init)
    def str_from_struct(self, d):
461
462
463
464
465
466
467
468

469
470
471
472
473
474
475
473
474
475
476
477
478
479

480
481
482
483
484
485
486
487







-
+







        uikit.tree(self.gtk_cat, self.categories, title="Category", icon=gtk.STOCK_OPEN);

        # if it's a short list of categories, there's probably subfolders
        if len(self.categories) < 20:
            self.gtk_cat.expand_all()
            
        # select any first element
        self.gtk_cat.get_selection().select_path("0") #set_cursor
        #self.gtk_cat.get_selection().select_path("0") #set_cursor
        self.currentcat()

            
    # selected category
    def currentcat(self):
        (model, iter) = self.gtk_cat.get_selection().get_selected()
        if (type(iter) == gtk.TreeIter):