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

⌈⌋ ⎇ branch:  streamtuner2


Changes On Branch d3b1418bc6a9ef41

Changes In Branch py3 Through [d3b1418bc6] Excluding Merge-Ins

This is equivalent to a diff from 7ef1553f61 to d3b1418bc6

2014-04-27
22:19
Python3 support back into trunk check-in: 9ecea4fb26 user: mario tags: trunk
2014-04-08
21:50
more Python3 syntax fixes, introduce compat2and3 module check-in: 7911337325 user: mario tags: py3
21:16
rename http to ahttp to avoid conflict with Python3 modules, change .iteritems and xrange, remove same remaining plain print statements check-in: d3b1418bc6 user: mario tags: py3
2014-04-07
00:33
Move __print__ into config, add unified dbg.COLOR codes check-in: 7ef1553f61 user: mario tags: trunk
2014-04-06
02:16
rename ui.xml to gtk2.xml for parity with gtk3.xml; Gtk3 suddenly works with gi 1.33 (well, lots of errors still, but main window ok) check-in: e7a0fb24c8 user: mario tags: trunk

Modified _package.epm from [d2e2c94cae] to [789f724b9f].

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
f 644 root root /usr/share/pixmaps/streamtuner2.png		./logo.png
f 644 root root /usr/share/streamtuner2/gtk2.xml		./gtk2.xml
f 644 root root /usr/share/streamtuner2/gtk3.xml		./gtk3.xml
f 644 root root /usr/share/streamtuner2/pson.py			./pson.py
#f 644 root root /usr/share/streamtuner2/processing.py		./processing.py
f 644 root root /usr/share/streamtuner2/action.py		./action.py
f 644 root root /usr/share/streamtuner2/config.py		./config.py
f 644 root root /usr/share/streamtuner2/http.py			./http.py
f 644 root root /usr/share/streamtuner2/cli.py			./cli.py
f 644 root root /usr/share/streamtuner2/mygtk.py		./mygtk.py
f 644 root root /usr/share/streamtuner2/favicon.py		./favicon.py
f 644 root root /usr/share/streamtuner2/kronos.py		./kronos.py
f 644 root root /usr/share/streamtuner2/pq.py			./pq.py
#-- channels
d 755 root root /usr/share/streamtuner2/channels		-







|







33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
f 644 root root /usr/share/pixmaps/streamtuner2.png		./logo.png
f 644 root root /usr/share/streamtuner2/gtk2.xml		./gtk2.xml
f 644 root root /usr/share/streamtuner2/gtk3.xml		./gtk3.xml
f 644 root root /usr/share/streamtuner2/pson.py			./pson.py
#f 644 root root /usr/share/streamtuner2/processing.py		./processing.py
f 644 root root /usr/share/streamtuner2/action.py		./action.py
f 644 root root /usr/share/streamtuner2/config.py		./config.py
f 644 root root /usr/share/streamtuner2/ahttp.py		./ahttp.py
f 644 root root /usr/share/streamtuner2/cli.py			./cli.py
f 644 root root /usr/share/streamtuner2/mygtk.py		./mygtk.py
f 644 root root /usr/share/streamtuner2/favicon.py		./favicon.py
f 644 root root /usr/share/streamtuner2/kronos.py		./kronos.py
f 644 root root /usr/share/streamtuner2/pq.py			./pq.py
#-- channels
d 755 root root /usr/share/streamtuner2/channels		-

Modified action.py from [fc0650246a] to [cd681feda2].

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#
#
#


import re
import os
import http
from config import conf, __print__, dbg
import platform


main = None









|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#
#
#


import re
import os
import ahttp as http
from config import conf, __print__, dbg
import platform


main = None


77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
                pass

        
        # exec wrapper
        @staticmethod
        def run(cmd):
            if conf.windows:
 	        os.system("start \"%s\"")
 	    else:
                os.system(cmd + " &")


        # streamripper
        @staticmethod
        def record(url, audioformat="audio/mp3", listformat="text/x-href", append="", row={}):
            __print__( "record", url )







|
|







77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
                pass

        
        # exec wrapper
        @staticmethod
        def run(cmd):
            if conf.windows:
                os.system("start \"%s\"")
            else:
                os.system(cmd + " &")


        # streamripper
        @staticmethod
        def record(url, audioformat="audio/mp3", listformat="text/x-href", append="", row={}):
            __print__( "record", url )

Added ahttp.py version [2dbe950381].















































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
#
# encoding: UTF-8
# api: streamtuner2
# type: functions
# title: http download / methods
# description: http utility
# version: 1.3
#
#  Provides a http GET method with gtk.statusbar() callback.
#  And a function to add trailings slashes on http URLs.
#
#  The latter code is pretty much unreadable. But let's put the
#  blame on urllib2, the most braindamaged code in the Python
#  standard library.
#


# Python 2.x            
try:
    import urllib2
    from urllib import urlencode
    import urlparse
    import cookielib
    from StringIO import StringIO
# Python 3.x
except:
    import urllib.request as urllib2
    from urllib.parse import urlencode
    import urllib.parse as urlparse
    from http import cookiejar as cookielib
    from io import StringIO

from gzip import GzipFile

from config import conf, __print__, dbg


#-- url download                            ---------------------------------------------



#-- chains to progress meter and status bar in main window
feedback = None

