Completed
Pull Request — master (#168)
by Jace
07:27
created

Application.frame_text()   A

Complexity

Conditions 1

Size

Total Lines 15

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 1
dl 0
loc 15
rs 9.4285
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
0 ignored issues
show
Configuration introduced by
The import PIL could not be resolved.

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.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
98
        separator(frame).grid(row=1, padx=10, pady=5, **sticky)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
99
        frame_text(frame).grid(row=2, **sticky)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
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)
0 ignored issues
show
Coding Style introduced by
Usage of * or ** arguments should usually be done with care.

Generally, there is nothing wrong with usage of * or ** arguments. For readability of the code base, we suggest to not over-use these language constructs though.

For more information, we can recommend this blog post from Ned Batchelder including its comments which also touches this aspect.

Loading history...
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