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
]



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

    # desc
    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

    # 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",	{}],	],
       ["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,	{}],	],
    ]
    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")]
    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)












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


    # constructor
    def __init__(self, parent=None):







|

<
<








<
|











|
|
|
|


|
|
|
|
|
|





|






>
>
>
>
>
>
>
>
>







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):

    # control attributes
    meta = { "config": [] }


    base_url = ""
    listformat = "pls"
    audioformat = "audio/mpeg" # fallback value
    has_search = False

    # categories
    categories = ["empty", ]
    catmap = {}

    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",	{}],	],
       ["Bitrate",	35,	["bitrate",	int,	"t",	{}],	],
       ["Homepage",	160,	["homepage",	str,	"t",	{"underline":10}],	],
       [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="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
                row[0] = title


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






        # get data from cache or download
        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)








>
>
>
>
>

|







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:
            __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
                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)
        try:
            self.load(self.current)
        except:
            pass
    
        # put selection/cursor on last position

        __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):







|
|






>
|
|
|
|
|







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)
        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
            
        # 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
        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.currentcat()

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







|







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.currentcat()

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