source: trunk/StreamVision/StreamVision.py @ 324

Last change on this file since 324 was 324, checked in by Nicholas Riley, 13 years ago

StreamVision?.py: switch stereo on/off via X10 even if not using F9 to play/pause

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