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

⌈⌋ branch:  streamtuner2


Diff

Differences From Artifact [f737d4ecc1]:

To Artifact [1e504cb8ac]:


12
13
14
15
16
17
18
19

20
21

22
23

24
25
26
27
28
29
30
12
13
14
15
16
17
18

19
20

21


22
23
24
25
26
27
28
29







-
+

-
+
-
-
+






# url: http://freshcode.club/projects/streamtuner2
# config:  
#   { type: env, name: http_proxy, description: proxy for HTTP access }
#   { type: env, name: XDG_CONFIG_HOME, description: relocates user .config subdirectory }
# category: sound
# depends: pygtk | gi, threading, requests, pyquery, lxml
# id: streamtuner2
# pack: *.py, gtk*.xml, bin=/usr/bin/streamtuner2, channels/__init__.py, bundle/*.py,
# pack: *.py, gtk*.xml, bin, channels/__init__.py, bundle/*.py, CREDITS, help/index.page,
#   streamtuner2.desktop=/usr/share/applications/, README=/usr/share/doc/streamtuner2/,
#   NEWS.gz=/usr/share/doc/streamtuner2/changelog.gz, help/streamtuner2.1=/usr/share/man/man1/,
#   help/streamtuner2.1=/usr/share/man/man1/, NEWS.gz=/usr/share/doc/streamtuner2/changelog.gz,
#   help/*page=/usr/share/doc/streamtuner2/help/, help/img/*=/usr/share/doc/streamtuner2/help/img/,
#   logo.png=/usr/share/pixmaps/streamtuner2.png,
#   logo.png=/usr/share/pixmaps/streamtuner2.png
# architecture: all
#
# Streamtuner2 is a GUI for browsing internet radio directories, music
# collections, and video services - grouped by genres or categories.
# It runs your preferred audio player, and streamripper for recording.
#
# It's an independent rewrite of streamtuner1. Being written in Python,
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
82
83
84
85
86
87
88

89

90
91
92
93
94
95
96







-

-






        "config_load": [],
        "config_save": [],
    }
    meta = plugin_meta()


    # status variables
    channel_names = ["bookmarks"]    # order of channel notebook tabs
    current_channel = "bookmarks"    # currently selected channel name (as index in self.channels{})


    # constructor
    def __init__(self):
        
        # Load stylesheet, instantiate GtkBuilder in self, menu and logo hooks
        gui_startup(0/20.0), uikit.load_theme(conf.get("theme"))
        gui_startup(1/20.0), gtk.Builder.__init__(self)
228
229
230
231
232
233
234
235
236



237
238
239






240

241
242
243


244
245
246


247
248
249
250
251

252
253
254
255

256
257
258
259
260
261
262








263
264
265
266
267
268
269
225
226
227
228
229
230
231


232
233
234
235
236
237
238
239
240
241
242
243

244
245


246
247



248
249
250




251




252
253
254





255
256
257
258
259
260
261
262
263
264
265
266
267
268
269







-
-
+
+
+



+
+
+
+
+
+
-
+

-
-
+
+
-
-
-
+
+

-
-
-
-
+
-
-
-
-
+


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







    # Custom-named widgets are available from .widgets{} not via .get_widget()
    def get_widget(self, name):
        if name in self.widgets:
            return self.widgets[name]
        else:
            return gtk.Builder.get_object(self, name)
            
    # returns the currently selected directory/channel object (remembered position)

            
    # Returns the currently selected directory/channel object (remembered position)
    def channel(self):
        return self.channels[self.current_channel]

    # List of module titles for channel tabs
    @property
    def channel_names(self):
        n = self.notebook_channels
        return [n.get_menu_label_text(n.get_nth_page(i)) for i in xrange(0, n.get_n_pages())]

    # returns the currently selected directory/channel object (from gtk)
    # Returns the currently selected directory/channel object (from gtk)
    def current_channel_gtk(self):
        i = self.notebook_channels.get_current_page()
        try: return self.channel_names[i]
        return self.channel_names[self.notebook_channels.get_current_page()]
    
        except: return "bookmarks"

    # Notebook tab clicked
        
    # Notebook tab has been clicked (receives numeric page_num), but *NOT* yet changed (visually).
    def channel_switch(self, notebook, page, page_num=0, *args):

        # can be called from channelmenu as well:
        if type(page) == str:
            self.current_channel = page
        self.current_channel = notebook.get_menu_label_text(notebook.get_nth_page(page_num))
            self.notebook_channels.set_current_page(self.channel_names.index(page))
        # notebook invocation:
        else: #if type(page_num) == int:
            self.current_channel = self.channel_names[page_num]
        __print__(dbg.UI, "main.channel_switch():", "set current_channel :=", self.current_channel)
        
        # if first selected, load current category
        try:
            __print__(dbg.PROC, "channel_switch: try .first_show", self.channel().module);
            self.channel().first_show()
        except:
            __print__(dbg.INIT, "channel .first_show() initialization error")
        __print__(dbg.STAT, "TRY", "main.channel_switch(): ", self.current_channel + ".first_show()")
        try: self.channel().first_show()
        except: __print__(dbg.INIT, ".first_show() initialization error")

    # Invoked from the menu instead, uses module name instead of numeric tab id
    def channel_switch_by_name(self, name):
        self.notebook_channels.set_current_page(self.channel_names.index(name))


    # Convert ListStore iter to row number
    def rowno(self):
        (model, iter) = self.model_iter()
        return model.get_path(iter)[0]

    # Currently selected entry in stations list, return complete data dict
410
411
412
413
414
415
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
444
410
411
412
413
414
415
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







-
+


















-
-






    # load plugins from /usr/share/streamtuner2/channels/
    def load_plugin_channels(self):

        # initialize plugin modules (pre-ordered)
        ls = module_list()
        for module in ls:
            gui_startup(4/20.0 + 13.5/20.0 * float(ls.index(module))/len(ls), "loading module "+module)
                            

            # skip module if disabled
            if conf.plugins.get(module, 1) == False:
                __print__(dbg.STAT, "disabled plugin:", module)
                continue
            # or if it's a built-in (already imported)
            elif module in self.features or module in self.channels:
                continue
            
            # load plugin
            try:
                plugin = __import__("channels."+module, globals(), None, [""])
                #print [name for name,c in inspect.getmembers(plugin) if inspect.isclass(c)]
                plugin_class = plugin.__dict__[module]
                plugin_obj = plugin_class(parent=self)

                # add to .channels{}
                if issubclass(plugin_class, channels.GenericChannel):
                    self.channels[module] = plugin_obj
                    if module not in self.channel_names:  # skip (glade) built-in channels
                        self.channel_names.append(module)
                # or .features{} for other plugin types
                else:
                    self.features[module] = plugin_obj
                
            except Exception as e:
                __print__(dbg.INIT, "load_plugin_channels: error initializing:", module, ", exception:")
                traceback.print_exc()