# sets either text or percentage, so may take two parameters
def progress_feedback(*args):

  # use reset values if none given
  if not args:
     args = ["", 1.0]

  # send to main win
  if feedback:
    try: [feedback(d) for d in args]
    except: pass




#-- GET
def get(url, maxsize=1<<19, feedback="old"):
    __print__("GET", url)

    # statusbar info
    progress_feedback(url, 0.0)
    
    # read
    content = ""
    f = urllib2.urlopen(url)
    max = 222000  # mostly it's 200K, but we don't get any real information
    read_size = 1
    
    # multiple steps
    while (read_size and len(content) < maxsize):
    
        # partial read
        add = f.read(8192)
        content = content + add
        read_size = len(add)

        # set progress meter
        progress_feedback(float(len(content)) / float(max))

    # done
    
    # clean statusbar
    progress_feedback()
        
    # fin
    __print__(len(content))
    return content





#-- fix invalid URLs
def fix_url(url):
    if url is None:
        url = ""
    if len(url):
        # remove whitespace
        url = url.strip()
        # add scheme
        if (url.find("://") < 0):
            url = "http://" + url
        # add mandatory path
        if (url.find("/", 10) < 0):
            url = url + "/"
    return url




# default HTTP headers for AJAX/POST request
default_headers = {
    "User-Agent": "streamtuner2/2.1 (X11; U; Linux AMD64; en; rv:1.5.0.1) like WinAmp/2.1 but not like Googlebot/2.1", #"Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6",
    "Accept": "*/*;q=0.5, audio/*, url/*",
    "Accept-Language": "en-US,en,de,es,fr,it,*;q=0.1",
    "Accept-Encoding": "gzip,deflate",
    "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.1",
    "Keep-Alive": "115",
    "Connection": "keep-alive",
   #"Content-Length", "56",
   #"Cookie": "s_pers=%20s_getnr%3D1278607170446-Repeat%7C1341679170446%3B%20s_nrgvo%3DRepeat%7C1341679170447%3B; s_sess=%20s_cc%3Dtrue%3B%20s_sq%3Daolshtcst%252Caolsvc%253D%252526pid%25253Dsht%25252520%2525253A%25252520SHOUTcast%25252520Radio%25252520%2525257C%25252520Search%25252520Results%252526pidt%25253D1%252526oid%25253Dfunctiononclick%25252528event%25252529%2525257BshowMoreGenre%25252528%25252529%2525253B%2525257D%252526oidt%25253D2%252526ot%25253DDIV%3B; aolDemoChecked=1.849061",
    "Pragma": "no-cache",
    "Cache-Control": "no-cache",
}



# simulate ajax calls
def ajax(url, post, referer=""):
    
    # request
    headers = default_headers
    headers.update({
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "X-Requested-With": "XMLHttpRequest",
        "Referer": (referer if referer else url),
    })
    if type(post) == dict:
        post = urlencode(post)
    request = urllib2.Request(url, post, headers)
    
    # open url
    __print__( vars(request) )
    progress_feedback(url, 0.2)
    r = urllib2.urlopen(request)
    
    # get data
    __print__( r.info() )
    progress_feedback(0.5)
    data = r.read()
    progress_feedback()
    return data



# http://techknack.net/python-urllib2-handlers/    
class ContentEncodingProcessor(urllib2.BaseHandler):
  """A handler to add gzip capabilities to urllib2 requests """

  # add headers to requests
  def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req

  # decode
  def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
        gz = GzipFile(
                    fileobj=StringIO(resp.read()),
                    mode="r"
                  )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
        resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
        gz = StringIO( deflate(resp.read()) )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)  # 'class to add info() and geturl() methods to an open file.'
        resp.msg = old_resp.msg
    return resp

# deflate support
import zlib
def deflate(data):   # zlib only provides the zlib compress format, not the deflate format;
  try:               # so on top of all there's this workaround:
    return zlib.decompress(data, -zlib.MAX_WBITS)
  except zlib.error:
    return zlib.decompress(data)







#-- init for later use
if urllib2:

    # config 1
    handlers = [None, None, None]
    
    # base
    handlers[0] = urllib2.HTTPHandler()
    if conf.debug:
        handlers[0].set_http_debuglevel(3)
        
    # content-encoding
    handlers[1] = ContentEncodingProcessor()
    
    # store cookies at runtime
    cj = cookielib.CookieJar()
    handlers[2] = urllib2.HTTPCookieProcessor( cj )
    
    # inject into urllib2
    urllib2.install_opener( urllib2.build_opener(*handlers) )




# alternative function names
AJAX=ajax
POST=ajax
GET=get
URL=fix_url


Modified channels/_generic.py from [f927301f5f] to [5d7f7dc2ae].

19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#  adds the required gtk Widgets manually.
#


import gtk
from mygtk import mygtk
from config import conf, __print__, dbg
import http
import action
import favicon
import os.path
import xml.sax.saxutils
import re
import copy








|







19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#  adds the required gtk Widgets manually.
#


import gtk
from mygtk import mygtk
from config import conf, __print__, dbg
import ahttp as http
import action
import favicon
import os.path
import xml.sax.saxutils
import re
import copy

145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
            self.gtk_cat = parent.get_widget(self.module+"_cat")
            
            # category tree
            self.display_categories()
            #mygtk.tree(self.gtk_cat, self.categories, title="Category", icon=gtk.STOCK_OPEN);
            
            # update column names
            for field,title in self.titles.iteritems():
                self.update_datamap(field, title=title)
            
            # prepare stream list
            if (not self.rowmap):
                for row in self.datamap:
                    for x in xrange(2, len(row)):
                        self.rowmap.append(row[x][0])

            # load default category
            if (self.current):
                self.load(self.current)
            else:
                mygtk.columns(self.gtk_list, self.datamap, [{}])







