forked from tspivey/tdsr
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmac
executable file
·163 lines (145 loc) · 4.29 KB
/
mac
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
#!/usr/bin/python
#Copyright (C) 2016, 2017 Tyler Spivey
#See the license in COPYING.txt
from Foundation import (
NSObject, NSFileHandle, NSNotificationCenter,
NSFileHandleReadCompletionNotification, NSFileHandleNotificationDataItem,
)
from AppKit import NSSpeechSynthesizer, NSSpeechRecentSyncProperty
from PyObjCTools import AppHelper
import threading
rate = None
volume = None
class QueuedSynth(NSObject):
def init_(self, handler):
self = super(QueuedSynth, self).init()
self.synth = NSSpeechSynthesizer.alloc().init()
self.speaking = False
self.lock = threading.Lock()
self.synth.setDelegate_(self)
self.queue = []
self.last_index = None
self.handle_index = handler
return self
def speak(self, text, index=None):
with self.lock:
self.queue.append((text, index))
if self.speaking:
return
self.speak_more()
def speechSynthesizer_didFinishSpeaking_(self, synth, success):
with self.lock:
if not self.speaking:
return
if self.last_index:
self.handle_index(self.last_index)
if len(self.queue) == 0:
self.speaking = False
return
self.speak_more()
def speak_more(self):
text, index = self.queue.pop(0)
res = self.synth.startSpeakingString_(text)
if res == False:
self.speaking = False
return
self.speaking = True
self.last_index = index
def stop(self):
with self.lock:
if not self.speaking: return
self.synth.stopSpeaking()
self.queue = []
def speechSynthesizer_didEncounterSyncMessage_(self, synth, message):
n = synth.objectForProperty_error_(NSSpeechRecentSyncProperty, None)[0]
self.handle_index(n)
class FileObserver(NSObject):
def initWithFileDescriptor_readCallback_errorCallback_(self,
fileDescriptor, readCallback, errorCallback):
self = self.init()
self.readCallback = readCallback
self.errorCallback = errorCallback
self.fileHandle = NSFileHandle.alloc().initWithFileDescriptor_(
fileDescriptor)
self.nc = NSNotificationCenter.defaultCenter()
self.nc.addObserver_selector_name_object_(
self,
'fileHandleReadCompleted:',
NSFileHandleReadCompletionNotification,
self.fileHandle)
self.fileHandle.readInBackgroundAndNotify()
return self
def fileHandleReadCompleted_(self, aNotification):
ui = aNotification.userInfo()
newData = ui.objectForKey_(NSFileHandleNotificationDataItem)
if newData is None:
if self.errorCallback is not None:
self.errorCallback(self, ui.objectForKey_(NSFileHandleError))
self.close()
else:
self.fileHandle.readInBackgroundAndNotify()
if self.readCallback is not None:
self.readCallback(self, str(newData))
def close(self):
self.nc.removeObserver_(self)
if self.fileHandle is not None:
self.fileHandle.closeFile()
self.fileHandle = None
# break cycles in case these functions are closed over
# an instance of us
self.readCallback = None
self.errorCallback = None
def __del__(self):
# Without this, if a notification fires after we are GC'ed
# then the app will crash because NSNotificationCenter
# doesn't retain observers. In this example, it doesn't
# matter, but it's worth pointing out.
self.close()
def prompt():
sys.stdout.write("write something: ")
sys.stdout.flush()
def gotLine(observer, line):
if not line:
AppHelper.stopEventLoop()
return
line = line.strip('\n')
for l in line.split('\n'):
handle_line(l)
def handle_line(line):
global rate, volume
line = line.decode('utf-8', 'replace')
if line[0] == u"s":
l = ""
if rate:
l += u"[[rate %s]]" % rate
if volume:
l += u"[[volm %s]]" % volume
l += line[1:].replace('[[', ' ')
l = l.replace(u'\u23ce', ' ')
synth.speak(l)
elif line[0] == u"x":
synth.stop()
elif line[0] == u"l":
prefix = u"[[char ltrl]]"
suffix = "[[char norm]]"
if len(line) == 2 and line[1].isupper():
prefix += u"[[pbas +10]]"
suffix += u"[[pbas -10]]"
synth.speak(prefix+line[1:]+suffix)
elif line[0] == u"r":
rate = line[1:]
elif line[0] == u"v":
volume = str(int(line[1:]) / 100.0)
def gotError(observer, err):
print "error:", err
AppHelper.stopEventLoop()
synth = None
def main():
global synth
import sys
observer = FileObserver.alloc().initWithFileDescriptor_readCallback_errorCallback_(
sys.stdin.fileno(), gotLine, gotError)
synth = QueuedSynth.alloc().init_(None)
AppHelper.runConsoleEventLoop(installInterrupt=True)
if __name__ == '__main__':
main()