source: trunk/StreamVision/StreamVision.py @ 235

Last change on this file since 235 was 235, checked in by Nicholas Riley, 15 years ago

StreamVision?.py: Pass through cmd-ctrl-\ in Emacs and X11 (C-M-\ is used in Emacs, and it doesn't handle being zoomed through the accessibility interface properly anyway); handle exceptions better when app doesn't respond to zooming Apple Events

File size: 7.2 KB
Line 
1#!/usr/bin/pythonw
2# -*- coding: utf-8 -*-
3
4from appscript import app, k, its, CommandError
5from AppKit import NSApplication, 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
21growl = app('GrowlHelperApp')
22
23growl.register(
24    as_application=GROWL_APP_NAME,
25    all_notifications=NOTIFICATIONS_ALL,
26    default_notifications=NOTIFICATIONS_ALL,
27    icon_of_application='iTunes.app')
28    # if we leave off the .app, we can get Classic iTunes's icon
29
30def growlNotify(title, description, **kw):
31    growl.notify(
32        with_name=NOTIFICATION_TRACK_INFO,
33        title=title,
34        description=description,
35        application_name=GROWL_APP_NAME,
36        **kw)
37
38def radioParadiseURL():
39    session = scrape.Session()
40    session.go('http://www2.radioparadise.com/nowplay_b.php')
41    return session.region.firsttag('a')['href']
42
43def cleanStreamTitle(title):
44    if title == k.MissingValue:
45        return ''
46    title = title.split(' [')[0] # XXX move to description
47    title = title.replace('`', u'’')
48    return title
49
50def cleanStreamTrackName(name):
51    name = name.split('. ')[0]
52    name = name.split(': ')[0]
53    name = name.split(' - ')
54    if len(name) > 1:
55        name = ' - '.join(name[:-1])
56    else:
57        name = name[0]
58    return name
59
60def iTunesApp(): return app(id='com.apple.iTunes')
61def XTensionApp(): return app(creator='SHEx')
62
63HAVE_XTENSION = False
64try:
65    XTensionApp()
66    HAVE_XTENSION = True
67except:
68    pass
69
70class StreamVision(NSApplication):
71
72    hotKeyActions = {}
73    hotKeys = []
74
75    def displayTrackInfo(self):
76        iTunes = iTunesApp()
77
78        trackClass = iTunes.current_track.class_.get()
79        trackName = ''
80        if trackClass != k.Property:
81            trackName = iTunes.current_track.name.get()
82
83        if iTunes.player_state.get() != k.playing:
84            growlNotify('iTunes is not playing.', trackName)
85            return
86        if trackClass == k.URL_track:
87            growlNotify(cleanStreamTitle(iTunes.current_stream_title.get()),
88                        cleanStreamTrackName(trackName))
89            return
90        if trackClass == k.Property:
91           growlNotify('iTunes is playing.', '')
92           return
93        kw = {}
94        # XXX iTunes doesn't let you get artwork for shared tracks
95        if trackClass != k.shared_track:
96            artwork = iTunes.current_track.artworks.get()
97            if artwork:
98                kw['pictImage'] = artwork[0].data.get()
99        growlNotify(trackName + '  ' +
100                    '★' * (iTunes.current_track.rating.get() / 20),
101                    iTunes.current_track.album.get() + "\n" +
102                    iTunes.current_track.artist.get(),
103                    **kw)
104
105    def goToSite(self):
106        iTunes = iTunesApp()
107        if iTunes.player_state.get() == k.playing:
108            url = iTunes.current_stream_URL.get()
109            if url:
110                if 'radioparadise.com' in url:
111                    url = radioParadiseURL()
112                NSWorkspace.sharedWorkspace().openURL_(NSURL.URLWithString_(url))
113                return
114        NSBeep()
115
116    def registerHotKey(self, func, keyCode, mods=0):
117        hotKeyRef = RegisterEventHotKey(keyCode, mods, (0, 0),
118                                        GetApplicationEventTarget(), 0)
119        self.hotKeys.append(hotKeyRef)
120        self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)] = func
121        return hotKeyRef
122
123    def unregisterHotKey(self, hotKeyRef):
124        self.hotKeys.remove(hotKeyRef)
125        del self.hotKeyActions[HotKey.HotKeyAddress(hotKeyRef)]
126        hotKeyRef.UnregisterEventHotKey()
127
128    def incrementRatingBy(self, increment):
129        iTunes = iTunesApp()
130        rating = iTunes.current_track.rating.get()
131        rating += increment
132        if rating < 0:
133            rating = 0
134            NSBeep()
135        elif rating > 100:
136            rating = 100
137            NSBeep()
138        iTunes.current_track.rating.set(rating)
139
140    def playPause(self):
141        iTunes = iTunesApp()
142        was_playing = (iTunes.player_state.get() == k.playing)
143        iTunes.playpause()
144        if not was_playing and iTunes.player_state.get() == k.stopped:
145            # most likely, we're focused on the iPod, so playing does nothing
146            iTunes.browser_windows[1].view.set(iTunes.user_playlists.filter(its.name=='Stations')[1].get())
147            iTunes.play()
148        if HAVE_XTENSION:
149            if iTunes.player_state.get() == k.playing:
150                XTensionApp().turnon('Stereo')
151            else:
152                XTensionApp().turnoff('Stereo')
153
154    def registerZoomWindowHotKey(self):
155        self.zoomWindowHotKey = self.registerHotKey(self.zoomWindow, 42, cmdKey | controlKey) # cmd-ctrl-\
156
157    def unregisterZoomWindowHotKey(self):
158        self.unregisterHotKey(self.zoomWindowHotKey)
159        self.zoomWindowHotKey = None
160
161    def zoomWindow(self):
162        systemEvents = app(id='com.apple.systemEvents')
163        frontName = systemEvents.processes.filter(its.frontmost)[1].name()
164        if frontName == 'iTunes':
165            systemEvents.processes['iTunes'].menu_bars[1]. \
166                menu_bar_items['Window'].menus.menu_items['Zoom'].click()
167            return
168        elif frontName in ('X11', 'Emacs'): # preserve C-M-\
169            self.unregisterZoomWindowHotKey()
170            systemEvents.key_code(42, using=[k.command_down, k.control_down])
171            self.registerZoomWindowHotKey()
172            return
173        try:
174            zoomed = app(frontName).windows[1].zoomed
175            zoomed.set(not zoomed())
176        except (CommandError, RuntimeError):
177            systemEvents.processes[frontName].windows. \
178                filter(its.subrole == 'AXStandardWindow').windows[1]. \
179                buttons.filter(its.subrole == 'AXZoomButton').buttons[1].click()
180
181    def finishLaunching(self):
182        super(StreamVision, self).finishLaunching()
183        self.registerHotKey(self.displayTrackInfo, 100) # F8
184        self.registerHotKey(self.goToSite, 100, cmdKey) # cmd-F8
185        self.registerHotKey(self.playPause, 101) # F9
186        self.registerHotKey(lambda: iTunesApp().previous_track(), 109) # F10
187        self.registerHotKey(lambda: iTunesApp().next_track(), 103) # F11
188        self.registerHotKey(lambda: self.incrementRatingBy(-20), 109, shiftKey) # shift-F10
189        self.registerHotKey(lambda: self.incrementRatingBy(20), 103, shiftKey) # shift-F11
190        self.registerZoomWindowHotKey()
191        NSDistributedNotificationCenter.defaultCenter().addObserver_selector_name_object_(self, self.displayTrackInfo, 'com.apple.iTunes.playerInfo', None)
192
193    def sendEvent_(self, theEvent):
194        if theEvent.type() == NSSystemDefined and \
195               theEvent.subtype() == kEventHotKeyPressedSubtype:
196            self.hotKeyActions[theEvent.data1()]()
197        super(StreamVision, self).sendEvent_(theEvent)
198
199if __name__ == "__main__":
200    AppHelper.runEventLoop()
Note: See TracBrowser for help on using the repository browser.