|





|







145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
            self.gtk_cat = parent.get_widget(self.module+"_cat")
            
            # category tree
            self.display_categories()
            #mygtk.tree(self.gtk_cat, self.categories, title="Category", icon=gtk.STOCK_OPEN);
            
            # update column names
            for field,title in list(self.titles.items()):
                self.update_datamap(field, title=title)
            
            # prepare stream list
            if (not self.rowmap):
                for row in self.datamap:
                    for x in range(2, len(row)):
                        self.rowmap.append(row[x][0])

            # load default category
            if (self.current):
                self.load(self.current)
            else:
                mygtk.columns(self.gtk_list, self.datamap, [{}])
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
        def prepare(self, streams):
            for i,row in enumerate(streams):
                                            # oh my, at least it's working
                                            # at start the bookmarks module isn't fully registered at instantiation in parent.channels{} - might want to do that step by step rather
                                            # then display() is called too early to take effect - load() & co should actually be postponed to when a notebook tab gets selected first
                                            # => might be fixed now, 1.9.8
                # state icon: bookmark star
                if (conf.show_bookmarks and self.parent.channels.has_key("bookmarks") and self.parent.bookmarks.is_in(streams[i].get("url", "file:///tmp/none"))):
                    streams[i]["favourite"] = 1
                
                # state icon: INFO or DELETE
                if (not row.get("state")):
                    if row.get("favourite"):
                        streams[i]["state"] = gtk.STOCK_ABOUT
                    if conf.retain_deleted and row.get("deleted"):







|







247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
        def prepare(self, streams):
            for i,row in enumerate(streams):
                                            # oh my, at least it's working
                                            # at start the bookmarks module isn't fully registered at instantiation in parent.channels{} - might want to do that step by step rather
                                            # then display() is called too early to take effect - load() & co should actually be postponed to when a notebook tab gets selected first
                                            # => might be fixed now, 1.9.8
                # state icon: bookmark star
                if (conf.show_bookmarks and "bookmarks" in self.parent.channels and self.parent.bookmarks.is_in(streams[i].get("url", "file:///tmp/none"))):
                    streams[i]["favourite"] = 1
                
                # state icon: INFO or DELETE
                if (not row.get("state")):
                    if row.get("favourite"):
                        streams[i]["state"] = gtk.STOCK_ABOUT
                    if conf.retain_deleted and row.get("deleted"):
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325

            if (self.shown != 55555):
            
                # if category tree is empty, initialize it
                if not self.categories:
                    __print__(dbg.PROC, "first_show: reload_categories");
                    #self.parent.thread(self.reload_categories)
                    print("reload categories");
                    self.reload_categories()
                    self.display_categories()
                    self.current = self.categories.keys()[0]
                    print self.current
                    self.load(self.current)
            
                # load current category
                else:
                    __print__(dbg.STAT, "first_show: load current category");
                    self.load(self.current)
                







<



|







307
308
309
310
311
312
313

314
315
316
317
318
319
320
321
322
323
324

            if (self.shown != 55555):
            
                # if category tree is empty, initialize it
                if not self.categories:
                    __print__(dbg.PROC, "first_show: reload_categories");
                    #self.parent.thread(self.reload_categories)

                    self.reload_categories()
                    self.display_categories()
                    self.current = self.categories.keys()[0]
                    __print__(dbg.STAT, self.current)
                    self.load(self.current)
            
                # load current category
                else:
                    __print__(dbg.STAT, "first_show: load current category");
                    self.load(self.current)
                

Modified channels/basicch.py from [2ad035d297] to [c507665eb7].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# realaudio archive is not available anymore.
#
# Needs manual initialisation of categories first.
#


import re
import http
from config import conf
from channels import *
from xml.sax.saxutils import unescape











|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# realaudio archive is not available anymore.
#
# Needs manual initialisation of categories first.
#


import re
import ahttp as http
from config import conf
from channels import *
from xml.sax.saxutils import unescape




Modified channels/google.py from [3f0788927c] to [36a6ee6c2d].

47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


import re, os, gtk
from channels import *
from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities
import http


### constants #################################################################


GOOGLE_DIRECTORY_ROOT	= "http://www.dmoz.org"
CATEGORIES_URL_POSTFIX	= "/Arts/Music/Sound_Files/MP3/Streaming/Stations/"







|







47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.


import re, os, gtk
from channels import *
from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities
import ahttp as http


### constants #################################################################


GOOGLE_DIRECTORY_ROOT	= "http://www.dmoz.org"
CATEGORIES_URL_POSTFIX	= "/Arts/Music/Sound_Files/MP3/Streaming/Stations/"

Modified channels/internet_radio_org_uk.py from [4dcc6f65d4] to [6c77259f65].

11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#



from channels import *
import re
from config import conf, __print__, dbg
import http
from pq import pq




# streams and gui
class internet_radio_org_uk (ChannelPlugin):







|







11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#



from channels import *
import re
from config import conf, __print__, dbg
import ahttp as http
from pq import pq




# streams and gui
class internet_radio_org_uk (ChannelPlugin):

