source: trunk/StreamVision/StreamVision.py @ 301

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

StreamVision?.py: Play/pause VLC or RealPlayer? if in front with Logitech headphones.

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