Spaces:
Sleeping
Sleeping
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:v', 'libx264', # Codec de vídeo | |
'-c:a', 'aac', # Codec de áudio | |
'-strict', 'experimental', | |
'-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""" | |
# Cria arquivo de lista | |
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:v', 'libx264', # Codec de vídeo | |
'-c:a', 'aac', # Codec de áudio | |
'-strict', 'experimental', | |
'-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: | |
# Extrair áudio para análise | |
temp_audio = os.path.join(temp_dir, "temp_audio.wav") | |
extract_audio_ffmpeg(video_path, temp_audio) | |
# Analisar áudio para detectar silêncio | |
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 | |
# Converter para segundos | |
nonsilent_ranges_sec = [(start/1000.0, end/1000.0) for start, end in nonsilent_ranges] | |
# Preparar chunks de vídeo | |
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) | |
# Processar chunks em paralelo | |
with ThreadPoolExecutor(max_workers=max_workers) as executor: | |
list(executor.map(process_video_chunk, chunk_args)) | |
# Concatenar chunks | |
output_path = os.path.join(temp_dir, "processed_video.mp4") | |
concatenate_videos_ffmpeg(chunk_outputs, output_path) | |
# Copiar resultado final | |
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" | |
) | |
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) |