Modified channels/jamendo.py from [0df05c5caf] to [9d3d3ac039].

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

# api: streamtuner2
# title: jamendo browser
#
# For now this is really just a browser, doesn't utilizt the jamendo API yet.
# Requires more rework of streamtuner2 list display to show album covers.
#


import re
import http
from config import conf
from channels import *
from xml.sax.saxutils import unescape














|







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

# api: streamtuner2
# title: jamendo browser
#
# For now this is really just a browser, doesn't utilizt the jamendo API yet.
# Requires more rework of streamtuner2 list display to show album covers.
#


import re
import ahttp as http
from config import conf
from channels import *
from xml.sax.saxutils import unescape




Modified channels/live365.py from [17860a6a46] to [10562bd5f9].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# api: st2
# title: live365 channel
#
# 2.0.9 fixed by Abhisek Sanyal
#




# streamtuner2 modules
from config import conf
from mygtk import mygtk
import http
from channels import *
from config import __print__, dbg

# python modules
import re
import xml.dom.minidom
from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities












|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# api: st2
# title: live365 channel
#
# 2.0.9 fixed by Abhisek Sanyal
#




# streamtuner2 modules
from config import conf
from mygtk import mygtk
import ahttp as http
from channels import *
from config import __print__, dbg

# python modules
import re
import xml.dom.minidom
from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities

Modified channels/modarchive.py from [bf829e8b72] to [a906bb560c].

8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# MOD files dodn't work with all audio players. And with the default
# download method, it'll receive a .zip archive with embeded .mod file.
# VLC in */* seems to work fine however.
#


import re
import http
from config import conf
from channels import *
from config import __print__, dbg
from xml.sax.saxutils import unescape










|







8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# MOD files dodn't work with all audio players. And with the default
# download method, it'll receive a .zip archive with embeded .mod file.
# VLC in */* seems to work fine however.
#


import re
import ahttp as http
from config import conf
from channels import *
from config import __print__, dbg
from xml.sax.saxutils import unescape



Modified channels/musicgoal.py from [1d593a3fc6] to [faf1e3671e].

12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#



# st2 modules
from config import conf
from mygtk import mygtk
import http
from channels import *

# python modules
import re
import json









|







12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#



# st2 modules
from config import conf
from mygtk import mygtk
import ahttp as http
from channels import *

# python modules
import re
import json


Modified channels/punkcast.py from [6f838d457b] to [57433ea58f].

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

# api: streamtuner2
# title: punkcast listing
#
#
# Disables itself per default.
# ST1 looked prettier with random images within.
#


import re
import http
from config import conf
import action
from channels import *
from config import __print__, dbg














|







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

# api: streamtuner2
# title: punkcast listing
#
#
# Disables itself per default.
# ST1 looked prettier with random images within.
#


import re
import ahttp as http
from config import conf
import action
from channels import *
from config import __print__, dbg



Modified channels/shoutcast.py from [3296716516] to [0c3da6eedb].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# there's not a lot of information to fetch left. And this plugin is now back
# to defaulting to regex extraction instead of HTML parsing & DOM extraction.
#
#
#


import http
import urllib
import re
from config import conf, __print__, dbg
from pq import pq
#from channels import *    # works everywhere but in this plugin(???!)
import channels








|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# there's not a lot of information to fetch left. And this plugin is now back
# to defaulting to regex extraction instead of HTML parsing & DOM extraction.
#
#
#


import ahttp as http
import urllib
import re
from config import conf, __print__, dbg
from pq import pq
#from channels import *    # works everywhere but in this plugin(???!)
import channels

Modified channels/tv.py from [e09d4b20ac] to [2d1fa5a0ac].

14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Pasing with lxml is dead simple in this case, so we use etree directly
# instead of PyQuery. Like with the Xiph plugin, downloaded streams are simply
# stored in .streams["all"] pseudo-category.
#
# icon: http://cemagraphics.deviantart.com/art/Little-Tv-Icon-96461135

from channels import *
import http
import lxml.etree




# TV listings from shoutcast.com
class tv(ChannelPlugin):







|







14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
# Pasing with lxml is dead simple in this case, so we use etree directly
# instead of PyQuery. Like with the Xiph plugin, downloaded streams are simply
# stored in .streams["all"] pseudo-category.
#
# icon: http://cemagraphics.deviantart.com/art/Little-Tv-Icon-96461135

from channels import *
import ahttp as http
import lxml.etree




# TV listings from shoutcast.com
class tv(ChannelPlugin):

Modified channels/xiph.py from [6bbc72e8d5] to [e14b45bb99].

15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#



# streamtuner2 modules
from config import conf
from mygtk import mygtk
import http
from channels import *
from config import __print__, dbg

# python modules
import re
from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities
import xml.dom.minidom







|







15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#



# streamtuner2 modules
from config import conf
from mygtk import mygtk
import ahttp as http
from channels import *
from config import __print__, dbg

# python modules
import re
from xml.sax.saxutils import unescape as entity_decode, escape as xmlentities
import xml.dom.minidom

Modified cli.py from [a682821821] to [ea53579a96].

13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#
#
#


import sys
#from channels import *
import http
import action
from config import conf
import json











|







13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#
#
#


import sys
#from channels import *
import ahttp
import action
from config import conf
import json




Modified favicon.py from [f5d4a162a1] to [997ecd51ce].

26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
delete_google_stub = 1   # don't keep placeholder images
google_placeholder_filesizes = (726,896)


