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

⌈⌋ branch:  streamtuner2


Check-in [33e106bce5]

Overview
Comment:Manually register addon widget signals. Otherwise main keeps bugging with GtkWarnings when timer plugin is disabled.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA1: 33e106bce578d8a0d74c6eafcf425e7c38489688
User & Date: mario on 2015-04-05 14:08:16
Other Links: manifest | tags
Context
2015-04-05
14:09
Wrap `gzip_decode` as fallback for Python 2. check-in: d88aab3981 user: mario tags: trunk
14:08
Manually register addon widget signals. Otherwise main keeps bugging with GtkWarnings when timer plugin is disabled. check-in: 33e106bce5 user: mario tags: trunk
14:07
Use os.path.expand* for env vars and `~` homedir placeholder. check-in: 7aafeff157 user: mario tags: trunk
Changes

Modified channels/timer.py from [6a77dbcb84] to [0dc91cdebf].

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
#
# api: streamtuner2
# title: Recording timer
# description: Schedules play/record events for bookmarked radio stations.
# type: feature
# category: hook
# depends: kronos
# version: 0.5
# config: -
# priority: optional
# support: unsupported
#
# Okay, while programming this, I missed the broadcast I wanted to hear. Again(!)
# But still this is a useful extension, as it allows recording and playing specific
# stations at a programmed time and interval. It accepts a natural language time

# string when registering a stream. (Via streams menu > extension > add timer)
#
# Programmed events are visible in "timer" under the "bookmarks" channel. Times
# are stored in the description field, and can thus be edited. However, after editing
# times manually, streamtuner2 must be restarted for the changes to take effect.
#


from config import *
from channels import *
import bundle.kronos as kronos  # Doesn't work with Python3
from uikit import uikit







|




|
<
|
>
|



|







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
#
# api: streamtuner2
# title: Recording timer
# description: Schedules play/record events for bookmarked radio stations.
# type: feature
# category: hook
# depends: kronos
# version: 0.6
# config: -
# priority: optional
# support: unsupported
#
# Provides an internal timer, to configure recording and playback times/intervals

# for stations. It accepts a natural language time string when registering a stream.
#
# Context menu > Extension > Add timer
#
# 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 any changes to take effect.
#


from config import *
from channels import *
import bundle.kronos as kronos  # Doesn't work with Python3
from uikit import uikit
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
class timer:

    # plugin info
    module = "timer"
    title = "Timer"
    meta = plugin_meta()
    
    
    # configuration settings
    timefield = "playing"
    
    
    # kronos scheduler list
    sched = None
    
    
    
    
    # prepare gui
    def __init__(self, parent):
      if parent:
          







<



<


<







35
36
37
38
39
40
41

42
43
44

45
46

