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

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


Check-in [21d6d1cf4b]

Overview
Comment:Basic rewrites to transition to fully plugin meta data capable implementation.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 21d6d1cf4bec4425a0f7f0919f9b0df1ba354b13
User & Date: mario on 2015-03-28 07:33:09
Other Links: manifest | tags
Context
2015-03-28
07:34
Minor additions, more cross references, and Mallard note icons. Document Jamendo plugin options. check-in: 89ba7b5c8e user: mario tags: trunk
07:33
Basic rewrites to transition to fully plugin meta data capable implementation. check-in: 21d6d1cf4b user: mario tags: trunk
07:32
Moved `bookmarks` channel into plugin. Implemented plugin .meta data consumption to replace .config = [] builtins. (Still need to rescan disabled channel/feature plugins later..) check-in: 9de894c13c user: mario tags: trunk
Changes

Modified channels/file.py from [f92686b376] to [4d4dd4f9cc].

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
#
# api: streamtuner2
# title: File browser
# description: Displays mp3/oggs or m3u/pls files from local media file directories.
# type: channel
# category: media
# version: 0.0
# priority: optional
# depends: mutagen



#
#
# Local file browser.
#
#



# modules
import os
import re

from channels import *
from config import conf

# ID3 libraries
try:
    from mutagen import File as get_meta
except:
    try:
        print("just basic ID3 support")
        from ID3 import ID3

        get_meta = lambda fn: dict([(k.lower(),v) for k,v in ID3(fn).iteritems()])
    except:
        print("you are out of luck in regards to mp3 browsing")
        get_meta = lambda *x: {}


