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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [a7b0cd39a2]

Overview
Comment:Remove remnant module .title/etc attributes, and .current category overwriting.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: a7b0cd39a295bb98bcd6873cf9743ad627b7444b
User & Date: mario on 2015-04-17 21:14:32
Other Links: manifest | tags
Context
2015-04-17
22:15
Move PYZ shebang prefixing into `xpm` codebase. Simplifies local Makefile. Renamed PKG_PYZ into PKG_ZIP, and added extra target rules to Packfile. check-in: 9b93393d5e user: mario tags: trunk
21:14
Remove remnant module .title/etc attributes, and .current category overwriting. check-in: a7b0cd39a2 user: mario tags: trunk
21:13
Make .current a dynamic property for debugging. Skip load() update for category==None calls (doesn't happen with previous appstate). Undo default category setting. Now main.appstate_init values are used again. Don't position category liststore selection to #0. Enable "Format" station column. check-in: 99ee6c4ef4 user: mario tags: trunk
Changes

Modified PKG-INFO from [0051e89703] to [3db37af7c7].

1
2
3
4
5
6
7
8
9
10


Metadata-Version: 1.0
Name: streamtuner2
Version: 2.1.6
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
11
12
Metadata-Version: 1.0
Name: streamtuner2
Version: 2.1.6
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
Keywords: internet-radio, python, streaming, audio

Modified Packfile from [1fe577eec6] to [3da3b35078].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# The "Packfile" is just a make script, which is used as mid-packing
# helper. It's run within fpm/xmp execution context in /tmp/staging123/,
# where final distribution file arrangements are made.
# Keeps the Makefile more „build“-related.

all:  preprocess  $(PACK_TYPE)

# More selective file rewriting
preprocess: usr/share/streamtuner2/channels/search.pym
%.pym: %.py
	preprocess -D $(PKG_FLAG) -o $< $<

deb:	# complainy lintian needs a custom changelog.gz
	gzip -9c usr/share/doc/streamtuner2/NEWS > usr/share/doc/streamtuner2/changelog.gz
|
|
<
<










1
2


3
4
5
6
7
8
9
10
11
12
# See http://fossil.include-once.org/xpm/wiki/Packfile
# Applies minor file tweaks right before -t package generation.



all:  preprocess  $(PACK_TYPE)

# More selective file rewriting
preprocess: usr/share/streamtuner2/channels/search.pym
%.pym: %.py
	preprocess -D $(PKG_FLAG) -o $< $<

deb:	# complainy lintian needs a custom changelog.gz
	gzip -9c usr/share/doc/streamtuner2/NEWS > usr/share/doc/streamtuner2/changelog.gz

Modified README from [fe1c20a84a] to [3f020cac1d].

30
31
32
33
34
35
36

37
38
39
40
41
42
43
installed per default. There's a few more Python packages required though:

 · python | python3
 · pygtk | python-gi
 · python-requests
 · python-pyquery
 · python-lxml

 · python-keybinder (optional)
 · python-xdg (optional)

Use your distro package manager with e.g.:

  sudo apt-get install python python-gtk2 python-xdg python-requests ...








>







30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
installed per default. There's a few more Python packages required though:

 · python | python3
 · pygtk | python-gi
 · python-requests
 · python-pyquery
 · python-lxml
 · python-imaging | pillow
 · python-keybinder (optional)
 · python-xdg (optional)

Use your distro package manager with e.g.:

  sudo apt-get install python python-gtk2 python-xdg python-requests ...

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
manger, and are somewhat incomplete. (See "Dependencies" section.)


PYZ
---

Other users may wish to try the new Python archive (.PYZ) instead. Which
requires little installation, but can just be run in-place:

    python streamtuner-2.1.5.pyz

You could even make this Python ZIP executable, and copy it in your PATH.


Manual installation
-------------------

If you've checked out the source code repository, or did download the
*.src.txz archive, then you can just run it right there:

   ./st2.py

The easy way:

 · Run `sudo make install`
   which installs into the default location (/usr/share/streamtuner2).

To install it manually:

  · Create a /usr/share/streamtuner2/
  · Copy all *.py files there.
  · Also copy the "gtk3.xml" file into /usr/share/streamtuner2/
  · Copy the channels/ subdir into /usr/share/streamtuner2/channels/







|
















|
|







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
manger, and are somewhat incomplete. (See "Dependencies" section.)


PYZ
---

Other users may wish to try the new Python archive (.PYZ) instead. Which
requires little installation and can be run asis:

    python streamtuner-2.1.5.pyz

You could even make this Python ZIP executable, and copy it in your PATH.


Manual installation
-------------------

If you've checked out the source code repository, or did download the
*.src.txz archive, then you can just run it right there:

   ./st2.py

The easy way:

  · Run `sudo make install`
    which installs into the default location (/usr/share/streamtuner2).

To install it manually:

  · Create a /usr/share/streamtuner2/
  · Copy all *.py files there.
  · Also copy the "gtk3.xml" file into /usr/share/streamtuner2/
  · Copy the channels/ subdir into /usr/share/streamtuner2/channels/
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
Take note of any red error messages.

If it's just one channel plugin that hangs at startup, you
can alternatively disable it once:

   streamtuner2 -d xiph

Use [save] in the settings dialog (via F12) if you wish to
permanently disable it.

You can also manually edit the configuration file, located
in ~/.config/streamtuner2/settings.json


Hacking
-------







|
|







109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
Take note of any red error messages.

If it's just one channel plugin that hangs at startup, you
can alternatively disable it once:

   streamtuner2 -d xiph

Start the settings dialog (via F12) and press [save] there
if you wish to permanently disable it.

You can also manually edit the configuration file, located
in ~/.config/streamtuner2/settings.json


Hacking
-------
150
151
152
153
154
155
156



157
158
159
160
161
162
163
Or browse the contents

  fossil ui

Alternatively there are git and svn exports.

  fossil export --svn



  http://fossil.include-once.org/streamtuner2/git-fast-export

You can send in patches, a fossil bundle, or set up your cloned
repo publically per `fossil cgi`. Else just create an account on
fossil.include-once.org, and send a mail, so I can elevate that
account to developer/commit/push permissions quickly. (Fossil
repos don't break. So dealing out sync access is a no-brainer.)







>
>
>







151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
Or browse the contents

  fossil ui

Alternatively there are git and svn exports.

  fossil export --svn

Or via

  http://fossil.include-once.org/streamtuner2/git-fast-export

You can send in patches, a fossil bundle, or set up your cloned
repo publically per `fossil cgi`. Else just create an account on
fossil.include-once.org, and send a mail, so I can elevate that
account to developer/commit/push permissions quickly. (Fossil
repos don't break. So dealing out sync access is a no-brainer.)

Modified channels/bookmarks.py from [8d733cb84a] to [d9e55cafbe].

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
59
60






61







62
63
64
65
66
67
68

69
70
71
72
73
74
75
# Any bookmarked station will appear with a star ★ icon in other channels.
#
# Some feature extensions inject custom subcategories here. For example the
# "search" feature adds its own result list here, as does the "timer" plugin.


from config import *
from uikit import uikit
from channels import *



# The bookmarks tab is a core feature and built into the GtkBuilder
# layout. Which is why it derives from GenericChannel, and requires
# less setup.
#
# Furthermore it pretty much only handles a static streams{} list.
# Sub-plugins simply append a new category, and populate the streams
# list themselves.
#
# It's accessible as `parent.bookmarks` in the ST2 window and elsewhere.
#
class bookmarks(GenericChannel):

    # desc
    module = "bookmarks"
    title = "bookmarks"
    base_url = "file:.config/streamtuner2/bookmarks.json"
    listformat = "any"

    # content
    categories = ["favourite", ]  # timer, links, search, and links show up as needed
    current = "favourite"
    default = "favourite"
    finder_song = { "genre": "Youtube ", "format": "video/youtube", "playing": "current_", "title": "The Finder song", "url": "http://youtube.com/v/omyZy4H8y9M", "homepage": "http://youtu.be/omyZy4H8y9M" }
    streams = {"favourite":[finder_song], "search":[], "scripts":[], "timer":[], "history":[], }


    # cache list, to determine if a PLS url is bookmarked
    urls = []










    def gui(self, parent):
        GenericChannel.gui(self, parent)
        parent.notebook_channels.set_menu_label_text(parent.v_bookmarks, "bookmarks")















    # this channel does not actually retrieve/parse data from anywhere
    def update_categories(self):
        pass
        
    # but category sub-plugins might provide a hook
    category_plugins = {}
    def update_streams(self, cat):

        if cat in self.category_plugins:
            return self.category_plugins[cat].update_streams(cat) or []
        else:
            return self.streams.get(cat, [])

        
    # streams are already loaded at instantiation







|
















|
<
<
<

<
<

<
<







>
>
>
>
>
>
>
>





>
>
>
>
>
>

>
>
>
>
>
>
>







>







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
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
# Any bookmarked station will appear with a star ★ icon in other channels.
#
# Some feature extensions inject custom subcategories here. For example the
# "search" feature adds its own result list here, as does the "timer" plugin.


from config import *
from uikit import *
from channels import *



# The bookmarks tab is a core feature and built into the GtkBuilder
# layout. Which is why it derives from GenericChannel, and requires
# less setup.
#
# Furthermore it pretty much only handles a static streams{} list.
# Sub-plugins simply append a new category, and populate the streams
# list themselves.
#
# It's accessible as `parent.bookmarks` in the ST2 window and elsewhere.
#
class bookmarks(GenericChannel):

    # content



    listformat = "any"


    categories = ["favourite", ]  # timer, links, search, and links show up as needed


    finder_song = { "genre": "Youtube ", "format": "video/youtube", "playing": "current_", "title": "The Finder song", "url": "http://youtube.com/v/omyZy4H8y9M", "homepage": "http://youtu.be/omyZy4H8y9M" }
    streams = {"favourite":[finder_song], "search":[], "scripts":[], "timer":[], "history":[], }


    # cache list, to determine if a PLS url is bookmarked
    urls = []

    drag_types = [
      ("UTF8_STRING", 0, 5),
      ("STRING", 0, 5),
      ("text/plain", 0, 10),
      ("text/uri-list", 0, 11),
      ("application/x-scpls", 0, 21),
      ("*/*", 0, 22),
    ]

    def gui(self, parent):
        GenericChannel.gui(self, parent)
        parent.notebook_channels.set_menu_label_text(parent.v_bookmarks, "bookmarks")

        #DND
        w = self.gtk_list
        #self.gtk_list.drag_source_set_icon_stock("gtk-folder")
        w.enable_model_drag_source(gtk.gdk.BUTTON1_MASK, self.drag_types, gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY|gtk.gdk.ACTION_MOVE)
        w.enable_model_drag_dest(self.drag_types, gtk.gdk.ACTION_DEFAULT|gtk.gdk.ACTION_COPY)
        w.connect('drag_drop', self.drop_cb)

    # function to print out the mime type of the drop item
    def drop_cb(self, wid, context, x, y, time, *e):
        print '\n'.join([str(t) for t in context.targets])
        # What should I put here to get the URL of the link?
        context.finish(True, False, time)
        return True
                
    # this channel does not actually retrieve/parse data from anywhere
    def update_categories(self):
        pass
        
    # but category sub-plugins might provide a hook
    category_plugins = {}
    def update_streams(self, cat):

        if cat in self.category_plugins:
            return self.category_plugins[cat].update_streams(cat) or []
        else:
            return self.streams.get(cat, [])

        
    # streams are already loaded at instantiation

Modified channels/configwin.py from [7180117d4f] to [913bf48a5c].

21
22
23
24
25
26
27



28
29
30
31
32
33
34
# Settings window
#
# Interacts with main.* window (gtkBuilder widgets)
# and conf.* dictionary.
#
class configwin (AuxiliaryWindow):





    # Display win_config, pre-fill text fields from global conf. object
    def open(self, widget):
        if self.first_open:
            self.add_plugins()
            self.first_open = 0
            self.win_config.resize(565, 625)







>
>
>







21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# Settings window
#
# Interacts with main.* window (gtkBuilder widgets)
# and conf.* dictionary.
#
class configwin (AuxiliaryWindow):

    # control flags
    meta = plugin_meta()


    # Display win_config, pre-fill text fields from global conf. object
    def open(self, widget):
        if self.first_open:
            self.add_plugins()
            self.first_open = 0
            self.win_config.resize(565, 625)

Modified channels/exportcat.py from [e085c85a05] to [2dd3da6db4].

27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
import action
import re


# provides another export window, and custom file generation - does not use action.save()
class exportcat():

    module = ""
    meta = plugin_meta()

    # Register callback
    def __init__(self, parent):
        conf.add_plugin_defaults(self.meta, self.module)
        if parent:
            self.parent = parent







<







27
28
29
30
31
32
33

34
35
36
37
38
39
40
import action
import re


# provides another export window, and custom file generation - does not use action.save()
class exportcat():


    meta = plugin_meta()

    # Register callback
    def __init__(self, parent):
        conf.add_plugin_defaults(self.meta, self.module)
        if parent:
            self.parent = parent

Modified channels/file.py from [76d2506448] to [62f947846d].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69




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







|
<
<
|
<
<







50
51
52
53
54
55
56
57


58


59
60
61
62
63
64
65




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

    # data


    listtype = "href"


    streams = {}
    categories = []
    dir = []
    ext = []
    
    # display
    datamap = [ # coltitle   width	[ datasrc key, type, renderer, attrs ]	[cellrenderer2], ...

Modified channels/global_key.py from [6c7e3b19fe] to [d213cc695c].

25
26
27
28
29
30
31

32
33
34
35
36
37
38
39
40
import random



# register a key
class global_key(object):


    module = "global_key"
    title = "keyboard shortcut"
    meta = plugin_meta()
    last = 0


    # register
    def __init__(self, parent):
        self.parent = parent







>

<







25
26
27
28
29
30
31
32
33

34
35
36
37
38
39
40
import random



# register a key
class global_key(object):

    # control attributes
    module = "global_key"

    meta = plugin_meta()
    last = 0


    # register
    def __init__(self, parent):
        self.parent = parent

Modified channels/history.py from [7a53bd0585] to [140c84d35b].

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

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):







|

<

<







15
16
17
18
19
20
21
22
23

24

25
26
27
28
29
30
31

from config import *
from channels import *


class history:

    # plugin attributes
    module = "history"

    meta = plugin_meta()

    
    # store
    bm = None


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

Modified channels/icast.py from [202f901cdd] to [e1a9b5a7b8].

37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from channels import *
import ahttp as http


# Surfmusik sharing site
class icast (ChannelPlugin):

    # description
    homepage = "http://www.icast.io/"
    has_search = True
    listformat = "pls"
    titles = dict(listeners=False, bitrate=False, playing=False)

    categories = []
    
    base = "http://api.icast.io/1/"
    

    # Categories require little post-processing, just dict into list conversion
    def update_categories(self):
        self.categories = []
        for genre,cats in json.loads(http.get(self.base + "genres"))["genres"].items():







|
<



<

<







37
38
39
40
41
42
43
44

45
46
47

48

49
50
51
52
53
54
55
from channels import *
import ahttp as http


# Surfmusik sharing site
class icast (ChannelPlugin):

    # control attributes

    has_search = True
    listformat = "pls"
    titles = dict(listeners=False, bitrate=False, playing=False)

    categories = []

    base = "http://api.icast.io/1/"
    

    # Categories require little post-processing, just dict into list conversion
    def update_categories(self):
        self.categories = []
        for genre,cats in json.loads(http.get(self.base + "genres"))["genres"].items():

Modified channels/internet_radio.py from [c485fff9cd] to [3b80b93221].

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




# streams and gui
class internet_radio (ChannelPlugin):


    # description
    title = "InternetRadio"
    module = "internet_radio"
    homepage = "http://www.internet-radio.org.uk/"
    listformat = "pls"
    
    # category map
    categories = []
    current = ""
    default = ""


    # load genres
    def update_categories(self):
    
        html = http.get(self.homepage)
        rx = re.compile("""="/stations/[-+&.\w\s%]+/">([^<]+)<""")







|
<
<
<
<

<
<

<
<







34
35
36
37
38
39
40
41




42


43


44
45
46
47
48
49
50




# streams and gui
class internet_radio (ChannelPlugin):

    # control data




    listformat = "pls"


    categories = []




    # load genres
    def update_categories(self):
    
        html = http.get(self.homepage)
        rx = re.compile("""="/stations/[-+&.\w\s%]+/">([^<]+)<""")

Modified channels/itunes.py from [53ab5aa446] to [4b51b14809].

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50



51
52
53
54
55
56
57
from channels import *
import ahttp as http


# Surfmusik sharing site
class itunes (ChannelPlugin):

    # description
    title = "iTunes RS"
    module = "itunes"
    #module = "rs_playlist"
    homepage = "http://www.itunes.com?"
    has_search = False
    listformat = "pls"
    titles = dict(listeners=False, bitrate=False, playing=False)




    categories = [
        "Adult Contemporary",
        "Alternative Rock",
        "Ambient",
        "Blues",
        "Classic Rock",







|
<
<
<
<



>
>
>







36
37
38
39
40
41
42
43




44
45
46
47
48
49
50
51
52
53
54
55
56
from channels import *
import ahttp as http


# Surfmusik sharing site
class itunes (ChannelPlugin):

    # control attribues




    has_search = False
    listformat = "pls"
    titles = dict(listeners=False, bitrate=False, playing=False)
    base = "http://lab.rolisoft.net/playlists/itunes.php"
    #base = "http://aws-eu.rolisoft.net/playlists/itunes.php"
    #base = "http://aws-us.rolisoft.net/playlists/itunes.php"

    categories = [
        "Adult Contemporary",
        "Alternative Rock",
        "Ambient",
        "Blues",
        "Classic Rock",
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
        "Sports Radio",
        "Top 40",
        "'70s Retro",
        "'80s Flashback",
        "'90s Hits",
    ]
    
    base = "http://lab.rolisoft.net/playlists/itunes.php"
    #base = "http://aws-eu.rolisoft.net/playlists/itunes.php"
    #base = "http://aws-us.rolisoft.net/playlists/itunes.php"
    

    # static list for iTunes
    def update_categories(self):
        pass

    # Just copy over stream URLs and station titles
    def update_streams(self, cat):







<
<
<
<







72
73
74
75
76
77
78




79
80
81
82
83
84
85
        "Sports Radio",
        "Top 40",
        "'70s Retro",
        "'80s Flashback",
        "'90s Hits",
    ]
    





    # static list for iTunes
    def update_categories(self):
        pass

    # Just copy over stream URLs and station titles
    def update_streams(self, cat):

Modified channels/jamendo.py from [833b488235] to [55defe1856].

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
#  [+]  http://api.jamendo.com/get2/stream/track/xspf/?playlist_id=171574&n=all&order=random
#  [+]  http://api.jamendo.com/get2/stream/track/xspf/?album_id=%s&streamencoding=ogg2&n=all
#
# Seem to resolve to OGG Vorbis each.
#
class jamendo (ChannelPlugin):

    # description
    title = "Jamendo"
    module = "jamendo"
    homepage = "http://www.jamendo.com/"
    version = 0.3
    has_search = True

    base = "http://www.jamendo.com/en/"
    listformat = "srv"
    api_base = "http://api.jamendo.com/v3.0/"
    cid = "49daa4f5"

    categories = []

    titles = dict( title="Title", playing="Album/Artist/User", bitrate=False, listeners=False )


    # refresh category list
    def update_categories(self):

        self.categories = [







|
<
<
<
<

<




<

<







50
51
52
53
54
55
56
57




58

59
60
61
62

63

64
65
66
67
68
69
70
#  [+]  http://api.jamendo.com/get2/stream/track/xspf/?playlist_id=171574&n=all&order=random
#  [+]  http://api.jamendo.com/get2/stream/track/xspf/?album_id=%s&streamencoding=ogg2&n=all
#
# Seem to resolve to OGG Vorbis each.
#
class jamendo (ChannelPlugin):

    # control flags




    has_search = True

    base = "http://www.jamendo.com/en/"
    listformat = "srv"
    api_base = "http://api.jamendo.com/v3.0/"
    cid = "49daa4f5"

    categories = []

    titles = dict( title="Title", playing="Album/Artist/User", bitrate=False, listeners=False )


    # refresh category list
    def update_categories(self):

        self.categories = [

Modified channels/links.py from [c05f5fe1bb] to [a70b7e1793].

20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35


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

    # plugin info
    module = "links"
    title = "Links"
    version = 0.1
    meta = plugin_meta()
    
    # list
    streams = [    ]
    default = [
        ("stream", "rad.io", "http://www.rad.io/"),
        ("stream", "RadioTower", "http://www.radiotower.com/"),







<
<







20
21
22
23
24
25
26


27
28
29
30
31
32
33


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

    # plugin info
    module = "links"


    meta = plugin_meta()
    
    # list
    streams = [    ]
    default = [
        ("stream", "rad.io", "http://www.rad.io/"),
        ("stream", "RadioTower", "http://www.radiotower.com/"),

Modified channels/live365.py from [e2484ab83b] to [7936759236].

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
#
# And using a HTML5 player direct URL now:
#
#    /cgi-bin/play.pls?stationid=%s&direct=1&file=%s.pls
#
class live365(ChannelPlugin):

    # desc
    module = "live365"
    title = "Live365"
    homepage = "http://www.live365.com/"
    base_url = "http://www.live365.com/"
    has_search = True
    listformat = "pls"
    mediatype = "audio/mpeg"
    has_search = False

    # content
    categories = ['Alternative', 'Blues', 'Classical', 'Country', 'Easy Listening', 'Electronic/Dance', 'Folk', 'Freeform', 'Hip-Hop/Rap', 'Inspirational', 'International', 'Jazz', 'Latin', 'Metal', 'New Age', 'Oldies', 'Pop', 'R&B/Urban', 'Reggae', 'Rock', 'Seasonal/Holiday', 'Soundtracks', 'Talk']
    current = "Alternative"
    default = "Pop"
    empty = None
    
    # redefine
    streams = {}
    

    def __init__(self, parent=None):
    







|
<
<
<








<
<
<







49
50
51
52
53
54
55
56



57
58
59
60
61
62
63
64



65
66
67
68
69
70
71
#
# And using a HTML5 player direct URL now:
#
#    /cgi-bin/play.pls?stationid=%s&direct=1&file=%s.pls
#
class live365(ChannelPlugin):

    # control attributes



    base_url = "http://www.live365.com/"
    has_search = True
    listformat = "pls"
    mediatype = "audio/mpeg"
    has_search = False

    # content
    categories = ['Alternative', 'Blues', 'Classical', 'Country', 'Easy Listening', 'Electronic/Dance', 'Folk', 'Freeform', 'Hip-Hop/Rap', 'Inspirational', 'International', 'Jazz', 'Latin', 'Metal', 'New Age', 'Oldies', 'Pop', 'R&B/Urban', 'Reggae', 'Rock', 'Seasonal/Holiday', 'Soundtracks', 'Talk']



    
    # redefine
    streams = {}
    

    def __init__(self, parent=None):
    

Modified channels/modarchive.py from [499e517889] to [b5a34220a3].

36
37
38
39
40
41
42
43
44
45
46
47

48
49
50
51
52
53
54
#
# Modarchive actually provides an API
# http://modarchive.org/index.php?xml-api
# (If only it wasn't XML based..)
#
class modarchive (ChannelPlugin):

    # description
    title = "modarchive"
    module = "modarchive"
    homepage = "http://www.modarchive.org/"
    base = "http://modarchive.org/"

    listformat = "href"
    titles = dict(genre="Genre", title="Song", playing="File", listeners="Rating", bitrate=0)

    # keeps category titles->urls    
    catmap = {"Chiptune": "54", "Electronic - Ambient": "2", "Electronic - Other": "100", "Rock (general)": "13", "Trance - Hard": "64", "Swing": "75", "Rock - Soft": "15", "R &amp; B": "26", "Big Band": "74", "Ska": "24", "Electronic - Rave": "65", "Electronic - Progressive": "11", "Piano": "59", "Comedy": "45", "Christmas": "72", "Chillout": "106", "Reggae": "27", "Electronic - Industrial": "34", "Grunge": "103", "Medieval": "28", "Demo Style": "55", "Orchestral": "50", "Soundtrack": "43", "Electronic - Jungle": "60", "Fusion": "102", "Electronic - IDM": "99", "Ballad": "56", "Country": "18", "World": "42", "Jazz - Modern": "31", "Video Game": "8", "Funk": "32", "Electronic - Drum &amp; Bass": "6", "Alternative": "48", "Electronic - Minimal": "101", "Electronic - Gabber": "40", "Vocal Montage": "76", "Metal (general)": "36", "Electronic - Breakbeat": "9", "Soul": "25", "Electronic (general)": "1", "Punk": "35", "Pop - Synth": "61", "Electronic - Dance": "3", "Pop (general)": "12", "Trance - Progressive": "85", "Trance (general)": "71", "Disco": "58", "Electronic - House": "10", "Experimental": "46", "Trance - Goa": "66", "Rock - Hard": "14", "Trance - Dream": "67", "Spiritual": "47", "Metal - Extreme": "37", "Jazz (general)": "29", "Trance - Tribal": "70", "Classical": "20", "Hip-Hop": "22", "Bluegrass": "105", "Halloween": "82", "Jazz - Acid": "30", "Easy Listening": "107", "New Age": "44", "Fantasy": "52", "Blues": "19", "Other": "41", "Trance - Acid": "63", "Gothic": "38", "Electronic - Hardcore": "39", "One Hour Compo": "53", "Pop - Soft": "62", "Electronic - Techno": "7", "Religious": "49", "Folk": "21"}
    categories = []
    







|
|
<
<

>







36
37
38
39
40
41
42
43
44


45
46
47
48
49
50
51
52
53
#
# Modarchive actually provides an API
# http://modarchive.org/index.php?xml-api
# (If only it wasn't XML based..)
#
class modarchive (ChannelPlugin):

    # control attributes
    has_search = False


    base = "http://modarchive.org/"
    audioformat = "audio/mod+zip"
    listformat = "href"
    titles = dict(genre="Genre", title="Song", playing="File", listeners="Rating", bitrate=0)

    # keeps category titles->urls    
    catmap = {"Chiptune": "54", "Electronic - Ambient": "2", "Electronic - Other": "100", "Rock (general)": "13", "Trance - Hard": "64", "Swing": "75", "Rock - Soft": "15", "R &amp; B": "26", "Big Band": "74", "Ska": "24", "Electronic - Rave": "65", "Electronic - Progressive": "11", "Piano": "59", "Comedy": "45", "Christmas": "72", "Chillout": "106", "Reggae": "27", "Electronic - Industrial": "34", "Grunge": "103", "Medieval": "28", "Demo Style": "55", "Orchestral": "50", "Soundtrack": "43", "Electronic - Jungle": "60", "Fusion": "102", "Electronic - IDM": "99", "Ballad": "56", "Country": "18", "World": "42", "Jazz - Modern": "31", "Video Game": "8", "Funk": "32", "Electronic - Drum &amp; Bass": "6", "Alternative": "48", "Electronic - Minimal": "101", "Electronic - Gabber": "40", "Vocal Montage": "76", "Metal (general)": "36", "Electronic - Breakbeat": "9", "Soul": "25", "Electronic (general)": "1", "Punk": "35", "Pop - Synth": "61", "Electronic - Dance": "3", "Pop (general)": "12", "Trance - Progressive": "85", "Trance (general)": "71", "Disco": "58", "Electronic - House": "10", "Experimental": "46", "Trance - Goa": "66", "Rock - Hard": "14", "Trance - Dream": "67", "Spiritual": "47", "Metal - Extreme": "37", "Jazz (general)": "29", "Trance - Tribal": "70", "Classical": "20", "Hip-Hop": "22", "Bluegrass": "105", "Halloween": "82", "Jazz - Acid": "30", "Easy Listening": "107", "New Age": "44", "Fantasy": "52", "Blues": "19", "Other": "41", "Trance - Acid": "63", "Gothic": "38", "Electronic - Hardcore": "39", "One Hour Compo": "53", "Pop - Soft": "62", "Electronic - Techno": "7", "Religious": "49", "Folk": "21"}
    categories = []
    

Modified channels/myoggradio.py from [b090a1507b] to [cce93c4126].

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
import copy
from uikit import gtk


# open source radio sharing stie
class myoggradio(ChannelPlugin):

    # settings
    title ="MOR"
    #module = "myoggradio"
    api = "http://www.myoggradio.org/"
    listformat = "mixed(pls/m3u/srv)"
    
    # hide unused columns
    titles = dict(playing=False, listeners=False, bitrate=False)
    
    # category map
    categories = ['common', 'personal']
    default = 'common'
    current = 'common'
    
    # netrc instance
    netrc = None
    
    
    
    # prepare GUI
    def __init__(self, parent):
        ChannelPlugin.__init__(self, parent)
        if parent:







|
|
|

<






<
<
<
<
<







39
40
41
42
43
44
45
46
47
48
49

50
51
52
53
54
55





56
57
58
59
60
61
62
import copy
from uikit import gtk


# open source radio sharing stie
class myoggradio(ChannelPlugin):

    # control flags
    listformat = "mixed(pls/m3u/srv)"
    has_search = False
    api = "http://www.myoggradio.org/"

    
    # hide unused columns
    titles = dict(playing=False, listeners=False, bitrate=False)
    
    # category map
    categories = ['common', 'personal']





    
    
    
    # prepare GUI
    def __init__(self, parent):
        ChannelPlugin.__init__(self, parent)
        if parent:

Modified channels/punkcast.py from [eea2622232] to [946f14f97b].

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
from channels import *
from config import __print__, dbg


# basic.ch broadcast archive
class punkcast (ChannelPlugin):

    # description
    title = "punkcast"
    module = "punkcast"
    homepage = "http://www.punkcast.com/"

    # keeps category titles->urls    
    catmap = {}
    categories = ["list"]
    default = "list"
    current = "list"
    titles = dict(playing=False, listeners=False, bitrate=False, homepage=False) 


    # don't do anything
    def update_categories(self):
        pass








<
<
<
<
<



<
<







30
31
32
33
34
35
36





37
38
39


40
41
42
43
44
45
46
from channels import *
from config import __print__, dbg


# basic.ch broadcast archive
class punkcast (ChannelPlugin):






    # keeps category titles->urls    
    catmap = {}
    categories = ["list"]


    titles = dict(playing=False, listeners=False, bitrate=False, homepage=False) 


    # don't do anything
    def update_categories(self):
        pass

Modified channels/radiobrowser.py from [efa310538d] to [eb4063d5ff].

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
# {"id":63,"name": "Energy Sachsen", "url":"http://www.energyradio.de/sachsen",
#  "homepage":"http://www.energy.de", "favicon":"http://www.energy.de/favicon.ico",
#  "tags":"Pop Dance RnB Techno","country":"Germany","subcountry":"","language":"German",
# "votes":4,"negativevotes":10},
#
class radiobrowser (ChannelPlugin):

    # description
    homepage = "http://www.radio-browser.info/"
    has_search = True
    listformat = "pls"
    titles = dict(listeners="Votes+", bitrate="Votes-", playing="Country")

    categories = []
    pricat = ("topvote", "topclick")
    catmap = { "tags": "bytag", "countries": "bycountry", "languages": "bylanguage" }
    
    base = "http://www.radio-browser.info/webservice/json/"
    

    # votes, and tags, no countries or languages
    def update_categories(self):
        self.categories = list(self.pricat)
        for sub in [conf.radiobrowser_cat]:
            cats = []
            for entry in self.api(sub):







|
<



|




<
<







54
55
56
57
58
59
60
61

62
63
64
65
66
67
68
69


70
71
72
73
74
75
76
# {"id":63,"name": "Energy Sachsen", "url":"http://www.energyradio.de/sachsen",
#  "homepage":"http://www.energy.de", "favicon":"http://www.energy.de/favicon.ico",
#  "tags":"Pop Dance RnB Techno","country":"Germany","subcountry":"","language":"German",
# "votes":4,"negativevotes":10},
#
class radiobrowser (ChannelPlugin):

    # control flags

    has_search = True
    listformat = "pls"
    titles = dict(listeners="Votes+", bitrate="Votes-", playing="Country")
    base = "http://www.radio-browser.info/webservice/json/"
    categories = []
    pricat = ("topvote", "topclick")
    catmap = { "tags": "bytag", "countries": "bycountry", "languages": "bylanguage" }
    



    # votes, and tags, no countries or languages
    def update_categories(self):
        self.categories = list(self.pricat)
        for sub in [conf.radiobrowser_cat]:
            cats = []
            for entry in self.api(sub):

Modified channels/shoutcast.py from [e812eed775] to [1fad5ce44e].

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
#   POST http://www.shoutcast.com/Home/BrowseByGenre {genrename: Pop}
#
# We do need a catmap now too, but that's easy to aquire and will be kept
# within the cache dirs.
#
class shoutcast(channels.ChannelPlugin):

    # desc
    module = "shoutcast"
    title = "SHOUTcast"
    base_url = "http://shoutcast.com/"
    listformat = "pls"

    
    # categories
    categories = []
    catmap = {"Choral": 35, "Winter": 275, "JROCK": 306, "Motown": 237, "Political": 290, "Tango": 192, "Ska": 22, "Comedy": 283, "Decades": 212, "European": 143, "Reggaeton": 189, "Islamic": 307, "Freestyle": 114, "French": 145, "Western": 53, "Dancepunk": 6, "News": 287, "Xtreme": 23, "Bollywood": 138, "Celtic": 141, "Kids": 278, "Filipino": 144, "Hanukkah": 270, "Greek": 146, "Punk": 21, "Spiritual": 211, "Industrial": 14, "Baroque": 33, "Talk": 282, "JPOP": 227, "Scanner": 291, "Mediterranean": 154, "Swing": 174, "Themes": 89, "IDM": 75, "40s": 214, "Funk": 236, "Rap": 110, "House": 74, "Educational": 285, "Caribbean": 140, "Misc": 295, "30s": 213, "Anniversary": 266, "Sports": 293, "International": 134, "Tribute": 107, "Piano": 41, "Romantic": 42, "90s": 219, "Latin": 177, "Grunge": 10, "Dubstep": 312, "Government": 286, "Country": 44, "Salsa": 191, "Hardcore": 11, "Afrikaans": 309, "Downtempo": 69, "Merengue": 187, "Psychedelic": 260, "Female": 95, "Bop": 167, "Tribal": 80, "Metal": 195, "70s": 217, "Tejano": 193, "Exotica": 55, "Anime": 277, "BlogTalk": 296, "African": 135, "Patriotic": 101, "Blues": 24, "Turntablism": 119, "Chinese": 142, "Garage": 72, "Dance": 66, "Valentine": 273, "Barbershop": 222, "Alternative": 1, "Technology": 294, "Folk": 82, "Klezmer": 152, "Samba": 315, "Turkish": 305, "Trance": 79, "Dub": 245, "Rock": 250, "Polka": 59, "Modern": 39, "Lounge": 57, "Indian": 149, "Hindi": 148, "Brazilian": 139, "Eclectic": 93, "Korean": 153, "Creole": 316, "Dancehall": 244, "Surf": 264, "Reggae": 242, "Goth": 9, "Oldies": 226, "Zouk": 162, "Environmental": 207, "Techno": 78, "Adult": 90, "Rockabilly": 262, "Wedding": 274, "Russian": 157, "Sexy": 104, "Chill": 92, "Opera": 40, "Emo": 8, "Experimental": 94, "Showtunes": 280, "Breakbeat": 65, "Jungle": 76, "Soundtracks": 276, "LoFi": 15, "Metalcore": 202, "Bachata": 178, "Kwanzaa": 272, "Banda": 179, "Americana": 46, "Classical": 32, "German": 302, "Tamil": 160, "Bluegrass": 47, "Halloween": 269, "College": 300, "Ambient": 63, "Birthday": 267, "Meditation": 210, "Electronic": 61, "50s": 215, "Chamber": 34, "Heartache": 96, "Britpop": 3, "Soca": 158, "Grindcore": 199, "Reality": 103, "00s": 303, "Symphony": 43, "Pop": 220, "Ranchera": 188, "Electro": 71, "Christmas": 268, "Christian": 123, "Progressive": 77, "Jazz": 163, "Trippy": 108, "Instrumental": 97, "Tropicalia": 194, "Fusion": 170, "Healing": 209, "Glam": 255, "80s": 218, "KPOP": 308, "Worldbeat": 161, "Mixtapes": 117, "60s": 216, "Mariachi": 186, "Soul": 240, "Cumbia": 181, "Inspirational": 122, "Impressionist": 38, "Gospel": 129, "Disco": 68, "Arabic": 136, "Idols": 225, "Ragga": 247, "Demo": 67, "LGBT": 98, "Honeymoon": 271, "Japanese": 150, "Community": 284, "Weather": 317, "Asian": 137, "Hebrew": 151, "Flamenco": 314, "Shuffle": 105}
    current = ""
    default = "Alternative"
    empty = ""
    
    # redefine
    streams = {}
    
        
    # Extracts the category list from www.shoutcast.com,
    # stores a catmap (title => id)







|
<
<


>




<
<
<







46
47
48
49
50
51
52
53


54
55
56
57
58
59
60



61
62
63
64
65
66
67
#   POST http://www.shoutcast.com/Home/BrowseByGenre {genrename: Pop}
#
# We do need a catmap now too, but that's easy to aquire and will be kept
# within the cache dirs.
#
class shoutcast(channels.ChannelPlugin):

    # attrs


    base_url = "http://shoutcast.com/"
    listformat = "pls"
    has_search = False
    
    # categories
    categories = []
    catmap = {"Choral": 35, "Winter": 275, "JROCK": 306, "Motown": 237, "Political": 290, "Tango": 192, "Ska": 22, "Comedy": 283, "Decades": 212, "European": 143, "Reggaeton": 189, "Islamic": 307, "Freestyle": 114, "French": 145, "Western": 53, "Dancepunk": 6, "News": 287, "Xtreme": 23, "Bollywood": 138, "Celtic": 141, "Kids": 278, "Filipino": 144, "Hanukkah": 270, "Greek": 146, "Punk": 21, "Spiritual": 211, "Industrial": 14, "Baroque": 33, "Talk": 282, "JPOP": 227, "Scanner": 291, "Mediterranean": 154, "Swing": 174, "Themes": 89, "IDM": 75, "40s": 214, "Funk": 236, "Rap": 110, "House": 74, "Educational": 285, "Caribbean": 140, "Misc": 295, "30s": 213, "Anniversary": 266, "Sports": 293, "International": 134, "Tribute": 107, "Piano": 41, "Romantic": 42, "90s": 219, "Latin": 177, "Grunge": 10, "Dubstep": 312, "Government": 286, "Country": 44, "Salsa": 191, "Hardcore": 11, "Afrikaans": 309, "Downtempo": 69, "Merengue": 187, "Psychedelic": 260, "Female": 95, "Bop": 167, "Tribal": 80, "Metal": 195, "70s": 217, "Tejano": 193, "Exotica": 55, "Anime": 277, "BlogTalk": 296, "African": 135, "Patriotic": 101, "Blues": 24, "Turntablism": 119, "Chinese": 142, "Garage": 72, "Dance": 66, "Valentine": 273, "Barbershop": 222, "Alternative": 1, "Technology": 294, "Folk": 82, "Klezmer": 152, "Samba": 315, "Turkish": 305, "Trance": 79, "Dub": 245, "Rock": 250, "Polka": 59, "Modern": 39, "Lounge": 57, "Indian": 149, "Hindi": 148, "Brazilian": 139, "Eclectic": 93, "Korean": 153, "Creole": 316, "Dancehall": 244, "Surf": 264, "Reggae": 242, "Goth": 9, "Oldies": 226, "Zouk": 162, "Environmental": 207, "Techno": 78, "Adult": 90, "Rockabilly": 262, "Wedding": 274, "Russian": 157, "Sexy": 104, "Chill": 92, "Opera": 40, "Emo": 8, "Experimental": 94, "Showtunes": 280, "Breakbeat": 65, "Jungle": 76, "Soundtracks": 276, "LoFi": 15, "Metalcore": 202, "Bachata": 178, "Kwanzaa": 272, "Banda": 179, "Americana": 46, "Classical": 32, "German": 302, "Tamil": 160, "Bluegrass": 47, "Halloween": 269, "College": 300, "Ambient": 63, "Birthday": 267, "Meditation": 210, "Electronic": 61, "50s": 215, "Chamber": 34, "Heartache": 96, "Britpop": 3, "Soca": 158, "Grindcore": 199, "Reality": 103, "00s": 303, "Symphony": 43, "Pop": 220, "Ranchera": 188, "Electro": 71, "Christmas": 268, "Christian": 123, "Progressive": 77, "Jazz": 163, "Trippy": 108, "Instrumental": 97, "Tropicalia": 194, "Fusion": 170, "Healing": 209, "Glam": 255, "80s": 218, "KPOP": 308, "Worldbeat": 161, "Mixtapes": 117, "60s": 216, "Mariachi": 186, "Soul": 240, "Cumbia": 181, "Inspirational": 122, "Impressionist": 38, "Gospel": 129, "Disco": 68, "Arabic": 136, "Idols": 225, "Ragga": 247, "Demo": 67, "LGBT": 98, "Honeymoon": 271, "Japanese": 150, "Community": 284, "Weather": 317, "Asian": 137, "Hebrew": 151, "Flamenco": 314, "Shuffle": 105}



    
    # redefine
    streams = {}
    
        
    # Extracts the category list from www.shoutcast.com,
    # stores a catmap (title => id)

Modified channels/surfmusik.py from [cab3938c1e] to [a963360d8b].

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
from channels import *



# Surfmusik sharing site
class surfmusik (ChannelPlugin):

    # description
    title = "SurfMusik"
    module = "surfmusik"
    homepage = "http://www.surfmusik.de/"
    listformat = "m3u"

    lang = "DE"   # last configured categories
    base = {
       "DE": ("http://www.surfmusik.de/", "genre/", "land/"),
       "EN": ("http://www.surfmusic.de/", "format/", "country/"),
    }

    categories = []
    titles = dict( genre="Genre", title="Station", playing="Location", bitrate=False, listeners=False )

    
    # Set channel title
    def __init__(self, parent=None):
        ChannelPlugin.__init__(self, parent)
        # title updating is a workaround, because the fixed .meta attribute are read first
        self.title = ("SurfMusik", "SurfMusic")[conf.get("surfmusik_lang", "EN") == "EN"]
        self.meta["title"] = self.title







<
|

<

|





<


>







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
from channels import *



# Surfmusik sharing site
class surfmusik (ChannelPlugin):


    # module attributes
    module = "surfmusik"

    listformat = "m3u"
    has_search = False
    lang = "DE"   # last configured categories
    base = {
       "DE": ("http://www.surfmusik.de/", "genre/", "land/"),
       "EN": ("http://www.surfmusic.de/", "format/", "country/"),
    }

    categories = []
    titles = dict( genre="Genre", title="Station", playing="Location", bitrate=False, listeners=False )

    
    # Set channel title
    def __init__(self, parent=None):
        ChannelPlugin.__init__(self, parent)
        # title updating is a workaround, because the fixed .meta attribute are read first
        self.title = ("SurfMusik", "SurfMusic")[conf.get("surfmusik_lang", "EN") == "EN"]
        self.meta["title"] = self.title

Modified channels/timer.py from [0abfc97e9b] to [f6d2128cb5].

32
33
34
35
36
37
38
39
40
41
42
43
44
45
46


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

    # plugin info
    module = "timer"
    title = "Timer"
    meta = plugin_meta()
    
    # configuration settings
    timefield = "playing"
    
    # kronos scheduler list
    sched = None







<







32
33
34
35
36
37
38

39
40
41
42
43
44
45


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

    # plugin info
    module = "timer"

    meta = plugin_meta()
    
    # configuration settings
    timefield = "playing"
    
    # kronos scheduler list
    sched = None

Modified channels/tunein.py from [6f7da7eac6] to [9721a1e7b7].

31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
import ahttp as http
from xml.etree import ElementTree


# TuneIn radio directory
class tunein (ChannelPlugin):

    # description
    title = "TuneIn"
    module = "tunein"
    homepage = "http://tunein.com/"
    has_search = False
    listformat = "pls"
    titles = dict(listeners=False)
    base = "http://opml.radiotime.com/"

    categories = ["local", "60's", "70's", "80's", "90's", "Adult Contemporary", "Alternative Rock", "Ambient", "Bluegrass", "Blues", "Bollywood", "Children's Music", "Christmas", "Classic Hits", "Classic Rock", "Classical", "College Radio", "Country", "Decades", "Disco", "Easy Listening", "Eclectic", "Electronic", "Folk", "Hip Hop", "Indie", "Internet Only", "Jazz", "Live Music", "Oldies", "Polka", "Reggae", "Reggaeton", "Religious", "Rock", "Salsa", "Soul and R&B", "Spanish Music", "Specialty", "Tango", "Top 40/Pop", "World"]
    catmap = {"60's": "g407", "Live Music": "g2778", "Children's Music": "c530749", "Polka": "g84", "Tango": "g3149", "Top 40/Pop": "c57943", "90's": "g2677", "Eclectic": "g78", "Decades": "c481372", "Christmas": "g375", "Reggae": "g85", "Reggaeton": "g2771", "Oldies": "c57947", "Jazz": "c57944", "Specialty": "c418831", "Hip Hop": "c57942", "College Radio": "c100000047", "Salsa": "g124", "Bollywood": "g2762", "70's": "g92", "Country": "c57940", "Classic Hits": "g2755", "Internet Only": "c417833", "Disco": "g385", "Rock": "c57951", "Soul and R&B": "c1367173", "Blues": "g106", "Classic Rock": "g54", "Alternative Rock": "c57936", "Adult Contemporary": "c57935", "Classical": "c57939", "World": "c57954", "Indie": "g2748", "Religious": "c57950", "Bluegrass": "g63", "Spanish Music": "c57945", "Easy Listening": "c10635888", "Ambient": "g2804", "80's": "g42", "Electronic": "c57941", "Folk": "g79"}







|
<
<
<







31
32
33
34
35
36
37
38



39
40
41
42
43
44
45
import ahttp as http
from xml.etree import ElementTree


# TuneIn radio directory
class tunein (ChannelPlugin):

    # control flags



    has_search = False
    listformat = "pls"
    titles = dict(listeners=False)
    base = "http://opml.radiotime.com/"

    categories = ["local", "60's", "70's", "80's", "90's", "Adult Contemporary", "Alternative Rock", "Ambient", "Bluegrass", "Blues", "Bollywood", "Children's Music", "Christmas", "Classic Hits", "Classic Rock", "Classical", "College Radio", "Country", "Decades", "Disco", "Easy Listening", "Eclectic", "Electronic", "Folk", "Hip Hop", "Indie", "Internet Only", "Jazz", "Live Music", "Oldies", "Polka", "Reggae", "Reggaeton", "Religious", "Rock", "Salsa", "Soul and R&B", "Spanish Music", "Specialty", "Tango", "Top 40/Pop", "World"]
    catmap = {"60's": "g407", "Live Music": "g2778", "Children's Music": "c530749", "Polka": "g84", "Tango": "g3149", "Top 40/Pop": "c57943", "90's": "g2677", "Eclectic": "g78", "Decades": "c481372", "Christmas": "g375", "Reggae": "g85", "Reggaeton": "g2771", "Oldies": "c57947", "Jazz": "c57944", "Specialty": "c418831", "Hip Hop": "c57942", "College Radio": "c100000047", "Salsa": "g124", "Bollywood": "g2762", "70's": "g92", "Country": "c57940", "Classic Hits": "g2755", "Internet Only": "c417833", "Disco": "g385", "Rock": "c57951", "Soul and R&B": "c1367173", "Blues": "g106", "Classic Rock": "g54", "Alternative Rock": "c57936", "Adult Contemporary": "c57935", "Classical": "c57939", "World": "c57954", "Indie": "g2748", "Religious": "c57950", "Bluegrass": "g63", "Spanish Music": "c57945", "Easy Listening": "c10635888", "Ambient": "g2804", "80's": "g42", "Electronic": "c57941", "Folk": "g79"}

Modified channels/ubuntuusers.py from [2d23392ed6] to [8e76324f70].

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


# UU Wiki radio list
class ubuntuusers (ChannelPlugin):

    # description
    module = "ubuntuusers"
    has_search = False
    listformat = "srv"
    titles = dict(playing=False, listeners=False, bitrate=False)
    base = "http://wiki.ubuntuusers.de/Internetradio/Stationen?action=export&format=raw"
    categories = ["stations"]









<







27
28
29
30
31
32
33

34
35
36
37
38
39
40
import ahttp


# UU Wiki radio list
class ubuntuusers (ChannelPlugin):

    # description

    has_search = False
    listformat = "srv"
    titles = dict(playing=False, listeners=False, bitrate=False)
    base = "http://wiki.ubuntuusers.de/Internetradio/Stationen?action=export&format=raw"
    categories = ["stations"]


Modified channels/xiph.py from [87b6bef7f2] to [df3676cdef].

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
# 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
#
class xiph (ChannelPlugin):

  # desc
  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 = "srv"
  has_search = True



  # content
  categories = [ "pop", "top40" ]
  current = ""
  default = "pop"
  empty = None
  
  
  # prepare category names
  def __init__(self, parent=None):
      
      self.categories = []
      self.filter = {}







|
<
<
<
<
<


>
>



<
<
<







52
53
54
55
56
57
58
59





60
61
62
63
64
65
66



67
68
69
70
71
72
73
# 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
#
class xiph (ChannelPlugin):

  # attributes





  listformat = "srv"
  has_search = True
  json_url = "http://api.include-once.org/xiph/cache.php"
  #xml_url = "http://dir.xiph.org/yp.xml"

  # content
  categories = [ "pop", "top40" ]



  
  
  # prepare category names
  def __init__(self, parent=None):
      
      self.categories = []
      self.filter = {}

Modified channels/youtube.py from [0f1183b76b] to [0b836cb39e].

62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
#
#   videoCat  Music  id= 10
#   guideCat  Music  id= GCTXVzaWM   channelid= UCBR8-60-B28hp2BmDPdntcQ
#   topicId   Music  mid= /m/0kpv0g
#
class youtube (ChannelPlugin):

    # description
    title = "Youtube"
    module = "youtube"
    homepage = "http://www.youtube.com/"
    listformat = "url/youtube"
    has_search = True
    fmt = "video/youtube"
    titles = dict( genre="Channel", title="Title", playing="Playlist", bitrate=False, listeners=False )

    # API config
    service = {
        2: [ "http://gdata.youtube.com/",
            {
                "v": 2,







|
<
<
<


|







62
63
64
65
66
67
68
69



70
71
72
73
74
75
76
77
78
79
#
#   videoCat  Music  id= 10
#   guideCat  Music  id= GCTXVzaWM   channelid= UCBR8-60-B28hp2BmDPdntcQ
#   topicId   Music  mid= /m/0kpv0g
#
class youtube (ChannelPlugin):

    # control attributes



    listformat = "url/youtube"
    has_search = True
    audioformat = "video/youtube"
    titles = dict( genre="Channel", title="Title", playing="Playlist", bitrate=False, listeners=False )

    # API config
    service = {
        2: [ "http://gdata.youtube.com/",
            {
                "v": 2,
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
        # for /playlistItems
        elif "resourceId" in row["snippet"]:
            id = row["snippet"]["resourceId"]["videoId"]

        data.update(dict(
            url = "http://youtube.com/v/" + id,
            homepage = "http://youtu.be/" + id + ("?wadsworth=1" if conf.youtube_wadsworth else ""),
            format = self.fmt,
            title = row["snippet"]["title"],
        ))
        
        # optional values
        if "playing" not in data:
            data["playing"] = row["snippet"]["channelTitle"]
        if "description" in row["snippet"]:
            data["description"] = row["snippet"]["description"],

        return data


    # API version 2.0s jsonified XML needs different unpacking:
    def wrap2(self, row):
        #__print__(dbg.DATA, row)
        return dict(
            genre = row["category"][1]["term"],
            title = row["title"]["$t"],
            playing = row["author"][0]["name"]["$t"],
            format = self.fmt,
            url = row["content"]["src"].split("?")[0],
            homepage = row["media$group"]["media$player"]["url"],
            image = row["media$group"]["media$thumbnail"][0]["url"],
        )










|



















|







285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
        # for /playlistItems
        elif "resourceId" in row["snippet"]:
            id = row["snippet"]["resourceId"]["videoId"]

        data.update(dict(
            url = "http://youtube.com/v/" + id,
            homepage = "http://youtu.be/" + id + ("?wadsworth=1" if conf.youtube_wadsworth else ""),
            format = self.audioformat,
            title = row["snippet"]["title"],
        ))
        
        # optional values
        if "playing" not in data:
            data["playing"] = row["snippet"]["channelTitle"]
        if "description" in row["snippet"]:
            data["description"] = row["snippet"]["description"],

        return data


    # API version 2.0s jsonified XML needs different unpacking:
    def wrap2(self, row):
        #__print__(dbg.DATA, row)
        return dict(
            genre = row["category"][1]["term"],
            title = row["title"]["$t"],
            playing = row["author"][0]["name"]["$t"],
            format = self.audioformat,
            url = row["content"]["src"].split("?")[0],
            homepage = row["media$group"]["media$player"]["url"],
            image = row["media$group"]["media$thumbnail"][0]["url"],
        )