source: trunk/StreamVision/StreamVision.py @ 340

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

StreamVision?.py: Handle Growl disappearance. Syntax updated for
py-appscript 0.18.0.

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