# work around mutagens difficult interface
def mutagen_postprocess(d):
    if d.get("TIT2"):
        return {






|


>
>
>













|






<

>


|







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
#
# api: streamtuner2
# title: File browser
# description: Displays mp3/oggs or m3u/pls files from local media file directories.
# type: channel
# category: media
# version: 0.1
# priority: optional
# depends: mutagen
# config:  
#   {name:"file_browser_dir", "type":"text", "value": "~/Music, /media/music", "description":"list of directories to scan for audio files"},
#   {name:"file_browser_ext", "type":"text", "value":"mp3,ogg, m3u,pls,xspf, avi,flv,mpg,mp4", "description":"file type filter"},
#
#
# Local file browser.
#
#



# modules
import os
import re

from channels import *
from config import *

# ID3 libraries
try:
    from mutagen import File as get_meta
except:
    try:

        from ID3 import ID3
        __print__(dbg.INFO, "Just basic ID3 support")
        get_meta = lambda fn: dict([(k.lower(),v) for k,v in ID3(fn).iteritems()])
    except:
        __print__(dbg.INIT, "You are out of luck in regards to mp3 browsing. No ID3 support.")
        get_meta = lambda *x: {}


# work around mutagens difficult interface
def mutagen_postprocess(d):
    if d.get("TIT2"):
        return {
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



# file browser / mp3 directory listings
class file (ChannelPlugin):

    # info
    api = "streamtuner2"
    module = "file"
    title = "file browser"
    version = -0.5
    listtype = "url/file"
    
    
    # data
    config = [
        {"name":"file_browser_dir", "type":"text", "value":os.environ["HOME"]+"/Music, /media/music", "description":"list of directories to scan for audio files"},
        {"name":"file_browser_ext", "type":"text", "value":"mp3,ogg, m3u,pls,xspf, avi,flv,mpg,mp4", "description":"file type filter"},
    ]
    streams = {}
    categories = []
    dir = []
    ext = []
    
    # display
    datamap = [ # coltitle   width	[ datasrc key, type, renderer, attrs ]	[cellrenderer2], ...
           ["",		20,	["state",	str,  "pixbuf",	{}],	],
           ["Genre",	65,	['genre',	str,	"t",	{"editable":8}],	],
           ["File",	160,	["filename",	str,	"t",	{"strikethrough":11, "cell-background":12, "cell-background-set":13}],	],
           ["Title",	205,	["title",	str,    "t",	{"editable":8}], ],
           ["Artist",	125,	["artist",	str,	"t",	{"editable":8}],	],
           ["Album", 	125,	["album",	str,	"t",	{"editable":8}],	],
           ["Bitrate",	35,	["bitrate",	int,	"t",	{}],	],
           ["Format",	50,	["format",	str,	None,	{}],	],
           [False,	0,	["editable",	bool,	None,	{}],	],
           [False,	0,	["favourite",	bool,	None,	{}],	],







<


<


<

<
<
<
<









|







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



# file browser / mp3 directory listings
class file (ChannelPlugin):

    # info

    module = "file"
    title = "file browser"

    listtype = "url/file"
    

    # data




    streams = {}
    categories = []
    dir = []
    ext = []
    
    # display
    datamap = [ # coltitle   width	[ datasrc key, type, renderer, attrs ]	[cellrenderer2], ...
           ["",		20,	["state",	str,  "pixbuf",	{}],	],
           ["Genre",	65,	['genre',	str,	"t",	{"editable":8}],	],
           ["File",	160,	["filename",	str,	"t",	{"strikethrough":10, "cell-background":11, "cell-background-set":12}],	],
           ["Title",	205,	["title",	str,    "t",	{"editable":8}], ],
           ["Artist",	125,	["artist",	str,	"t",	{"editable":8}],	],
           ["Album", 	125,	["album",	str,	"t",	{"editable":8}],	],
           ["Bitrate",	35,	["bitrate",	int,	"t",	{}],	],
           ["Format",	50,	["format",	str,	None,	{}],	],
           [False,	0,	["editable",	bool,	None,	{}],	],
           [False,	0,	["favourite",	bool,	None,	{}],	],

Modified channels/global_key.py from [3a244b8b88] to [8937954709].

25
26
27
28
29
30
31

32
33
34
35
36
37
38


# register a key
class global_key(object):

    module = "global_key"
    title = "keyboard shortcut"

    
    config = [
        dict(name="switch_key", type="text", value="XF86Forward", description="global key for switching radio"),
        dict(name="switch_channel", type="text", value="bookmarks:favourite", description="station list to alternate in"),
        dict(name="switch_random", type="boolean", value=0, description="pick random channel, instead of next"),
    ]
    last = 0







>







25
26
27
28
29
30
31
32
33
34
35
36
37
38
39


# register a key
class global_key(object):

    module = "global_key"
    title = "keyboard shortcut"
    meta = plugin_meta()
    
    config = [
        dict(name="switch_key", type="text", value="XF86Forward", description="global key for switching radio"),
        dict(name="switch_channel", type="text", value="bookmarks:favourite", description="station list to alternate in"),
        dict(name="switch_random", type="boolean", value=0, description="pick random channel, instead of next"),
    ]
    last = 0

Modified channels/history.py from [d33de09886] to [07819073c4].

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
#
# api: streamtuner2
# title: History
# description: List recently played stations under favourites > history.
# version: 1.0
# type: category
# category: ui
# priority: optional


# 
# Lists last activated streams in a new [history] tab in the favourites
# channel.
#
#
#



from config import conf, __print__, dbg
from channels import *



class history:

    # plugin info
    module = "history"
    title = "History"
    
    
    # configuration settings
    config = [
        {
            "name": "history",
            "type": "int",
            "value": "20",
            "description": "Number of last played streams to keep in history list.",
            "category": "limit"
        }
    ]
    
    # store
    bm = None


    # hook up to main tab
    def __init__(self, parent):


        # keep reference to main window    
        self.bm = parent.channels["bookmarks"]

        # create category
        self.bm.add_category("history");
        self.bm.reload_if_current(self.module)








>
>









|









|
|
<
<
<
<
<
<
<
<
<
<







>







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
#
# api: streamtuner2
# title: History
# description: List recently played stations under favourites > history.
# version: 1.0
# type: category
# category: ui
# priority: optional
# config:  { name: history,  type: int,  value: 20,  description: Number of last played streams to keep in history list.,  category: limit }
#
# 
# Lists last activated streams in a new [history] tab in the favourites
# channel.
#
#
#



from config import *
from channels import *



class history:

    # plugin info
    module = "history"
    title = "History"
    meta = plugin_meta()











    
    # store
    bm = None


    # hook up to main tab
    def __init__(self, parent):
        self.config = self.meta["config"]

        # keep reference to main window    
        self.bm = parent.channels["bookmarks"]

        # create category
        self.bm.add_category("history");
        self.bm.reload_if_current(self.module)

Modified channels/jamendo.py from [949c1eaeb0] to [fc2a3bf698].

1
2
3
4
5
6
7
8
9



10
11
12
13
14
15
16

# api: streamtuner2
# title: Jamendo
# description: A license-free music collection and artist hub.
# type: channel
# version: 2.2
# category: radio
# depends: json
# priority: default



#
# Now utilizes the Jamendo /v3.0/ API.
#
# Radio station lists are fixed for now. Querying the API twice per station
# doesn't seem overly sensible.
#
# Tracks are queried by genre, where currently there's just a small built-in









>
>
>







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# api: streamtuner2
# title: Jamendo
# description: A license-free music collection and artist hub.
# type: channel
# version: 2.2
# category: radio
# depends: json
# priority: default
# config: 
#    { name: "jamendo_stream_format", type: "select", value: "ogg", select: "ogg=Ogg Vorbis|mp32=MP3, 192vbr|mp31=MP3, 96kbps|flac=Xiph FLAC", description: "Default streaming audio format. Albums and playlists still return Vorbis mostly for best quality." }
#
#
# Now utilizes the Jamendo /v3.0/ API.
#
# Radio station lists are fixed for now. Querying the API twice per station
# doesn't seem overly sensible.
#
# Tracks are queried by genre, where currently there's just a small built-in

Modified channels/links.py from [fb5b65ac66] to [4b9e35f7fc].

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
#
# Simply adds a "links" entry in bookmarks tab, where known channels
# and some others are listed with homepage links.
#
#



from channels import *
import copy



# hooks into main.bookmarks
class links (object):

    # plugin info
    module = "links"
    title = "Links"
    version = 0.1

    
    
    # configuration settings
    config = [    ]
    
    # list
    streams = [    ]







>












>







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
#
# Simply adds a "links" entry in bookmarks tab, where known channels
# and some others are listed with homepage links.
#
#


from config import *
from channels import *
import copy



# hooks into main.bookmarks
class links (object):

    # plugin info
    module = "links"
    title = "Links"
    version = 0.1
    meta = plugin_meta()
    
    
    # configuration settings
    config = [    ]
    
    # list
    streams = [    ]

Modified channels/surfmusik.py from [02e3c00437] to [f86a97dfce].

10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# recognizes: max_streams
#
# This plugin comes in German (SurfMusik) and English (SurfMusic) variations.
# It provides a vast collection of international stations and genres.
# While it's not an open source project, most entries are user contributed.
#
# They do have a Windows client, hencewhy it's even more important for
# streamtuner2 to support it on other plattforms.
#
# TV stations don't seem to work mostly. And loading the webtv/ pages would
# be somewhat slow (for querying the actual mms:// streams).
#
#
#








|







10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# recognizes: max_streams
#
# This plugin comes in German (SurfMusik) and English (SurfMusic) variations.
# It provides a vast collection of international stations and genres.
# While it's not an open source project, most entries are user contributed.
#
# They do have a Windows client, hencewhy it's even more important for
# streamtuner2 to support it on other platforms.
#
# TV stations don't seem to work mostly. And loading the webtv/ pages would
# be somewhat slow (for querying the actual mms:// streams).
#
#
#

Modified channels/timer.py from [cb57b7822f] to [2de8202f62].

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
#
# Programmed events are visible in "timer" under the "bookmarks" channel. Times
# are stored in the description field, and can thus be edited. However, after editing
# times manually, streamtuner2 must be restarted for the changes to take effect.
#


from config import __print__, dbg
from channels import *
import kronos
from mygtk import mygtk
from action import action
import copy
import re



# timed events (play/record) within bookmarks tab
class timer:

    # plugin info
    module = "timer"
    title = "Timer"

    
    
    # configuration settings
    config = [
    ]
    timefield = "playing"
    







|















>







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
#
# Programmed events are visible in "timer" under the "bookmarks" channel. Times
# are stored in the description field, and can thus be edited. However, after editing
# times manually, streamtuner2 must be restarted for the changes to take effect.
#


from config import *
from channels import *
import kronos
from mygtk import mygtk
from action import action
import copy
import re



# timed events (play/record) within bookmarks tab
class timer:

    # plugin info
    module = "timer"
    title = "Timer"
    meta = plugin_meta()
    
    
    # configuration settings
    config = [
    ]
    timefield = "playing"
    

Modified channels/xiph.py from [7363e5b0c8] to [a46d943cfe].

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









17
18
19
20
21
22
23
#
# api: streamtuner2
# title: Xiph.org
# description: ICEcast radio directory. Now utilizes a cached JSON API.
# type: channel
# category: radio
# version: 0.3
# priority: standard
#
#
# Xiph.org maintains the Ogg streaming standard and Vorbis audio compression
# format, amongst others. The ICEcast server is an alternative to SHOUTcast.
#
# It meanwhile provides a JSOL dump, which is faster to download and process.
# So we'll use that over the older yp.xml. (Sadly it also doesn't output
# homepage URLs, listeners, etc.)









#
#



# streamtuner2 modules
from config import conf
|








<






>
>
>
>
>
>
>
>
>







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
# encoding: UTF-8
# api: streamtuner2
# title: Xiph.org
# description: ICEcast radio directory. Now utilizes a cached JSON API.
# type: channel
# category: radio
# version: 0.3
# priority: standard
#

# Xiph.org maintains the Ogg streaming standard and Vorbis audio compression
# format, amongst others. The ICEcast server is an alternative to SHOUTcast.
#
# It meanwhile provides a JSOL dump, which is faster to download and process.
# So we'll use that over the older yp.xml. (Sadly it also doesn't output
# homepage URLs, listeners, etc.)
#
# Xiphs JSON is a horrible mysqldump concatenation, not parseable. Thus it's
# refurbished on api.io for consumption. Which also provides compressed HTTP
# transfers and category slicing.
#
# Xiph won't be updating the directory for another while. The original feature
# request is now further delayed as summer of code project:
# ยท https://trac.xiph.org/ticket/1958
# ยท https://wiki.xiph.org/Summer_of_Code_2015#Stream_directory_API
#
#



# streamtuner2 modules
from config import conf
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
class xiph (ChannelPlugin):

        # desc
        api = "streamtuner2"
        module = "xiph"
        title = "Xiph.org"
        homepage = "http://dir.xiph.org/"
        #base_url = "http://api.dir.xiph.org/"
        json_url = "http://api.include-once.org/xiph/cache.php"
        listformat = "url/http"
        config = [
           {"name":"xiph_min_bitrate", "value":64, "type":"int", "description":"minimum bitrate, filter anything below", "category":"filter"}
        ]
        has_search = True








|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
class xiph (ChannelPlugin):

        # desc
        api = "streamtuner2"
        module = "xiph"
        title = "Xiph.org"
        homepage = "http://dir.xiph.org/"
        #xml_url = "http://dir.xiph.org/yp.xml"
        json_url = "http://api.include-once.org/xiph/cache.php"
        listformat = "url/http"
        config = [
           {"name":"xiph_min_bitrate", "value":64, "type":"int", "description":"minimum bitrate, filter anything below", "category":"filter"}
        ]
        has_search = True