import gradio as gr import moviepy.editor as mp from pydub import AudioSegment from pydub.silence import detect_nonsilent import tempfile import os import subprocess from concurrent.futures import ThreadPoolExecutor import shutil from pathlib import Path def extract_audio_ffmpeg(video_path, output_path): """Extrai áudio usando FFmpeg diretamente para maior velocidade""" command = [ 'ffmpeg', '-i', video_path, '-vn', # Pula o vídeo '-acodec', 'pcm_s16le', # Formato de áudio '-ar', '44100', # Sample rate '-ac', '2', # Canais '-y', # Sobrescreve arquivo se existir output_path ] subprocess.run(command, stderr=subprocess.PIPE) def cut_video_ffmpeg(input_path, output_path, start, end): """Corta vídeo usando FFmpeg diretamente""" command = [ 'ffmpeg', '-i', input_path, '-ss', str(start), '-t', str(end - start), '-c', 'copy', # Usa codec copying para maior velocidade '-avoid_negative_ts', '1', '-y', output_path ] subprocess.run(command, stderr=subprocess.PIPE) def process_video_chunk(args): """Processa um chunk do vídeo""" input_path, output_path, start, end = args cut_video_ffmpeg(input_path, output_path, start, end) return output_path def concatenate_videos_ffmpeg(video_list, output_path): """Concatena vídeos usando FFmpeg""" list_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt') for video in video_list: list_file.write(f"file '{video}'\n") list_file.close() command = [ 'ffmpeg', '-f', 'concat', '-safe', '0', '-i', list_file.name, '-c', 'copy', '-y', output_path ] subprocess.run(command, stderr=subprocess.PIPE) os.unlink(list_file.name) def process_video(video_path, min_silence_len=1000, silence_thresh=-40, max_workers=4): """Remove segmentos silenciosos do vídeo com processamento otimizado.""" if not os.path.exists(video_path): raise ValueError("Arquivo de vídeo não encontrado") temp_dir = tempfile.mkdtemp() try: temp_audio = os.path.join(temp_dir, "temp_audio.wav") extract_audio_ffmpeg(video_path, temp_audio) audio = AudioSegment.from_wav(temp_audio) nonsilent_ranges = detect_nonsilent( audio, min_silence_len=min_silence_len, silence_thresh=silence_thresh ) if not nonsilent_ranges: return video_path nonsilent_ranges_sec = [(start/1000.0, end/1000.0) for start, end in nonsilent_ranges] chunk_args = [] chunk_outputs = [] for i, (start, end) in enumerate(nonsilent_ranges_sec): output_chunk = os.path.join(temp_dir, f"chunk_{i}.mp4") chunk_args.append((video_path, output_chunk, start, end)) chunk_outputs.append(output_chunk) with ThreadPoolExecutor(max_workers=max_workers) as executor: list(executor.map(process_video_chunk, chunk_args)) output_path = os.path.join(temp_dir, "processed_video.mp4") concatenate_videos_ffmpeg(chunk_outputs, output_path) final_output = str(Path(video_path).parent / f"processed_{Path(video_path).name}") shutil.copy2(output_path, final_output) return final_output except Exception as e: raise Exception(f"Erro ao processar vídeo: {str(e)}") finally: shutil.rmtree(temp_dir) def remove_silence(video_input, silence_duration, silence_threshold): """Função para remoção normal de silêncio""" try: if video_input is None: raise ValueError("Por favor, faça upload de um vídeo") processed_video = process_video( video_input, min_silence_len=int(silence_duration * 1000), silence_thresh=silence_threshold ) return processed_video except Exception as e: gr.Error(str(e)) return None def remove_max_silence(video_input): """Função para remoção máxima de silêncio""" try: if video_input is None: raise ValueError("Por favor, faça upload de um vídeo") # Configurações mais agressivas para detectar todo silêncio processed_video = process_video( video_input, min_silence_len=100, # Detecta silêncios de 0.1 segundos silence_thresh=-30 # Limite mais alto para detectar mais silêncio ) return processed_video except Exception as e: gr.Error(str(e)) return None # Interface Gradio with gr.Blocks(title="Removedor de Silêncio de Vídeos") as app: gr.Markdown("# Removedor de Silêncio de Vídeos") with gr.Row(): with gr.Column(): video_input = gr.Video( label="Selecione ou Arraste o Vídeo", format="mp4" ) with gr.Row(): # Botão para remoção máxima de silêncio remove_max_btn = gr.Button("🔇 Remover 100% do Silêncio", variant="primary") # Botão para remoção personalizada remove_custom_btn = gr.Button("Remover Silêncio Personalizado") with gr.Group(): gr.Markdown("### Configurações Personalizadas") silence_duration = gr.Slider( minimum=0.1, maximum=5.0, value=1.0, step=0.1, label="Duração Mínima do Silêncio (segundos)" ) silence_threshold = gr.Slider( minimum=-60, maximum=-20, value=-40, step=1, label="Limite de Silêncio (dB)" ) with gr.Row(): video_output = gr.Video(label="Vídeo Processado") # Event handlers remove_max_btn.click( fn=remove_max_silence, inputs=[video_input], outputs=[video_output] ) remove_custom_btn.click( fn=remove_silence, inputs=[ video_input, silence_duration, silence_threshold ], outputs=[video_output] ) if __name__ == "__main__": app.launch(show_error=True)