source: trunk/StreamVision/StreamVision.py@ 313

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

StreamVision.py: Replace filter() with [] for compatibility with
appscript 0.17.0.

File size: 8.6 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[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[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 # XXX detect if "enable access for assistive devices" needs to be enabled
181 systemEvents = app(id='com.apple.systemEvents')
182 frontName = systemEvents.processes[its.frontmost][1].name()
183 if frontName == 'iTunes':
184 systemEvents.processes['iTunes'].menu_bars[1]. \
185 menu_bar_items['Window'].menus.menu_items['Zoom'].click()
186 return
187 elif frontName in ('X11', 'Emacs'): # preserve C-M-\
188 self.unregisterZoomWindowHotKey()
189 systemEvents.key_code(42, using=[k.command_down, k.control_down])
190 self.registerZoomWindowHotKey()
191 return
192 try:
193 zoomed = app(frontName).windows[1].zoomed
194 zoomed.set(not zoomed())
195 except (CommandError, RuntimeError):
196 systemEvents.processes[frontName].windows \
197 [its.subrole == 'AXStandardWindow'].windows[1]. \
198 buttons[its.subrole == 'AXZoomButton'].buttons[1].click()
199
200 def finishLaunching(self):
201 super(StreamVision, self).finishLaunching()
202 self.registerHotKey(self.displayTrackInfo, 100) # F8
203 self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
204 self.registerHotKey(self.playPause, 101) # F9
205 self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
206 self.registerHotKey(lambda: iTunesApp().next_track(), 103) # F11
207 self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
208 self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
209 self.registerZoomWindowHotKey()
210 NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
211 try:
212 import HIDRemote
213 HIDRemote.connect()
214 except ImportError:
215 print "failed to import HIDRemote (XXX fix - on Intel)"
216 except OSError, e:
217 print "failed to connect to remote: ", e
218
219 def sendEvent_(self, theEvent):
220 eventType = theEvent.type()
221 if eventType == NSSystemDefined and \
222 theEvent.subtype() == kEventHotKeyPressedSubtype:
223 self.hotKeyActions[theEvent.data1()]()
224 elif eventType == NSApplicationDefined:
225 key = theEvent.data1()
226 if key == kHIDUsage_Csmr_ScanNextTrack:
227 iTunesApp().next_track()
228 elif key == kHIDUsage_Csmr_ScanPreviousTrack:
229 iTunesApp().previous_track()
230 elif key == kHIDUsage_Csmr_PlayOrPause:
231 self.playPauseFront()
232 super(StreamVision, self).sendEvent_(theEvent)
233
234if __name__ == "__main__":
235 AppHelper.runEventLoop()
236 HIDRemote.disconnect() # XXX do we get here?
Note: See TracBrowser for help on using the repository browser.