File size: 6,383 Bytes
ab496c0
6b4eada
ab496c0
 
d83a342
6b4eada
 
ab496c0
6b4eada
 
 
d83a342
 
6b4eada
 
d83a342
6b4eada
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d83a342
6b4eada
 
d83a342
6b4eada
 
 
 
d83a342
6b4eada
 
 
d83a342
6b4eada
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d83a342
6b4eada
 
 
 
 
 
 
 
 
 
 
ab496c0
6b4eada
 
 
 
d83a342
6b4eada
 
 
 
 
 
 
d83a342
 
 
ab496c0
6b4eada
 
ab496c0
1f4e0e1
12d32cd
ab496c0
1f4e0e1
 
6b4eada
 
1f4e0e1
6b4eada
 
1f4e0e1
12d32cd
 
 
 
 
 
 
 
 
6b4eada
 
12d32cd
6b4eada
 
12d32cd
ab496c0
a79fe10
 
ab496c0
12d32cd
ab496c0
 
 
 
 
d83a342
8a80570
ab496c0
12d32cd
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1f4e0e1
 
12d32cd
ab496c0
1f4e0e1
12d32cd
 
1f4e0e1
12d32cd
1f4e0e1
 
12d32cd
ab496c0
1f4e0e1
 
 
 
 
12d32cd
ab496c0
 
 
1f4e0e1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
import gradio as gr
import subprocess
import tempfile
import os
from pathlib import Path
import json
import shutil

def detect_silence_ffmpeg(video_path, silence_thresh=-40, min_silence_len=1):
    """Detecta silêncio usando FFmpeg diretamente, muito mais rápido que pydub"""
    
    command = [
        'ffmpeg', '-i', video_path,
        '-af', f'silencedetect=noise={silence_thresh}dB:d={min_silence_len}',
        '-f', 'null', '-'
    ]
    
    # Executa FFmpeg e captura a saída de erro (onde está a informação do silêncio)
    result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
    
    # Processa a saída para encontrar timestamps
    silence_data = []
    start_times = []
    end_times = []
    
    for line in result.stderr.split('\n'):
        if 'silence_start' in line:
            start_time = float(line.split('silence_start: ')[1].split()[0])
            start_times.append(start_time)
        elif 'silence_end' in line:
            end_time = float(line.split('silence_end: ')[1].split()[0])
            end_times.append(end_time)
    
    # Cria lista de intervalos não silenciosos
    if not start_times:
        return []
        
    nonsilent_ranges = []
    video_duration = float(get_video_duration(video_path))
    
    # Adiciona segmento do início até o primeiro silêncio
    if start_times[0] > 0:
        nonsilent_ranges.append((0, start_times[0]))
    
    # Adiciona segmentos entre silêncios
    for i in range(len(end_times)):
        if i < len(start_times):
            nonsilent_ranges.append((end_times[i], start_times[i]))
    
    # Adiciona segmento final se necessário
    if end_times and end_times[-1] < video_duration:
        nonsilent_ranges.append((end_times[-1], video_duration))
    
    return nonsilent_ranges

def get_video_duration(video_path):
    """Obtém a duração do vídeo usando FFmpeg"""
    command = [
        'ffprobe', '-v', 'error',
        '-show_entries', 'format=duration',
        '-of', 'json',
        video_path
    ]
    result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    data = json.loads(result.stdout)
    return float(data['format']['duration'])

def create_filter_complex(ranges):
    """Cria o filtro complexo para FFmpeg baseado nos intervalos não silenciosos"""
    parts = []
    for i, (start, end) in enumerate(ranges):
        parts.append(f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS[v{i}]; "
                    f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[a{i}]")
    
    # Concatena os vídeos
    v_list = ''.join(f'[v{i}]' for i in range(len(ranges)))
    a_list = ''.join(f'[a{i}]' for i in range(len(ranges)))
    
    concat = f"; {v_list}concat=n={len(ranges)}:v=1:a=0[vout]; "
    concat += f"{a_list}concat=n={len(ranges)}:v=0:a=1[aout]"
    
    return ''.join(parts) + concat

def process_video_fast(video_path, silence_thresh=-40, min_silence_len=1):
    """Processa o vídeo removendo silêncio usando FFmpeg diretamente"""
    
    # Detecta intervalos não silenciosos
    nonsilent_ranges = detect_silence_ffmpeg(video_path, silence_thresh, min_silence_len)
    
    if not nonsilent_ranges:
        return video_path
        
    # Cria arquivo de saída
    output_path = str(Path(video_path).parent / f"processed_{Path(video_path).name}")
    
    # Cria filtro complexo
    filter_complex = create_filter_complex(nonsilent_ranges)
    
    # Processa o vídeo em uma única passagem
    command = [
        'ffmpeg', '-i', video_path,
        '-filter_complex', filter_complex,
        '-map', '[vout]',
        '-map', '[aout]',
        '-c:v', 'libx264',
        '-preset', 'ultrafast',  # Mais rápido encoding
        '-c:a', 'aac',
        '-y',
        output_path
    ]
    
    subprocess.run(command, stderr=subprocess.PIPE)
    return output_path

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")
        
        return process_video_fast(
            video_input,
            silence_thresh=silence_threshold,
            min_silence_len=silence_duration
        )
    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")
        
        return process_video_fast(
            video_input,
            silence_thresh=-30,
            min_silence_len=0.1
        )
    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)