source: trunk/StreamVision/StreamVision.py@ 340

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

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

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