source: trunk/StreamVision/StreamVision.py@ 302

Last change on this file since 302 was 302, checked in by Nicholas Riley, 16 years ago

setup.py: Use setuptools.

StreamVision.py: HIDRemote helpers aren't compiled for Intel yet; just
skip them for now.

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