source: trunk/StreamVision/StreamVision.py@ 431

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

StreamVision: actually bother to test this time

File size: 10.7 KB
RevLine 
[188]1#!/usr/bin/pythonw
2# -*- coding: utf-8 -*-
3
[232]4from appscript import app, k, its, CommandError
[300]5from AppKit import NSApplication, NSApplicationDefined, NSBeep, NSSystemDefined, NSURL, NSWorkspace
[192]6from Foundation import NSDistributedNotificationCenter
[188]7from PyObjCTools import AppHelper
8from Carbon.CarbonEvt import RegisterEventHotKey, GetApplicationEventTarget
[232]9from Carbon.Events import cmdKey, shiftKey, controlKey
[188]10import struct
11import scrape
[211]12import HotKey
[188]13
14GROWL_APP_NAME = 'StreamVision'
15NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
16NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]
17
18kEventHotKeyPressedSubtype = 6
19kEventHotKeyReleasedSubtype = 9
20
[300]21kHIDUsage_Csmr_ScanNextTrack = 0xB5
22kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
23kHIDUsage_Csmr_PlayOrPause = 0xCD
24
[340]25def growlRegister():
26 global growl
27 growl = app(id='com.Growl.GrowlHelperApp')
[188]28
[340]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
[188]35
36def growlNotify(title, description, **kw):
[340]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)
[195]47
[188]48def radioParadiseURL():
[315]49 # XXX better to use http://www2.radioparadise.com/playlist.xml ?
[188]50 session = scrape.Session()
[195]51 session.go('http://www2.radioparadise.com/nowplay_b.php')
52 return session.region.firsttag('a')['href']
[188]53
[194]54def cleanStreamTitle(title):
[316]55 if title == k.missing_value:
[194]56 return ''
57 title = title.split(' [')[0] # XXX move to description
[341]58 try: # incorrectly encoded?
59 title = title.encode('iso-8859-1').decode('utf-8')
60 except UnicodeDecodeError:
61 pass
[194]62 title = title.replace('`', u'’')
[323]63 return title
[195]64
[194]65def cleanStreamTrackName(name):
[188]66 name = name.split('. ')[0]
67 name = name.split(': ')[0]
[190]68 name = name.split(' - ')
69 if len(name) > 1:
70 name = ' - '.join(name[:-1])
71 else:
72 name = name[0]
[188]73 return name
[195]74
[199]75def iTunesApp(): return app(id='com.apple.iTunes')
76def XTensionApp(): return app(creator='SHEx')
[414]77def AmuaApp(): return app('Amua.app')
[188]78
[199]79HAVE_XTENSION = False
80try:
81 XTensionApp()
82 HAVE_XTENSION = True
83except:
84 pass
85
[414]86HAVE_AMUA = False
87try:
88 AmuaApp()
89 HAVE_AMUA = True
90except:
91 pass
92
[324]93needsStereoPowerOn = HAVE_XTENSION
94
[414]95def amuaPlaying():
96 if not HAVE_AMUA:
97 return False
98 return AmuaApp().is_playing()
99
[188]100class StreamVision(NSApplication):
101
102 hotKeyActions = {}
103 hotKeys = []
104
[414]105 def displayTrackInfo(self, playerInfo=None):
[324]106 global needsStereoPowerOn
107
[188]108 iTunes = iTunesApp()
[195]109
[315]110 trackClass = iTunes.current_track.class_()
[211]111 trackName = ''
[315]112 if trackClass != k.property:
113 trackName = iTunes.current_track.name()
[211]114
[315]115 if iTunes.player_state() != k.playing:
[211]116 growlNotify('iTunes is not playing.', trackName)
[324]117 if HAVE_XTENSION:
118 if not needsStereoPowerOn and XTensionApp().status('Stereo'):
119 XTensionApp().turnoff('Stereo')
120 needsStereoPowerOn = True
[211]121 return
[324]122 if needsStereoPowerOn:
123 if not XTensionApp().status('Stereo'):
124 XTensionApp().turnon('Stereo')
125 needsStereoPowerOn = False
[211]126 if trackClass == k.URL_track:
[414]127 if amuaPlaying():
128 if playerInfo is None: # Amua displays it itself
129 AmuaApp().display_song_information()
130 return
[315]131 growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
[211]132 cleanStreamTrackName(trackName))
133 return
[315]134 if trackClass == k.property:
[324]135 growlNotify('iTunes is playing.', '')
136 return
[211]137 kw = {}
138 # XXX iTunes doesn't let you get artwork for shared tracks
139 if trackClass != k.shared_track:
[315]140 artwork = iTunes.current_track.artworks()
[211]141 if artwork:
[315]142 kw['pictImage'] = artwork[0].data()
[211]143 growlNotify(trackName + ' ' +
[323]144 u'★' * (iTunes.current_track.rating() / 20),
145 iTunes.current_track.album() + '\n' +
[315]146 iTunes.current_track.artist(),
[211]147 **kw)
148
[188]149 def goToSite(self):
150 iTunes = iTunesApp()
[315]151 if iTunes.player_state() == k.playing:
[414]152 if amuaPlaying():
153 AmuaApp().display_album_details()
154 return
[315]155 url = iTunes.current_stream_URL()
[316]156 if url != k.missing_value:
[252]157 if 'radioparadise.com' in url and 'review' not in url:
[188]158 url = radioParadiseURL()
159 NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
160 return
161 NSBeep()
[195]162
[188]163 def registerHotKey(self, func, keyCode, mods=0):
164 hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
165 GetApplicationEventTarget(), 0)
166 self.hotKeys.append(hotKeyRef)
[211]167 self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
[235]168 return hotKeyRef
[188]169
[235]170 def unregisterHotKey(self, hotKeyRef):
171 self.hotKeys.remove(hotKeyRef)
172 del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
173 hotKeyRef.UnregisterEventHotKey()
174
[199]175 def incrementRatingBy(self, increment):
176 iTunes = iTunesApp()
[414]177 if amuaPlaying():
[415]178 if increment < 0:
[414]179 AmuaApp().ban_song()
180 growlNotify('Banned song.', '')
181 else:
182 AmuaApp().love_song()
183 growlNotify('Loved song.', '')
184 return
[315]185 rating = iTunes.current_track.rating()
[199]186 rating += increment
187 if rating < 0:
188 rating = 0
189 NSBeep()
190 elif rating > 100:
191 rating = 100
192 NSBeep()
193 iTunes.current_track.rating.set(rating)
194
[300]195 def playPause(self, useStereo=True):
[324]196 global needsStereoPowerOn
197
[199]198 iTunes = iTunesApp()
[315]199 was_playing = (iTunes.player_state() == k.playing)
[324]200 if not useStereo:
201 needsStereoPowerOn = False
[414]202 if was_playing and amuaPlaying():
203 AmuaApp().stop()
204 else:
205 iTunes.playpause()
[315]206 if not was_playing and iTunes.player_state() == k.stopped:
[234]207 # most likely, we're focused on the iPod, so playing does nothing
[315]208 iTunes.browser_windows[1].view.set(iTunes.user_playlists[its.name=='Stations'][1]())
[234]209 iTunes.play()
[300]210 if HAVE_XTENSION and useStereo:
[315]211 if iTunes.player_state() == k.playing:
[199]212 XTensionApp().turnon('Stereo')
[324]213 needsStereoPowerOn = False
[199]214 else:
215 XTensionApp().turnoff('Stereo')
[324]216 needsStereoPowerOn = True
[235]217
[301]218 def playPauseFront(self):
219 systemEvents = app(id='com.apple.systemEvents')
[340]220 frontName = systemEvents.processes[its.frontmost == True][1].name()
[301]221 if frontName == 'RealPlayer':
222 realPlayer = app(id='com.RealNetworks.RealPlayer')
[315]223 if len(realPlayer.players()) > 0:
224 if realPlayer.players[1].state() == k.playing:
225 realPlayer.pause()
226 else:
227 realPlayer.play()
228 return
[301]229 elif frontName == 'VLC':
230 app(id='org.videolan.vlc').play() # equivalent to playpause
231 else:
[302]232 self.playPause(useStereo=False)
[301]233
[414]234 def nextTrack(self):
235 if amuaPlaying():
236 AmuaApp().skip_song()
237 return
238 iTunesApp().next_track()
239
[235]240 def registerZoomWindowHotKey(self):
241 self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
242
243 def unregisterZoomWindowHotKey(self):
244 self.unregisterHotKey(self.zoomWindowHotKey)
245 self.zoomWindowHotKey = None
246
[232]247 def zoomWindow(self):
[313]248 # XXX detect if "enable access for assistive devices" needs to be enabled
[232]249 systemEvents = app(id='com.apple.systemEvents')
[340]250 frontName = systemEvents.processes[its.frontmost == True][1].name()
[233]251 if frontName == 'iTunes':
[232]252 systemEvents.processes['iTunes'].menu_bars[1]. \
253 menu_bar_items['Window'].menus.menu_items['Zoom'].click()
254 return
[235]255 elif frontName in ('X11', 'Emacs'): # preserve C-M-\
256 self.unregisterZoomWindowHotKey()
257 systemEvents.key_code(42, using=[k.command_down, k.control_down])
258 self.registerZoomWindowHotKey()
259 return
[340]260 frontPID = systemEvents.processes[its.frontmost == True][1].unix_id()
[232]261 try:
[339]262 zoomed = app(pid=frontPID).windows[1].zoomed
[232]263 zoomed.set(not zoomed())
[235]264 except (CommandError, RuntimeError):
[313]265 systemEvents.processes[frontName].windows \
266 [its.subrole == 'AXStandardWindow'].windows[1]. \
267 buttons[its.subrole == 'AXZoomButton'].buttons[1].click()
[199]268
[188]269 def finishLaunching(self):
270 super(StreamVision, self).finishLaunching()
271 self.registerHotKey(self.displayTrackInfo, 100) # F8
272 self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
[199]273 self.registerHotKey(self.playPause, 101) # F9
[188]274 self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
[414]275 self.registerHotKey(self.nextTrack, 103) # F11
[211]276 self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
277 self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
[235]278 self.registerZoomWindowHotKey()
[193]279 NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
[300]280 try:
[302]281 import HIDRemote
[300]282 HIDRemote.connect()
[302]283 except ImportError:
284 print "failed to import HIDRemote (XXX fix - on Intel)"
[300]285 except OSError, e:
286 print "failed to connect to remote: ", e
[188]287
288 def sendEvent_(self, theEvent):
[300]289 eventType = theEvent.type()
290 if eventType == NSSystemDefined and \
[188]291 theEvent.subtype() == kEventHotKeyPressedSubtype:
292 self.hotKeyActions[theEvent.data1()]()
[300]293 elif eventType == NSApplicationDefined:
294 key = theEvent.data1()
295 if key == kHIDUsage_Csmr_ScanNextTrack:
[415]296 self.nextTrack()
[300]297 elif key == kHIDUsage_Csmr_ScanPreviousTrack:
298 iTunesApp().previous_track()
299 elif key == kHIDUsage_Csmr_PlayOrPause:
[301]300 self.playPauseFront()
[188]301 super(StreamVision, self).sendEvent_(theEvent)
302
303if __name__ == "__main__":
[340]304 growlRegister()
[195]305 AppHelper.runEventLoop()
[339]306 try:
307 HIDRemote.disconnect()
308 except:
309 pass
Note: See TracBrowser for help on using the repository browser.