DHEIVER's picture
Update app.py
8a80570 verified
raw
history blame
6.94 kB
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)