import os, os.path
import urllib
import re
import urlparse
from config import conf
try: from processing import Process as Thread
except: from threading import Thread
import http



# ensure that we don't try to download a single favicon twice per session,
# if it's not available the first time, we won't get it after switching stations back and forth
tried_urls = []








<



|







26
27
28
29
30
31
32

33
34
35
36
37
38
39
40
41
42
43
delete_google_stub = 1   # don't keep placeholder images
google_placeholder_filesizes = (726,896)


import os, os.path
import urllib
import re

from config import conf
try: from processing import Process as Thread
except: from threading import Thread
import ahttp



# ensure that we don't try to download a single favicon twice per session,
# if it's not available the first time, we won't get it after switching stations back and forth
tried_urls = []

85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104

        # extract first title parts
        title = rx_t.search(row["title"])
        if title:
            title = title.group(0).replace(" ", "%20")
            
            # do a google search
            html = http.ajax("http://www.google.de/search?hl=de&q="+title, None)
            
            # find first URL hit
            url = rx_u.search(html)
            if url:
                row["homepage"] = http.fix_url(url.group(1))
    pass
#-----------------



# extract domain name
def domain(url):







|




|







84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103

        # extract first title parts
        title = rx_t.search(row["title"])
        if title:
            title = title.group(0).replace(" ", "%20")
            
            # do a google search
            html = ahttp.ajax("http://www.google.de/search?hl=de&q="+title, None)
            
            # find first URL hit
            url = rx_u.search(html)
            if url:
                row["homepage"] = ahttp.fix_url(url.group(1))
    pass
#-----------------



# extract domain name
def domain(url):
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#  try: 
    # URL download
    r = urllib.urlopen(favicon)
    headers = r.info()
    
    # abort on
    if r.getcode() >= 300:
       raise "HTTP error", r.getcode()
    if not headers["Content-Type"].lower().find("image/"):
       raise "can't use text/* content"
       
    # save file
    fn_tmp = fn+".tmp"
    f = open(fn_tmp, "wb")
    f.write(r.read(32768))
    f.close()
        







|

|







190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
#  try: 
    # URL download
    r = urllib.urlopen(favicon)
    headers = r.info()
    
    # abort on
    if r.getcode() >= 300:
       raise Error("HTTP error" + r.getcode())
    if not headers["Content-Type"].lower().find("image/"):
       raise Error("can't use text/* content")
       
    # save file
    fn_tmp = fn+".tmp"
    f = open(fn_tmp, "wb")
    f.write(r.read(32768))
    f.close()
        
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
    favicon = "".join(rx.findall(html))
    
    # url or
    if favicon.startswith("http://"):
       None
    # just /pathname
    else:
       favicon = urlparse.urljoin(url, favicon)
       #favicon = "http://" + domain(url) + "/" + favicon

    # download
    direct_download(favicon, file(url))










|







231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
    favicon = "".join(rx.findall(html))
    
    # url or
    if favicon.startswith("http://"):
       None
    # just /pathname
    else:
       favicon = ahttp.urlparse.urljoin(url, favicon)
       #favicon = "http://" + domain(url) + "/" + favicon

    # download
    direct_download(favicon, file(url))



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
#
         
import operator
import struct

try:
    from PIL import BmpImagePlugin, PngImagePlugin, Image
except Exception, e:
    print("no PIL", e)
    always_google = 1
    only_google = 1


def load_icon(file, index=None):
    '''







|







261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#
         
import operator
import struct

try:
    from PIL import BmpImagePlugin, PngImagePlugin, Image
except Exception as e:
    print("no PIL", e)
    always_google = 1
    only_google = 1


def load_icon(file, index=None):
    '''

Deleted http.py version [0d4dcfee94].

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
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
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
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
#
# encoding: UTF-8
# api: streamtuner2
# type: functions
# title: http download / methods
# description: http utility
# version: 1.3
#
#  Provides a http GET method with gtk.statusbar() callback.
#  And a function to add trailings slashes on http URLs.
#
#  The latter code is pretty much unreadable. But let's put the
#  blame on urllib2, the most braindamaged code in the Python
#  standard library.
#
            
try:
    import urllib2
    from urllib import urlencode
except:
    import urllib.request as urllib2
    import urllib.parse.urlencode as urlencode
import config
from config import __print__, dbg



#-- url download                            ---------------------------------------------



#-- chains to progress meter and status bar in main window
feedback = None

# sets either text or percentage, so may take two parameters
def progress_feedback(*args):

  # use reset values if none given
  if not args:
     args = ["", 1.0]

  # send to main win
  if feedback:
    try: [feedback(d) for d in args]
    except: pass




#-- GET
def get(url, maxsize=1<<19, feedback="old"):
    __print__("GET", url)

    # statusbar info
    progress_feedback(url, 0.0)
    
    # read
    content = ""
    f = urllib2.urlopen(url)
    max = 222000  # mostly it's 200K, but we don't get any real information
    read_size = 1
    
    # multiple steps
    while (read_size and len(content) < maxsize):
    
        # partial read
        add = f.read(8192)
        content = content + add
        read_size = len(add)

        # set progress meter
        progress_feedback(float(len(content)) / float(max))

    # done
    
    # clean statusbar
    progress_feedback()
        
    # fin
    __print__(len(content))
    return content





