leicam commited on
Commit
186e431
·
verified ·
1 Parent(s): 47c0626

delete core.py

Browse files
Files changed (1) hide show
  1. core.py +0 -354
core.py DELETED
@@ -1,354 +0,0 @@
1
- """
2
- Core functions for video transcription and cutting.
3
- """
4
-
5
- import whisper
6
- import subprocess
7
- from pathlib import Path
8
- from dataclasses import dataclass
9
- from typing import List, Tuple
10
- import tempfile
11
- import os
12
-
13
- @dataclass
14
- class Segment:
15
- """Representa um segmento de transcrição com timestamps."""
16
- start: float
17
- end: float
18
- text: str
19
-
20
- def __repr__(self):
21
- return f"Segment({self.start:.1f}-{self.end:.1f}: {self.text[:50]}...)"
22
-
23
-
24
- def transcribe(video_file: str, model_size: str = "small") -> List[Segment]:
25
- """
26
- Transcreve um vídeo usando Whisper.
27
-
28
- Args:
29
- video_file: Caminho do arquivo de vídeo
30
- model_size: Tamanho do modelo Whisper (tiny, base, small, medium, large)
31
-
32
- Returns:
33
- Lista de Segments com texto e timestamps
34
- """
35
- print(f"🎙️ Carregando modelo Whisper: {model_size}")
36
- model = whisper.load_model(model_size)
37
-
38
- print(f"🎬 Transcrevendo: {video_file}")
39
- result = model.transcribe(video_file, language="pt", verbose=False)
40
-
41
- segments = []
42
- for seg in result["segments"]:
43
- segments.append(Segment(
44
- start=seg["start"],
45
- end=seg["end"],
46
- text=seg["text"].strip()
47
- ))
48
-
49
- print(f"✓ Transcrição completa: {len(segments)} segmentos")
50
- return segments
51
-
52
-
53
- def extract_video_segment(
54
- input_video: str,
55
- output_video: str,
56
- start_time: float,
57
- end_time: float
58
- ) -> bool:
59
- """
60
- Extrai um segmento do vídeo usando FFmpeg.
61
-
62
- Args:
63
- input_video: Vídeo de entrada
64
- output_video: Vídeo de saída
65
- start_time: Tempo inicial em segundos
66
- end_time: Tempo final em segundos
67
-
68
- Returns:
69
- True se sucesso
70
- """
71
- duration = end_time - start_time
72
-
73
- cmd = [
74
- "ffmpeg", "-y",
75
- "-ss", str(start_time),
76
- "-i", input_video,
77
- "-t", str(duration),
78
- "-c:v", "libx264",
79
- "-c:a", "aac",
80
- "-strict", "experimental",
81
- output_video
82
- ]
83
-
84
- try:
85
- subprocess.run(cmd, check=True, capture_output=True)
86
- return True
87
- except subprocess.CalledProcessError as e:
88
- print(f"❌ Erro ao extrair segmento: {e}")
89
- return False
90
-
91
-
92
- def apply_aspect_ratio(
93
- input_video: str,
94
- output_video: str,
95
- ar_mode: str
96
- ) -> bool:
97
- """
98
- Aplica aspect ratio ao vídeo com crop centralizado.
99
-
100
- Args:
101
- input_video: Vídeo de entrada
102
- output_video: Vídeo de saída
103
- ar_mode: Modo ("Original", "Vertical 9:16", "Quadrado 1:1", "Retrato 4:5")
104
-
105
- Returns:
106
- True se sucesso
107
- """
108
- if ar_mode == "Original":
109
- import shutil
110
- shutil.copy(input_video, output_video)
111
- return True
112
-
113
- # Mapeia modos para dimensões
114
- ar_dims = {
115
- "Vertical 9:16": (1080, 1920),
116
- "Quadrado 1:1": (1080, 1080),
117
- "Retrato 4:5": (1080, 1350),
118
- }
119
-
120
- if ar_mode not in ar_dims:
121
- print(f"⚠️ Modo inválido: {ar_mode}")
122
- return False
123
-
124
- width, height = ar_dims[ar_mode]
125
-
126
- # Comando FFmpeg com scale e crop centralizado
127
- cmd = [
128
- "ffmpeg", "-y",
129
- "-i", input_video,
130
- "-vf", f"scale={width}:{height}:force_original_aspect_ratio=increase,crop={width}:{height}",
131
- "-c:a", "copy",
132
- output_video
133
- ]
134
-
135
- try:
136
- subprocess.run(cmd, check=True, capture_output=True)
137
- return True
138
- except subprocess.CalledProcessError as e:
139
- print(f"❌ Erro ao aplicar aspect ratio: {e}")
140
- return False
141
-
142
-
143
- def generate_linear_cuts(
144
- video_file: str,
145
- segments: List[Segment],
146
- output_dir: str,
147
- min_len: float = 600,
148
- max_len: float = 900,
149
- ideal_len: float = 900,
150
- k: int = 2,
151
- gap_threshold: float = 0.60,
152
- pad: float = 0.08,
153
- ar_mode: str = "Original",
154
- face_tracking: bool = False
155
- ) -> List[str]:
156
- """
157
- Gera k cortes lineares do vídeo.
158
-
159
- Args:
160
- video_file: Vídeo de entrada
161
- segments: Lista de Segments da transcrição
162
- output_dir: Diretório de saída
163
- min_len: Duração mínima em segundos
164
- max_len: Duração máxima em segundos
165
- ideal_len: Duração ideal em segundos
166
- k: Número de cortes a gerar
167
- gap_threshold: Threshold para gaps entre segmentos
168
- pad: Padding em segundos
169
- ar_mode: Modo de aspect ratio
170
- face_tracking: Se True, usa face tracking (TODO)
171
-
172
- Returns:
173
- Lista de caminhos dos vídeos gerados
174
- """
175
- if not segments:
176
- return []
177
-
178
- Path(output_dir).mkdir(parents=True, exist_ok=True)
179
-
180
- # Calcula duração total
181
- total_duration = segments[-1].end - segments[0].start
182
-
183
- # Divide em k partes aproximadamente iguais
184
- target_duration = min(max_len, max(min_len, total_duration / k))
185
-
186
- outputs = []
187
- current_start = segments[0].start
188
-
189
- for i in range(k):
190
- # Encontra fim do corte
191
- target_end = current_start + target_duration
192
-
193
- # Ajusta para não cortar no meio de uma frase
194
- best_end = target_end
195
- for seg in segments:
196
- if abs(seg.end - target_end) < gap_threshold and seg.end > current_start:
197
- best_end = seg.end
198
- break
199
-
200
- # Garante que não excede máximo
201
- if best_end - current_start > max_len:
202
- best_end = current_start + max_len
203
-
204
- # Aplica padding
205
- start_with_pad = max(0, current_start - pad)
206
- end_with_pad = best_end + pad
207
-
208
- # Extrai segmento
209
- temp_file = Path(output_dir) / f"temp_linear_{i+1}.mp4"
210
- final_file = Path(output_dir) / f"cut_linear_{i+1}.mp4"
211
-
212
- print(f"✂️ Gerando corte {i+1}/{k}: {start_with_pad:.1f}s - {end_with_pad:.1f}s")
213
-
214
- if extract_video_segment(video_file, str(temp_file), start_with_pad, end_with_pad):
215
- # Aplica aspect ratio se necessário
216
- if ar_mode != "Original":
217
- if apply_aspect_ratio(str(temp_file), str(final_file), ar_mode):
218
- temp_file.unlink()
219
- outputs.append(str(final_file))
220
- else:
221
- temp_file.rename(final_file)
222
- outputs.append(str(final_file))
223
- else:
224
- temp_file.rename(final_file)
225
- outputs.append(str(final_file))
226
-
227
- # Próximo início
228
- current_start = best_end + gap_threshold
229
-
230
- # Para se chegou no fim
231
- if current_start >= segments[-1].end:
232
- break
233
-
234
- return outputs
235
-
236
-
237
- def generate_creative_cuts(
238
- video_file: str,
239
- segments: List[Segment],
240
- output_dir: str,
241
- min_len: float = 600,
242
- max_len: float = 900,
243
- ideal_len: float = 900,
244
- min_blocks: int = 3,
245
- max_blocks: int = 8,
246
- k: int = 2,
247
- gap_threshold: float = 0.60,
248
- pad: float = 0.08,
249
- ar_mode: str = "Original",
250
- face_tracking: bool = False
251
- ) -> List[str]:
252
- """
253
- Gera k cortes criativos com múltiplos blocos não-contíguos.
254
-
255
- Args:
256
- Similar a generate_linear_cuts, mas com:
257
- min_blocks: Mínimo de blocos por corte
258
- max_blocks: Máximo de blocos por corte
259
-
260
- Returns:
261
- Lista de caminhos dos vídeos gerados
262
- """
263
- if not segments or len(segments) < min_blocks:
264
- return []
265
-
266
- Path(output_dir).mkdir(parents=True, exist_ok=True)
267
-
268
- outputs = []
269
-
270
- for i in range(k):
271
- # Seleciona blocos aleatórios
272
- import random
273
- num_blocks = random.randint(min_blocks, min(max_blocks, len(segments)))
274
-
275
- # Seleciona segmentos espaçados
276
- step = max(1, len(segments) // num_blocks)
277
- selected_indices = [j * step for j in range(num_blocks)]
278
- selected_segments = [segments[idx] for idx in selected_indices if idx < len(segments)]
279
-
280
- # Extrai cada bloco
281
- block_files = []
282
- for j, seg in enumerate(selected_segments):
283
- block_file = Path(output_dir) / f"temp_creative_{i+1}_block_{j+1}.mp4"
284
-
285
- start = max(0, seg.start - pad)
286
- end = seg.end + pad
287
-
288
- if extract_video_segment(video_file, str(block_file), start, end):
289
- block_files.append(str(block_file))
290
-
291
- if not block_files:
292
- continue
293
-
294
- # Concatena blocos
295
- concat_file = Path(output_dir) / f"temp_creative_{i+1}_concat.mp4"
296
- if concatenate_videos(block_files, str(concat_file)):
297
- # Aplica aspect ratio
298
- final_file = Path(output_dir) / f"cut_creative_{i+1}.mp4"
299
-
300
- if ar_mode != "Original":
301
- if apply_aspect_ratio(str(concat_file), str(final_file), ar_mode):
302
- concat_file.unlink()
303
- outputs.append(str(final_file))
304
- else:
305
- concat_file.rename(final_file)
306
- outputs.append(str(final_file))
307
- else:
308
- concat_file.rename(final_file)
309
- outputs.append(str(final_file))
310
-
311
- # Limpa blocos temporários
312
- for bf in block_files:
313
- Path(bf).unlink(missing_ok=True)
314
-
315
- return outputs
316
-
317
-
318
- def concatenate_videos(video_files: List[str], output_file: str) -> bool:
319
- """
320
- Concatena múltiplos vídeos em um único arquivo.
321
-
322
- Args:
323
- video_files: Lista de vídeos a concatenar
324
- output_file: Arquivo de saída
325
-
326
- Returns:
327
- True se sucesso
328
- """
329
- if not video_files:
330
- return False
331
-
332
- # Cria arquivo de lista para FFmpeg
333
- with tempfile.NamedTemporaryFile(mode='w', suffix='.txt', delete=False) as f:
334
- list_file = f.name
335
- for vf in video_files:
336
- f.write(f"file '{os.path.abspath(vf)}'\n")
337
-
338
- try:
339
- cmd = [
340
- "ffmpeg", "-y",
341
- "-f", "concat",
342
- "-safe", "0",
343
- "-i", list_file,
344
- "-c", "copy",
345
- output_file
346
- ]
347
-
348
- subprocess.run(cmd, check=True, capture_output=True)
349
- return True
350
- except subprocess.CalledProcessError as e:
351
- print(f"❌ Erro ao concatenar: {e}")
352
- return False
353
- finally:
354
- Path(list_file).unlink(missing_ok=True)