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

⌈⌋ branch:  streamtuner2


Check-in [07d2a291cd]

Overview
Comment:Add conf.get_data() alias, which automatically fetches resource relative to config module (that is, works on the global path, or within pyzip archive). Move module_list() from channels. into config, as it combines plugins and config management anyway.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 07d2a291cd491d4947f1280d7ed8df05ff5dcfda
User & Date: mario on 2015-04-01 17:39:44
Other Links: manifest | tags
Context
2015-04-01
20:31
Update documentation files (dependencies and manual installation paths). check-in: 186f91779d user: mario tags: trunk
17:39
Add conf.get_data() alias, which automatically fetches resource relative to config module (that is, works on the global path, or within pyzip archive). Move module_list() from channels. into config, as it combines plugins and config management anyway. check-in: 07d2a291cd user: mario tags: trunk
15:49
Trim down plugin comment. check-in: c8c55c79da user: mario tags: trunk
Changes

Modified NEWS from [aa1a43b578] to [57a25343a1].





1
2
3
4
5
6
7





2.1.4 (2015-03-25)
- Fixed Internet-Radio extraction.
- Added basic TuneIn channel.
- Removed Dirble and MusicGoal channels.
- Fix desktop and packaging infos according to Debian guidelines.
- Switch to fpm/xml for package building.
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
2.1.5 (unreleased)
- Rewrite, cleanup, new PYZ package.
- Plugin meta data, embedded PNGs.
- Notebook tabs now on the left per default.

2.1.4 (2015-03-25)
- Fixed Internet-Radio extraction.
- Added basic TuneIn channel.
- Removed Dirble and MusicGoal channels.
- Fix desktop and packaging infos according to Debian guidelines.
- Switch to fpm/xml for package building.

Modified PKG-INFO from [30c39c6021] to [c177f44b1a].

1
2
3
4
5
6
7
8
9
10
Metadata-Version: 1.0
Name: streamtuner2
Version: 2.1.4
Summary: Streamtuner2 is an internet radio browser
Home-page: http://fossil.include-once.org/streamtuner2/
Author: Mario Salzer
Author-email: xmilky+st2@gmail....
License: Public Domain
Description: Streamtuner2 lists radio directory services like Shoutcast, Xiph, Live365, MyOggRadio, Jamendo. It allows listening via any audio player, and recording of streams via streamripper.
Platform: ALL


|







1
2
3
4
5
6
7
8
9
10
Metadata-Version: 1.0
Name: streamtuner2
Version: 2.1.5
Summary: Streamtuner2 is an internet radio browser
Home-page: http://fossil.include-once.org/streamtuner2/
Author: Mario Salzer
Author-email: xmilky+st2@gmail....
License: Public Domain
Description: Streamtuner2 lists radio directory services like Shoutcast, Xiph, Live365, MyOggRadio, Jamendo. It allows listening via any audio player, and recording of streams via streamripper.
Platform: ALL

Modified channels/__init__.py from [ed3951c050] to [3382badfa2].

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
import action
import favicon
import os.path
import xml.sax.saxutils
import re
import copy
import inspect
import pkgutil


# Only export plugin classes
__all__ = [
    "GenericChannel", "ChannelPlugin", "module_list"
]



# Search through ./channels/ and get module basenames.
# Also order them by conf.channel_order
#
def module_list():

    # Should list plugins within zips as well as local paths
    ls = pkgutil.iter_modules([conf.share+"/channels", "channels"])
    ls = [name for loader,name,ispkg in ls]
    
    # resort with tab order
    order = [module.strip() for module in conf.channel_order.lower().replace(".","_").replace("-","_").split(",")]
    ls = [module for module in (order) if (module in ls)] + [module for module in (ls) if (module not in order)]

    return ls



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

    # desc







<




|

<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<







42
43
44
45
46
47
48

49
50
51
52
53
54

















55
56
57
58
59
60
61
import action
import favicon
import os.path
import xml.sax.saxutils
import re
import copy
import inspect



# Only export plugin classes
__all__ = [
    "GenericChannel", "ChannelPlugin"
]




















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

    # desc
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    # 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(None, 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["config"], self.module)

        # only if streamtuner2 is run in graphical mode        







|