#-- fix invalid URLs
def fix_url(url):
    if url is None:
        url = ""
    if len(url):
        # remove whitespace
        url = url.strip()
        # add scheme
        if (url.find("://") < 0):
            url = "http://" + url
        # add mandatory path
        if (url.find("/", 10) < 0):
            url = url + "/"
    return url




# default HTTP headers for AJAX/POST request
default_headers = {
    "User-Agent": "streamtuner2/2.1 (X11; U; Linux AMD64; en; rv:1.5.0.1) like WinAmp/2.1 but not like Googlebot/2.1", #"Mozilla/5.0 (X11; U; Linux x86_64; de; rv:1.9.2.6) Gecko/20100628 Ubuntu/10.04 (lucid) Firefox/3.6.6",
    "Accept": "*/*;q=0.5, audio/*, url/*",
    "Accept-Language": "en-US,en,de,es,fr,it,*;q=0.1",
    "Accept-Encoding": "gzip,deflate",
    "Accept-Charset": "ISO-8859-1,utf-8;q=0.7,*;q=0.1",
    "Keep-Alive": "115",
    "Connection": "keep-alive",
   #"Content-Length", "56",
   #"Cookie": "s_pers=%20s_getnr%3D1278607170446-Repeat%7C1341679170446%3B%20s_nrgvo%3DRepeat%7C1341679170447%3B; s_sess=%20s_cc%3Dtrue%3B%20s_sq%3Daolshtcst%252Caolsvc%253D%252526pid%25253Dsht%25252520%2525253A%25252520SHOUTcast%25252520Radio%25252520%2525257C%25252520Search%25252520Results%252526pidt%25253D1%252526oid%25253Dfunctiononclick%25252528event%25252529%2525257BshowMoreGenre%25252528%25252529%2525253B%2525257D%252526oidt%25253D2%252526ot%25253DDIV%3B; aolDemoChecked=1.849061",
    "Pragma": "no-cache",
    "Cache-Control": "no-cache",
}



# simulate ajax calls
def ajax(url, post, referer=""):
    
    # request
    headers = default_headers
    headers.update({
        "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
        "X-Requested-With": "XMLHttpRequest",
        "Referer": (referer if referer else url),
    })
    if type(post) == dict:
        post = urlencode(post)
    request = urllib2.Request(url, post, headers)
    
    # open url
    __print__( vars(request) )
    progress_feedback(url, 0.2)
    r = urllib2.urlopen(request)
    
    # get data
    __print__( r.info() )
    progress_feedback(0.5)
    data = r.read()
    progress_feedback()
    return data



# http://techknack.net/python-urllib2-handlers/    
from gzip import GzipFile
from StringIO import StringIO
class ContentEncodingProcessor(urllib2.BaseHandler):
  """A handler to add gzip capabilities to urllib2 requests """

  # add headers to requests
  def http_request(self, req):
    req.add_header("Accept-Encoding", "gzip, deflate")
    return req

  # decode
  def http_response(self, req, resp):
    old_resp = resp
    # gzip
    if resp.headers.get("content-encoding") == "gzip":
        gz = GzipFile(
                    fileobj=StringIO(resp.read()),
                    mode="r"
                  )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)
        resp.msg = old_resp.msg
    # deflate
    if resp.headers.get("content-encoding") == "deflate":
        gz = StringIO( deflate(resp.read()) )
        resp = urllib2.addinfourl(gz, old_resp.headers, old_resp.url, old_resp.code)  # 'class to add info() and geturl() methods to an open file.'
        resp.msg = old_resp.msg
    return resp

# deflate support
import zlib
def deflate(data):   # zlib only provides the zlib compress format, not the deflate format;
  try:               # so on top of all there's this workaround:
    return zlib.decompress(data, -zlib.MAX_WBITS)
  except zlib.error:
    return zlib.decompress(data)







#-- init for later use
if urllib2:

    # config 1
    handlers = [None, None, None]
    
    # base
    handlers[0] = urllib2.HTTPHandler()
    if config.conf.debug:
        handlers[0].set_http_debuglevel(3)
        
    # content-encoding
    handlers[1] = ContentEncodingProcessor()
    
    # store cookies at runtime
    import cookielib
    cj = cookielib.CookieJar()
    handlers[2] = urllib2.HTTPCookieProcessor( cj )
    
    # inject into urllib2
    urllib2.install_opener( urllib2.build_opener(*handlers) )




# alternative function names
AJAX=ajax
POST=ajax
GET=get
URL=fix_url


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
































































































































































































































































































































































































































































Modified mygtk.py from [98fa2cab88] to [91f32ed114].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#
# encoding: UTF-8
# api: python
# type: functions
# title: mygtk helper functions
# description: simplify usage of some gtk widgets
# version: 1.6
# author: mario
# license: public domain
#
#
# Wrappers around gtk methods. The TreeView method .columns() allows
# to fill a treeview. It adds columns and data rows with a mapping
# dictionary (which specifies many options and data positions).






|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
#
# encoding: UTF-8
# api: python
# type: functions
# title: mygtk helper functions
# description: simplify usage of some gtk widgets
# version: 1.7
# author: mario
# license: public domain
#
#
# Wrappers around gtk methods. The TreeView method .columns() allows
# to fill a treeview. It adds columns and data rows with a mapping
# dictionary (which specifies many options and data positions).
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




# debug
from config import __print__, dbg
















