commit 0: 6d59e56f0d2b
branch: default
initial commit
Derek Payton / dmpayton
7 months ago

Changed (Δ15.5 KB):

raw changeset »

LICENSE (22 lines added, 0 lines removed)

icons/control-stop-square.png (binary file changed)

icons/control.png (binary file changed)

icons/information.png (binary file changed)

icons/kusc-logo.png (binary file changed)

icons/kusc.ico (binary file changed)

icons/slash.png (binary file changed)

kusc-player.kpf (7 lines added, 0 lines removed)

kusc.py (189 lines added, 0 lines removed)

Up to file-list LICENSE:

1
 Copyright (c) 2010 Derek Payton <derek.payton@gmail.com>
2
3
 Permission is hereby granted, free of charge, to any person
4
 obtaining a copy of this software and associated documentation
5
 files (the "Software"), to deal in the Software without
6
 restriction, including without limitation the rights to use,
7
 copy, modify, merge, publish, distribute, sublicense, and/or sell
8
 copies of the Software, and to permit persons to whom the
9
 Software is furnished to do so, subject to the following
10
 conditions:
11
12
 The above copyright notice and this permission notice shall be
13
 included in all copies or substantial portions of the Software.
14
15
 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
 OTHER DEALINGS IN THE SOFTWARE.

Up to file-list icons/control-stop-square.png:

Binary file has changed or diff was empty.

Up to file-list icons/control.png:

Binary file has changed or diff was empty.

Up to file-list icons/information.png:

Binary file has changed or diff was empty.

Up to file-list icons/kusc-logo.png:

Binary file has changed or diff was empty.

Up to file-list icons/kusc.ico:

Binary file has changed or diff was empty.

Up to file-list icons/slash.png:

Binary file has changed or diff was empty.

Up to file-list kusc-player.kpf:

1
<?xml version="1.0" encoding="UTF-8"?>
2
<!-- Komodo Project File - DO NOT EDIT -->
3
<project id="211ca8be-cec5-4706-907f-bd1ca7ef2ea0" kpf_version="4" name="kusc-player.kpf">
4
<preference-set idref="211ca8be-cec5-4706-907f-bd1ca7ef2ea0">
5
  <boolean id="import_live">1</boolean>
6
</preference-set>
7
</project>

Up to file-list kusc.py:

1
#!/usr/bin/env python
2
3
__appname__ = 'KUSC.org Standalone Player'
4
__author__ = 'Derek M. Payton <derek.payton@gmail.com>'
5
__copyright__ = 'Copyright (c) 2010 Derek M. Payton'
6
__license__ = 'MIT License'
7
__version__ = '0.1-dev'
8
9
import os
10
import platform
11
import re
12
import sys
13
import urllib2
14
from BeautifulSoup import BeautifulSoup
15
from PyQt4 import QtCore, QtGui, QtWebKit
16
17
KUSC_HOMEPAGE = 'http://www.kusc.org/classical/'
18
KUSC_PLAYER = 'http://www.kusc.org/classical/stream/listen.html'
19
PYTHON_VERSION = platform.python_version()
20
SYSTEM = platform.system()
21
22
class DataStore(object):
23
    def __init__(self, **kwargs):
24
        for key, value in kwargs.iteritems():
25
            setattr(self, key, value)
26
27
def load_icon(icon_name):
28
    path = os.path.join(os.getcwd(), 'icons', icon_name)
29
    return QtGui.QIcon(path)
30
31
## http://effbot.org/zone/re-sub.htm#unescape-html
32
def unescape(text):
33
    def fixup(m):
34
        text = m.group(0)
35
        if text[:2] == "&#":
36
            # character reference
37
            try:
38
                if text[:3] == "&#x":
39
                    return unichr(int(text[3:-1], 16))
40
                else:
41
                    return unichr(int(text[2:-1]))
42
            except ValueError:
43
                pass
44
        else:
45
            # named entity
46
            try:
47
                text = unichr(htmlentitydefs.name2codepoint[text[1:-1]])
48
            except KeyError:
49
                pass
50
        return text # leave as is
51
    return re.sub("&#?\w+;", fixup, text)
52
53
class SysTrayIcon(QtGui.QSystemTrayIcon):
54
    def __init__(self, parent, app):
55
        ## Initial setup for our sys tray icon
56
        super(QtGui.QSystemTrayIcon, self).__init__(parent)
57
        self.app = app
58
        self.setIcon(load_icon('kusc.ico'))
59
        self.setToolTip('KUSC Player')
60
61
        ## Setup the menu
62
        self.menu = QtGui.QMenu(parent)
63
        self.actions = DataStore(
64
            play=self.menu.addAction(load_icon('control-stop-square.png'), 'Stop Music'),
65
            about=self.menu.addAction(load_icon('information.png'), 'About'),
66
            exit=self.menu.addAction(load_icon('slash.png'), 'Exit')
67
            )
68
        self.connect(self.actions.play, QtCore.SIGNAL('triggered()'), self.toggle_playing)
