streamtuner2: Check-in [ea924e3c27]
Internet radio browser GUI for music/video streams from various directory services.

⌈⌋ branch:  streamtuner2


Check-in [ea924e3c27]

Overview
Comment:Introduce FeaturePlugin as new base class for channels and all other plugins. Pre-defines the meta, module attributes and calls init2().
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1:ea924e3c27911b844b8b6159392ce90b29cb609c
User & Date: mario on 2017-01-05 21:23:23
Other Links: manifest | tags
Context
2017-01-05
21:33
Fix `links` plugin format: attribute; make it understood by channel.play() that a homepage-only row triggers the web browser. check-in: f48ad79aa1 user: mario tags: trunk
21:23
Introduce FeaturePlugin as new base class for channels and all other plugins. Pre-defines the meta, module attributes and calls init2(). check-in: ea924e3c27 user: mario tags: trunk
21:22
Detect more absent variables/login, introduce UI delay on submission. check-in: 9eeccf1f29 user: mario tags: trunk
Changes

Modified channels/__init__.py from [c58402c723] to [4a4e01489e].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
34
35
36
37
38
39
40
41
42
43
44
45
46
47


















































48
49
50
51
52
53
54
55
56
..
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
...
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143


144
145
146
147
148
149
150
...
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
...
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
...
678
679
680
681
682
683
684


685
686
687
688
689
690
691
# encoding: UTF-8
# api: streamtuner2
# type: class
# category: ui
# title: Channel plugins
# description: Base implementation for channels and feature plugins
# version: 1.6
# license: public domain
# author: mario
# url: http://fossil.include-once.org/streamtuner2/
# pack:
#    *.py
# config: -
# priority: core
................................................................................
import re
import copy
import inspect


# Only export plugin classes and a few utility functions
__all__ = [
    "GenericChannel", "ChannelPlugin", "use_rx", "mime_fmt",
    "entity_decode", "strip_tags", "nl", "unhtml", "to_int"
]
__path__.insert(0, conf.dir + "/plugins")





















































# Generic channel module
class GenericChannel(object):

    # control attributes
    meta = { "config": [] }
    base_url = ""
    listformat = "pls"
    audioformat = "audio/mpeg" # fallback value
    has_search = False
