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

⌈⌋ ⎇ branch:  streamtuner2


Check-in [7911337325]

Overview
Comment:more Python3 syntax fixes, introduce compat2and3 module
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | py3
Files: files | file ages | folders
SHA1: 7911337325807d54a047947842f1b57aa48ea7ff
User & Date: mario on 2014-04-08 21:50:21
Other Links: branch diff | manifest | tags
Context
2014-04-08
21:53
fix dict + dict back into list join check-in: d09e020ecf user: mario tags: py3
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
Changes

Modified ahttp.py from [2dbe950381] to [11eca44aed].

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
1
2
3
4
5
6

7
8
9
10
11



12
13
14















15
16

17
18
19
20
21
22
23






-
+




-
-
-



-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+

-







#
# encoding: UTF-8
# api: streamtuner2
# type: functions
# title: http download / methods
# description: http utility
# version: 1.3
# version: 1.4
#
#  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 compat2and3 import urllib2, urlencode, urlparse, cookielib, StringIO, xrange
from gzip import GzipFile

from config import conf, __print__, dbg


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



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

179
180
181
182
183
184
185
186

187
188
189
190
191
192
193
179
180
181
182
183
184
185

186
187
188
189
190
191
192
193







-
+








        # switch stream category,
        # load data,
        # update treeview content
        def load(self, category, force=False):
        
            # get data from cache or download
            if (force or not self.streams.has_key(category)):
            if (force or not category in self.streams):
                new_streams = self.update_streams(category)
      
                if new_streams:
                
                    # modify
                    [self.postprocess(row) for row in new_streams]
      
233
234
235
236
237
238
239
240

241
242
243
244
245
246
247
233
234
235
236
237
238
239

240
241
242
243
244
245
246
247







-
+








            
        # finds differences in new/old streamlist, marks deleted with flag
        def deleted_streams(self, new, old):
            diff = []
            new = [row.get("url","http://example.com/") for row in new]
            for row in old:
                if (row.has_key("url") and (row.get("url") not in new)):
                if (url in row and (row.get("url") not in new)):
                    row["deleted"] = 1
                    diff.append(row)
            return diff

        
        # prepare data for display
        def prepare(self, streams):

Modified channels/links.py from [692f83efe7] to [e2e193464a].

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







-
+







-
+









        bookmarks = parent.bookmarks
        if not bookmarks.streams.get(self.module):
            bookmarks.streams[self.module] = []
        bookmarks.add_category(self.module)


        # collect links from channel plugins
        for name,channel in parent.channels.iteritems():
        for name,channel in parent.channels.items():
          try:
            self.streams.append({
                "favourite": 1,
                "title": channel.title,
                "homepage": channel.homepage,
            })
          except: pass
        for title,homepage in self.default.iteritems():
        for title,homepage in self.default.items():
            self.streams.append({
                "title": title,
                "homepage": homepage,
            })

        # add to bookmarks
        bookmarks.streams[self.module] = self.streams
        
        

Modified channels/myoggradio.py from [37210dae1e] to [6c4dac0144].

22
23
24
25
26
27
28
29

30
31
32
33
34
35
36
22
23
24
25
26
27
28

29
30
31
32
33
34
35
36







-
+








from channels import *
from config import conf
from action import action

import re
import json
from StringIO import StringIO
from compat2and3 import StringIO
import copy



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

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

62
63
64
65
66
67
68
69

70
71
72
73

74
75
76
77
78
79
80
62
63
64
65
66
67
68

69
70
71
72

73
74
75
76
77
78
79
80







-
+



-
+







        # sub-categories are queried per 'AJAX'
        def update_categories(self):
            html = http.get(self.base_url)
            self.categories = []
            __print__( dbg.DATA, html )

            # <h2>Radio Genres</h2>
	    rx = re.compile(r'<li((?:\s+id="\d+"\s+class="files")?)><a href="\?action=sub&cat=([\w\s]+)#(\d+)">[\w\s]+</a>', re.S)
            rx = re.compile(r'<li((?:\s+id="\d+"\s+class="files")?)><a href="\?action=sub&cat=([\w\s]+)#(\d+)">[\w\s]+</a>', re.S)
            sub = []
            for uu in rx.findall(html):
                __print__( dbg.DATA, uu )
		(main,name,id) = uu
                (main,name,id) = uu
                name = urllib.unquote(name)

                # main category
                if main:
                    if sub:
                        self.categories.append(sub)
                        sub = []

Modified channels/timer.py from [c50adafaed] to [73a61c99cb].

16
17
18
19
20
21
22

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







+







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


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


72
73
74
75
76
77
78
79

80
81
82
83
84
85
86
73
74
75
76
77
78
79

80
81
82
83
84
85
86
87







-
+







            "timer_cancel": lambda w,*a: self.parent.timer_dialog.hide() or 1,
        })
        
        # prepare spool
        self.sched = kronos.ThreadedScheduler()
        for row in self.streams:
            try: self.queue(row)
            except Exception,e: print("queuing error", e)
            except Exception as e: __print__(dbg.ERR, "queuing error", e)
        self.sched.start()


    # display GUI for setting timespec
    def edit_timer(self, *w):
        self.parent.timer_dialog.show()
        self.parent.timer_value.set_text("Fri,Sat 20:00-21:00 play")

Added compat2and3.py version [022ad69c97].



























