69
        self.connect(self.actions.about, QtCore.SIGNAL('triggered()'), self.about)
70
        self.connect(self.actions.exit, QtCore.SIGNAL('triggered()'), self.exit)
71
        self.setContextMenu(self.menu)
72
73
        self.current_song = None
74
75
        def init():
76
            ''' This is a hack '''
77
            self.start_playing()
78
            self.now_playing()
79
        QtCore.QTimer.singleShot(1000, init)
80
81
    def exit(self):
82
        ''' Exit the program '''
83
        self.stop_playing()
84
        self.app.exit()
85
86
    def toggle_playing(self):
87
        if hasattr(self, 'web'):
88
            self.stop_playing()
89
        else:
90
            self.start_playing()
91
92
    def start_playing(self):
93
        if not hasattr(self, 'web'):
94
            self.showMessage('Loading...', 'Please wait while the player loads.')
95
            self.actions.play.setIcon(load_icon('control-stop-square.png'))
96
            self.actions.play.setText('Stop Music')
97
            self.web = QtWebKit.QWebView()
98
            self.web.settings().setAttribute(QtWebKit.QWebSettings.PluginsEnabled, True)
99
            self.web.load(QtCore.QUrl(KUSC_PLAYER))
100
101
    def stop_playing(self):
102
        if hasattr(self, 'web'):
103
            self.actions.play.setIcon(load_icon('control.png'))
104
            self.actions.play.setText('Play Music')
105
            del self.web
106
107
    def now_playing(self):
108
        print 'fetching song...'
109
110
        ## Grab the HTML of the homepage...
111
        request = urllib2.Request(
112
            url=KUSC_HOMEPAGE,
113
            headers={'User-agent': 'KUSC Standalone Player v%s / python %s (%s)' % (
114
                __version__,
115
                PYTHON_VERSION,
116
                SYSTEM
117
                )}
118
            )
119
        response = urllib2.urlopen(request).read()
120
121
        ## Parse out the currently playing song...
122
        soup = BeautifulSoup(response)
123
        onnowtext = soup.findAll('p', {'id': 'onnowtext'})[0]
124
        program = unescape(onnowtext.findAll('span')[1].contents[0])
125
        song = unescape(onnowtext.findAll('span')[2].contents[0])
126
127
        ## If it's a new song, set it
128
        if song == self.current_song:
129
            ## Check again in 1 minute
130
            print '\told song, try again in 1 minute'
131
            QtCore.QTimer.singleShot(60000, self.now_playing)
132
        else:
133
            ## Set the new song, and dont check again for 5 minutes
134
            print '\tnew song, check again in 5 minutes'
135
            self.current_song = song
136
            now_playing = '\n'.join((program, song))
137
            self.setToolTip(now_playing)
138
            self.showMessage('Now Playing on KUSC:', now_playing)
139
            QtCore.QTimer.singleShot(300000, self.now_playing)
140
141
    def about(self):
142
        content = '''
143
            <p>
144
                <b>%(appname)s</b> v%(version)s<br />
145
                <small>Python %(py_ver)s - Qt %(qt_ver)s - PyQt %(pyqt_ver)s - %(system)s</small>
146
            </p>
147
            <p><b>KUSC</b> is a listener-supported classical music radio station,
148
                owned and operated by the University of Southern California.  It
149
                is the largest non-profit classical music station in the country
150
                and the only classical radio station in Southern California.
151
            </p>
152
            <p>This program was written by Derek Payton, a software developer living
153
                just outside KUSC's broadcast area in Southern California. Visit
154
                him online at <a href="http://www.dmpayton.com">dmpayton.com</a>.
155
            </p>
156
            <p>
157
                <b>Support KUSC</b> —
158
                <a href="http://www.kusc.org/classical/SupportKUSC/">Click here to learn more!</a>
159
            </p>
160
            <p><small>
161
                This software is not affiliate with or endorsed by KUSC.<br />
162
                Released under an open source license.
163
                <a href="http://bitbucket.org/dmpayton/kusc-player/">Get the source.</a>
164
            </small></p>
165
            ''' % {
166
                'appname': __appname__,
167
                'version': __version__,
168
                'copyright': __copyright__.replace('(c)', '©'),
169
                'py_ver': PYTHON_VERSION,
170
                'qt_ver': QtCore.QT_VERSION_STR,
171
                'pyqt_ver': QtCore.PYQT_VERSION_STR,
172
                'system': SYSTEM,
173
            }
174
        QtGui.QMessageBox.about(self.parent(), __appname__, content)
175
176
def main():
177
    ## Create the app and widget
178
    app = QtGui.QApplication(sys.argv)
179
    widget = QtGui.QWidget()
180
    widget.setWindowIcon(load_icon('kusc-logo.png'))
181
182
    tray_icon = SysTrayIcon(widget, app)
183
    tray_icon.show()
184
    app.setQuitOnLastWindowClosed(False)
185
186
    sys.exit(app.exec_())
187
188
if __name__ == '__main__':
189
    main()