108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
    # 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["config"], self.module)

        # only if streamtuner2 is run in graphical mode        
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
            vbox.pack2(sw2, resize=True, shrink=True)

            # prepare label
            pixbuf = None
            if "png" in self.meta:
                pixbuf = uikit.pixbuf(self.meta["png"])
            else:
                png = pkgutil.get_data("config",  "channels/" + self.module + ".png")
                pixbuf = uikit.pixbuf(png)
            if pixbuf:
                icon = gtk.image_new_from_pixbuf(pixbuf)
            else:
                icon = gtk.image_new_from_stock(gtk.STOCK_DIRECTORY, size=1)
            label = gtk.HBox()
            label.pack_start(icon, expand=False, fill=True)







|







534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
            vbox.pack2(sw2, resize=True, shrink=True)

            # prepare label
            pixbuf = None
            if "png" in self.meta:
                pixbuf = uikit.pixbuf(self.meta["png"])
            else:
                png = get_data("channels/" + self.module + ".png")
                pixbuf = uikit.pixbuf(png)
            if pixbuf:
                icon = gtk.image_new_from_pixbuf(pixbuf)
            else:
                icon = gtk.image_new_from_stock(gtk.STOCK_DIRECTORY, size=1)
            label = gtk.HBox()
            label.pack_start(icon, expand=False, fill=True)

Modified config.py from [a316308630] to [ec183e5649].

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




16
17
18
19
20
21
22
23
24
25
26

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42




43
44
45
46
47
48
49
#
# encoding: UTF-8
# api: streamtuner2
# type: class
# title: global config object
# description: reads ~/.config/streamtuner/*.json files
# config: {type:var, name:z, description:v}
#
# In the main application or module files which need access
# to a global conf object, just import this module as follows:
#
#   from config import conf
#
# Here conf is already an instantiation of the underlying
# Config class.




#


import os
import sys
import json
import gzip
import platform
import re
import zipfile
import inspect



# export symbols
__all__ = ["conf", "__print__", "dbg", "plugin_meta"]


#-- create a stub instance of config object
conf = object()

# separate instance of netrc, if needed
netrc = None




#-- global configuration data               ---------------------------------------------




class ConfigDict(dict):


    # start
    def __init__(self):
    
        # object==dict means conf.var is conf["var"]









|

|


|
>
>
>
>











>



|











|
>
>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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
#
# encoding: UTF-8
# api: streamtuner2
# type: class
# title: global config object
# description: reads ~/.config/streamtuner/*.json files
# config: {type:var, name:z, description:v}
#
# In the main application or module files which need access
# to a global conf.* object, just import this module as follows:
#
#   from config import *
#
# Here conf is already an instantiation of the underlying
# ConfigDoct class.
#
# Also provides the logging function __print__, and basic
# plugin handling code: plugin_meta() and module_list(),
# and the relative get_data() alias (files from pyzip/path).
#


import os
import sys
import json
import gzip
import platform
import re
import zipfile
import inspect
import pkgutil


# export symbols
__all__ = ["conf", "__print__", "dbg", "plugin_meta", "module_list", "get_data"]


#-- create a stub instance of config object
conf = object()

# separate instance of netrc, if needed
netrc = None




# Global configuration store
#
# Autointializes itself on startup, makes conf.vars available.
# Also provides .load() and .save() for JSON data/cache files.
#
class ConfigDict(dict):


    # start
    def __init__(self):
    
        # object==dict means conf.var is conf["var"]
230
231
232
233
234
235
236






























237
238
239
240
241

242
243
244
245
246
247
248
            except:
                pass
        for server in varhosts:
            if server in netrc:
                return netrc[server]
        
































# Plugin meta data extraction
#
# Extremely crude version for Python and streamtuner2 plugin usage.
# Fetches module source, or reads from filename / out of zip package.

def plugin_meta(fn=None, src=None, frame=1):

    # get source directly from caller
    if not src and not fn:
        module = inspect.getmodule(sys._getframe(frame))
        fn = inspect.getsourcefile(module)
        src = inspect.getcomments(module)







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





>







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
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
            except:
                pass
        for server in varhosts:
            if server in netrc:
                return netrc[server]
        


# Retrieve content from install path or pyzip archive (alias for pkgutil.get_data)
#
def get_data(fn, decode=False):
    try:
        bin = pkgutil.get_data("config", fn)
        if decode:
            return bin.decode("utf-8")
        else:
            return str(bin)
    except:
        pass


