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

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


Diff

Differences From Artifact [98bda28d4c]:

To Artifact [f281706365]:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# encoding: UTF-8
# api: streamtuner2
# type: base
# category: ui
# title: Channel plugins
# description: Base implementation for channels and feature plugins
# version: 1.1
# license: public domain
# author: mario
# url: http://fossil.include-once.org/streamtuner2/
# pack:
#    bookmarks.py  configwin.py  global_key.py  history.py
#    icast.py  internet_radio.py  itunes.py  jamendo.py links.py  live365.py
#    modarchive.py  myoggradio.py  punkcast.py  radiotray.py  search.py
#    shoutcast.py  streamedit.py  surfmusik.py  timer.py  tunein.py  xiph.py
#    youtube.py  *.png
# config: -
# priority: core
#
#
# Just exports GenericChannel and ChannelPlugin.
# GenericChannel implements the basic GUI functions and defines
# the default channel data structure. It implements base and











|
|
|
|
<







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

16
17
18
19
20
21
22
# encoding: UTF-8
# api: streamtuner2
# type: base
# category: ui
# title: Channel plugins
# description: Base implementation for channels and feature plugins
# version: 1.1
# license: public domain
# author: mario
# url: http://fossil.include-once.org/streamtuner2/
# pack:
#    bookmarks.py configwin.py streamedit.py history.py search.py links.py 
#    icast.py internet_radio.py itunes.py jamendo.py live365.py global_key.py
#    modarchive.py myoggradio.py punkcast.py radiobrowser.py radiotray.py
#    shoutcast.py surfmusik.py timer.py tunein.py xiph.py youtube.py

# config: -
# priority: core
#
#
# Just exports GenericChannel and ChannelPlugin.
# GenericChannel implements the basic GUI functions and defines
# the default channel data structure. It implements base and
95
96
97
98
99
100
101




102
103
104
105
106
107
108
       [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




    
    # regex            
    rx_www_url = re.compile("""(www(\.\w+[\w-]+){2,}|(\w+[\w-]+[ ]?\.)+(com|FM|net|org|de|PL|fr|uk))""", re.I)


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







>
>
>
>







94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
       [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")]
    
    # regex            
    rx_www_url = re.compile("""(www(\.\w+[\w-]+){2,}|(\w+[\w-]+[ ]?\.)+(com|FM|net|org|de|PL|fr|uk))""", re.I)


    # constructor
    def __init__(self, parent=None):
193
194
195
196
197
198
199
200
201
202
203
204
205



206
207
208
209
210
211
212
213
                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.parent.status("Updating streams...")
            self.parent.status(-0.1)



            new_streams = self.update_streams(category)
  
            if new_streams:

                # check and modify entry;
                # assert that title and url are present
                modified = []
                for row in new_streams:







|





>
>
>
|







196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
                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.parent.status("Updating streams...")
            self.parent.status(-0.1)
            if category == "empty":
                new_streams = self.empty_stub
            else:
                new_streams = self.update_streams(category)
  
            if new_streams:

                # check and modify entry;
                # assert that title and url are present
                modified = []
                for row in new_streams:
352
353
354
355
356
357
358
359
360
361

362
363
364
365
366

367



368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400

401
402
403
404
405
406
407
    def reload_if_current(self, category):
        if self.current == category:
            self.reload()
    
        
    # display .current category, once notebook/channel tab is first opened
    def first_show(self):
        __print__(dbg.PROC, "first_show ", self.module, self.shown)

        if (self.shown != 55555):

        
            # if category tree is empty, initialize it
            if not self.categories:
                __print__(dbg.PROC, "first_show: reload_categories");
                #self.parent.thread(self.reload_categories)

                self.reload_categories()



                self.display_categories()
                self.current = self.categories.keys()[0]
                __print__(dbg.STAT, self.current)
                self.load(self.current)
        
            # load current category
            else:
                __print__(dbg.STAT, "first_show: load current category");
                self.load(self.current)
            
            # put selection/cursor on last position
            try:
                __print__(dbg.STAT, "first_show: select last known category treelist position")
                self.gtk_list.get_selection().select_path(self.shown)
            except:
                pass
                
            # this method will only be invoked once
            self.shown = 55555


    # update categories, save, and display                
    def reload_categories(self):
    
        # get data and save
        self.update_categories()
        if self.categories:
            conf.save("cache/categories_"+self.module, self.categories)
        if self.catmap:
            conf.save("cache/catmap_" + self.module, self.catmap);

        # display outside of this non-main thread            
        uikit.do(self.display_categories)


    # insert content into gtk category list
    def display_categories(self):
    
        # remove any existing columns
        if self.gtk_cat:
            [self.gtk_cat.remove_column(c) for c in self.gtk_cat.get_columns()]







<


>



|

>
|
>
>
>


|




|




|




















>







358
359
360
361
362
363
364

365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
    def reload_if_current(self, category):
        if self.current == category:
            self.reload()
    
        
    # display .current category, once notebook/channel tab is first opened
    def first_show(self):


        if (self.shown != 55555):
            __print__(dbg.PROC, self.module+".first_show()")
        
            # if category tree is empty, initialize it
            if not self.categories:
                __print__(dbg.PROC, self.module+"first_show: reload_categories");
                #self.parent.thread(self.reload_categories)
                try:
                    self.reload_categories()
                except:
                    __print__(dbg.ERR, "HTTP Error or something")
                    self.categories = ["empty"]
                self.display_categories()
                self.current = self.categories.keys()[0]
                __print__(dbg.STAT, "Use first category as current =", self.current)
                self.load(self.current)
        
            # load current category
            else:
                __print__(dbg.STAT, self.module+".first_show(): load current category =", self.current);
                self.load(self.current)
            
            # put selection/cursor on last position
            try:
                __print__(dbg.STAT, self.module+".first_show()", "select last known category treelist position =", self.shown)
                self.gtk_list.get_selection().select_path(self.shown)
            except:
                pass
                
            # this method will only be invoked once
            self.shown = 55555


    # update categories, save, and display                
    def reload_categories(self):
    
        # get data and save
        self.update_categories()
        if self.categories:
            conf.save("cache/categories_"+self.module, self.categories)
        if self.catmap:
            conf.save("cache/catmap_" + self.module, self.catmap);

        # display outside of this non-main thread            
        uikit.do(self.display_categories)


    # insert content into gtk category list
    def display_categories(self):
    
        # remove any existing columns
        if self.gtk_cat:
            [self.gtk_cat.remove_column(c) for c in self.gtk_cat.get_columns()]