source: trunk/StreamVision/StreamVision.py @ 315

Last change on this file since 315 was 315, checked in by Nicholas Riley, 14 years ago

StreamVision?.py: Added more support for recent appscript versions
(k.Property => k.property). Removed unnecessary .get invocations
since we've dropped support for older appscript versions where it was
required. Robustified RealPlayer? support.

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    # 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.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_()
84        trackName = ''
85        if trackClass != k.property:
86            trackName = iTunes.current_track.name()
87
88        if iTunes.player_state() != k.playing:
89            growlNotify('iTunes is not playing.', trackName)
90            return
91        if trackClass == k.URL_track:
92            growlNotify(cleanStreamTitle(iTunes.current_stream_title()),
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()
102            if artwork:
103                kw['pictImage'] = artwork[0].data()
104        growlNotify(trackName + '  ' +
105                    '★' * (iTunes.current_track.rating() / 20),
106                    iTunes.current_track.album() + "\n" +
107                    iTunes.current_track.artist(),
108                    **kw)
109
110    def goToSite(self):
111        iTunes = iTunesApp()
112        if iTunes.player_state() == k.playing:
113            url = iTunes.current_stream_URL()
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()
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() == k.playing)
148        iTunes.playpause()
149        if not was_playing and iTunes.player_state() == 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[its.name=='Stations'][1]())
152            iTunes.play()
153        if HAVE_XTENSION and useStereo:
154            if iTunes.player_state() == 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[its.frontmost][1].name()
162        if frontName == 'RealPlayer':
163            realPlayer = app(id='com.RealNetworks.RealPlayer')
164            if len(realPlayer.players()) > 0:
165                if realPlayer.players[1].state() == k.playing:
166                    realPlayer.pause()
167                else:
168                    realPlayer.play()
169                return
170        elif frontName == 'VLC':
171            app(id='org.videolan.vlc').play() # equivalent to playpause
172        else:
173            self.playPause(useStereo=False)
174
175    def registerZoomWindowHotKey(self):
176        self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
177
178    def unregisterZoomWindowHotKey(self):
179        self.unregisterHotKey(self.zoomWindowHotKey)
180        self.zoomWindowHotKey = None
181
182    def zoomWindow(self):
183        # XXX detect if "enable access for assistive devices" needs to be enabled
184        systemEvents = app(id='com.apple.systemEvents')
185        frontName = systemEvents.processes[its.frontmost][1].name()
186        if frontName == 'iTunes':
187            systemEvents.processes['iTunes'].menu_bars[1]. \
188                menu_bar_items['Window'].menus.menu_items['Zoom'].click()
189            return
190        elif frontName in ('X11', 'Emacs'): # preserve C-M-\
191            self.unregisterZoomWindowHotKey()
192            systemEvents.key_code(42, using=[k.command_down, k.control_down])
193            self.registerZoomWindowHotKey()
194            return
195        try:
196            zoomed = app(frontName).windows[1].zoomed
197            zoomed.set(not zoomed())
198        except (CommandError, RuntimeError):
199            systemEvents.processes[frontName].windows \
200                [its.subrole == 'AXStandardWindow'].windows[1]. \
201                buttons[its.subrole == 'AXZoomButton'].buttons[1].click()
202
203    def finishLaunching(self):
204        super(StreamVision, self).finishLaunching()
205        self.registerHotKey(self.displayTrackInfo, 100) # F8
206        self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
207        self.registerHotKey(self.playPause, 101) # F9
208        self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
209        self.registerHotKey(lambda: iTunesApp().next_track(), 103) # F11
210        self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
211        self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
212        self.registerZoomWindowHotKey()
213        NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
214        try:
215            import HIDRemote
216            HIDRemote.connect()
217        except ImportError:
218            print "failed to import HIDRemote (XXX fix - on Intel)"
219        except OSError, e:
220            print "failed to connect to remote: ", e
221
222    def sendEvent_(self, theEvent):
223        eventType = theEvent.type()
224        if eventType == NSSystemDefined and \
225               theEvent.subtype() == kEventHotKeyPressedSubtype:
226            self.hotKeyActions[theEvent.data1()]()
227        elif eventType == NSApplicationDefined:
228            key = theEvent.data1()
229            if key == kHIDUsage_Csmr_ScanNextTrack:
230                iTunesApp().next_track()
231            elif key == kHIDUsage_Csmr_ScanPreviousTrack:
232                iTunesApp().previous_track()
233            elif key == kHIDUsage_Csmr_PlayOrPause:
234                self.playPauseFront()
235        super(StreamVision, self).sendEvent_(theEvent)
236
237if __name__ == "__main__":
238    AppHelper.runEventLoop()
239    HIDRemote.disconnect() # XXX do we get here?
Note: See TracBrowser for help on using the repository browser.