source: trunk/StreamVision/StreamVision.py@ 300

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

HIDRemotemodule.m: Support for Logitech Music Anywhere USB headphones.

libHIDUtilities.a, HID_Utilities_External.h: built from <http://homepage.mac.com/geowar1/.Public/Sample%20Code/HID_Utilities.sit>

setup.py: Build HIDRemote module.

StreamVision.py: Support Bluetooth headphones; don't turn on/off the
stereo if we use the play/pause control on the headphones.

File size: 7.9 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 registerZoomWindowHotKey(self):
160 self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
161
162 def unregisterZoomWindowHotKey(self):
163 self.unregisterHotKey(self.zoomWindowHotKey)
164 self.zoomWindowHotKey = None
165
166 def zoomWindow(self):
167 systemEvents = app(id='com.apple.systemEvents')
168 frontName = systemEvents.processes.filter(its.frontmost)[1].name()
169 if frontName == 'iTunes':
170 systemEvents.processes['iTunes'].menu_bars[1]. \
171 menu_bar_items['Window'].menus.menu_items['Zoom'].click()
172 return
173 elif frontName in ('X11', 'Emacs'): # preserve C-M-\
174 self.unregisterZoomWindowHotKey()
175 systemEvents.key_code(42, using=[k.command_down, k.control_down])
176 self.registerZoomWindowHotKey()
177 return
178 try:
179 zoomed = app(frontName).windows[1].zoomed
180 zoomed.set(not zoomed())
181 except (CommandError, RuntimeError):
182 systemEvents.processes[frontName].windows. \
183 filter(its.subrole == 'AXStandardWindow').windows[1]. \
184 buttons.filter(its.subrole == 'AXZoomButton').buttons[1].click()
185
186 def finishLaunching(self):
187 super(StreamVision, self).finishLaunching()
188 self.registerHotKey(self.displayTrackInfo, 100) # F8
189 self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
190 self.registerHotKey(self.playPause, 101) # F9
191 self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
192 self.registerHotKey(lambda: iTunesApp().next_track(), 103) # F11
193 self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
194 self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
195 self.registerZoomWindowHotKey()
196 NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
197 try:
198 HIDRemote.connect()
199 except OSError, e:
200 print "failed to connect to remote: ", e
201
202 def sendEvent_(self, theEvent):
203 eventType = theEvent.type()
204 if eventType == NSSystemDefined and \
205 theEvent.subtype() == kEventHotKeyPressedSubtype:
206 self.hotKeyActions[theEvent.data1()]()
207 elif eventType == NSApplicationDefined:
208 key = theEvent.data1()
209 if key == kHIDUsage_Csmr_ScanNextTrack:
210 iTunesApp().next_track()
211 elif key == kHIDUsage_Csmr_ScanPreviousTrack:
212 iTunesApp().previous_track()
213 elif key == kHIDUsage_Csmr_PlayOrPause:
214 self.playPause(useStereo=False)
215 super(StreamVision, self).sendEvent_(theEvent)
216
217if __name__ == "__main__":
218 AppHelper.runEventLoop()
219 HIDRemote.disconnect() # XXX do we get here?
Note: See TracBrowser for help on using the repository browser.