................................................................................
    gtk_list = None   # Gtk widget for station treeview
    gtk_cat = None    # Gtk widget for category columns
    ls = None         # ListStore for station treeview
    rowmap = None     # Preserve streams-datamap
    pix_entry = None  # ListStore entry that contains favicon
    img_resize = None  # Rescale `img` references to icon size
    fixed_size = [24,24]  # Default height+width for favicons
    parent = None     # reference to main window

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

    # constructor
    def __init__(self, parent=None):
    
        #self.streams = {}
        self.gtk_list = None
        self.gtk_cat = None
        self.module = self.__class__.__name__
        self.meta = plugin_meta(src = inspect.getcomments(inspect.getmodule(self)))
        self.config = self.meta.get("config", [])
        self.title = self.meta.get("title", self.module)

        # add default options values to config.conf.* dict
        conf.add_plugin_defaults(self.meta, self.module)
        
        # extra init function
        if hasattr(self, "init2"):
            self.init2(parent)
        if hasattr(self, "resolve_urn"):
            action.handler["urn:%s" % self.module] = self.resolve_urn


        
        # Only if streamtuner2 is run in graphical mode        
        if (parent):
            # Update/display stream processors
            if not self.prepare_filters:
                self.prepare_filters += [
                    self.prepare_filter_icons,
................................................................................
    def columns(self, entries=None):
        self.ls, self.rowmap, self.pix_entry = uikit.columns(
            self.gtk_list, self.datamap, entries,
            show_favicons=True, fixed_size=self.fixed_size
        )
        # no longer using `conf.show_favicons`


    # Statusbar stub (defers to parent/main window, if in GUI mode)
    def status(self, *args, **kw):
        if self.parent: self.parent.status(*args, **kw)
        else: log.INFO("status():", *v)
    def warn(self, *args, **kw):
        self.status(*args, icon="gtk-dialog-warning", **kw)


        
    #--------------------- streams/model data accesss ---------------------------


    # Traverse category TreeModel to set current, expand parent nodes
    def select_current(self, name):
................................................................................
            listformat = row.get("listformat", self.listformat)
            action.record(row, audioformat, listformat)
        return row



    

        
    








# channel plugin without glade-pre-defined notebook tab
#
class ChannelPlugin(GenericChannel):

    module = "abstract"
................................................................................

        # try to initialize superclass now, before adding to channel tabs
        GenericChannel.gui(self, parent)

        # add notebook tab
        tab = parent.notebook_channels.insert_page_menu(vbox, ev_label, plain_label, -1)
        parent.notebook_channels.set_tab_reorderable(vbox, True)





# WORKAROUND for direct channel module imports,
# eases instantiations without GUI a little,
# reducing module dependencies (conf. / ahttp. / channels. / parent.) would be better
def stub_parent(object):






|







 







|
|





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>

|







 







<







 







<
<
<
<
|
<
<
<
<
<
<
<
<
>
>







 







<
<
<
<
<
<
<
<







 







<
<
<
<
<
<
<
<
<







 







>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
..
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
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
...
117
118
119
120
121
122
123

124
125
126
127
128
129
130
...
173
174
175
176
177
178
179




180








181
182
183
184
185
186
187
188
189
...
229
230
231
232
233
234
235








236
237
238
239
240
241
242
...
616
617
618
619
620
621
622









623
624
625
626
627
628
629
...
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
# encoding: UTF-8
# api: streamtuner2
# type: class
# category: ui
# title: Channel plugins
# description: Base implementation for channels and feature plugins
# version: 1.7
# license: public domain
# author: mario
# url: http://fossil.include-once.org/streamtuner2/
# pack:
#    *.py
# config: -
# priority: core
................................................................................
import re
import copy
import inspect


# Only export plugin classes and a few utility functions
__all__ = [
    "FeaturePlugin", "GenericChannel", "ChannelPlugin", "use_rx", "mime_fmt",
    "stub_parent", "entity_decode", "strip_tags", "nl", "unhtml", "to_int"
]
__path__.insert(0, conf.dir + "/plugins")



# Base class for plugins (features or channels)
class FeaturePlugin(object):

    # plugin meta and object references
    module = ""
    meta = { "config": [] }
    parent = None     # main window

    # minimum setup for ChannelPlugins and feature hooks
    def __init__(self, parent, *k, **kw):

        # set up meta infos
        self.parent = parent
        self.module = self.__class__.__name__
        self.meta = plugin_meta(src = inspect.getcomments(inspect.getmodule(self)))
        self.config = self.meta.get("config", [])
        self.title = self.meta.get("title", self.module)

        # add default options values to config.conf.* dict
        conf.add_plugin_defaults(self.meta, self.module)

        # implicit action handler registration
        if hasattr(self, "resolve_urn"):
            action.handler["urn:%s" % self.module] = self.resolve_urn

        # secondary init function
        self.init2(parent)

    # optionally to be overriden by plugins (run after base __init__)
    def init2(self, parent, *k, **kw):
        pass

    # Statusbar stub (defers to parent/main window, if in GUI mode)
    def status(self, *args, **kw):
        if self.parent:
            self.parent.status(*args, **kw)
        else:
            log.INFO("status():", *v)
        
    # Statusbar with highlighting and default icon
    def warn(self, text, *args, **kw):
        if isinstance(text, (str, unicode)) and "status_color" in conf:
            text = "<span background='%s'>%s</span>" % (conf.status_color, text)
            kw["markup"] = 1
        if not "icon" in kw:
            kw["icon"] = "gtk-dialog-warning"
        self.status(text, *args, **kw)



# Generic channel module
class GenericChannel(FeaturePlugin):

    # control attributes
    meta = { "config": [] }
    base_url = ""
    listformat = "pls"
    audioformat = "audio/mpeg" # fallback value
    has_search = False
................................................................................
    gtk_list = None   # Gtk widget for station treeview
    gtk_cat = None    # Gtk widget for category columns
    ls = None         # ListStore for station treeview
    rowmap = None     # Preserve streams-datamap
    pix_entry = None  # ListStore entry that contains favicon
    img_resize = None  # Rescale `img` references to icon size
    fixed_size = [24,24]  # Default height+width for favicons


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

    # constructor
    def __init__(self, parent=None):
    
        #self.streams = {}
        self.gtk_list = None
        self.gtk_cat = None




        








        # base init (meta infos, parent reference, init2)
        FeaturePlugin.__init__(self, parent)
        
        # Only if streamtuner2 is run in graphical mode        
        if (parent):
            # Update/display stream processors
            if not self.prepare_filters:
                self.prepare_filters += [
                    self.prepare_filter_icons,
................................................................................
    def columns(self, entries=None):
        self.ls, self.rowmap, self.pix_entry = uikit.columns(
            self.gtk_list, self.datamap, entries,
            show_favicons=True, fixed_size=self.fixed_size
        )
        # no longer using `conf.show_favicons`










        
    #--------------------- streams/model data accesss ---------------------------


    # Traverse category TreeModel to set current, expand parent nodes
    def select_current(self, name):
................................................................................
            listformat = row.get("listformat", self.listformat)
            action.record(row, audioformat, listformat)
        return row



    











# channel plugin without glade-pre-defined notebook tab
#
class ChannelPlugin(GenericChannel):

    module = "abstract"
................................................................................

        # try to initialize superclass now, before adding to channel tabs
        GenericChannel.gui(self, parent)

        # add notebook tab
        tab = parent.notebook_channels.insert_page_menu(vbox, ev_label, plain_label, -1)
        parent.notebook_channels.set_tab_reorderable(vbox, True)





# WORKAROUND for direct channel module imports,
# eases instantiations without GUI a little,
# reducing module dependencies (conf. / ahttp. / channels. / parent.) would be better
def stub_parent(object):

Modified channels/jamendo.py from [0db1a39017] to [d440527930].

320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
                    #"url": "http://api.jamendo.com/get2/stream/track/xspf/?album_id=%s&streamencoding=ogg2&n=all&from=app-%s" % (e["id"], self.cid),
                    #"format": "audio/ogg",
                    #"listformat": "xspf",
                    "url": "http://api.jamendo.com/v3.0/tracks?client_id={}&audioformat={}&album_id={}".format(self.cid, fmt, e["id"]),
                    "listformat": "jamj",
                    "format": fmt_mime,
                })
		
        # Feeds (News)
        elif cat == "feeds":
            for e in self.api(method="feeds", order="date_start_desc", target="notlogged"):
              if e.get("joinid") and e.get("subtitle"):
                entries.append({
                    "genre": e["type"],
                    "title": e["title"]["en"],







|







320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
                    #"url": "http://api.jamendo.com/get2/stream/track/xspf/?album_id=%s&streamencoding=ogg2&n=all&from=app-%s" % (e["id"], self.cid),
                    #"format": "audio/ogg",
                    #"listformat": "xspf",
                    "url": "http://api.jamendo.com/v3.0/tracks?client_id={}&audioformat={}&album_id={}".format(self.cid, fmt, e["id"]),
                    "listformat": "jamj",
                    "format": fmt_mime,
                })

        # Feeds (News)
        elif cat == "feeds":
            for e in self.api(method="feeds", order="date_start_desc", target="notlogged"):
              if e.get("joinid") and e.get("subtitle"):
                entries.append({
                    "genre": e["type"],
                    "title": e["title"]["en"],