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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [7726e18571]

Overview
Comment:Less indentation, starting to overhaul action.save() at least. (Whole `action` module is overdue.)
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 7726e185716a60596f4040b0d84b2cfa1cd1c3e0
User & Date: mario on 2015-04-07 05:54:57
Other Links: manifest | tags
Context
2015-04-07
05:55
Temporary export mechanism (saves whole category into .pls file). check-in: 8b7b270591 user: mario tags: trunk
05:54
Less indentation, starting to overhaul action.save() at least. (Whole `action` module is overdue.) check-in: 7726e18571 user: mario tags: trunk
05:53
Fix a few CLI bugs (doesn't work yet with dynamic module list), stub_parent() implementations for non-GUI mode should be merged. check-in: a7c3f7336a user: mario tags: trunk
Changes

Makefile became a regular file with contents [d3f05bad3b].

whitespace changes only

Modified action.py from [93903d7a98] to [b136303bf1].

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

# encoding: UTF-8
# api: streamtuner2
# type: functions
# title: play/record actions
# description: Starts audio applications, guesses MIME types for URLs
# version: 0.7
#
# Multimedia interface for starting audio players, recording app,
# or web browser (listed as "url/http" association in players).
#
# Each channel plugin has a .listtype which describes the linked
# audio playlist format. It's audio/x-scpls mostly, seldomly m3u,
# but sometimes url/direct if the entry[url] directly leads to the






|







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

# encoding: UTF-8
# api: streamtuner2
# type: functions
# title: play/record actions
# description: Starts audio applications, guesses MIME types for URLs
# version: 0.8
#
# Multimedia interface for starting audio players, recording app,
# or web browser (listed as "url/http" association in players).
#
# Each channel plugin has a .listtype which describes the linked
# audio playlist format. It's audio/x-scpls mostly, seldomly m3u,
# but sometimes url/direct if the entry[url] directly leads to the
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
import re
import os
import ahttp as http
from config import conf, __print__, dbg
import platform



main = None


#-- media actions                           ---------------------------------------------
#
# implements "play" and "record" methods,
# but also "browser" for web URLs
#        
class action:

        # streamlink formats




        lt = {"asx":"video/x-ms-asf", "pls":"audio/x-scpls", "m3u":"audio/x-mpegurl", "xspf":"application/xspf+xml", "href":"url/http", "ram":"audio/x-pn-realaudio", "smil":"application/smil"}





        # media formats

        mf = {"mp3":"audio/mpeg", "ogg":"audio/ogg", "aac":"audio/aac"}



        
        
        # web
        @staticmethod
        def browser(url):
            bin = conf.play.get("url/http", "sensible-browser")
            __print__( dbg.CONF, bin )







>



|






|
>
>
>
>
|
>
>
>
>
>
|
>
|
>
>
>







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
import re
import os
import ahttp as http
from config import conf, __print__, dbg
import platform


# coupling to main window
main = None


#-- media actions
#
# implements "play" and "record" methods,
# but also "browser" for web URLs
#        
class action:

    # streamlink map
    lt = dict(
       asx = "video/x-ms-asf",
       pls = "audio/x-scpls",
       m3u = "audio/x-mpegurl",
       xspf = "application/xspf+xml",
       href = "url/http",
       src = "url/direct",
       ram = "audio/x-pn-realaudio",
       smil = "application/smil",
    )
    # media map
    mf = dict(
       mp3 = "audio/mpeg",
       ogg = "audio/ogg",
       aac = "audio/aac",
    )
        
        
        # web
        @staticmethod
        def browser(url):
            bin = conf.play.get("url/http", "sensible-browser")
            __print__( dbg.CONF, bin )
100
101
102
103
104
105
106




107
108
109
110

111
112
113
114
115

116
117
118
119
120
121
122
123
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
151
152

153
154

155
156
157
158
159
160
161
162
                if cmd_list.get(match, None):
                    return cmd_list[match]


        # save as .m3u
        @staticmethod
        def save(row, fn, listformat="audio/x-scpls"):




            # modify stream url
            row["url"] = action.url(row["url"], listformat)
            stream_urls = action.extract_urls(row["url"], listformat)
            # output format

            if (re.search("\.m3u", fn)):
                txt = "#M3U\n"
                for url in stream_urls:
                    txt += http.fix_url(url) + "\n"
            # output format

            elif (re.search("\.pls", fn)):
                txt = "[playlist]\n" + "numberofentries=1\n"
                for i,u in enumerate(stream_urls):
                    i = str(i + 1)
                    txt += "File"+i + "=" + u + "\n"
                    txt += "Title"+i + "=" + row["title"] + "\n"
                    txt += "Length"+i + "=-1\n"
                txt += "Version=2\n"
            # output format

            elif (re.search("\.xspf", fn)):
                txt = '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
                txt += '<?http header="Content-Type: application/xspf+xml" ?>' + "\n"
                txt += '<playlist version="1" xmlns="http://xspf.org/ns/0/">' + "\n"
                for attr,tag in [("title","title"), ("homepage","info"), ("playing","annotation"), ("description","annotation")]:
                    if row.get(attr):
                        txt += "  <"+tag+">" + xmlentities(row[attr]) + "</"+tag+">\n"
                txt += "  <trackList>\n"
                for u in stream_urls:
                    txt += '	<track><location>' + xmlentities(u) + '</location></track>' + "\n"
                txt += "  </trackList>\n</playlist>\n"


            # output format

            elif (re.search("\.json", fn)):


                row["stream_urls"] = stream_urls
                txt = str(row)   # pseudo-json (python format)
            # output format

            elif (re.search("\.asx", fn)):
                txt = "<ASX version=\"3.0\">\n"			\
                    + " <Title>" + xmlentities(row["title"]) + "</Title>\n"	\
                    + " <Entry>\n"				\
                    + "  <Title>" + xmlentities(row["title"]) + "</Title>\n"	\
                    + "  <MoreInfo href=\"" + row["homepage"] + "\"/>\n"	\
                    + "  <Ref href=\"" + stream_urls[0] + "\"/>\n"		\
                    + " </Entry>\n</ASX>\n"
            # output format

            elif (re.search("\.smil", fn)):
                txt = "<smil>\n<head>\n  <meta name=\"title\" content=\"" + xmlentities(row["title"]) + "\"/>\n</head>\n"	\
                    + "<body>\n  <seq>\n    <audio src=\"" + stream_urls[0] + "\"/>\n  </seq>\n</body>\n</smil>\n"

            # unknown
            else:

                txt = ""
            # write
            if txt:
                f = open(fn, "wb")
                f.write(txt)
                f.close()
            pass








>
>
>
>



|
>
|



|
>
|







|
>
|










>
>
|
>
|
>
>


|
>
|







|
>
|


>


>
|







114
115
116
117
118
119
120
121
122
123
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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
                if cmd_list.get(match, None):
                    return cmd_list[match]


        # save as .m3u
        @staticmethod
        def save(row, fn, listformat="audio/x-scpls"):

        # output format
        format = re.findall("\.(m3u|pls|xspf|jspf|json|asx|smil)", fn)

            # modify stream url
            row["url"] = action.url(row["url"], listformat)
            stream_urls = action.extract_urls(row["url"], listformat)

        # M3U
        if "m3u" in format:
                txt = "#M3U\n"
                for url in stream_urls:
                    txt += http.fix_url(url) + "\n"

        # PLS
        elif "pls" in format:
                txt = "[playlist]\n" + "numberofentries=1\n"
                for i,u in enumerate(stream_urls):
                    i = str(i + 1)
                    txt += "File"+i + "=" + u + "\n"
                    txt += "Title"+i + "=" + row["title"] + "\n"
                    txt += "Length"+i + "=-1\n"
                txt += "Version=2\n"

        # XSPF
        elif "xspf" in format:
                txt = '<?xml version="1.0" encoding="UTF-8"?>' + "\n"
                txt += '<?http header="Content-Type: application/xspf+xml" ?>' + "\n"
                txt += '<playlist version="1" xmlns="http://xspf.org/ns/0/">' + "\n"
                for attr,tag in [("title","title"), ("homepage","info"), ("playing","annotation"), ("description","annotation")]:
                    if row.get(attr):
                        txt += "  <"+tag+">" + xmlentities(row[attr]) + "</"+tag+">\n"
                txt += "  <trackList>\n"
                for u in stream_urls:
                    txt += '	<track><location>' + xmlentities(u) + '</location></track>' + "\n"
                txt += "  </trackList>\n</playlist>\n"

        # JSPF
        elif "jspf" in format:
            pass

        # JSON
        elif "json" in format:
                row["stream_urls"] = stream_urls
                txt = str(row)   # pseudo-json (python format)
        
        # ASX
        elif "asx" in format:
                txt = "<ASX version=\"3.0\">\n"			\
                    + " <Title>" + xmlentities(row["title"]) + "</Title>\n"	\
                    + " <Entry>\n"				\
                    + "  <Title>" + xmlentities(row["title"]) + "</Title>\n"	\
                    + "  <MoreInfo href=\"" + row["homepage"] + "\"/>\n"	\
                    + "  <Ref href=\"" + stream_urls[0] + "\"/>\n"		\
                    + " </Entry>\n</ASX>\n"

        # SMIL
        elif "smil" in format:
                txt = "<smil>\n<head>\n  <meta name=\"title\" content=\"" + xmlentities(row["title"]) + "\"/>\n</head>\n"	\
                    + "<body>\n  <seq>\n    <audio src=\"" + stream_urls[0] + "\"/>\n  </seq>\n</body>\n</smil>\n"

            # unknown
            else:
            return

            # write
            if txt:
                f = open(fn, "wb")
                f.write(txt)
                f.close()
            pass

264
265
266
267
268
269
270

271
272
273
274
275
276
277
278
279
280
281
                f.write("\n".join(url_list) + "\n")
                f.close()
                # return path/name of temporary file
                return tmp_fn
            else:
                __print__( dbg.ERR, "error, there were no URLs in ", pls )
                raise "Empty PLS"


        # open help browser                
        @staticmethod
        def help(*args):
        
            action.run("yelp /usr/share/doc/streamtuner2/help/")
            #or action.browser("/usr/share/doc/streamtuner2/")

#class action









>




<

<

<
<
<
294
295
296
297
298
299
300
301
302
303
304
305

306

307



                f.write("\n".join(url_list) + "\n")
                f.close()
                # return path/name of temporary file
                return tmp_fn
            else:
                __print__( dbg.ERR, "error, there were no URLs in ", pls )
                raise "Empty PLS"


        # open help browser                
        @staticmethod
        def help(*args):

            action.run("yelp /usr/share/doc/streamtuner2/help/")





Modified channels/bookmarks.py from [26db498411] to [dbf59ee271].

116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
        self.save()
        self.load(self.default)
        self.urls.append(row["url"])


    # simplified gtk TreeStore display logic (just one category for the moment, always rebuilt)
    def load(self, category, force=False):
        __print__(dbg.UI, category, self.streams.keys())
        self.streams[category] = self.update_streams(category)
        #self.liststore[category] = \
        uikit.columns(self.gtk_list, self.datamap, self.prepare(self.streams[category]))


    # add a categories[]/streams{} subcategory, update treeview
    def add_category(self, cat, plugin=None):







<







116
117
118
119
120
121
122

123
124
125
126
127
128
129
        self.save()
        self.load(self.default)
        self.urls.append(row["url"])


    # simplified gtk TreeStore display logic (just one category for the moment, always rebuilt)
    def load(self, category, force=False):

        self.streams[category] = self.update_streams(category)
        #self.liststore[category] = \
        uikit.columns(self.gtk_list, self.datamap, self.prepare(self.streams[category]))


    # add a categories[]/streams{} subcategory, update treeview
    def add_category(self, cat, plugin=None):