Spaces:
Sleeping
Sleeping
| import os | |
| import subprocess | |
| import shutil | |
| import speech_recognition as sr | |
| import traceback | |
| import wave | |
| import contextlib | |
| import gradio as gr | |
| import tempfile | |
| from pathlib import Path | |
| import asyncio | |
| # ================== Core Functions ================== | |
| def find_ffmpeg(): | |
| """Finds FFmpeg executable in system PATH.""" | |
| ffmpeg_path = shutil.which('ffmpeg') | |
| if ffmpeg_path: | |
| return ffmpeg_path | |
| raise FileNotFoundError("FFmpeg não encontrado. O Space precisa ter FFmpeg instalado.") | |
| def get_audio_duration(wav_path): | |
| """Get the duration of a WAV file in seconds""" | |
| try: | |
| with contextlib.closing(wave.open(wav_path, 'r')) as f: | |
| return f.getnframes() / float(f.getframerate()) | |
| except Exception as e: | |
| raise RuntimeError(f"Erro ao obter duração do áudio: {str(e)}") | |
| def convert_audio(input_path, ffmpeg_path): | |
| """Converts any audio file to WAV format for processing.""" | |
| try: | |
| if not os.path.exists(input_path): | |
| raise FileNotFoundError(f"Arquivo de áudio não encontrado: {input_path}") | |
| output_path = input_path + "_converted.wav" | |
| command = [ | |
| ffmpeg_path, | |
| "-y", "-i", input_path, | |
| "-vn", "-acodec", "pcm_s16le", | |
| "-ar", "16000", "-ac", "1", | |
| "-hide_banner", "-loglevel", "error", | |
| output_path | |
| ] | |
| result = subprocess.run(command, capture_output=True, text=True, timeout=300) | |
| if result.returncode != 0: | |
| raise RuntimeError(f"Falha na conversão: {result.stderr}") | |
| if not os.path.exists(output_path): | |
| raise FileNotFoundError("Arquivo convertido não foi criado") | |
| return output_path | |
| except Exception as e: | |
| raise RuntimeError(f"Erro ao converter áudio: {str(e)}") | |
| def transcribe_chunk(chunk_path, language, recognizer, attempt=0): | |
| """Transcribes a single audio chunk with retry logic""" | |
| try: | |
| with sr.AudioFile(chunk_path) as source: | |
| audio_data = recognizer.record(source) | |
| return recognizer.recognize_google(audio_data, language=language) | |
| except sr.UnknownValueError: | |
| if attempt < 2: | |
| return transcribe_chunk(chunk_path, language, recognizer, attempt + 1) | |
| return "[Áudio não compreendido]" | |
| except sr.RequestError as e: | |
| if attempt < 2: | |
| return transcribe_chunk(chunk_path, language, recognizer, attempt + 1) | |
| return f"[Erro na API: {str(e)}]" | |
| def transcribe_audio(audio_path, language='pt-BR', progress_callback=None): | |
| """Transcribes audio file with progress updates""" | |
| recognizer = sr.Recognizer() | |
| recognizer.energy_threshold = 300 | |
| recognizer.dynamic_energy_threshold = True | |
| temp_chunks = [] | |
| full_transcript = [] | |
| try: | |
| duration = get_audio_duration(audio_path) | |
| chunk_size = 30 # seconds | |
| total_chunks = int(duration // chunk_size) + 1 | |
| for i in range(total_chunks): | |
| chunk_path = f"temp_chunk_{i}.wav" | |
| temp_chunks.append(chunk_path) | |
| # Extract chunk using ffmpeg | |
| result = subprocess.run([ | |
| "ffmpeg", "-y", | |
| "-ss", str(i * chunk_size), | |
| "-t", str(chunk_size), | |
| "-i", audio_path, | |
| "-ac", "1", "-ar", "16000", | |
| "-loglevel", "error", chunk_path | |
| ], capture_output=True, text=True) | |
| if result.returncode != 0: | |
| raise RuntimeError(f"Erro ao extrair chunk: {result.stderr}") | |
| # Transcribe chunk | |
| transcript = transcribe_chunk(chunk_path, language, recognizer) | |
| full_transcript.append(transcript) | |
| # Update progress | |
| if progress_callback: | |
| progress = (i + 1) / total_chunks | |
| progress_callback(progress, f"Transcrevendo parte {i+1}/{total_chunks}") | |
| return " ".join(full_transcript) | |
| except Exception as e: | |
| raise RuntimeError(f"Erro durante transcrição: {str(e)}") | |
| finally: | |
| # Clean up temp files | |
| for chunk in temp_chunks: | |
| if os.path.exists(chunk): | |
| try: | |
| os.remove(chunk) | |
| except: | |
| pass | |
| async def process_audio(audio_file, language, progress=gr.Progress()): | |
| """Main processing function for Gradio interface""" | |
| try: | |
| # Validate inputs | |
| if audio_file is None: | |
| return "Por favor, selecione um arquivo de áudio.", None | |
| progress(0, desc="🔍 Verificando FFmpeg...") | |
| # Find FFmpeg | |
| try: | |
| ffmpeg_path = find_ffmpeg() | |
| except FileNotFoundError as e: | |
| return f"❌ Erro: {str(e)}", None | |
| # Create temporary directory for processing | |
| with tempfile.TemporaryDirectory() as temp_dir: | |
| temp_audio_path = os.path.join(temp_dir, "audio_input") | |
| shutil.copy(audio_file, temp_audio_path) | |
| progress(0.1, desc="🔄 Convertendo áudio...") | |
| # Convert if needed | |
| audio_ext = Path(audio_file).suffix.lower() | |
| if audio_ext not in ['.wav']: | |
| progress(0.2, desc="🎵 Convertendo para WAV...") | |
| converted_path = convert_audio(temp_audio_path, ffmpeg_path) | |
| audio_to_transcribe = converted_path | |
| else: | |
| audio_to_transcribe = temp_audio_path | |
| progress(0.3, desc="📝 Preparando transcrição...") | |
| # Transcribe with progress updates | |
| def update_progress(pct, msg): | |
| progress(0.3 + (pct * 0.7), desc=msg) | |
| transcript = transcribe_audio( | |
| audio_to_transcribe, | |
| language, | |
| progress_callback=update_progress | |
| ) | |
| progress(1.0, desc="✅ Transcrição concluída!") | |
| # Prepare output filename | |
| original_name = Path(audio_file).stem | |
| output_filename = f"{original_name}_transcricao.txt" | |
| # Save to temporary file for download | |
| temp_output = tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False, encoding='utf-8') | |
| temp_output.write(transcript) | |
| temp_output.close() | |
| # Return results | |
| return transcript, temp_output.name | |
| except Exception as e: | |
| error_msg = f"❌ Erro durante o processamento: {str(e)}" | |
| print(traceback.format_exc()) | |
| return error_msg, None | |
| # ================== Gradio Interface ================== | |
| def create_interface(): | |
| """Creates the Gradio interface""" | |
| with gr.Blocks( | |
| title="Transcrição de Áudio", | |
| theme=gr.themes.Soft(), | |
| css=""" | |
| .gradio-container { | |
| max-width: 900px !important; | |
| } | |
| .success-box { | |
| border: 1px solid #4CAF50; | |
| background-color: #E8F5E8; | |
| padding: 10px; | |
| border-radius: 5px; | |
| margin: 10px 0; | |
| } | |
| .warning { | |
| color: #ff9800; | |
| font-weight: bold; | |
| } | |
| .header { | |
| text-align: center; | |
| margin-bottom: 20px; | |
| } | |
| """ | |
| ) as interface: | |
| gr.Markdown( | |
| """ | |
| <div class="header"> | |
| <h1>🎙️ Transcrição de Áudio</h1> | |
| <p>Faça a transcrição de arquivos de áudio para texto usando reconhecimento de fala</p> | |
| </div> | |
| """ | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| # Input section | |
| with gr.Group(): | |
| gr.Markdown("### 📁 Entrada de Áudio") | |
| audio_input = gr.Audio( | |
| label="Selecione o arquivo de áudio", | |
| type="filepath", | |
| sources=["upload"], | |
| interactive=True | |
| ) | |
| with gr.Group(): | |
| gr.Markdown("### 🌐 Configurações") | |
| language_radio = gr.Radio( | |
| choices=[ | |
| ("Português (Brasil)", "pt-BR"), | |
| ("Inglês (EUA)", "en-US"), | |
| ("Espanhol", "es-ES"), | |
| ("Francês", "fr-FR"), | |
| ("Alemão", "de-DE") | |
| ], | |
| value="pt-BR", | |
| label="Idioma do Áudio", | |
| info="Selecione o idioma falado no áudio" | |
| ) | |
| transcribe_btn = gr.Button( | |
| "🎯 Transcrever Áudio", | |
| variant="primary", | |
| size="lg", | |
| scale=1 | |
| ) | |
| with gr.Column(scale=2): | |
| # Output section | |
| with gr.Group(): | |
| gr.Markdown("### 📝 Resultado da Transcrição") | |
| transcript_output = gr.Textbox( | |
| label="Texto Transcrito", | |
| placeholder="A transcrição aparecerá aqui...", | |
| lines=12, | |
| max_lines=20, | |
| show_copy_button=True, | |
| autoscroll=True | |
| ) | |
| with gr.Group(): | |
| gr.Markdown("### 💾 Download") | |
| download_output = gr.File( | |
| label="Baixar Transcrição", | |
| file_count="single", | |
| height=100 | |
| ) | |
| # Examples section | |
| with gr.Accordion("📚 Exemplos de Uso", open=False): | |
| gr.Markdown(""" | |
| **Formatos suportados:** MP3, WAV, M4A, FLAC, AAC, OGG, WMA | |
| **Dicas para melhor qualidade:** | |
| - Use áudios com boa qualidade e pouco ruído de fundo | |
| - Arquivos com voz clara e pausadas têm melhor reconhecimento | |
| - Para áudios longos, o sistema divide automaticamente em partes | |
| """) | |
| # Processing info | |
| with gr.Accordion("ℹ️ Informações Técnicas", open=False): | |
| gr.Markdown(""" | |
| **Como funciona:** | |
| 1. O áudio é convertido para formato WAV (se necessário) | |
| 2. Arquivos longos são divididos em partes de 30 segundos | |
| 3. Cada parte é transcrita usando Google Speech Recognition | |
| 4. As transcrições são combinadas no resultado final | |
| **Tecnologias utilizadas:** | |
| - Gradio 5.49.1 para interface web | |
| - SpeechRecognition para reconhecimento de fala | |
| - FFmpeg para processamento de áudio | |
| - Google Speech Recognition API | |
| **Limitações:** | |
| - Requer conexão com internet | |
| - Qualidade depende da clareza do áudio | |
| - Limitações da API gratuita do Google | |
| - Timeout para arquivos muito longos | |
| """) | |
| # Event handlers | |
| transcribe_btn.click( | |
| fn=process_audio, | |
| inputs=[audio_input, language_radio], | |
| outputs=[transcript_output, download_output], | |
| show_progress="full", | |
| api_name="transcribe" | |
| ) | |
| # Clear outputs when new audio is selected | |
| def clear_outputs(): | |
| return "", None | |
| audio_input.change( | |
| fn=clear_outputs, | |
| outputs=[transcript_output, download_output] | |
| ) | |
| return interface | |
| # ================== Main Execution ================== | |
| def main(): | |
| """Main function to launch the Gradio interface""" | |
| print("🚀 Iniciando aplicação de transcrição de áudio...") | |
| print(f"📦 Versão do Gradio: {gr.__version__}") | |
| try: | |
| # Test FFmpeg availability | |
| ffmpeg_path = find_ffmpeg() | |
| print(f"✅ FFmpeg encontrado em: {ffmpeg_path}") | |
| # Test speech recognition | |
| recognizer = sr.Recognizer() | |
| print("✅ SpeechRecognition configurado") | |
| except Exception as e: | |
| print(f"⚠️ Aviso na inicialização: {str(e)}") | |
| # Launch interface | |
| interface = create_interface() | |
| interface.launch( | |
| server_name="0.0.0.0", | |
| server_port=7860, | |
| share=False, | |
| show_error=True, | |
| favicon_path=None, | |
| inbrowser=False | |
| ) | |
| if __name__ == "__main__": | |
| main() |