Spaces:
Running
Running
| # utils.py | |
| import os | |
| import subprocess | |
| from pathlib import Path | |
| from pydub import AudioSegment | |
| from pydub.silence import split_on_silence | |
| import pysrt | |
| from tqdm import tqdm | |
| import asyncio | |
| def remove_silence(input_file, output_file): | |
| """Lê um arquivo MP3, remove o silêncio e salva como MP3 com alta qualidade, mantendo pequenas pausas.""" | |
| audio = AudioSegment.from_mp3(input_file) | |
| segments = split_on_silence( | |
| audio, | |
| min_silence_len=500, | |
| silence_thresh=-40, | |
| keep_silence=250 | |
| ) | |
| non_silent_audio = AudioSegment.silent(duration=0) | |
| for segment in segments: | |
| non_silent_audio += segment | |
| non_silent_audio.export(output_file, format="mp3", bitrate="192k") | |
| def timetoms(time_obj): | |
| """Converte um objeto de tempo do Pysrt para milissegundos.""" | |
| return time_obj.hours * 3600000 + time_obj.minutes * 60000 + time_obj.seconds * 1000 + time_obj.milliseconds | |
| # --- VERSÃO COMPLETAMENTE NOVA E ROBUSTA --- | |
| async def adjust_audio_speed(input_file, output_file, target_duration_ms): | |
| """Ajusta a velocidade do áudio usando o filtro 'atempo' do FFmpeg para máxima qualidade.""" | |
| # Usa ffprobe para obter a duração exata, é mais confiável que pydub | |
| try: | |
| probe_cmd = [ | |
| "ffprobe", "-v", "error", "-show_entries", "format=duration", | |
| "-of", "default=noprint_wrappers=1:nokey=1", input_file | |
| ] | |
| result = subprocess.run(probe_cmd, capture_output=True, text=True, check=True) | |
| original_duration_ms = float(result.stdout.strip()) * 1000 | |
| except (subprocess.CalledProcessError, FileNotFoundError): | |
| # Fallback para pydub se ffprobe não estiver disponível ou falhar | |
| original_duration_ms = len(AudioSegment.from_mp3(input_file)) | |
| if original_duration_ms == 0 or target_duration_ms <= 0: | |
| silent_audio = AudioSegment.silent(duration=target_duration_ms) | |
| silent_audio.export(output_file, format="mp3", bitrate="192k") | |
| return silent_audio | |
| speed_factor = original_duration_ms / target_duration_ms | |
| # Se a velocidade já for quase perfeita, apenas renomeia para evitar re-compressão | |
| if 0.99 < speed_factor < 1.01: | |
| Path(input_file).rename(output_file) | |
| return AudioSegment.from_mp3(output_file) | |
| # Constrói a cadeia de filtros 'atempo' | |
| atempo_filters = [] | |
| current_factor = speed_factor | |
| # Para aceleração > 2.0x | |
| while current_factor > 2.0: | |
| atempo_filters.append("atempo=2.0") | |
| current_factor /= 2.0 | |
| # Para desaceleração < 0.5x | |
| while current_factor < 0.5: | |
| atempo_filters.append("atempo=0.5") | |
| current_factor /= 0.5 | |
| # Adiciona o fator final (que agora está entre 0.5 e 2.0) | |
| if current_factor != 1.0: | |
| atempo_filters.append(f"atempo={current_factor:.5f}") | |
| filter_string = ",".join(atempo_filters) | |
| # Executa o comando FFmpeg | |
| ffmpeg_cmd = [ | |
| "ffmpeg", "-y", "-i", input_file, "-filter:a", filter_string, | |
| "-b:a", "192k", "-ar", "44100", # Define bitrate e sample rate de alta qualidade | |
| "-hide_banner", "-loglevel", "error", output_file | |
| ] | |
| try: | |
| # Roda o subprocesso bloqueante em uma thread separada para não congelar a UI | |
| proc = await asyncio.create_subprocess_exec( | |
| *ffmpeg_cmd, | |
| stdout=asyncio.subprocess.PIPE, | |
| stderr=asyncio.subprocess.PIPE | |
| ) | |
| stdout, stderr = await proc.communicate() | |
| if proc.returncode != 0: | |
| print(f"Erro no FFmpeg ao ajustar a velocidade: {stderr.decode()}") | |
| # Em caso de erro, cria silêncio para não quebrar o processo | |
| silent = AudioSegment.silent(duration=target_duration_ms) | |
| silent.export(output_file, format="mp3") | |
| except FileNotFoundError: | |
| print("ERRO: FFmpeg não encontrado. Verifique se ele está instalado e no PATH do sistema.") | |
| raise | |
| return AudioSegment.from_mp3(output_file) | |
| async def merge_audio_files(output_folder, srt_file_path): | |
| """Mescla segmentos de áudio baseados nos tempos de um arquivo SRT com sincronização correta.""" | |
| subs = pysrt.open(srt_file_path) | |
| final_audio = AudioSegment.silent(duration=0) | |
| base_name = Path(srt_file_path).stem | |
| with tqdm(total=len(subs), desc=f"Mesclando áudios para {base_name}", unit="segmento") as pbar: | |
| for sub in subs: | |
| start_time_ms = timetoms(sub.start) | |
| end_time_ms = timetoms(sub.end) | |
| audio_file = Path(output_folder) / f"{sub.index:02d}.mp3" | |
| silence_duration = start_time_ms - len(final_audio) | |
| if silence_duration > 5: # Adiciona uma pequena margem para evitar micro-silêncios | |
| final_audio += AudioSegment.silent(duration=silence_duration) | |
| if audio_file.exists() and audio_file.stat().st_size > 0: | |
| audio_segment = AudioSegment.from_mp3(str(audio_file)) | |
| final_audio += audio_segment | |
| else: | |
| segment_duration = end_time_ms - start_time_ms | |
| final_audio += AudioSegment.silent(duration=max(0, segment_duration)) | |
| pbar.update(1) | |
| srt_output_dir = Path("output/srt_output") | |
| srt_output_dir.mkdir(parents=True, exist_ok=True) | |
| output_file_path = srt_output_dir / f"{base_name}_final.mp3" | |
| final_audio.export(str(output_file_path), format="mp3", bitrate="192k") | |
| print(f"\nÁudio final salvo em: {output_file_path}\n") | |
| return str(output_file_path) | |
| def listar_audios(): | |
| """Lista os arquivos de áudio na pasta de saída do SRT.""" | |
| try: | |
| srt_output_dir = "output/srt_output" | |
| if not os.path.exists(srt_output_dir): | |
| os.makedirs(srt_output_dir, exist_ok=True) | |
| return ["Nenhum áudio gerado ainda"] | |
| arquivos = [f for f in os.listdir(srt_output_dir) if f.endswith(('.mp3', '.wav'))] | |
| return arquivos if arquivos else ["Nenhum áudio gerado ainda"] | |
| except Exception as e: | |
| print(f"Erro ao listar áudios: {e}") | |
| return ["Erro ao listar arquivos"] | |
| def tocar_audio(arquivo): | |
| """Retorna o caminho completo para um arquivo de áudio selecionado para tocar.""" | |
| if arquivo and arquivo != "Nenhum áudio gerado ainda": | |
| return f"output/srt_output/{arquivo}" | |
| return None |