DHEIVER commited on
Commit
6b4eada
·
verified ·
1 Parent(s): 8a80570

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +102 -114
app.py CHANGED
@@ -1,134 +1,128 @@
1
  import gradio as gr
2
- import moviepy.editor as mp
3
- from pydub import AudioSegment
4
- from pydub.silence import detect_nonsilent
5
  import tempfile
6
  import os
7
- import subprocess
8
- from concurrent.futures import ThreadPoolExecutor
9
- import shutil
10
  from pathlib import Path
 
 
11
 
12
- def extract_audio_ffmpeg(video_path, output_path):
13
- """Extrai áudio usando FFmpeg diretamente para maior velocidade"""
 
14
  command = [
15
  'ffmpeg', '-i', video_path,
16
- '-vn', # Pula o vídeo
17
- '-acodec', 'pcm_s16le', # Formato de áudio
18
- '-ar', '44100', # Sample rate
19
- '-ac', '2', # Canais
20
- '-y', # Sobrescreve arquivo se existir
21
- output_path
22
  ]
23
- subprocess.run(command, stderr=subprocess.PIPE)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
24
 
25
- def cut_video_ffmpeg(input_path, output_path, start, end):
26
- """Corta vídeo usando FFmpeg diretamente"""
27
  command = [
28
- 'ffmpeg', '-i', input_path,
29
- '-ss', str(start),
30
- '-t', str(end - start),
31
- '-c:v', 'libx264', # Codec de vídeo
32
- '-c:a', 'aac', # Codec de áudio
33
- '-strict', 'experimental',
34
- '-y',
35
- output_path
36
  ]
37
- subprocess.run(command, stderr=subprocess.PIPE)
 
 
38
 
39
- def process_video_chunk(args):
40
- """Processa um chunk do vídeo"""
41
- input_path, output_path, start, end = args
42
- cut_video_ffmpeg(input_path, output_path, start, end)
43
- return output_path
 
 
 
 
 
 
 
 
 
 
44
 
45
- def concatenate_videos_ffmpeg(video_list, output_path):
46
- """Concatena vídeos usando FFmpeg"""
47
- # Cria arquivo de lista
48
- list_file = tempfile.NamedTemporaryFile(mode='w', delete=False, suffix='.txt')
49
- for video in video_list:
50
- list_file.write(f"file '{video}'\n")
51
- list_file.close()
 
 
 
 
52
 
 
 
 
 
53
  command = [
54
- 'ffmpeg', '-f', 'concat',
55
- '-safe', '0',
56
- '-i', list_file.name,
57
- '-c:v', 'libx264', # Codec de vídeo
58
- '-c:a', 'aac', # Codec de áudio
59
- '-strict', 'experimental',
 
60
  '-y',
61
  output_path
62
  ]
63
- subprocess.run(command, stderr=subprocess.PIPE)
64
- os.unlink(list_file.name)
65
-
66
- def process_video(video_path, min_silence_len=1000, silence_thresh=-40, max_workers=4):
67
- """Remove segmentos silenciosos do vídeo com processamento otimizado."""
68
- if not os.path.exists(video_path):
69
- raise ValueError("Arquivo de vídeo não encontrado")
70
-
71
- temp_dir = tempfile.mkdtemp()
72
 
73
- try:
74
- # Extrair áudio para análise
75
- temp_audio = os.path.join(temp_dir, "temp_audio.wav")
76
- extract_audio_ffmpeg(video_path, temp_audio)
77
-
78
- # Analisar áudio para detectar silêncio
79
- audio = AudioSegment.from_wav(temp_audio)
80
- nonsilent_ranges = detect_nonsilent(
81
- audio,
82
- min_silence_len=min_silence_len,
83
- silence_thresh=silence_thresh
84
- )
85
-
86
- if not nonsilent_ranges:
87
- return video_path
88
-
89
- # Converter para segundos
90
- nonsilent_ranges_sec = [(start/1000.0, end/1000.0) for start, end in nonsilent_ranges]
91
-
92
- # Preparar chunks de vídeo
93
- chunk_args = []
94
- chunk_outputs = []
95
- for i, (start, end) in enumerate(nonsilent_ranges_sec):
96
- output_chunk = os.path.join(temp_dir, f"chunk_{i}.mp4")
97
- chunk_args.append((video_path, output_chunk, start, end))
98
- chunk_outputs.append(output_chunk)
99
-
100
- # Processar chunks em paralelo
101
- with ThreadPoolExecutor(max_workers=max_workers) as executor:
102
- list(executor.map(process_video_chunk, chunk_args))
103
-
104
- # Concatenar chunks
105
- output_path = os.path.join(temp_dir, "processed_video.mp4")
106
- concatenate_videos_ffmpeg(chunk_outputs, output_path)
107
-
108
- # Copiar resultado final
109
- final_output = str(Path(video_path).parent / f"processed_{Path(video_path).name}")
110
- shutil.copy2(output_path, final_output)
111
-
112
- return final_output
113
-
114
- except Exception as e:
115
- raise Exception(f"Erro ao processar vídeo: {str(e)}")
116
- finally:
117
- shutil.rmtree(temp_dir)
118
 
