source: trunk/StreamVision/StreamVision.py @ 302

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

setup.py: Use setuptools.

StreamVision?.py: HIDRemote helpers aren't compiled for Intel yet; just
skip them for now.

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