47
48
49
50
51
52
53
class timer:

    # plugin info
    module = "timer"
    title = "Timer"
    meta = plugin_meta()
    

    # configuration settings
    timefield = "playing"
    

    # kronos scheduler list
    sched = None

    
    
    
    # prepare gui
    def __init__(self, parent):
      if parent:
          
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
        # target channel
        if not self.bookmarks.streams.get("timer"):
            self.bookmarks.streams["timer"] = [{"title":"--- timer events ---"}]
        self.bookmarks.add_category("timer")
        self.streams = self.bookmarks.streams["timer"]
        
        # widgets
        parent.add_signals.update({
            "timer_ok": self.add_timer,
            "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 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")





    # close dialog,get data
    def add_timer(self, *w):
        self.parent.timer_dialog.hide()
        row = self.parent.row()
        row = copy.copy(row)
        
        # add data







|
|
|
>
>















>
>
>
>







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
        # target channel
        if not self.bookmarks.streams.get("timer"):
            self.bookmarks.streams["timer"] = [{"title":"--- timer events ---"}]
        self.bookmarks.add_category("timer")
        self.streams = self.bookmarks.streams["timer"]
        
        # widgets
        uikit.add_signals(parent, {
            ("timer_ok", "clicked"): self.add_timer,
            ("timer_cancel", "clicked"): self.hide,
            ("timer_dialog", "close"): self.hide,
            ("timer_dialog", "delete-event"): self.hide,
        })
        
        # prepare spool
        self.sched = kronos.ThreadedScheduler()
        for row in self.streams:
            try: self.queue(row)
            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")

    # done        
    def hide(self, *w):
        return self.parent.timer_dialog.hide()

    # close dialog,get data
    def add_timer(self, *w):
        self.parent.timer_dialog.hide()
        row = self.parent.row()
        row = copy.copy(row)
        
        # add data

Modified uikit.py from [f968e46470] to [c920bbe1ed].

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

# system
import os.path
import copy
import sys
import re
import base64
import zlib
import inspect
from compat2and3 import unicode, xrange, PY3


# gtk version (2=gtk2, 3=gtk3, 7=tk;)
ver = 2
# if running on Python3 or with commandline flag
if PY3 or "--gtk3" in sys.argv:
    ver = 3







<

|







25
26
27
28
29
30
31

32
33
34
35
36
37
38
39
40

# system
import os.path
import copy
import sys
import re
import base64

import inspect
from compat2and3 import unicode, xrange, PY3, gzip_decode


# gtk version (2=gtk2, 3=gtk3, 7=tk;)
ver = 2
# if running on Python3 or with commandline flag
if PY3 or "--gtk3" in sys.argv:
    ver = 3
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
    import gtk
    import gobject
    GdkPixbuf = gtk.gdk
    empty_pixbuf = GdkPixbuf.Pixbuf(gtk.gdk.COLORSPACE_RGB,True,8,16,16)
    empty_pixbuf.fill(0xFFFFFFFF)

# prepare gtkbuilder data
ui_xml = get_data("gtk3.xml.zlib", decode=True, z=True) or get_data("gtk3.xml", decode=True)
if ver == 2:
    ui_xml = ui_xml.replace('version="3.0"', 'version="2.16"')



# simplified gtk constructors               ---------------------------------------------
class uikit:







|







54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
    import gtk
    import gobject
    GdkPixbuf = gtk.gdk
    empty_pixbuf = GdkPixbuf.Pixbuf(gtk.gdk.COLORSPACE_RGB,True,8,16,16)
    empty_pixbuf.fill(0xFFFFFFFF)

# prepare gtkbuilder data
ui_xml = get_data("gtk3.xml.gz", decode=True, gz=True) #or get_data("gtk3.xml", decode=True)
if ver == 2:
    ui_xml = ui_xml.replace('version="3.0"', 'version="2.16"')



# simplified gtk constructors               ---------------------------------------------
class uikit:
447
448
449
450
451
452
453







454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
    # gtk.messagebox
    @staticmethod
    def msg(text, style=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE):
        m = gtk.MessageDialog(None, 0, style, buttons, message_format=text)
        m.show()
        m.connect("response", lambda *w: m.destroy())
        







        
    # Pixbug loader (from inline string, as in `logo.png`)
    @staticmethod
    def pixbuf(buf, fmt="png", decode=True, gzip=False):
        if not buf or len(buf) < 16:
            return None
        if fmt and ver==3:
            p = GdkPixbuf.PixbufLoader.new_with_type(fmt)
        elif fmt:
            p = GdkPixbuf.PixbufLoader(fmt)
        else:
            p = GdkPixbuf.PixbufLoader()
        if decode and re.match("^[\w+/=\s]+$", str(buf)):
            buf = base64.b64decode(buf)  # inline encoding
        if gzip:
            buf = zlib.decompress(buf)
        if buf:
            p.write(buf)
        pix = p.get_pixbuf()
        p.close()
        return pix
            
            







>
>
>
>
>
>
>















|







446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
    # gtk.messagebox
    @staticmethod
    def msg(text, style=gtk.MESSAGE_INFO, buttons=gtk.BUTTONS_CLOSE):
        m = gtk.MessageDialog(None, 0, style, buttons, message_format=text)
        m.show()
        m.connect("response", lambda *w: m.destroy())
        

    # manual signal binding with a dict of { (widget, signal): callback }
    @staticmethod
    def add_signals(builder, map):
        for (widget,signal),func in map.items():
            builder.get_widget(widget).connect(signal, func)

        
    # Pixbug loader (from inline string, as in `logo.png`)
    @staticmethod
    def pixbuf(buf, fmt="png", decode=True, gzip=False):
        if not buf or len(buf) < 16:
            return None
        if fmt and ver==3:
            p = GdkPixbuf.PixbufLoader.new_with_type(fmt)
        elif fmt:
            p = GdkPixbuf.PixbufLoader(fmt)
        else:
            p = GdkPixbuf.PixbufLoader()
        if decode and re.match("^[\w+/=\s]+$", str(buf)):
            buf = base64.b64decode(buf)  # inline encoding
        if gzip:
            buf = gzip_decode(buf)
        if buf:
            p.write(buf)
        pix = p.get_pixbuf()
        p.close()
        return pix