import gradio as gr from moviepy.editor import VideoFileClip, concatenate_videoclips import numpy as np from scipy.io import wavfile import tempfile import os from pathlib import Path def detect_silence(audio_array, sample_rate, threshold=0.01, min_silence_len=1000): """Detecta períodos de silêncio no áudio""" # Converte o threshold para amplitude amplitude_threshold = threshold * np.max(np.abs(audio_array)) # Calcula a energia do áudio energy = np.abs(audio_array) if len(energy.shape) > 1: energy = np.mean(energy, axis=1) # Encontra regiões não silenciosas is_sound = energy > amplitude_threshold # Converte frames para segundos frame_length = int(sample_rate * (min_silence_len / 1000)) # Suaviza a detecção para evitar cortes muito curtos sound_chunks = [] start = None for i in range(len(is_sound)): if start is None and is_sound[i]: start = i elif start is not None and not is_sound[i]: if i - start > frame_length: sound_chunks.append((start / sample_rate, i / sample_rate)) start = None if start is not None: sound_chunks.append((start / sample_rate, len(is_sound) / sample_rate)) return sound_chunks def process_video(video_path, threshold=0.01, min_silence_len=1000): """Remove silêncio do vídeo""" # Carrega o vídeo video = VideoFileClip(video_path) # Extrai o áudio para análise audio_array = video.audio.to_soundarray() sample_rate = video.audio.fps # Detecta regiões não silenciosas sound_chunks = detect_silence(audio_array, sample_rate, threshold, min_silence_len) if not sound_chunks: video.close() return video_path # Corta e concatena os segmentos não silenciosos clips = [] for start, end in sound_chunks: clip = video.subclip(start, end) clips.append(clip) # Concatena os clips final_clip = concatenate_videoclips(clips) # Salva o resultado output_path = str(Path(video_path).parent / f"processed_{Path(video_path).name}") final_clip.write_videofile(output_path) # Limpa os recursos video.close() final_clip.close() for clip in clips: clip.close() return output_path def remove_silence(video_input, silence_duration, silence_threshold): """Interface para remoção normal de silêncio""" try: if video_input is None: raise ValueError("Por favor, faça upload de um vídeo") # Converte o threshold de dB para amplitude relativa amplitude_threshold = 10 ** (silence_threshold / 20) return process_video( video_input, threshold=amplitude_threshold, min_silence_len=int(silence_duration * 1000) ) except Exception as e: gr.Error(str(e)) return None def remove_max_silence(video_input): """Interface 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 agressivas para máxima remoção return process_video( video_input, threshold=0.05, # Mais sensível ao som min_silence_len=100 # Remove silêncios mais curtos ) 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" ) with gr.Row(): remove_max_btn = gr.Button("🔇 Remover 100% do Silêncio", variant="primary") 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)