Coverage for src/botadi/mokadi/gui_mokadi.py: 19%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# -*- coding: utf-8 -*-
2"""
3@file
4@brief Tkinter application for Mokadi
5"""
6import tkinter
7import tkinter.ttk as tttk
8import tkinter.scrolledtext as ScrolledText
9import os
10import threading
11from queue import Queue
12from PIL import Image, ImageTk
13from pyquickhelper.loghelper import CustomLog, fLOG, get_password
14from .grammars import MokadiGrammar_frParser, MokadiGrammar_frLexer, MokadiGrammar_frListener
15from .mokadi_message import MokadiMessage
16from .mokadi_engine import MokadiEngine
17from .mokadi_action_conversation import MokadiActionConversation
18from .mokadi_action_emotion import MokadiActionEmotion
19from .mokadi_action_mail import MokadiActionMail
20from .mokadi_action_news import MokadiActionNews
21from .mokadi_action_slides import MokadiActionSlides
22from .mokadi_record import play_speech, record_speech
23from .mokadi_speak import speak
24from .cognitive_services_helper import call_api_speech_reco
25from .mokadi_picture import take_picture
26from .gui_mokadi_process import start_process_listen
29class ThreadSpeech(threading.Thread):
31 def __init__(self, win, subkey, fLOG):
32 threading.Thread.__init__(self)
33 self.win = win
34 self.subkey = subkey
35 self.fLOG = fLOG
37 def run(self):
38 self.fLOG("[BingSpeech] start listening")
39 speech = record_speech()
40 self.fLOG("[BingSpeech] call API ", len(speech))
41 try:
42 reco = call_api_speech_reco(self.subkey, memwav=speech)
43 exc = None
44 except Exception as e:
45 reco = "error, Je n'ai rien entendu. Etes-vous connecté ?", 1
46 exc = e
47 self.fLOG("[BingSpeech] received ", len(speech))
48 if exc is not None:
49 self.fLOG("[BingSpeech] exc", exc)
50 elif "results" not in reco:
51 reco = "error, Je n'ai rien entendu. Etes-vous sûr d'avoir parlé ?", 1
52 else:
53 results = reco["results"]
54 if len(results) != 1:
55 reco = "error, Je n'ai rien compris. Avez-vous parlé assez fort ?", 1
56 else:
57 res = results[0]
58 if "lexical" not in res:
59 reco = "error, Quelle langue avez-vous parlé ?", 1
60 else:
61 conf = res["confidence"]
62 reco = res["lexical"], conf
64 self.win.queue.put_nowait(reco)
65 self.win.event_generate("<<thread_fini>>")
68class ThreadListenProcess(threading.Thread):
70 def __init__(self, win, fLOG):
71 threading.Thread.__init__(self)
72 self.win = win
73 self.fLOG = fLOG
75 def run(self):
76 self.fLOG("[Listen] start")
77 process, parent_conn, _ = start_process_listen()
78 self.process = process
79 good = {'123', 'et 23', '23', 'et 223', '923', '2123', ', 923', 'l\'un des trois',
80 '0123', 'Trois'}
81 while True:
82 text = parent_conn.recv()
83 self.fLOG("[Listen]", text)
84 if text in good:
85 speak("Je t'écoute.")
86 self.win.event_generate("<<Listener>>")
88 def stop_listening(self):
89 self.process.terminate()
92class ThreadListen(threading.Thread):
94 def __init__(self, win, fLOG, folder):
95 threading.Thread.__init__(self)
96 self.win = win
97 self.fLOG = fLOG
98 self.folder = folder
99 self.filename = os.path.join(folder, "roaming_listening.wav")
101 def run(self):
102 self.fLOG("[Listen] start")
103 # Implementation was removed.
104 # from ensae_teaching_cs.cspython import vocal_recognition_system
105 good = {'123', 'et 23', '23', 'et 223', '923', '2123', ', 923', 'l\'un des trois',
106 '0123'}
107 self.continue_listening = True
108 while self.continue_listening:
109 self.fLOG("[Listen] .")
110 sound = record_speech(RECORD_SECONDS=2)
111 with open(self.filename, "wb") as f:
112 f.write(sound)
113 res = None # vocal_recognition_system(self.filename)
114 if res is not None:
115 text = " ".join(_[1] for _ in res) # pylint: disable=E1133
116 self.fLOG("[Listen] .", text)
117 if text in good:
118 self.win.event_generate("<<Listener>>")
120 def stop_listening(self):
121 self.continue_listening = False
124class TkinterMokadi(tkinter.Frame):
125 """
126 Defines a frame.
127 """
129 def __init__(self, parent, mokadi, vocal=False, subkey_speech=None,
130 fLOG=fLOG, folder="."):
131 """
132 @param parent a frame
133 @param mokadi the bot @see cl MokadiEngine
134 @param vocal speak the answer and not just display it
135 @param subkey_speech key for the speech
136 @param folder temporary folder
137 @param fLOG logging function
138 """
139 tkinter.Frame.__init__(self, parent)
140 self._mokadi = mokadi
141 self._speak = vocal
142 self._subkey_speech = subkey_speech
143 self.fLOG = fLOG
144 self.queue = Queue()
145 self._folder = folder
146 self.initialize()
148 def initialize(self):
149 """
150 Initialisation.
151 """
152 self.grid()
153 self.subframe1 = tkinter.Frame(self)
154 self.subframe2 = tkinter.Frame(self)
155 self.subframe1.grid(column=0, row=0)
156 self.subframe2.grid(column=1, row=0)
158 self.respond = tttk.Button(
159 self.subframe1, text='Demander', command=self.get_response)
160 self.respond.grid(column=1, row=0, sticky='nesw', padx=3, pady=3)
161 self.respond.config(width=5)
163 self.listen = tttk.Button(
164 self.subframe1, text='Ecouter', command=self.start_speech_listening)
165 self.listen.grid(column=1, row=1, sticky='nesw', padx=3, pady=3)
166 self.listen.config(width=5)
168 self.usr_input = tttk.Entry(self.subframe1, state='normal')
169 self.usr_input.grid(column=0, row=0, sticky='nesw', padx=3, pady=3)
170 self.usr_input.bind("<Return>", self.bound_enter)
171 self.usr_input.config(width=5)
173 self.conversation_lbl = tttk.Label(
174 self.subframe1, anchor=tkinter.E, text='Conversation')
175 self.conversation_lbl.grid(
176 column=0, row=1, sticky='nesw', padx=3, pady=3)
177 self.conversation_lbl.config(width=50)
179 self.conversation = ScrolledText.ScrolledText(
180 self.subframe1, state='disabled')
181 self.conversation.grid(column=0, row=2, columnspan=2,
182 sticky='nesw', padx=3, pady=3)
183 self.conversation.config(width=50)
184 self.bind("<<thread_fini>>", self.receive_speech)
185 self._waiting = False
187 def bound_enter(self, *args):
188 """
189 Returned was pressed.
190 """
191 self.get_response()
193 def get_response(self):
194 """
195 Get the text in the message box.
196 Retrieve the answer.
197 """
198 if self._waiting:
199 self.fLOG("[TkinterMokadi] already waiting")
200 return
201 user_text = self.usr_input.get()
202 self.usr_input.delete(0, tkinter.END)
204 if len(user_text) == 0:
205 # We record the speech instead.
206 self.start_speech_recording()
207 else:
208 user_input = MokadiMessage("MOKADI " + user_text, 1)
209 self.process(user_input)
211 def start_speech_recording(self):
212 """
213 Launches a thread which record the speech.
214 """
215 self.conversation_lbl.config(text='Parlez (< 5s) et attendez.')
216 self.conversation_lbl.update_idletasks()
217 self._waiting = True
218 th = ThreadSpeech(self, self._subkey_speech, self.fLOG)
219 th.start()
221 def start_speech_listening(self):
222 """
223 Launches a thread which record the speech.
224 """
225 if hasattr(self, "thread_listen") and self.thread_listen is not None:
226 self.thread_listen.stop_listening()
227 self.listen.config(text="Ecouter")
228 self.thread_listen = None
229 else:
230 self.listen.config(text="Arrêter l'écoute")
231 th = ThreadListen(self, self.fLOG, self._folder)
232 th.start()
233 self.thread_listen = th
234 self.bind("<<Listener>>", self.from_listener)
236 def from_listener(self, *args):
237 """
238 Listener tells to start.
239 """
240 speak("Je t'écoute.")
241 self.get_response()
243 def receive_speech(self, *args):
244 """
245 Reveices the recognized speech.
246 """
247 self.conversation_lbl.config(text='Conversation')
248 self.conversation_lbl.update_idletasks()
250 data = self.queue.get()
251 self.queue.task_done()
252 self._waiting = False
254 if isinstance(data, tuple):
255 data = data[0]
257 self.usr_input.insert(0, data)
258 self.usr_input.event_generate("<Return>")
260 def process(self, user_input):
261 """
262 Process an input.
264 @param user_input user input
265 """
266 self.conversation['state'] = 'normal'
267 self.conversation.insert(
268 tkinter.END, "\n\nVous : " + user_input.message)
269 iter = self._mokadi.process(user_input, exc=False)
270 self.fLOG("[TkinterMokadi] sent:", user_input)
272 for info in iter:
273 speakable = []
275 self.fLOG("[TkinterMokadi] received:", info,
276 info.has_sound, info.has_image)
278 if info.status == "ok":
279 if info.has_sound:
280 play_speech(info.sound)
281 elif info.has_image:
282 if isinstance(info.image, str) and not info.image.startswith("http"):
283 monimage = Image.open(info.image)
284 photo = ImageTk.PhotoImage(monimage)
285 label = tkinter.Label(self.subframe2, image=photo)
286 label.image = photo
287 label.grid(column=1, row=0)
288 if info.info:
289 self.conversation.insert(
290 tkinter.END, "\nMokadi : " + info.info)
291 if not info.has_sound:
292 speakable.append(info.info)
293 elif info.status == "error":
294 self.conversation.insert(
295 tkinter.END, "\nMokadi : " + info.info)
296 speakable.append(info.info)
298 if self._speak and len(speakable) > 0:
299 for text in speakable:
300 speak(text)
302 self.conversation['state'] = 'disabled'
303 self.conversation.see(tkinter.END)
306def gui_mokadi(fLOG=None, folder_slides=None):
307 """
308 Launches the application.
310 There is a bug somewhere. Taking a picture fails if the vocal synthesis
311 runs first. We need to take a picture first.
312 """
313 take_picture()
314 user = get_password("gmail", os.environ["COMPUTERNAME"] + "user")
315 pwd = get_password("gmail", os.environ["COMPUTERNAME"] + "pwd")
316 server = "imap.gmail.com"
317 subkey_news = get_password(
318 "cogser", os.environ["COMPUTERNAME"] + "news")
319 subkey_emo = get_password(
320 "cogser", os.environ["COMPUTERNAME"] + "emotions")
321 subkey_speech = get_password(
322 "cogser", os.environ["COMPUTERNAME"] + "voicereco")
324 if folder_slides is None:
325 folder_slides = os.path.abspath(os.path.dirname(__file__))
326 folder_slides = os.path.join(folder_slides, "demo")
328 folder = os.path.abspath("temp_mokadi_folder")
329 if fLOG is not None:
330 fLOG("[gui_mokadi] saving into", folder)
331 if not os.path.exists(folder):
332 os.mkdir(folder)
333 clog = CustomLog(folder)
335 actions = [MokadiActionSlides(folder=folder_slides, fLOG=fLOG),
336 MokadiActionMail(user=user, pwd=pwd, server=server, fLOG=fLOG),
337 MokadiActionNews(subkey_news, fLOG=fLOG),
338 MokadiActionEmotion(subkey_emo, folder, fLOG=fLOG),
339 MokadiActionConversation(fLOG=fLOG),
340 ]
342 engine = MokadiEngine(folder, clog, actions, MokadiGrammar_frParser,
343 MokadiGrammar_frLexer, MokadiGrammar_frListener)
345 tk = tkinter.Tk()
346 tk.iconbitmap(os.path.join(os.path.dirname(__file__), 'project_ico.ico'))
347 tk.title("Mokadi")
348 window = TkinterMokadi(tk, engine, vocal=True, subkey_speech=subkey_speech)
349 tk.mainloop()
350 if hasattr(window, "thread_listen") and window.thread_listen is not None:
351 window.thread_listen.stop_listening()
352 fLOG("Stop listening.")
353 fLOG("END.")