source: trunk/StreamVision/StreamVision.py@ 340

Last change on this file since 340 was 340, checked in by Nicholas Riley, 15 years ago

StreamVision.py: Handle Growl disappearance. Syntax updated for
py-appscript 0.18.0.

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