1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
#
# encoding: UTF-8
# api: python 
# type: functions
# title: Python2 and Python3 compatibility
# version: 0.1
#
#  Renames some Python3 modules into their Py2 equivalent.
#  Slim local alternative to `six` module.
#


import sys


# Python 2
if sys.version_info < (3,0):

    # version tags
    PY2 = 1
    PY3 = 0

    # basic functions
    xrange = xrange
    range = xrange

    # urllib modules
    import urllib
    import urllib2
    from urllib import urlencode
    import urlparse
    import cookielib
    
    # filesys
    from StringIO import StringIO


# Python 3
else:

    # version tags
    PY2 = 0
    PY3 = 1

    # basic functions
    xrange = range

    # urllib modules
    import urllib.request as urllib
    import urllib.request as urllib2
    from urllib.parse import urlencode
    import urllib.parse as urlparse
    from http import cookiejar as cookielib
    
    # filesys
    from io import StringIO

    

Modified favicon.py from [997ecd51ce] to [983556d34c].

24
25
26
27
28
29
30
31

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

31
32
33
34
35
36
37
38







-
+







always_google = 1      # use favicon service for speed
only_google = 1        # if that fails, try our other/slower methods?
delete_google_stub = 1   # don't keep placeholder images
google_placeholder_filesizes = (726,896)


import os, os.path
import urllib
from compat2and3 import xrange, urllib
import re
from config import conf
try: from processing import Process as Thread
except: from threading import Thread
import ahttp


Modified kronos.py from [6ae12b7565] to [24a87e43ef].

270
271
272
273
274
275
276
277

278
279
280
281
282
283
284
270
271
272
273
274
275
276

277
278
279
280
281
282
283
284







-
+







            self.sched.queue[:] = []                    

    def _run(self):
        # Low-level run method to do the actual scheduling loop.
        while self.running:
            try:
                self.sched.run()
            except Exception,x:
            except Exception as x:
                print >>sys.stderr, "ERROR DURING SCHEDULER EXECUTION",x
                print >>sys.stderr, "".join(
                    traceback.format_exception(*sys.exc_info()))
                print >>sys.stderr, "-" * 20
            # queue is empty; sleep a short while before checking again
            if self.running:
                time.sleep(5)
294
295
296
297
298
299
300
301

302
303
304
305
306
307
308
294
295
296
297
298
299
300

301
302
303
304
305
306
307
308







-
+







        self.args=args
        self.kw=kw

    def __call__(self, schedulerref):
        """Execute the task action in the scheduler's thread."""
        try:
            self.execute()
        except Exception,x:
        except Exception as x:
            self.handle_exception(x)
        self.reschedule(schedulerref())

    def reschedule(self, scheduler):
        """This method should be defined in one of the sub classes!"""
        raise NotImplementedError("You're using the abstract base class 'Task',"
            " use a concrete class instead")
462
463
464
465
466
467
468
469

470
471
472
473
474
475
476
462
463
464
465
466
467
468

469
470
471
472
473
474
475
476







-
+







            self.reschedule(schedulerref())

        def threadedcall(self):
            # This method is run within its own thread, so we have to
            # do the execute() call and exception handling here.
            try:
                self.execute()
            except Exception,x:
            except Exception as x:
                self.handle_exception(x)

    class ThreadedIntervalTask(ThreadedTaskMixin, IntervalTask):
        """Interval Task that executes in its own thread."""
        pass

    class ThreadedSingleTask(ThreadedTaskMixin, SingleTask):
529
530
531
532
533
534
535
536

537
538
539
540
541
542
543
529
530
531
532
533
534
535

536
537
538
539
540
541
542
543







-
+







        def __call__(self, schedulerref):
            """Execute the task action in its own process."""
            pid = os.fork()
            if pid == 0:
                # we are the child
                try:
                    self.execute()
                except Exception,x:
                except Exception as x:
                    self.handle_exception(x)
                os._exit(0)
            else:
                # we are the parent
                self.reschedule(schedulerref())


Modified pq.py from [774d8a07cf] to [9ad9a3d426].

15
16
17
18
19
20
21
22

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

22
23
24
25
26
27
28
29







-
+







try:

    from pyquery import PyQuery as pq

    # pq.each_pq = lambda self,func:  self.each(   lambda i,html: func( pq(html, parser="html") )   )


except Exception, e:
except Exception as e:

    # disable use
    pq = None
    config.conf.pyquery = False

    # error hint
    print("LXML is missing\n", e)

Modified st2.py from [1b132a9fda] to [9090947823].

216
217
218
219
220
221
222
223

224
225
226
227
228
229
230
216
217
218
219
220
221
222

223
224
225
226
227
228
229
230







-
+







                "search_google": search.google,
                "search_cancel": search.cancel,
                "true": lambda w,*args: True,
                "streamedit_open": streamedit.open,
                "streamedit_save": streamedit.save,
                "streamedit_new": streamedit.new,
                "streamedit_cancel": streamedit.cancel,
            }.items() + self.add_signals.items() ))
            }.items() | self.add_signals.items() ))
            
            # actually display main window
            gui_startup(99/100.0)
            self.win_streamtuner2.show()
            
            # WHY DON'T YOU WANT TO WORK?!
            #self.shoutcast.gtk_list.set_enable_search(True)
521
522
523
524
525
526
527
528

529
530
531
532
533
534
535
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:
                try:  # doesn't work with gtk3 yet
                try:  # doesn't work with gtk3 yet (probably just hooking at the wrong time)
                    self.app_state(widget)
                except:
                    None
            gtk.main_quit()