source: trunk/StreamVision/StreamVision.py@ 301

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

StreamVision.py: Play/pause VLC or RealPlayer if in front with Logitech headphones.

File size: 8.4 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 HIDRemote
13import HotKey
14
15GROWL_APP_NAME = 'StreamVision'
16NOTIFICATION_TRACK_INFO = 'iTunes Track Info'
17NOTIFICATIONS_ALL = [NOTIFICATION_TRACK_INFO]
18
19kEventHotKeyPressedSubtype = 6
20kEventHotKeyReleasedSubtype = 9
21
22kHIDUsage_Csmr_ScanNextTrack = 0xB5
23kHIDUsage_Csmr_ScanPreviousTrack = 0xB6
24kHIDUsage_Csmr_PlayOrPause = 0xCD
25
26growl = app('GrowlHelperApp')
27
28growl.register(
29 as_application=GROWL_APP_NAME,
30 all_notifications=NOTIFICATIONS_ALL,
31 default_notifications=NOTIFICATIONS_ALL,
32 icon_of_application='iTunes.app')
33 # if we leave off the .app, we can get Classic iTunes's icon
34
35def growlNotify(title, description, **kw):
36 growl.notify(
37 with_name=NOTIFICATION_TRACK_INFO,
38 title=title,
39 description=description,
40 application_name=GROWL_APP_NAME,
41 **kw)
42
43def radioParadiseURL():
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.MissingValue:
50 return ''
51 title = title.split(' [')[0] # XXX move to description
52 title = title.replace('`', u'’')
53 return title
54
55def cleanStreamTrackName(name):
56 name = name.split('. ')[0]
57 name = name.split(': ')[0]
58 name = name.split(' - ')
59 if len(name) > 1:
60 name = ' - '.join(name[:-1])
61 else:
62 name = name[0]
63 return name
64
65def iTunesApp(): return app(id='com.apple.iTunes')
66def XTensionApp(): return app(creator='SHEx')
67
68HAVE_XTENSION = False
69try:
70 XTensionApp()
71 HAVE_XTENSION = True
72except:
73 pass
74
75class StreamVision(NSApplication):
76
77 hotKeyActions = {}
78 hotKeys = []
79
80 def displayTrackInfo(self):
81 iTunes = iTunesApp()
82
83 trackClass = iTunes.current_track.class_.get()
84 trackName = ''
85 if trackClass != k.Property:
86 trackName = iTunes.current_track.name.get()
87
88 if iTunes.player_state.get() != k.playing:
89 growlNotify('iTunes is not playing.', trackName)
90 return
91 if trackClass == k.URL_track:
92 growlNotify(cleanStreamTitle(iTunes.current_stream_title.get()),
93 cleanStreamTrackName(trackName))
94 return
95 if trackClass == k.Property:
96 growlNotify('iTunes is playing.', '')
97 return
98 kw = {}
99 # XXX iTunes doesn't let you get artwork for shared tracks
100 if trackClass != k.shared_track:
101 artwork = iTunes.current_track.artworks.get()
102 if artwork:
103 kw['pictImage'] = artwork[0].data.get()
104 growlNotify(trackName + ' ' +
105 '★' * (iTunes.current_track.rating.get() / 20),
106 iTunes.current_track.album.get() + "\n" +
107 iTunes.current_track.artist.get(),
108 **kw)
109
110 def goToSite(self):
111 iTunes = iTunesApp()
112 if iTunes.player_state.get() == k.playing:
113 url = iTunes.current_stream_URL.get()
114 if url:
115 if 'radioparadise.com' in url and 'review' not in url:
116 url = radioParadiseURL()
117 NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
118 return
119 NSBeep()
120
121 def registerHotKey(self, func, keyCode, mods=0):
122 hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
123 GetApplicationEventTarget(), 0)
124 self.hotKeys.append(hotKeyRef)
125 self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
126 return hotKeyRef
127
128 def unregisterHotKey(self, hotKeyRef):
129 self.hotKeys.remove(hotKeyRef)
130 del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
131 hotKeyRef.UnregisterEventHotKey()
132
133 def incrementRatingBy(self, increment):
134 iTunes = iTunesApp()
135 rating = iTunes.current_track.rating.get()
136 rating += increment
137 if rating < 0:
138 rating = 0
139 NSBeep()
140 elif rating > 100:
141 rating = 100
142 NSBeep()
143 iTunes.current_track.rating.set(rating)
144
145 def playPause(self, useStereo=True):
146 iTunes = iTunesApp()
147 was_playing = (iTunes.player_state.get() == k.playing)
148 iTunes.playpause()
149 if not was_playing and iTunes.player_state.get() == k.stopped:
150 # most likely, we're focused on the iPod, so playing does nothing
151 iTunes.browser_windows[1].view.set(iTunes.user_playlists.filter(its.name=='Stations')[1].get())
152 iTunes.play()
153 if HAVE_XTENSION and useStereo:
154 if iTunes.player_state.get() == k.playing:
155 XTensionApp().turnon('Stereo')
156 else:
157 XTensionApp().turnoff('Stereo')
158
159 def playPauseFront(self):
160 systemEvents = app(id='com.apple.systemEvents')
161 frontName = systemEvents.processes.filter(its.frontmost)[1].name()
162 if frontName == 'RealPlayer':
163 realPlayer = app(id='com.RealNetworks.RealPlayer')
164 if realPlayer.players[0].state.get() == k.playing:
165 realPlayer.pause()
166 else:
167 realPlayer.play()
168 elif frontName == 'VLC':
169 app(id='org.videolan.vlc').play() # equivalent to playpause
170 else:
171 self.playPause(useStereo=False)
172
173 def registerZoomWindowHotKey(self):
174 self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
175
176 def unregisterZoomWindowHotKey(self):
177 self.unregisterHotKey(self.zoomWindowHotKey)
178 self.zoomWindowHotKey = None
179
180 def zoomWindow(self):
181 systemEvents = app(id='com.apple.systemEvents')
182 frontName = systemEvents.processes.filter(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 filter(its.subrole == 'AXStandardWindow').windows[1]. \
198 buttons.filter(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 HIDRemote.connect()
213 except OSError, e:
214 print "failed to connect to remote: ", e
215
216 def sendEvent_(self, theEvent):
217 eventType = theEvent.type()
218 if eventType == NSSystemDefined and \
219 theEvent.subtype() == kEventHotKeyPressedSubtype:
220 self.hotKeyActions[theEvent.data1()]()
221 elif eventType == NSApplicationDefined:
222 key = theEvent.data1()
223 if key == kHIDUsage_Csmr_ScanNextTrack:
224 iTunesApp().next_track()
225 elif key == kHIDUsage_Csmr_ScanPreviousTrack:
226 iTunesApp().previous_track()
227 elif key == kHIDUsage_Csmr_PlayOrPause:
228 self.playPauseFront()
229 super(StreamVision, self).sendEvent_(theEvent)
230
231if __name__ == "__main__":
232 AppHelper.runEventLoop()
233 HIDRemote.disconnect() # XXX do we get here?
Note: See TracBrowser for help on using the repository browser.