Spaces:
Running
Running
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""" | |
# 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() | |
# Concatena usando FFmpeg | |
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. | |
""" | |
# Criar diretório temporário | |
temp_dir = tempfile.mkdtemp() | |
# Extrair áudio | |
temp_audio = os.path.join(temp_dir, "temp_audio.wav") | |
extract_audio_ffmpeg(video_path, temp_audio) | |
# Detectar segmentos não silenciosos | |
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 argumentos para processamento paralelo | |
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 todos os chunks | |
output_path = os.path.join(temp_dir, "processed_video.mp4") | |
concatenate_videos_ffmpeg(chunk_outputs, output_path) | |
# Criar cópia do resultado em local permanente | |
final_output = str(Path(video_path).parent / f"processed_{Path(video_path).name}") | |
shutil.copy2(output_path, final_output) | |
# Limpar arquivos temporários | |
shutil.rmtree(temp_dir) | |
return final_output | |
def remove_silence(video, silence_duration, silence_threshold): | |
if video is None: | |
return None | |
try: | |
with gr.Progress() as progress: | |
progress(0, desc="Iniciando processamento...") | |
processed_video = process_video( | |
video, | |
min_silence_len=int(silence_duration * 1000), | |
silence_thresh=silence_threshold | |
) | |
progress(1, desc="Processamento concluído!") | |
return processed_video | |
except Exception as e: | |
return str(e) | |
# Interface Gradio com indicador de progresso | |
with gr.Blocks(title="Removedor de Silêncio de Vídeos") as app: | |
gr.Markdown("# Removedor de Silêncio de Vídeos") | |
gr.Markdown(""" | |
### Otimizado para processamento rápido | |
Faça upload de um vídeo e ajuste os parâmetros para remover segmentos silenciosos. | |
O processamento é feito em paralelo para maior velocidade. | |
""") | |
with gr.Row(): | |
with gr.Column(): | |
video_input = gr.Video( | |
label="Vídeo de Entrada", | |
type="filepath" # Usar filepath para upload mais rápido | |
) | |
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)" | |
) | |
process_btn = gr.Button("Processar Vídeo") | |
with gr.Column(): | |
video_output = gr.Video(label="Vídeo Processado") | |
process_btn.click( | |
fn=remove_silence, | |
inputs=[video_input, silence_duration, silence_threshold], | |
outputs=video_output | |
) | |
if __name__ == "__main__": | |
app.launch() |