# Search through ./channels/ and get module basenames.
# Also order them by conf.channel_order
#
def module_list():

    # Should list plugins within zips as well as local paths
    ls = pkgutil.iter_modules([conf.share+"/channels", "channels"])
    ls = [name for loader,name,ispkg in ls]
    
    # resort with tab order
    order = [module.strip() for module in conf.channel_order.lower().replace(".","_").replace("-","_").split(",")]
    ls = [module for module in (order) if (module in ls)] + [module for module in (ls) if (module not in order)]

    return ls



# Plugin meta data extraction
#
# Extremely crude version for Python and streamtuner2 plugin usage.
# Fetches module source, or reads from filename / out of zip package.
#
def plugin_meta(fn=None, src=None, frame=1):

    # get source directly from caller
    if not src and not fn:
        module = inspect.getmodule(sys._getframe(frame))
        fn = inspect.getsourcefile(module)
        src = inspect.getcomments(module)

Modified st2.py from [e68343b5e8] to [098afe2087].

405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        pass


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

        # initialize plugin modules (pre-ordered)
        ls = channels.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







|







405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
        pass


    # 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

Modified uikit.py from [e359ed9045] to [37cbc8f921].

17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# With the methodes .app_state() and .app_restore() named gtk widgets
# can be queried for attributes. The methods return a saveable dict,
# which contain current layout options for a few Widget types. Saving
# and restoring must be handled elsewhere.


# debug
from config import __print__, dbg, plugin_meta

# system
import os.path
import copy
import sys
import re
import base64
import zlib
import inspect
from compat2and3 import unicode, xrange, PY3
import pkgutil


# gtk version (2=gtk2, 3=gtk3, 7=tk;)
ver = 2
# if running on Python3 or with commandline flag
if PY3 or "--gtk3" in sys.argv:
    ver = 3







|










<







17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
# With the methodes .app_state() and .app_restore() named gtk widgets
# can be queried for attributes. The methods return a saveable dict,
# which contain current layout options for a few Widget types. Saving
# and restoring must be handled elsewhere.


# debug
from config import *

# system
import os.path
import copy
import sys
import re
import base64
import zlib
import inspect
from compat2and3 import unicode, xrange, PY3



# gtk version (2=gtk2, 3=gtk3, 7=tk;)
ver = 2
# if running on Python3 or with commandline flag
if PY3 or "--gtk3" in sys.argv:
    ver = 3
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
    import gtk
    import gobject
    GdkPixbuf = gtk.gdk
    empty_pixbuf = GdkPixbuf.Pixbuf(gtk.gdk.COLORSPACE_RGB,True,8,16,16)
    empty_pixbuf.fill(0xFFFFFFFF)

# prepare gtkbuilder data
ui_xml = pkgutil.get_data("config", "gtk3.xml").decode("utf-8")
if ver == 2:
    ui_xml = ui_xml.replace('version="3.0"', 'version="2.16"')



# simplified gtk constructors               ---------------------------------------------
class uikit:







|







55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    import gtk
    import gobject
    GdkPixbuf = gtk.gdk
    empty_pixbuf = GdkPixbuf.Pixbuf(gtk.gdk.COLORSPACE_RGB,True,8,16,16)
    empty_pixbuf.fill(0xFFFFFFFF)

# prepare gtkbuilder data
ui_xml = get_data("gtk3.xml", decode=True)
if ver == 2:
    ui_xml = ui_xml.replace('version="3.0"', 'version="2.16"')



# simplified gtk constructors               ---------------------------------------------
class uikit:
611
612
613
614
615
616
617
618
619
620
621
622
#
class AboutStreamtuner2(AuxiliaryWindow):
    def __init__(self, parent):
        a = gtk.AboutDialog()
        a.set_name(parent.meta["id"])
        a.set_version(parent.meta["version"])
        a.set_license(parent.meta["license"])
        a.set_authors((pkgutil.get_data("config", "CREDITS") or parent.meta["author"]).split("\n"))
        a.set_website(parent.meta["url"])
        a.connect("response", lambda a, ok: ( a.hide(), a.destroy() ) )
        a.show_all()
            







|




610
611
612
613
614
615
616
617
618
619
620
621
#
class AboutStreamtuner2(AuxiliaryWindow):
    def __init__(self, parent):
        a = gtk.AboutDialog()
        a.set_name(parent.meta["id"])
        a.set_version(parent.meta["version"])
        a.set_license(parent.meta["license"])
        a.set_authors((get_data("CREDITS") or parent.meta["author"]).split("\n"))
        a.set_website(parent.meta["url"])
        a.connect("response", lambda a, ok: ( a.hide(), a.destroy() ) )
        a.show_all()