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( """
Faça a transcrição de arquivos de áudio para texto usando reconhecimento de fala