PronunAI / app.py
Matheus Matos Rocha
teste
83d2714
import os
import speech_recognition as sr
import difflib
import time
from langchain_groq.chat_models import ChatGroq
from dotenv import load_dotenv
import tempfile
import gradio as gr
# Load environment variables
load_dotenv()
class PronunciaPratica:
def __init__(self, idioma='pt-BR'):
"""
Initializes the pronunciation practice application.
Args:
idioma (str): Language code for speech recognition (e.g., 'pt-BR', 'en-US')
"""
self.idioma = idioma
self.recognizer = sr.Recognizer()
# Configure the Groq client using an environment variable
api_key = os.getenv("GROQ_API_KEY")
if not api_key:
raise ValueError("⚠️ Groq API key not found. Set the GROQ_API_KEY environment variable.")
self.chat = ChatGroq(model="llama-3.1-8b-instant", api_key=api_key)
def gerar_frase(self):
"""Generate a random phrase for pronunciation practice."""
try:
# Map language code to language name for better prompt
language_map = {
'pt-BR': 'português',
'en-US': 'inglês',
'es-ES': 'espanhol',
'fr-FR': 'francês',
'it-IT': 'italiano',
'de-DE': 'alemão'
}
language_name = language_map.get(self.idioma, 'português')
# Create a more explicit prompt that specifies the language
prompt = f"""
Forneça uma frase curta em {language_name} para treinar pronúncia.
A frase deve ter entre 5 e 10 palavras.
Responda APENAS com a frase em {language_name}, sem explicações.
É MUITO IMPORTANTE que a frase seja apenas em {language_name} e não em qualquer outro idioma.
"""
resposta = self.chat.invoke([{
"role": "system",
"content": prompt
}])
return resposta.content.strip()
except Exception as e:
# Default phrases based on language
default_phrases = {
'pt-BR': "O sol está brilhando hoje.",
'en-US': "The sun is shining today.",
'es-ES': "El sol está brillando hoy.",
'fr-FR': "Le soleil brille aujourd'hui.",
'it-IT': "Il sole splende oggi.",
'de-DE': "Die Sonne scheint heute."
}
return f"{default_phrases.get(self.idioma, 'O sol está brilhando hoje.')} (Erro: {str(e)})"
def reconhecer_audio(self, audio_path):
"""Convert recorded audio to text."""
try:
# Load the audio file
with sr.AudioFile(audio_path) as source:
audio_data = self.recognizer.record(source)
# Recognize the text
texto_falado = self.recognizer.recognize_google(audio_data, language=self.idioma)
return texto_falado
except sr.UnknownValueError:
return "Erro: Não foi possível entender o áudio."
except sr.RequestError as e:
return f"Erro: Problema na requisição do serviço de reconhecimento. {e}"
except Exception as e:
return f"Erro: {e}"
def avaliar_pronuncia(self, frase_original, frase_falada):
"""
Evaluate the similarity between the original phrase and the spoken phrase.
Returns:
tuple: (similarity percentage, incorrect words)
"""
# Normalize phrases (remove punctuation and convert to lowercase)
import re
normalizar = lambda texto: re.sub(r'[^\w\s]', '', texto.lower())
original_norm = normalizar(frase_original)
falada_norm = normalizar(frase_falada)
# Calculate similarity
sequencia = difflib.SequenceMatcher(None, original_norm, falada_norm)
similaridade = sequencia.ratio() * 100
# Identify incorrect words
palavras_originais = original_norm.split()
palavras_faladas = falada_norm.split()
palavras_incorretas = []
for palavra in palavras_originais:
if palavra not in palavras_faladas:
palavras_incorretas.append(palavra)
return similaridade, palavras_incorretas
def obter_feedback(self, similaridade, palavras_incorretas, frase_original, frase_falada):
"""Generate detailed feedback on pronunciation."""
# Determine language for feedback based on idioma
feedback_lang = self.idioma
# Choose appropriate language for the feedback prompt
if feedback_lang.startswith('pt'):
prompt_template = """
Analise a pronúncia do usuário e forneça feedback específico:
Frase original: "{frase_original}"
Frase falada: "{frase_falada}"
Similaridade: {similaridade:.2f}%
Palavras possivelmente problemáticas: {palavras_prob}
Ofereça dicas específicas para melhorar a pronúncia, focando nos erros mais comuns.
Seja breve e construtivo, máximo de 3 linhas.
"""
palavras_prob = ', '.join(palavras_incorretas) if palavras_incorretas else 'Nenhuma'
elif feedback_lang.startswith('en'):
prompt_template = """
Analyze the user's pronunciation and provide specific feedback:
Original phrase: "{frase_original}"
Spoken phrase: "{frase_falada}"
Similarity: {similaridade:.2f}%
Potentially problematic words: {palavras_prob}
Offer specific tips to improve pronunciation, focusing on the most common errors.
Be brief and constructive, maximum of 3 lines.
"""
palavras_prob = ', '.join(palavras_incorretas) if palavras_incorretas else 'None'
else:
# Default to English for other languages
prompt_template = """
Analyze the user's pronunciation and provide specific feedback:
Original phrase: "{frase_original}"
Spoken phrase: "{frase_falada}"
Similarity: {similaridade:.2f}%
Potentially problematic words: {palavras_prob}
Offer specific tips to improve pronunciation, focusing on the most common errors.
Be brief and constructive, maximum of 3 lines.
"""
palavras_prob = ', '.join(palavras_incorretas) if palavras_incorretas else 'None'
prompt = prompt_template.format(
frase_original=frase_original,
frase_falada=frase_falada,
similaridade=similaridade,
palavras_prob=palavras_prob
)
try:
resposta = self.chat.invoke([{"role": "system", "content": prompt}])
return resposta.content.strip()
except Exception as e:
# Default feedback in case of error, based on language
if feedback_lang.startswith('pt'):
if similaridade > 90:
return "Excelente pronúncia! Continue praticando."
elif similaridade > 70:
return f"Boa pronúncia, mas pode melhorar. Preste atenção em: {', '.join(palavras_incorretas)}"
else:
return "Tente novamente, focando na pronúncia clara de cada palavra."
else:
if similaridade > 90:
return "Excellent pronunciation! Keep practicing."
elif similaridade > 70:
return f"Good pronunciation, but can be improved. Pay attention to: {', '.join(palavras_incorretas)}"
else:
return "Try again, focusing on clear pronunciation of each word."
# Function to map language dropdown to language code
def get_language_code(language_name):
idiomas = {
"Português (Brasil)": "pt-BR",
"Inglês (EUA)": "en-US",
"Espanhol": "es-ES",
"Francês": "fr-FR",
"Italiano": "it-IT",
"Alemão": "de-DE"
}
return idiomas.get(language_name, "pt-BR")
# Create a global instance of the app
app_instance = None
# Track the current language
current_language_code = "pt-BR"
# Functions for Gradio interface
def gerar_nova_frase(language_name):
global app_instance, current_language_code
language_code = get_language_code(language_name)
# Only create a new instance if the language has changed
if app_instance is None or current_language_code != language_code:
app_instance = PronunciaPratica(idioma=language_code)
current_language_code = language_code
return app_instance.gerar_frase()
def process_audio(audio_path, frase_atual, language_name, historico):
global app_instance, current_language_code
if audio_path is None:
return "Nenhum áudio gravado", "", 0, "", historico, ""
# Make sure we have an app instance with the current language
language_code = get_language_code(language_name)
if app_instance is None or current_language_code != language_code:
app_instance = PronunciaPratica(idioma=language_code)
current_language_code = language_code
# Recognize the speech
texto_falado = app_instance.reconhecer_audio(audio_path)
if texto_falado.startswith("Erro"):
return texto_falado, "", 0, "", historico, ""
# Evaluate pronunciation
similaridade, palavras_incorretas = app_instance.avaliar_pronuncia(frase_atual, texto_falado)
# Get detailed feedback
feedback = app_instance.obter_feedback(similaridade, palavras_incorretas, frase_atual, texto_falado)
# Add to history
entry = {
"frase": frase_atual,
"falado": texto_falado,
"similaridade": f"{similaridade:.1f}%",
"feedback": feedback,
"timestamp": time.strftime("%H:%M:%S")
}
historico = [entry] + historico
if len(historico) > 5:
historico = historico[:5]
# Modificação aqui para melhorar a visibilidade do histórico
history_html = ""
for entry in historico:
history_html += f"""
<div class="history-entry">
<b>{entry['timestamp']}</b> - Precisão: {entry['similaridade']}<br>
<b>Original:</b> "{entry['frase']}"<br>
<b>Sua fala:</b> "{entry['falado']}"<br>
<b>Feedback:</b> {entry['feedback']}
</div>
"""
return texto_falado, f"{similaridade:.1f}%", similaridade, feedback, historico, history_html
def initialize_app_and_get_phrase(language_name="Português (Brasil)"):
"""Initialize the app with a specific language and get first phrase"""
global app_instance, current_language_code
language_code = get_language_code(language_name)
app_instance = PronunciaPratica(idioma=language_code)
current_language_code = language_code
return app_instance.gerar_frase()
def main():
# Get initial phrase (will also initialize app_instance)
initial_phrase = initialize_app_and_get_phrase()
# CSS for styling
css = """
.gradio-container {
font-family: 'Helvetica Neue', Arial, sans-serif;
}
.phrase-box {
background-color: #828181;
padding: 20px;
border-radius: 10px;
border-left: 5px solid #1e3d59;
font-size: 22px;
margin: 20px 0;
}
.spoken-box {
background-color: #e6f7ff;
padding: 15px;
border-radius: 10px;
border-left: 5px solid #0074cc;
}
.score-display {
font-size: 36px;
font-weight: bold;
text-align: center;
}
.feedback-box {
background-color: #e8f4ea;
padding: 15px;
border-radius: 10px;
border-left: 5px solid #4CAF50;
}
.history-entry {
margin-bottom: 15px;
padding: 10px;
border-left: 3px solid #4CAF50;
background-color: #2a2a2a;
color: white;
border-radius: 8px;
}
"""
with gr.Blocks(css=css) as demo:
gr.Markdown("# 🎤 Pronúncia Prática")
gr.Markdown("### Melhore sua pronúncia com feedback em tempo real")
with gr.Row():
with gr.Column(scale=2):
idioma_dropdown = gr.Dropdown(
choices=["Português (Brasil)", "Inglês (EUA)", "Espanhol", "Francês", "Italiano", "Alemão"],
value="Português (Brasil)",
label="Selecione o idioma para praticar:"
)
with gr.Column(scale=1):
gerar_frase_btn = gr.Button("🔄 Gerar nova frase")
frase_atual = gr.Textbox(
value=initial_phrase,
label="Frase para praticar:",
elem_classes=["phrase-box"]
)
with gr.Row():
audio_input = gr.Audio(
sources=["microphone"],
type="filepath",
label="🎙️ Grave sua voz"
)
with gr.Row():
texto_reconhecido = gr.Textbox(label="Você disse:")
with gr.Row():
score_text = gr.Textbox(label="Pontuação:")
score_progress = gr.Slider(minimum=0, maximum=100, label="", interactive=False)
feedback_box = gr.Textbox(label="Feedback:")
# Hidden state for history
historico_state = gr.State([])
with gr.Accordion("📜 Histórico de Prática", open=False):
history_display = gr.HTML()
# Event handlers
gerar_frase_btn.click(
fn=gerar_nova_frase,
inputs=[idioma_dropdown],
outputs=[frase_atual]
)
idioma_dropdown.change(
fn=gerar_nova_frase,
inputs=[idioma_dropdown],
outputs=[frase_atual]
)
audio_input.change(
fn=process_audio,
inputs=[audio_input, frase_atual, idioma_dropdown, historico_state],
outputs=[texto_reconhecido, score_text, score_progress, feedback_box, historico_state, history_display]
)
# Instructions in the footer
gr.Markdown("""
### 📝 Como usar:
1. Selecione o idioma que deseja praticar
2. Clique em 'Gerar nova frase' para mudar a frase (opcional)
3. Leia a frase em voz alta
4. Clique no botão de microfone e fale a frase
5. Veja o feedback e sua pontuação
---
Desenvolvido com ❤️ usando Gradio e IA
""")
# Launch the app with share=True to create a shareable link
demo.launch(share=True, server_name="0.0.0.0")
if __name__ == "__main__":
main()