source: trunk/StreamVision/StreamVision.py@ 320

Last change on this file since 320 was 317, checked in by Nicholas Riley, 17 years ago

StreamVision.py: Fix recent character encoding problems with Radio Paradise stream title.

File size: 8.7 KB
Line 
1#!/usr/bin/pythonw
2# -*- coding: utf-8 -*-
3
4from appscript import app, k, its, CommandError
5from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
6from Foundation import NSDistributedNotificationCenter
7from PyObjCTools import AppHelper
8from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
9from Carbon.Events import cmdKey, shiftKey, controlKey
10import struct
11import scrape
12import HotKey
13
14GROWL_APP_NAME = 'StreamVision'
15NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
16NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]
17
18kEventHotKeyPressedSubtype = 6
19kEventHotKeyReleasedSubtype = 9
20
21kHIDUsage_Csmr_ScanNextTrack = 0xB5
22kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
23kHIDUsage_Csmr_PlayOrPause = 0xCD
24
25growl = app('GrowlHelperApp')
26
27growl.register(
28 as_application=GROWL_APP_NAME,
29 all_notifications=NOTIFICATIONS_ALL,
30 default_notifications=NOTIFICATIONS_ALL,
31 icon_of_application='iTunes.app')
32 # if we leave off the .app, we can get Classic iTunes's icon
33
34def growlNotify(title, description, **kw):
35 growl.notify(
36 with_name=NOTIFICATION_TRACK_INFO,
37 title=title,
38 description=description,
39 application_name=GROWL_APP_NAME,
40 **kw)
41
42def radioParadiseURL():
43 # XXX better to use http://www2.radioparadise.com/playlist.xml ?
44 session = scrape.Session()
45 session.go('http://www2.radioparadise.com/nowplay_b.php')
46 return session.region.firsttag('a')['href']
47
48def cleanStreamTitle(title):
49 if title == k.missing_value:
50 return ''
51 title = title.split(' [')[0] # XXX move to description
52 title = title.replace('`', u'’')
53 return title.encode('iso-8859-1').decode('utf-8') # XXX iTunes 7.1 or RP?
54
55def cleanStreamTrackName(name):
56 name = name.split('. ')[0]
57 name = name.split(': ')[0]
58 name = name.split(' - ')
59 if len(name) > 1:
60 name = ' - '.join(name[:-1])
61 else:
62 name = name[0]
63 return name
64
65def iTunesApp(): return app(id='com.apple.iTunes')
66def XTensionApp(): return app(creator='SHEx')
67
68HAVE_XTENSION = False
69try:
70 XTensionApp()
71 HAVE_XTENSION = True
72except:
73 pass
74
75class StreamVision(NSApplication):
76
77 hotKeyActions = {}
78 hotKeys = []
79
80 def displayTrackInfo(self):
81 iTunes = iTunesApp()
82
83 trackClass = iTunes.current_track.class_()
84 trackName = ''
85 if trackClass != k.property:
86 trackName = iTunes.current_track.name()
87
88 if iTunes.player_state() != k.playing:
89 growlNotify('iTunes is not playing.', trackName)
90 return
91 if trackClass == k.URL_track:
92 growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
93 cleanStreamTrackName(trackName))
94 return
95 if trackClass == k.property:
96 growlNotify('iTunes is playing.', '')
97 return
98 kw = {}
99 # XXX iTunes doesn't let you get artwork for shared tracks
100 if trackClass != k.shared_track:
101 artwork = iTunes.current_track.artworks()
102 if artwork:
103 kw['pictImage'] = artwork[0].data()
104 growlNotify(trackName + ' ' +
105 '★' * (iTunes.current_track.rating() / 20),
106 iTunes.current_track.album() + "\n" +
107 iTunes.current_track.artist(),
108 **kw)
109
110 def goToSite(self):
111 iTunes = iTunesApp()
112 if iTunes.player_state() == k.playing:
113 url = iTunes.current_stream_URL()
114 if url != k.missing_value:
115 if 'radioparadise.com' in url and 'review' not in url:
116 url = radioParadiseURL()
117 NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
118 return
119 NSBeep()
120
121 def registerHotKey(self, func, keyCode, mods=0):
122 hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
123 GetApplicationEventTarget(), 0)
124 self.hotKeys.append(hotKeyRef)
125 self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
126 return hotKeyRef
127
128 def unregisterHotKey(self, hotKeyRef):
129 self.hotKeys.remove(hotKeyRef)
130 del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
131 hotKeyRef.UnregisterEventHotKey()
132
133 def incrementRatingBy(self, increment):
134 iTunes = iTunesApp()
135 rating = iTunes.current_track.rating()
136 rating += increment
137 if rating < 0:
138 rating = 0
139 NSBeep()
140 elif rating > 100:
141 rating = 100
142 NSBeep()
143 iTunes.current_track.rating.set(rating)
144
145 def playPause(self, useStereo=True):
146 iTunes = iTunesApp()
147 was_playing = (iTunes.player_state() == k.playing)
148 iTunes.playpause()
149 if not was_playing and iTunes.player_state() == k.stopped:
150 # most likely, we're focused on the iPod, so playing does nothing
151 iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
152 iTunes.play()
153 if HAVE_XTENSION and useStereo:
154 if iTunes.player_state() == k.playing:
155 XTensionApp().turnon('Stereo')
156 else:
157 XTensionApp().turnoff('Stereo')
158
159 def playPauseFront(self):
160 systemEvents = app(id='com.apple.systemEvents')
161 frontName = systemEvents.processes[its.frontmost][1].name()
162 if frontName == 'RealPlayer':
163 realPlayer = app(id='com.RealNetworks.RealPlayer')
164 if len(realPlayer.players()) > 0:
165 if realPlayer.players[1].state() == k.playing:
166 realPlayer.pause()
167 else:
168 realPlayer.play()
169 return
170 elif frontName == 'VLC':
171 app(id='org.videolan.vlc').play() # equivalent to playpause
172 else:
173 self.playPause(useStereo=False)
174
175 def registerZoomWindowHotKey(self):
176 self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
177
178 def unregisterZoomWindowHotKey(self):
179 self.unregisterHotKey(self.zoomWindowHotKey)
180 self.zoomWindowHotKey = None
181
182 def zoomWindow(self):
183 # XXX detect if "enable access for assistive devices" needs to be enabled
184 systemEvents = app(id='com.apple.systemEvents')
185 frontName = systemEvents.processes[its.frontmost][1].name()
186 if frontName == 'iTunes':
187 systemEvents.processes['iTunes'].menu_bars[1]. \
188 menu_bar_items['Window'].menus.menu_items['Zoom'].click()
189 return
190 elif frontName in ('X11', 'Emacs'): # preserve C-M-\
191 self.unregisterZoomWindowHotKey()
192 systemEvents.key_code(42, using=[k.command_down, k.control_down])
193 self.registerZoomWindowHotKey()
194 return
195 try:
196 zoomed = app(frontName).windows[1].zoomed
197 zoomed.set(not zoomed())
198 except (CommandError, RuntimeError):
199 systemEvents.processes[frontName].windows \
200 [its.subrole == 'AXStandardWindow'].windows[1]. \
201 buttons[its.subrole == 'AXZoomButton'].buttons[1].click()
202
203 def finishLaunching(self):
204 super(StreamVision, self).finishLaunching()
205 self.registerHotKey(self.displayTrackInfo, 100) # F8
206 self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
207 self.registerHotKey(self.playPause, 101) # F9
208 self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
209 self.registerHotKey(lambda: iTunesApp().next_track(), 103) # F11
210 self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
211 self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
212 self.registerZoomWindowHotKey()
213 NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
214 try:
215 import HIDRemote
216 HIDRemote.connect()
217 except ImportError:
218 print "failed to import HIDRemote (XXX fix - on Intel)"
219 except OSError, e:
220 print "failed to connect to remote: ", e
221
222 def sendEvent_(self, theEvent):
223 eventType = theEvent.type()
224 if eventType == NSSystemDefined and \
225 theEvent.subtype() == kEventHotKeyPressedSubtype:
226 self.hotKeyActions[theEvent.data1()]()
227 elif eventType == NSApplicationDefined:
228 key = theEvent.data1()
229 if key == kHIDUsage_Csmr_ScanNextTrack:
230 iTunesApp().next_track()
231 elif key == kHIDUsage_Csmr_ScanPreviousTrack:
232 iTunesApp().previous_track()
233 elif key == kHIDUsage_Csmr_PlayOrPause:
234 self.playPauseFront()
235 super(StreamVision, self).sendEvent_(theEvent)
236
237if __name__ == "__main__":
238 AppHelper.runEventLoop()
239 HIDRemote.disconnect() # XXX do we get here?
Note: See TracBrowser for help on using the repository browser.