source: trunk/StreamVision/StreamVision.py@ 324

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

StreamVision.py: switch stereo on/off via X10 even if not using F9 to play/pause

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