Matheus Matos Rocha commited on
Commit
29c5931
·
1 Parent(s): e293b9b

Add application file

Browse files
Files changed (1) hide show
  1. main.py +406 -0
main.py ADDED
@@ -0,0 +1,406 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import speech_recognition as sr
3
+ import difflib
4
+ import time
5
+ from langchain_groq.chat_models import ChatGroq
6
+ from dotenv import load_dotenv
7
+ import tempfile
8
+ import gradio as gr
9
+
10
+ # Load environment variables
11
+ load_dotenv()
12
+
13
+ class PronunciaPratica:
14
+ def __init__(self, idioma='pt-BR'):
15
+ """
16
+ Initializes the pronunciation practice application.
17
+
18
+ Args:
19
+ idioma (str): Language code for speech recognition (e.g., 'pt-BR', 'en-US')
20
+ """
21
+ self.idioma = idioma
22
+ self.recognizer = sr.Recognizer()
23
+
24
+ # Configure the Groq client using an environment variable
25
+ api_key = os.getenv("GROQ_API_KEY")
26
+ if not api_key:
27
+ raise ValueError("⚠️ Groq API key not found. Set the GROQ_API_KEY environment variable.")
28
+
29
+ self.chat = ChatGroq(model="llama-3.1-8b-instant", api_key=api_key)
30
+
31
+ def gerar_frase(self):
32
+ """Generate a random phrase for pronunciation practice."""
33
+ try:
34
+ # Map language code to language name for better prompt
35
+ language_map = {
36
+ 'pt-BR': 'português',
37
+ 'en-US': 'inglês',
38
+ 'es-ES': 'espanhol',
39
+ 'fr-FR': 'francês',
40
+ 'it-IT': 'italiano',
41
+ 'de-DE': 'alemão'
42
+ }
43
+
44
+ language_name = language_map.get(self.idioma, 'português')
45
+
46
+ # Create a more explicit prompt that specifies the language
47
+ prompt = f"""
48
+ Forneça uma frase curta em {language_name} para treinar pronúncia.
49
+ A frase deve ter entre 5 e 10 palavras.
50
+ Responda APENAS com a frase em {language_name}, sem explicações.
51
+ É MUITO IMPORTANTE que a frase seja apenas em {language_name} e não em qualquer outro idioma.
52
+ """
53
+
54
+ resposta = self.chat.invoke([{
55
+ "role": "system",
56
+ "content": prompt
57
+ }])
58
+
59
+ return resposta.content.strip()
60
+ except Exception as e:
61
+ # Default phrases based on language
62
+ default_phrases = {
63
+ 'pt-BR': "O sol está brilhando hoje.",
64
+ 'en-US': "The sun is shining today.",
65
+ 'es-ES': "El sol está brillando hoy.",
66
+ 'fr-FR': "Le soleil brille aujourd'hui.",
67
+ 'it-IT': "Il sole splende oggi.",
68
+ 'de-DE': "Die Sonne scheint heute."
69
+ }
70
+ return f"{default_phrases.get(self.idioma, 'O sol está brilhando hoje.')} (Erro: {str(e)})"
71
+
72
+ def reconhecer_audio(self, audio_path):
73
+ """Convert recorded audio to text."""
74
+ try:
75
+ # Load the audio file
76
+ with sr.AudioFile(audio_path) as source:
77
+ audio_data = self.recognizer.record(source)
78
+
79
+ # Recognize the text
80
+ texto_falado = self.recognizer.recognize_google(audio_data, language=self.idioma)
81
+ return texto_falado
82
+ except sr.UnknownValueError:
83
+ return "Erro: Não foi possível entender o áudio."
84
+ except sr.RequestError as e:
85
+ return f"Erro: Problema na requisição do serviço de reconhecimento. {e}"
86
+ except Exception as e:
87
+ return f"Erro: {e}"
88
+
89
+ def avaliar_pronuncia(self, frase_original, frase_falada):
90
+ """
91
+ Evaluate the similarity between the original phrase and the spoken phrase.
92
+
93
+ Returns:
94
+ tuple: (similarity percentage, incorrect words)
95
+ """
96
+ # Normalize phrases (remove punctuation and convert to lowercase)
97
+ import re
98
+ normalizar = lambda texto: re.sub(r'[^\w\s]', '', texto.lower())
99
+
100
+ original_norm = normalizar(frase_original)
101
+ falada_norm = normalizar(frase_falada)
102
+
103
+ # Calculate similarity
104
+ sequencia = difflib.SequenceMatcher(None, original_norm, falada_norm)
105
+ similaridade = sequencia.ratio() * 100
106
+
107
+ # Identify incorrect words
108
+ palavras_originais = original_norm.split()
109
+ palavras_faladas = falada_norm.split()
110
+
111
+ palavras_incorretas = []
112
+ for palavra in palavras_originais:
113
+ if palavra not in palavras_faladas:
114
+ palavras_incorretas.append(palavra)
115
+
116
+ return similaridade, palavras_incorretas
117
+
118
+ def obter_feedback(self, similaridade, palavras_incorretas, frase_original, frase_falada):
119
+ """Generate detailed feedback on pronunciation."""
120
+ # Determine language for feedback based on idioma
121
+ feedback_lang = self.idioma
122
+
123
+ # Choose appropriate language for the feedback prompt
124
+ if feedback_lang.startswith('pt'):
125
+ prompt_template = """
126
+ Analise a pronúncia do usuário e forneça feedback específico:
127
+
128
+ Frase original: "{frase_original}"
129
+ Frase falada: "{frase_falada}"
130
+ Similaridade: {similaridade:.2f}%
131
+ Palavras possivelmente problemáticas: {palavras_prob}
132
+
133
+ Ofereça dicas específicas para melhorar a pronúncia, focando nos erros mais comuns.
134
+ Seja breve e construtivo, máximo de 3 linhas.
135
+ """
136
+ palavras_prob = ', '.join(palavras_incorretas) if palavras_incorretas else 'Nenhuma'
137
+ elif feedback_lang.startswith('en'):
138
+ prompt_template = """
139
+ Analyze the user's pronunciation and provide specific feedback:
140
+
141
+ Original phrase: "{frase_original}"
142
+ Spoken phrase: "{frase_falada}"
143
+ Similarity: {similaridade:.2f}%
144
+ Potentially problematic words: {palavras_prob}
145
+
146
+ Offer specific tips to improve pronunciation, focusing on the most common errors.
147
+ Be brief and constructive, maximum of 3 lines.
148
+ """
149
+ palavras_prob = ', '.join(palavras_incorretas) if palavras_incorretas else 'None'
150
+ else:
151
+ # Default to English for other languages
152
+ prompt_template = """
153
+ Analyze the user's pronunciation and provide specific feedback:
154
+
155
+ Original phrase: "{frase_original}"
156
+ Spoken phrase: "{frase_falada}"
157
+ Similarity: {similaridade:.2f}%
158
+ Potentially problematic words: {palavras_prob}
159
+
160
+ Offer specific tips to improve pronunciation, focusing on the most common errors.
161
+ Be brief and constructive, maximum of 3 lines.
162
+ """
163
+ palavras_prob = ', '.join(palavras_incorretas) if palavras_incorretas else 'None'
164
+
165
+ prompt = prompt_template.format(
166
+ frase_original=frase_original,
167
+ frase_falada=frase_falada,
168
+ similaridade=similaridade,
169
+ palavras_prob=palavras_prob
170
+ )
171
+
172
+ try:
173
+ resposta = self.chat.invoke([{"role": "system", "content": prompt}])
174
+ return resposta.content.strip()
175
+ except Exception as e:
176
+ # Default feedback in case of error, based on language
177
+ if feedback_lang.startswith('pt'):
178
+ if similaridade > 90:
179
+ return "Excelente pronúncia! Continue praticando."
180
+ elif similaridade > 70:
181
+ return f"Boa pronúncia, mas pode melhorar. Preste atenção em: {', '.join(palavras_incorretas)}"
182
+ else:
183
+ return "Tente novamente, focando na pronúncia clara de cada palavra."
184
+ else:
185
+ if similaridade > 90:
186
+ return "Excellent pronunciation! Keep practicing."
187
+ elif similaridade > 70:
188
+ return f"Good pronunciation, but can be improved. Pay attention to: {', '.join(palavras_incorretas)}"
189
+ else:
190
+ return "Try again, focusing on clear pronunciation of each word."
191
+
192
+ # Function to map language dropdown to language code
193
+ def get_language_code(language_name):
194
+ idiomas = {
195
+ "Português (Brasil)": "pt-BR",
196
+ "Inglês (EUA)": "en-US",
197
+ "Espanhol": "es-ES",
198
+ "Francês": "fr-FR",
199
+ "Italiano": "it-IT",
200
+ "Alemão": "de-DE"
201
+ }
202
+ return idiomas.get(language_name, "pt-BR")
203
+
204
+ # Create a global instance of the app
205
+ app_instance = None
206
+
207
+ # Track the current language
208
+ current_language_code = "pt-BR"
209
+
210
+ # Functions for Gradio interface
211
+ def gerar_nova_frase(language_name):
212
+ global app_instance, current_language_code
213
+ language_code = get_language_code(language_name)
214
+
215
+ # Only create a new instance if the language has changed
216
+ if app_instance is None or current_language_code != language_code:
217
+ app_instance = PronunciaPratica(idioma=language_code)
218
+ current_language_code = language_code
219
+
220
+ return app_instance.gerar_frase()
221
+
222
+ def process_audio(audio_path, frase_atual, language_name, historico):
223
+ global app_instance, current_language_code
224
+
225
+ if audio_path is None:
226
+ return "Nenhum áudio gravado", "", 0, "", historico, ""
227
+
228
+ # Make sure we have an app instance with the current language
229
+ language_code = get_language_code(language_name)
230
+ if app_instance is None or current_language_code != language_code:
231
+ app_instance = PronunciaPratica(idioma=language_code)
232
+ current_language_code = language_code
233
+
234
+ # Recognize the speech
235
+ texto_falado = app_instance.reconhecer_audio(audio_path)
236
+
237
+ if texto_falado.startswith("Erro"):
238
+ return texto_falado, "", 0, "", historico, ""
239
+
240
+ # Evaluate pronunciation
241
+ similaridade, palavras_incorretas = app_instance.avaliar_pronuncia(frase_atual, texto_falado)
242
+
243
+ # Get detailed feedback
244
+ feedback = app_instance.obter_feedback(similaridade, palavras_incorretas, frase_atual, texto_falado)
245
+
246
+ # Add to history
247
+ entry = {
248
+ "frase": frase_atual,
249
+ "falado": texto_falado,
250
+ "similaridade": f"{similaridade:.1f}%",
251
+ "feedback": feedback,
252
+ "timestamp": time.strftime("%H:%M:%S")
253
+ }
254
+
255
+ historico = [entry] + historico
256
+ if len(historico) > 5:
257
+ historico = historico[:5]
258
+
259
+ # Modificação aqui para melhorar a visibilidade do histórico
260
+ history_html = ""
261
+ for entry in historico:
262
+ history_html += f"""
263
+ <div class="history-entry">
264
+ <b>{entry['timestamp']}</b> - Precisão: {entry['similaridade']}<br>
265
+ <b>Original:</b> "{entry['frase']}"<br>
266
+ <b>Sua fala:</b> "{entry['falado']}"<br>
267
+ <b>Feedback:</b> {entry['feedback']}
268
+ </div>
269
+ """
270
+
271
+ return texto_falado, f"{similaridade:.1f}%", similaridade, feedback, historico, history_html
272
+
273
+ def initialize_app_and_get_phrase(language_name="Português (Brasil)"):
274
+ """Initialize the app with a specific language and get first phrase"""
275
+ global app_instance, current_language_code
276
+
277
+ language_code = get_language_code(language_name)
278
+ app_instance = PronunciaPratica(idioma=language_code)
279
+ current_language_code = language_code
280
+
281
+ return app_instance.gerar_frase()
282
+
283
+ def main():
284
+ # Get initial phrase (will also initialize app_instance)
285
+ initial_phrase = initialize_app_and_get_phrase()
286
+
287
+ # CSS for styling
288
+ css = """
289
+ .gradio-container {
290
+ font-family: 'Helvetica Neue', Arial, sans-serif;
291
+ }
292
+ .phrase-box {
293
+ background-color: #828181;
294
+ padding: 20px;
295
+ border-radius: 10px;
296
+ border-left: 5px solid #1e3d59;
297
+ font-size: 22px;
298
+ margin: 20px 0;
299
+ }
300
+ .spoken-box {
301
+ background-color: #e6f7ff;
302
+ padding: 15px;
303
+ border-radius: 10px;
304
+ border-left: 5px solid #0074cc;
305
+ }
306
+ .score-display {
307
+ font-size: 36px;
308
+ font-weight: bold;
309
+ text-align: center;
310
+ }
311
+ .feedback-box {
312
+ background-color: #e8f4ea;
313
+ padding: 15px;
314
+ border-radius: 10px;
315
+ border-left: 5px solid #4CAF50;
316
+ }
317
+ .history-entry {
318
+ margin-bottom: 15px;
319
+ padding: 10px;
320
+ border-left: 3px solid #4CAF50;
321
+ background-color: #2a2a2a;
322
+ color: white;
323
+ border-radius: 8px;
324
+ }
325
+ """
326
+
327
+ with gr.Blocks(css=css) as demo:
328
+ gr.Markdown("# 🎤 Pronúncia Prática")
329
+ gr.Markdown("### Melhore sua pronúncia com feedback em tempo real")
330
+
331
+ with gr.Row():
332
+ with gr.Column(scale=2):
333
+ idioma_dropdown = gr.Dropdown(
334
+ choices=["Português (Brasil)", "Inglês (EUA)", "Espanhol", "Francês", "Italiano", "Alemão"],
335
+ value="Português (Brasil)",
336
+ label="Selecione o idioma para praticar:"
337
+ )
338
+
339
+ with gr.Column(scale=1):
340
+ gerar_frase_btn = gr.Button("🔄 Gerar nova frase")
341
+
342
+ frase_atual = gr.Textbox(
343
+ value=initial_phrase,
344
+ label="Frase para praticar:",
345
+ elem_classes=["phrase-box"]
346
+ )
347
+
348
+ with gr.Row():
349
+ audio_input = gr.Audio(
350
+ sources=["microphone"],
351
+ type="filepath",
352
+ label="🎙️ Grave sua voz"
353
+ )
354
+
355
+ with gr.Row():
356
+ texto_reconhecido = gr.Textbox(label="Você disse:")
357
+
358
+ with gr.Row():
359
+ score_text = gr.Textbox(label="Pontuação:")
360
+ score_progress = gr.Slider(minimum=0, maximum=100, label="", interactive=False)
361
+
362
+ feedback_box = gr.Textbox(label="Feedback:")
363
+
364
+ # Hidden state for history
365
+ historico_state = gr.State([])
366
+
367
+ with gr.Accordion("📜 Histórico de Prática", open=False):
368
+ history_display = gr.HTML()
369
+
370
+ # Event handlers
371
+ gerar_frase_btn.click(
372
+ fn=gerar_nova_frase,
373
+ inputs=[idioma_dropdown],
374
+ outputs=[frase_atual]
375
+ )
376
+
377
+ idioma_dropdown.change(
378
+ fn=gerar_nova_frase,
379
+ inputs=[idioma_dropdown],
380
+ outputs=[frase_atual]
381
+ )
382
+
383
+ audio_input.change(
384
+ fn=process_audio,
385
+ inputs=[audio_input, frase_atual, idioma_dropdown, historico_state],
386
+ outputs=[texto_reconhecido, score_text, score_progress, feedback_box, historico_state, history_display]
387
+ )
388
+
389
+ # Instructions in the footer
390
+ gr.Markdown("""
391
+ ### 📝 Como usar:
392
+ 1. Selecione o idioma que deseja praticar
393
+ 2. Clique em 'Gerar nova frase' para mudar a frase (opcional)
394
+ 3. Leia a frase em voz alta
395
+ 4. Clique no botão de microfone e fale a frase
396
+ 5. Veja o feedback e sua pontuação
397
+
398
+ ---
399
+ Desenvolvido com ❤️ usando Gradio e IA
400
+ """)
401
+
402
+ # Launch the app with share=True to create a shareable link
403
+ demo.launch(share=True, server_name="0.0.0.0")
404
+
405
+ if __name__ == "__main__":
406
+ main()