|
1
|
|
|
#!env/bin/python |
|
2
|
|
|
|
|
3
|
|
|
import queue |
|
4
|
|
|
import threading |
|
5
|
|
|
import logging |
|
6
|
|
|
|
|
7
|
|
|
import tkinter as tk |
|
8
|
|
|
from tkinter import ttk |
|
9
|
|
|
from PIL import Image, ImageTk |
|
|
|
|
|
|
10
|
|
|
try: |
|
11
|
|
|
import speech_recognition # pylint: disable=import-error |
|
12
|
|
|
except ImportError: |
|
13
|
|
|
speech_recognition = None |
|
14
|
|
|
|
|
15
|
|
|
from memegen import __project__, __version__ |
|
16
|
|
|
from memegen.settings import ProdConfig |
|
17
|
|
|
from memegen.app import create_app |
|
18
|
|
|
from memegen.domain import Text |
|
19
|
|
|
|
|
20
|
|
|
log = logging.getLogger(__name__) |
|
21
|
|
|
|
|
22
|
|
|
|
|
23
|
|
|
class Application: |
|
24
|
|
|
|
|
25
|
|
|
def __init__(self, app): |
|
26
|
|
|
self.app = app |
|
27
|
|
|
self.label = None |
|
28
|
|
|
self.text = None |
|
29
|
|
|
self._image = None |
|
30
|
|
|
self._update_event = None |
|
31
|
|
|
self._clear_event = None |
|
32
|
|
|
|
|
33
|
|
|
# Configure root window |
|
34
|
|
|
self.root = tk.Tk() |
|
35
|
|
|
self.root.title("{} (v{})".format(__project__, __version__)) |
|
36
|
|
|
self.root.minsize(500, 500) |
|
37
|
|
|
|
|
38
|
|
|
# Configure speech recognition |
|
39
|
|
|
self._queue = queue.Queue() |
|
40
|
|
|
self._speech_recognizer = SpeechRecognizer(self._queue) |
|
41
|
|
|
self._speech_recognizer.start() |
|
42
|
|
|
self.process_speech() |
|
43
|
|
|
|
|
44
|
|
|
# Initialize the GUI |
|
45
|
|
|
self.label = None |
|
46
|
|
|
frame = self.init(self.root) |
|
47
|
|
|
frame.pack(fill=tk.BOTH, expand=1) |
|
48
|
|
|
|
|
49
|
|
|
# Start the event loop |
|
50
|
|
|
self.restart() |
|
51
|
|
|
self.root.protocol("WM_DELETE_WINDOW", self.close) |
|
52
|
|
|
self.root.mainloop() |
|
53
|
|
|
|
|
54
|
|
|
def init(self, root): |
|
55
|
|
|
padded = {'padding': 5} |
|
56
|
|
|
sticky = {'sticky': tk.NSEW} |
|
57
|
|
|
|
|
58
|
|
|
# Configure grid |
|
59
|
|
|
frame = ttk.Frame(root, **padded) |
|
|
|
|
|
|
60
|
|
|
frame.rowconfigure(0, weight=1) |
|
61
|
|
|
frame.rowconfigure(1, weight=0) |
|
62
|
|
|
frame.columnconfigure(0, weight=1) |
|
63
|
|
|
|
|
64
|
|
|
def frame_image(root): |
|
65
|
|
|
frame = ttk.Frame(root, **padded) |
|
|
|
|
|
|
66
|
|
|
|
|
67
|
|
|
# Configure grid |
|
68
|
|
|
frame.rowconfigure(0, weight=1) |
|
69
|
|
|
frame.columnconfigure(0, weight=1) |
|
70
|
|
|
|
|
71
|
|
|
# Place widgets |
|
72
|
|
|
self.label = ttk.Label(frame) |
|
73
|
|
|
self.label.grid(row=0, column=0) |
|
74
|
|
|
|
|
75
|
|
|
return frame |
|
76
|
|
|
|
|
77
|
|
|
def frame_text(root): |
|
78
|
|
|
frame = ttk.Frame(root, **padded) |
|
|
|
|
|
|
79
|
|
|
|
|
80
|
|
|
# Configure grid |
|
81
|
|
|
frame.rowconfigure(0, weight=1) |
|
82
|
|
|
frame.rowconfigure(1, weight=1) |
|
83
|
|
|
frame.columnconfigure(0, weight=1) |
|
84
|
|
|
|
|
85
|
|
|
# Place widgets |
|
86
|
|
|
self.text = ttk.Entry(frame) |
|
87
|
|
|
self.text.bind("<Key>", self.restart) |
|
88
|
|
|
self.text.grid(row=0, column=0, **sticky) |
|
|
|
|
|
|
89
|
|
|
self.text.focus_set() |
|
90
|
|
|
|
|
91
|
|
|
return frame |
|
92
|
|
|
|
|
93
|
|
|
def separator(root): |
|
94
|
|
|
return ttk.Separator(root) |
|
95
|
|
|
|
|
96
|
|
|
# Place widgets |
|
97
|
|
|
frame_image(frame).grid(row=0, **sticky) |
|
|
|
|
|
|
98
|
|
|
separator(frame).grid(row=1, padx=10, pady=5, **sticky) |
|
|
|
|
|
|
99
|
|
|
frame_text(frame).grid(row=2, **sticky) |
|
|
|
|
|
|
100
|
|
|
|
|
101
|
|
|
return frame |
|
102
|
|
|
|
|
103
|
|
|
def process_speech(self): |
|
104
|
|
|
try: |
|
105
|
|
|
value = self._queue.get(0) |
|
106
|
|
|
except queue.Empty: |
|
107
|
|
|
pass |
|
108
|
|
|
else: |
|
109
|
|
|
self.update(value) |
|
110
|
|
|
finally: |
|
111
|
|
|
self.root.after(10, self.process_speech) |
|
112
|
|
|
|
|
113
|
|
|
def update(self, value=None): |
|
114
|
|
|
if value: |
|
115
|
|
|
self.clear() |
|
116
|
|
|
self.text.insert(0, value) |
|
117
|
|
|
|
|
118
|
|
|
text = Text(self.text.get()) |
|
119
|
|
|
ratio = 0 |
|
120
|
|
|
match = None |
|
121
|
|
|
|
|
122
|
|
|
for template in self.app.template_service.all(): |
|
123
|
|
|
_ratio, path = template.match(str(text).lower()) |
|
124
|
|
|
if _ratio > ratio: |
|
125
|
|
|
ratio = _ratio |
|
126
|
|
|
log.info("Matched at %s: %s - %s", ratio, template, path) |
|
127
|
|
|
match = template, Text(path) |
|
128
|
|
|
|
|
129
|
|
|
if match: |
|
130
|
|
|
domain = self.app.image_service.create(*match) |
|
|
|
|
|
|
131
|
|
|
image = Image.open(domain.path) |
|
132
|
|
|
old_size = image.size |
|
133
|
|
|
max_size = self.root.winfo_width(), self.root.winfo_height() |
|
134
|
|
|
ratio = min(max_size[0] / old_size[0], max_size[1] / old_size[1]) |
|
135
|
|
|
new_size = [int(s * ratio * 0.9) for s in old_size] |
|
136
|
|
|
image = image.resize(new_size, Image.ANTIALIAS) |
|
137
|
|
|
self._image = ImageTk.PhotoImage(image) |
|
138
|
|
|
self.label.configure(image=self._image) |
|
139
|
|
|
|
|
140
|
|
|
self.clear() |
|
141
|
|
|
|
|
142
|
|
|
self.restart(update=True, clear=False) |
|
143
|
|
|
|
|
144
|
|
|
def clear(self, *_): |
|
145
|
|
|
self.text.delete(0, tk.END) |
|
146
|
|
|
self.restart() |
|
147
|
|
|
|
|
148
|
|
|
def restart(self, *_, update=True, clear=True): |
|
149
|
|
|
if update: |
|
150
|
|
|
if self._update_event: |
|
151
|
|
|
self.root.after_cancel(self._update_event) |
|
152
|
|
|
self._update_event = self.root.after(1000, self.update) |
|
153
|
|
|
if clear: |
|
154
|
|
|
if self._clear_event: |
|
155
|
|
|
self.root.after_cancel(self._clear_event) |
|
156
|
|
|
self._clear_event = self.root.after(5000, self.clear) |
|
157
|
|
|
|
|
158
|
|
|
def close(self): |
|
159
|
|
|
log.info("Closing the application...") |
|
160
|
|
|
self._speech_recognizer.stop = True |
|
161
|
|
|
self._speech_recognizer.join() |
|
162
|
|
|
self.root.destroy() |
|
163
|
|
|
|
|
164
|
|
|
|
|
165
|
|
|
class SpeechRecognizer(threading.Thread): |
|
166
|
|
|
|
|
167
|
|
|
def __init__(self, queue): # pylint: disable=redefined-outer-name |
|
168
|
|
|
super().__init__() |
|
169
|
|
|
self.queue = queue |
|
170
|
|
|
self.microphone = None |
|
171
|
|
|
self.recognizer = None |
|
172
|
|
|
self.stop = False |
|
173
|
|
|
|
|
174
|
|
|
def run(self): |
|
175
|
|
|
if speech_recognition: |
|
176
|
|
|
self.configure() |
|
177
|
|
|
while not self.stop: |
|
178
|
|
|
while not self.queue.empty(): |
|
179
|
|
|
pass |
|
180
|
|
|
audio = self.listen() |
|
181
|
|
|
if audio: |
|
182
|
|
|
result = self.recognize(audio) |
|
183
|
|
|
if result: |
|
184
|
|
|
self.queue.put(result) |
|
185
|
|
|
|
|
186
|
|
|
def configure(self): |
|
187
|
|
|
self.recognizer = speech_recognition.Recognizer() |
|
188
|
|
|
self.recognizer.energy_threshold = 1500 |
|
189
|
|
|
self.recognizer.dynamic_energy_adjustment_ratio = 3 |
|
190
|
|
|
self.microphone = speech_recognition.Microphone() |
|
191
|
|
|
with self.microphone as source: |
|
192
|
|
|
log.info("Adjusting for ambient noise...") |
|
193
|
|
|
self.recognizer.adjust_for_ambient_noise(source, duration=3) |
|
194
|
|
|
log.info("Engery threshold: %s", self.recognizer.energy_threshold) |
|
195
|
|
|
|
|
196
|
|
|
def listen(self): |
|
197
|
|
|
log.info("Listening for audio...") |
|
198
|
|
|
with self.microphone as source: |
|
199
|
|
|
try: |
|
200
|
|
|
audio = self.recognizer.listen(source, timeout=3) |
|
201
|
|
|
except speech_recognition.WaitTimeoutError: |
|
202
|
|
|
return None |
|
203
|
|
|
return audio |
|
204
|
|
|
|
|
205
|
|
|
def recognize(self, audio): |
|
206
|
|
|
log.info("Recognizing speech...") |
|
207
|
|
|
try: |
|
208
|
|
|
result = self.recognizer.recognize_google(audio) |
|
209
|
|
|
except speech_recognition.UnknownValueError: |
|
210
|
|
|
log.warning("No speech detected") |
|
211
|
|
|
else: |
|
212
|
|
|
log.info("Detected speech: %s", result) |
|
213
|
|
|
return result |
|
214
|
|
|
|
|
215
|
|
|
|
|
216
|
|
|
if __name__ == '__main__': |
|
217
|
|
|
Application(create_app(ProdConfig)) |
|
218
|
|
|
|
This can be caused by one of the following:
1. Missing Dependencies
This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.
2. Missing __init__.py files
This error could also result from missing
__init__.pyfiles in your module folders. Make sure that you place one file in each sub-folder.