119
  def remove_silence(video_input, silence_duration, silence_threshold):
120
  """Função para remoção normal de silêncio"""
121
  try:
122
  if video_input is None:
123
  raise ValueError("Por favor, faça upload de um vídeo")
124
-
125
- processed_video = process_video(
126
  video_input,
127
- min_silence_len=int(silence_duration * 1000),
128
- silence_thresh=silence_threshold
129
  )
130
-
131
- return processed_video
132
  except Exception as e:
133
  gr.Error(str(e))
134
  return None
@@ -138,15 +132,12 @@ def remove_max_silence(video_input):
138
  try:
139
  if video_input is None:
140
  raise ValueError("Por favor, faça upload de um vídeo")
141
-
142
- # Configurações mais agressivas para detectar todo silêncio
143
- processed_video = process_video(
144
  video_input,
145
- min_silence_len=100, # Detecta silêncios de 0.1 segundos
146
- silence_thresh=-30 # Limite mais alto para detectar mais silêncio
147
  )
148
-
149
- return processed_video
150
  except Exception as e:
151
  gr.Error(str(e))
152
  return None
@@ -162,10 +153,7 @@ with gr.Blocks(title="Removedor de Silêncio de Vídeos") as app:
162
  )
163
 
164
  with gr.Row():
165
- # Botão para remoção máxima de silêncio
166
  remove_max_btn = gr.Button("🔇 Remover 100% do Silêncio", variant="primary")
167
-
168
- # Botão para remoção personalizada
169
  remove_custom_btn = gr.Button("Remover Silêncio Personalizado")
170
 
171
  with gr.Group():
 
1
  import gradio as gr
2
+ import subprocess
 
 
3
  import tempfile
4
  import os
 
 
 
5
  from pathlib import Path
6
+ import json
7
+ import shutil
8
 
9
+ def detect_silence_ffmpeg(video_path, silence_thresh=-40, min_silence_len=1):
10
+ """Detecta silêncio usando FFmpeg diretamente, muito mais rápido que pydub"""
11
+
12
  command = [
13
  'ffmpeg', '-i', video_path,
14
+ '-af', f'silencedetect=noise={silence_thresh}dB:d={min_silence_len}',
15
+ '-f', 'null', '-'
 
 
 
 
16
  ]
17
+
18
+ # Executa FFmpeg e captura a saída de erro (onde está a informação do silêncio)
19
+ result = subprocess.run(command, stderr=subprocess.PIPE, text=True)
20
+
21
+ # Processa a saída para encontrar timestamps
22
+ silence_data = []
23
+ start_times = []
24
+ end_times = []
25
+
26
+ for line in result.stderr.split('\n'):
27
+ if 'silence_start' in line:
28
+ start_time = float(line.split('silence_start: ')[1].split()[0])
29
+ start_times.append(start_time)
30
+ elif 'silence_end' in line:
31
+ end_time = float(line.split('silence_end: ')[1].split()[0])
32
+ end_times.append(end_time)
33
+
34
+ # Cria lista de intervalos não silenciosos
35
+ if not start_times:
36
+ return []
37
+
38
+ nonsilent_ranges = []
39
+ video_duration = float(get_video_duration(video_path))
40
+
41
+ # Adiciona segmento do início até o primeiro silêncio
42
+ if start_times[0] > 0:
43
+ nonsilent_ranges.append((0, start_times[0]))
44
+
45
+ # Adiciona segmentos entre silêncios
46
+ for i in range(len(end_times)):
47
+ if i < len(start_times):
48
+ nonsilent_ranges.append((end_times[i], start_times[i]))
49
+
50
+ # Adiciona segmento final se necessário
51
+ if end_times and end_times[-1] < video_duration:
52
+ nonsilent_ranges.append((end_times[-1], video_duration))
53
+
54
+ return nonsilent_ranges
55
 