# gtk modules
gtk = 0   # 0=gtk2, else gtk3
if gtk:
    from gi import pygtkcompat as pygtk
    pygtk.enable() 
    pygtk.enable_gtk(version='3.0')
    from gi.repository import Gtk as gtk
    from gi.repository import GObject as gobject
    from gi.repository import GdkPixbuf
    ui_file = "gtk3.xml"
    __print__(gtk)
    __print__(gobject)
if not gtk:

    import pygtk
    import gtk
    import gobject
    ui_file = "gtk2.xml"

# filesystem
import os.path
import copy


try:
  empty_pixbuf = gtk.gdk.pixbuf_new_from_data(b"\0\0\0\0",gtk.gdk.COLORSPACE_RGB,True,8,1,1,4)
except:
  empty_pixbuf = GdkPixbuf.Pixbuf.new_from_data(b"\0\0\0\0", GdkPixbuf.Colorspace.RGB, True, 8, 1, 1, 4, None, None)









>
>
>
>

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







|
|
<
>





<
<
<
<







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




# debug
from config import __print__, dbg

# filesystem
import os.path
import copy
import sys

if sys.version_info[0] >= 3:
    unicode = str


# gtk version
ver = 2   # 2=gtk2, 3=gtk3
if "--gtk3" in sys.argv:
    ver = 3
if sys.version_info >= (3, 0):
    ver = 3
# load gtk modules

if ver==3:
    from gi import pygtkcompat as pygtk
    pygtk.enable() 
    pygtk.enable_gtk(version='3.0')
    from gi.repository import Gtk as gtk
    from gi.repository import GObject as gobject
    from gi.repository import GdkPixbuf
    ui_file = "gtk3.xml"
    __print__(dbg.PROC, gtk)
    __print__(dbg.PROC, gobject)

else:
    import pygtk
    import gtk
    import gobject
    ui_file = "gtk2.xml"






try:
  empty_pixbuf = gtk.gdk.pixbuf_new_from_data(b"\0\0\0\0",gtk.gdk.COLORSPACE_RGB,True,8,1,1,4)
except:
  empty_pixbuf = GdkPixbuf.Pixbuf.new_from_data(b"\0\0\0\0", GdkPixbuf.Colorspace.RGB, True, 8, 1, 1, 4, None, None)


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
                    col.set_resizable(True)
                    # width
                    if (desc[1] > 0):
                        col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
                        col.set_fixed_width(desc[1])

                    # loop through cells
                    for var in xrange(2, len(desc)):
                        cell = desc[var]
                        # cell renderer
                        if (cell[2] == "pixbuf"):
                            rend = gtk.CellRendererPixbuf()  # img cell
                            if (cell[1] == str):
                                cell[3]["stock_id"] = datapos  # for stock icons
                                expand = False
                            else:
                                pix_entry = datapos
                                cell[3]["pixbuf"] = datapos
                        else:
                            rend = gtk.CellRendererText()    # text cell
                            cell[3]["text"] = datapos
                            #col.set_sort_column_id(datapos)  # only on textual cells
   
                        # attach cell to column
                        col.pack_end(rend, expand=cell[3].get("expand",True))
                        # apply attributes
                        for attr,val in cell[3].iteritems():
                            col.add_attribute(rend, attr, val)
                        # next
                        datapos += 1

                        __print__(cell)
                    # add column to treeview
                    widget.append_column(col)







|


















|







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
                    col.set_resizable(True)
                    # width
                    if (desc[1] > 0):
                        col.set_sizing(gtk.TREE_VIEW_COLUMN_FIXED)
                        col.set_fixed_width(desc[1])

                    # loop through cells
                    for var in range(2, len(desc)):
                        cell = desc[var]
                        # cell renderer
                        if (cell[2] == "pixbuf"):
                            rend = gtk.CellRendererPixbuf()  # img cell
                            if (cell[1] == str):
                                cell[3]["stock_id"] = datapos  # for stock icons
                                expand = False
                            else:
                                pix_entry = datapos
                                cell[3]["pixbuf"] = datapos
                        else:
                            rend = gtk.CellRendererText()    # text cell
                            cell[3]["text"] = datapos
                            #col.set_sort_column_id(datapos)  # only on textual cells
   
                        # attach cell to column
                        col.pack_end(rend, expand=cell[3].get("expand",True))
                        # apply attributes
                        for attr,val in list(cell[3].items()):
                            col.add_attribute(rend, attr, val)
                        # next
                        datapos += 1

                        __print__(cell)
                    # add column to treeview
                    widget.append_column(col)
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
            # add data?
            if (entries):
                #- expand datamap            
                vartypes = []  #(str, str, bool, str, int, int, gtk.gdk.Pixbuf, str, int)
                rowmap = []    #["title", "desc", "bookmarked", "name", "count", "max", "img", ...]
                if (not rowmap):
                    for desc in datamap:
                        for var in xrange(2, len(desc)):
                            vartypes.append(desc[var][1])  # content types
                            rowmap.append(desc[var][0])    # dict{} column keys in entries[] list
                # create gtk array storage
                ls = gtk.ListStore(*vartypes)   # could be a TreeStore, too
                __print__(vartypes)
                __print__(rowmap)








|