56
+ def get_video_duration(video_path):
57
+ """Obtém a duração do vídeo usando FFmpeg"""
58
  command = [
59
+ 'ffprobe', '-v', 'error',
60
+ '-show_entries', 'format=duration',
61
+ '-of', 'json',
62
+ video_path
 
 
 
 
63
  ]
64
+ result = subprocess.run(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
65
+ data = json.loads(result.stdout)
66
+ return float(data['format']['duration'])
67
 
68
+ def create_filter_complex(ranges):
69
+ """Cria o filtro complexo para FFmpeg baseado nos intervalos não silenciosos"""
70
+ parts = []
71
+ for i, (start, end) in enumerate(ranges):
72
+ parts.append(f"[0:v]trim=start={start}:end={end},setpts=PTS-STARTPTS[v{i}]; "
73
+ f"[0:a]atrim=start={start}:end={end},asetpts=PTS-STARTPTS[a{i}]")
74
+
75
+ # Concatena os vídeos
76
+ v_list = ''.join(f'[v{i}]' for i in range(len(ranges)))
77
+ a_list = ''.join(f'[a{i}]' for i in range(len(ranges)))
78
+
79
+ concat = f"; {v_list}concat=n={len(ranges)}:v=1:a=0[vout]; "
80
+ concat += f"{a_list}concat=n={len(ranges)}:v=0:a=1[aout]"
81
+
82
+ return ''.join(parts) + concat
83
 
84
+ def process_video_fast(video_path, silence_thresh=-40, min_silence_len=1):
85
+ """Processa o vídeo removendo silêncio usando FFmpeg diretamente"""
86
+
87
+ # Detecta intervalos não silenciosos
88
+ nonsilent_ranges = detect_silence_ffmpeg(video_path, silence_thresh, min_silence_len)
89
+
90
+ if not nonsilent_ranges:
91
+ return video_path
92
+
93
+ # Cria arquivo de saída
94
+ output_path = str(Path(video_path).parent / f"processed_{Path(video_path).name}")
95
 
96
+ # Cria filtro complexo
97
+ filter_complex = create_filter_complex(nonsilent_ranges)
98
+
99
+ # Processa o vídeo em uma única passagem
100
  command = [
101
+ 'ffmpeg', '-i', video_path,
102
+ '-filter_complex', filter_complex,
103
+ '-map', '[vout]',
104
+ '-map', '[aout]',
105
+ '-c:v', 'libx264',
106
+ '-preset', 'ultrafast', # Mais rápido encoding
107
+ '-c:a', 'aac',
108
  '-y',
109
  output_path
110
  ]
 
 
 
 
 
 
 
 
 
111
 
112
+ subprocess.run(command, stderr=subprocess.PIPE)
113
+ return output_path
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
 
115
  def remove_silence(video_input, silence_duration, silence_threshold):
116
  """Função para remoção normal de silêncio"""
117
  try:
118
  if video_input is None:
119
  raise ValueError("Por favor, faça upload de um vídeo")
120
+
121
+ return process_video_fast(
122
  video_input,
123
+ silence_thresh=silence_threshold,
124
+ min_silence_len=silence_duration
125
  )
 
 
126
  except Exception as e:
127
  gr.Error(str(e))
128
  return None
 
132
  try:
133
  if video_input is None:
134
  raise ValueError("Por favor, faça upload de um vídeo")
135
+
136
+ return process_video_fast(
 
137
  video_input,
138
+ silence_thresh=-30,
139
+ min_silence_len=0.1
140
  )
 
 
141
  except Exception as e:
142
  gr.Error(str(e))
143
  return None
 
153
  )
154
 
155
  with gr.Row():
 
156
  remove_max_btn = gr.Button("🔇 Remover 100% do Silêncio", variant="primary")
 
 
157
  remove_custom_btn = gr.Button("Remover Silêncio Personalizado")
158
 
159
  with gr.Group():