154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
            # add data?
            if (entries):
                #- expand datamap            
                vartypes = []  #(str, str, bool, str, int, int, gtk.gdk.Pixbuf, str, int)
                rowmap = []    #["title", "desc", "bookmarked", "name", "count", "max", "img", ...]
                if (not rowmap):
                    for desc in datamap:
                        for var in range(2, len(desc)):
                            vartypes.append(desc[var][1])  # content types
                            rowmap.append(desc[var][0])    # dict{} column keys in entries[] list
                # create gtk array storage
                ls = gtk.ListStore(*vartypes)   # could be a TreeStore, too
                __print__(vartypes)
                __print__(rowmap)

Modified st2.py from [0b9e0b6b81] to [1b132a9fda].

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
# encoding: UTF-8
# api: python
# type: application
# title: streamtuner2
# description: directory browser for internet radio / audio streams
# depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos
# version: 2.0.9.5
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/projects/streamtuner2
# config: <env name="http_proxy" value="" description="proxy for HTTP access" />  <env name="XDG_CONFIG_HOME" description="relocates user .config subdirectory" />
# category: multimedia
# 
#







|







1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python
# encoding: UTF-8
# api: python
# type: application
# title: streamtuner2
# description: directory browser for internet radio / audio streams
# depends: gtk, pygtk, xml.dom.minidom, threading, lxml, pyquery, kronos
# version: 2.0.9.6
# author: mario salzer
# license: public domain
# url: http://freshmeat.net/projects/streamtuner2
# config: <env name="http_proxy" value="" description="proxy for HTTP access" />  <env name="XDG_CONFIG_HOME" description="relocates user .config subdirectory" />
# category: multimedia
# 
#
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

# gtk modules
from mygtk import pygtk, gtk, gobject, ui_file, mygtk

# custom modules
from config import conf   # initializes itself, so all conf.vars are available right away
from config import __print__, dbg
import http
import action  # needs workaround... (action.main=main)
from channels import *
import favicon
#from pq import pq










|







96
97
98
99
100
101
102
103
104
105
106
107
108
109
110

# gtk modules
from mygtk import pygtk, gtk, gobject, ui_file, mygtk

# custom modules
from config import conf   # initializes itself, so all conf.vars are available right away
from config import __print__, dbg
import ahttp
import action  # needs workaround... (action.main=main)
from channels import *
import favicon
#from pq import pq



233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

          

        #-- Shortcut for glade.get_widget()
        # Allows access to widgets as direct attributes instead of using .get_widget()
        # Also looks in self.channels[] for the named channel plugins
        def __getattr__(self, name):
            if (self.channels.has_key(name)):
                return self.channels[name]     # like self.shoutcast
            else:
                return self.get_object(name)   # or gives an error if neither exists


        # custom-named widgets are available from .widgets{} not via .get_widget()
        def get_widget(self, name):
            if self.widgets.has_key(name):
                return self.widgets[name]
            else:
                return gtk.Builder.get_object(self, name)
                


                







|







|







233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255

          

        #-- Shortcut for glade.get_widget()
        # Allows access to widgets as direct attributes instead of using .get_widget()
        # Also looks in self.channels[] for the named channel plugins
        def __getattr__(self, name):
            if (name in self.channels):
                return self.channels[name]     # like self.shoutcast
            else:
                return self.get_object(name)   # or gives an error if neither exists


        # custom-named widgets are available from .widgets{} not via .get_widget()
        def get_widget(self, name):
            if name in self.widgets:
                return self.widgets[name]
            else:
                return gtk.Builder.get_object(self, name)
                


                
521
522
523
524
525
526
527

528


529
530
531
532
533
534
535
                        gtk.rc_parse(f)
                pass


        # end application and gtk+ main loop
        def gtk_main_quit(self, widget, *x):
            if conf.auto_save_appstate:

                self.app_state(widget)


            gtk.main_quit()













>
|
>
>







521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
                        gtk.rc_parse(f)
                pass


        # end application and gtk+ main loop
        def gtk_main_quit(self, widget, *x):
            if conf.auto_save_appstate:
                try:  # doesn't work with gtk3 yet
                    self.app_state(widget)
                except:
                    None
            gtk.main_quit()






1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
#-- run main                                ---------------------------------------------
if __name__ == "__main__":

    #-- global configuration settings
    "conf = Config()"       # already happened with "from config import conf"

    # graphical
    if len(sys.argv) < 2:
    
        
        # prepare for threading in Gtk+ callbacks
        gobject.threads_init()
        gui_startup(1/100.0)
        
        # prepare main window
        main = StreamTunerTwo()
        
        # module coupling
        action.main = main      # action (play/record) module needs a reference to main window for gtk interaction and some URL/URI callbacks
        action = action.action  # shorter name
        http.feedback = main.status  # http module gives status feedbacks too
        
        # first invocation
        if (conf.get("firstrun")):
            config_dialog.open(None)
            del conf.firstrun









|












|







1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
#-- run main                                ---------------------------------------------
if __name__ == "__main__":

    #-- global configuration settings
    "conf = Config()"       # already happened with "from config import conf"

    # graphical
    if len(sys.argv) < 2 or "--gtk3" in sys.argv:
    
        
        # prepare for threading in Gtk+ callbacks
        gobject.threads_init()
        gui_startup(1/100.0)
        
        # prepare main window
        main = StreamTunerTwo()
        
        # module coupling
        action.main = main      # action (play/record) module needs a reference to main window for gtk interaction and some URL/URI callbacks
        action = action.action  # shorter name
        ahttp.feedback = main.status  # http module gives status feedbacks too
        
        # first invocation
        if (conf.get("firstrun")):
            config_dialog.open(None)
            del conf.firstrun