pcdoido2 commited on
Commit
ed77fbd
·
verified ·
1 Parent(s): 824fdfc

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +258 -170
app.py CHANGED
@@ -1,176 +1,264 @@
1
  import streamlit as st
2
  import subprocess
3
  import os
4
- import uuid
5
  import random
 
6
  import shutil
 
7
 
8
- st.set_page_config(page_title="Anti-Flop PRO MAX", layout="centered")
9
- st.title("🚀 Anti-Flop PRO MAX Gerador com Fingerprint Invisível")
10
-
11
- uploaded_file = st.file_uploader("📤 Envie seu vídeo (MP4)", type=["mp4"])
12
-
13
- if uploaded_file:
14
- raw_input_filename = f"raw_{uuid.uuid4().hex}.mp4"
15
- with open(raw_input_filename, "wb") as f:
16
- f.write(uploaded_file.read())
17
-
18
- input_filename = f"cleaned_{uuid.uuid4().hex}.mp4"
19
- subprocess.run(['ffmpeg', '-i', raw_input_filename, '-map_metadata', '-1', '-c', 'copy', input_filename])
20
- os.remove(raw_input_filename)
21
-
22
- st.video(input_filename)
23
- st.success("🎥 Vídeo carregado e metadados limpos!")
24
-
25
- st.subheader("🎛️ Ajustes Visuais e Áudio (padrão)")
26
-
27
- if st.button("🎲 RANDOMIZAR TUDO"):
28
- st.session_state.zoom_default = random.uniform(1.00, 1.05)
29
- st.session_state.brilho_default = random.uniform(-0.05, 0.05)
30
- st.session_state.contraste_default = random.uniform(0.95, 1.05)
31
- st.session_state.saturacao_default = random.uniform(0.95, 1.05)
32
- st.session_state.ruido_default = random.randint(3, 15)
33
- st.session_state.rotacao_default = random.uniform(0.2, 1.5)
34
- st.session_state.pitch_default = random.uniform(1.00, 1.08)
35
- st.session_state.fps_val = random.choice([30, 29.97, 25])
36
- st.session_state.bitrate_default = random.randint(900, 1500)
37
- st.session_state.velocidade_default = random.uniform(1.00, 1.03)
38
- st.rerun()
39
-
40
- zoom_default = st.slider("Zoom (%)", 100, 105, int(st.session_state.get('zoom_default', 1.01) * 100)) / 100
41
- brilho_default = st.slider("Brilho", -0.1, 0.1, st.session_state.get('brilho_default', 0.01))
42
- contraste_default = st.slider("Contraste", 0.9, 1.1, st.session_state.get('contraste_default', 1.01))
43
- saturacao_default = st.slider("Saturação", 0.9, 1.1, st.session_state.get('saturacao_default', 1.01))
44
- ruido_default = st.slider("Ruído Visual (0-20)", 0, 20, st.session_state.get('ruido_default', 5))
45
- rotacao_default = st.slider("Rotação (graus)", 0.0, 2.0, st.session_state.get('rotacao_default', 0.5))
46
- pitch_default = st.slider("Pitch do Áudio (%)", 100, 110, int(st.session_state.get('pitch_default', 1.01) * 100)) / 100
47
- fps = st.selectbox("FPS Final", [30, 29.97, 25], index=[30, 29.97, 25].index(st.session_state.get('fps_val', 30)))
48
- bitrate_default = st.slider("Bitrate (kbps)", 800, 2000, st.session_state.get('bitrate_default', 1100))
49
- velocidade_default = st.slider("Velocidade do Vídeo (%)", 100, 103, int(st.session_state.get('velocidade_default', 1.01) * 100)) / 100
50
-
51
- st.subheader("🧩 Extras Anti-Detecção")
52
- usar_marca = st.checkbox("Marca d’água Fantasma com perfil", value=True)
53
- usar_glitch = st.checkbox("Inserir Frame Preto Aleatório", value=True)
54
- res_random = st.checkbox("Resolução Aleatória", value=True)
55
- cortar = st.checkbox("Cortar trecho aleatório do vídeo", value=True)
56
- embaralhar = st.checkbox("Embaralhar trechos do vídeo", value=True)
57
-
58
- st.subheader("🎨 Avançado")
59
- lote_efeitos = st.checkbox("🎨 Aplicar efeitos por lote (aleatórios)", value=True)
60
- transicoes = st.checkbox("🔄 Transições visuais aleatórias", value=True)
61
-
62
- st.subheader("🕵️‍♂️ Perfil Fingerprint")
63
- fingerprint = st.selectbox("Escolha o Perfil", ["Android", "iPhone", "Câmera", "CapCut", "Samsung", "Xiaomi", "LG"])
64
-
65
- qtd_videos = st.number_input("Quantidade de vídeos únicos:", min_value=1, max_value=10, value=3)
66
-
67
- if st.button("🎬 Gerar Vídeos Únicos"):
68
- st.info("Processando vídeos... aguarde!")
69
- output_files = []
70
-
71
- if embaralhar:
72
- temp_dir = f"temp_{uuid.uuid4().hex}"
73
- os.makedirs(temp_dir, exist_ok=True)
74
- subprocess.run(['ffmpeg', '-i', input_filename, '-c', 'copy', '-f', 'segment', '-segment_time', '2', f'{temp_dir}/out%03d.mp4'])
75
- files = os.listdir(temp_dir)
76
- files.sort()
77
- random.shuffle(files)
78
- concat_list = f"{temp_dir}/list.txt"
79
- with open(concat_list, 'w') as f:
80
- for file in files:
81
- f.write(f"file '{os.path.join(temp_dir, file)}'\n")
82
- shuffled_input = f"shuffled_{uuid.uuid4().hex}.mp4"
83
- subprocess.run(['ffmpeg', '-f', 'concat', '-safe', '0', '-i', concat_list, '-c', 'copy', shuffled_input])
84
- shutil.rmtree(temp_dir)
85
- base_video = shuffled_input if os.path.exists(shuffled_input) else input_filename
86
- else:
87
- base_video = input_filename
88
-
89
- for _ in range(qtd_videos):
90
- encoder_name = f"{fingerprint}Cam_{random.randint(1000,9999)}"
91
- final_out = f"{encoder_name}_video_{uuid.uuid4().hex}.mp4"
92
-
93
- if lote_efeitos:
94
- zoom_r = random.uniform(1.00, 1.05)
95
- brilho_r = random.uniform(-0.05, 0.05)
96
- contraste_r = random.uniform(0.95, 1.05)
97
- saturacao_r = random.uniform(0.95, 1.05)
98
- ruido_r = random.randint(3, 15)
99
- rot_r = random.uniform(0.2, 1.5) * 0.01745
100
- pitch_r = random.uniform(1.00, 1.08)
101
- velocidade_r = random.uniform(1.00, 1.03)
102
- bitrate_r = f"{random.randint(900, 1500)}k"
103
- else:
104
- zoom_r = zoom_default
105
- brilho_r = brilho_default
106
- contraste_r = contraste_default
107
- saturacao_r = saturacao_default
108
- ruido_r = ruido_default
109
- rot_r = rotacao_default * 0.01745
110
- pitch_r = pitch_default
111
- velocidade_r = velocidade_default
112
- bitrate_r = f"{bitrate_default}k"
113
-
114
- vf = (
115
- f"scale=iw*{zoom_r}:ih*{zoom_r},"
116
- f"crop=iw/{zoom_r}:ih/{zoom_r},"
117
- f"[email protected]:t=fill,"
118
- f"eq=brightness={brilho_r}:contrast={contraste_r}:saturation={saturacao_r},"
119
- f"noise=alls={ruido_r}:allf=t,"
120
- f"rotate={rot_r}:[email protected]"
121
- )
122
-
123
- if usar_marca:
124
- vf += f",drawtext=text='{encoder_name}':fontcolor=white@0.005:x=10:y=10:fontsize=24"
125
- if usar_glitch:
126
- vf += ",blackframe=1:0"
127
- if res_random:
128
- w = random.choice([720, 960, 1080])
129
- h = random.choice([1280, 1440, 1920])
130
- vf += f",scale={w}:{h}"
131
- if transicoes:
132
- effects = [
133
- "fade=t=in:st=0:d=0.5",
134
- "fade=t=out:st=3:d=0.5",
135
- "zoompan=z='min(pzoom+0.0015,1.5)':d=1",
136
- "drawbox=x=0:y=0:w=iw:h=ih:color=white@0.02:t=fill"
137
- ]
138
- vf += "," + random.choice(effects)
139
-
140
- af = f"asetrate=44100*{pitch_r},aresample=44100"
141
- profile = "baseline" if fingerprint in ["Android", "Xiaomi"] else "main"
142
- if fingerprint == "iPhone":
143
- profile = "high"
144
- level = "4.0" if fingerprint == "Samsung" else "3.1"
145
- ar = "44100" if fingerprint in ["iPhone", "Xiaomi"] else "48000"
146
-
147
- codec_cmd = ['-c:v', 'libx264', '-profile:v', profile, '-level', level]
148
- audio_cmd = ['-ar', ar]
149
- filters = ['-vf', vf, '-af', af, '-r', str(fps)]
150
- speed_filter = ['-filter:v', f"setpts={1/velocidade_r}*PTS"]
151
- speed_audio = ['-filter:a', f"atempo={velocidade_r}"]
152
- duration_cmd = ['-ss', str(random.uniform(0, 1))] if cortar else []
153
-
154
- subprocess.run([
155
- 'ffmpeg', '-i', base_video, *duration_cmd,
156
- *filters, *speed_filter, *speed_audio,
157
- *codec_cmd, '-b:v', bitrate_r,
158
- *audio_cmd,
159
- '-preset', 'fast', '-tune', 'zerolatency',
160
- '-movflags', '+faststart',
161
- final_out
162
- ])
163
-
164
- output_files.append(final_out)
165
-
166
- if base_video != input_filename and os.path.exists(base_video):
167
- os.remove(base_video)
168
- os.remove(input_filename)
169
-
170
- st.success(f"✅ {qtd_videos} vídeos gerados com fingerprint invisível!")
171
-
172
- for file in output_files:
173
- st.video(file)
174
- with open(file, "rb") as f:
175
- st.download_button(f"📥 Baixar {file}", data=f, file_name=file, mime="video/mp4")
176
- os.remove(file)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import streamlit as st
2
  import subprocess
3
  import os
 
4
  import random
5
+ import tempfile
6
  import shutil
7
+ import time
8
 
9
+ st.set_page_config(page_title="TikTok Video Generator - PRO", layout="centered")
10
+ st.title("🎥 TikTok Video Generator - PRO")
11
+
12
+ st.markdown("Envie seus vídeos e gere conteúdo com efeitos, zoom, texto, música, filtros e antiflop!")
13
+
14
+ # Uploads dos vídeos de cortes (principal)
15
+ st.markdown("<div style='background-color:#F0F0FF;padding:10px;border-radius:8px'>", unsafe_allow_html=True)
16
+ st.subheader("🎬 Envie os vídeos principais (cortes)")
17
+ cortes = st.file_uploader("Vídeos de cortes", type=["mp4"], accept_multiple_files=True)
18
+ st.markdown("</div>", unsafe_allow_html=True)
19
+
20
+ # Upload do vídeo de fundo (opcional)
21
+ st.markdown("<div style='background-color:#FFF0F0;padding:10px;border-radius:8px;margin-top:15px;'>", unsafe_allow_html=True)
22
+ st.subheader("🌄 Envie o vídeo de fundo (opcional)")
23
+ video_fundo = st.file_uploader("Vídeo de Fundo (com blur e efeitos)", type=["mp4", "mov"])
24
+ st.markdown("</div>", unsafe_allow_html=True)
25
+
26
+ # Uploads adicionais
27
+ video_tutorial = st.file_uploader("📎 Tutorial (opcional)", type="mp4")
28
+ musica = st.file_uploader("🎵 Música (opcional, MP3)", type=["mp3"])
29
+
30
+ # Configurações gerais
31
+ num_videos_finais = st.number_input("Quantos vídeos gerar?", 1, 10, 1)
32
+ duracao_final = st.number_input("Duração final (s)", 10, 300, 30)
33
+ duracao_corte = st.number_input("Duração de cada corte (s)", 1, 30, 5)
34
+ zoom = st.slider("Zoom no vídeo principal", 1.0, 2.0, 1.0, 0.1)
35
+ blur_strength = st.slider("Blur no fundo", 1, 50, 10)
36
+ velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
37
+ velocidade_final = st.slider("Velocidade final", 0.5, 2.0, 1.0, 0.1)
38
+ crf_value = st.slider("Qualidade CRF", 18, 30, 23)
39
+ # Texto com emojis
40
+ st.write("### Texto no Vídeo")
41
+ ativar_texto = st.checkbox("Ativar texto", value=False)
42
+ if ativar_texto:
43
+ texto_personalizado = st.text_input("Texto (emojis permitidos)", "🔥 Siga para mais!")
44
+ posicao_texto = st.selectbox("Posição do texto", ["Topo", "Centro", "Base"])
45
+ duracao_texto = st.radio("Duração do texto", ["Vídeo todo", "Apenas primeiros segundos"])
46
+ segundos_texto = 5
47
+ if duracao_texto == "Apenas primeiros segundos":
48
+ segundos_texto = st.slider("Segundos", 1, 30, 5)
49
+ cor_texto = st.color_picker("Cor do texto", "#FFFF00")
50
+ cor_sombra = st.color_picker("Cor da sombra", "#000000")
51
+ tamanho_texto = st.slider("Tamanho da fonte", 20, 100, 60, step=2)
52
+
53
+ # Filtros no fundo
54
+ st.write("### Filtros no fundo")
55
+ ativar_blur_fundo = st.checkbox("Ativar blur no fundo", value=True)
56
+ ativar_sepia = st.checkbox("Sépia", False)
57
+ ativar_granulado = st.checkbox("Granulado", False)
58
+ ativar_pb = st.checkbox("Preto e branco", False)
59
+ ativar_vignette = st.checkbox("Vignette", False)
60
+
61
+ # Efeitos extras
62
+ st.write("### Outros efeitos")
63
+ ativar_espelhar = st.checkbox("Espelhar vídeo", True)
64
+ ativar_filtro_cor = st.checkbox("Ajuste de cor", True)
65
+ remover_borda = st.checkbox("Remover borda do vídeo")
66
+ tamanho_borda = st.slider("Tamanho da borda (px)", 0, 200, 0, 5)
67
+
68
+ # Borda personalizada
69
+ ativar_borda_personalizada = st.checkbox("Borda personalizada", False)
70
+ if ativar_borda_personalizada:
71
+ cor_borda = st.color_picker("Cor da borda", "#FF0000")
72
+ animacao_borda = st.selectbox("Animação da borda", ["Nenhuma", "Borda Pulsante", "Cor Animada", "Neon", "Ondulada"])
73
+
74
+ # 🔒 Anti-Flop (Configuração)
75
+ st.write("### 🔒 Anti-Flop (Evitar Detecção)")
76
+ ativar_antiflop = st.checkbox("Ativar Anti-Flop", value=False)
77
+ if ativar_antiflop:
78
+ zoom_af = st.slider("Zoom Anti-Flop (%)", 100, 105, 101) / 100
79
+ brilho_af = st.slider("Brilho Anti-Flop", -0.1, 0.1, 0.01)
80
+ contraste_af = st.slider("Contraste Anti-Flop", 0.9, 1.1, 1.01)
81
+ saturacao_af = st.slider("Saturação Anti-Flop", 0.9, 1.1, 1.01)
82
+ ruido_af = st.slider("Ruído Visual Anti-Flop (0-20)", 0, 20, 5)
83
+ rotacao_af = st.slider("Rotação Anti-Flop (graus)", 0.0, 2.0, 0.5)
84
+ # Botão principal
85
+ if st.button("Gerar Vídeo(s)"):
86
+ if not cortes:
87
+ st.error("❌ Envie os vídeos de cortes.")
88
+ else:
89
+ with st.spinner("🎬 Processando..."):
90
+ progresso = st.progress(0)
91
+ temp_dir = tempfile.mkdtemp()
92
+
93
+ try:
94
+ # Salvar ou gerar fundo
95
+ fundo_path = os.path.join(temp_dir, "fundo.mp4")
96
+ if video_fundo:
97
+ with open(fundo_path, "wb") as f:
98
+ f.write(video_fundo.read())
99
+ else:
100
+ # Gera fundo preto 720x1280
101
+ subprocess.run([
102
+ "ffmpeg", "-f", "lavfi", "-i", "color=black:s=720x1280:d=600",
103
+ "-c:v", "libx264", "-t", str(duracao_final),
104
+ "-pix_fmt", "yuv420p", "-y", fundo_path
105
+ ], check=True, stderr=subprocess.PIPE)
106
+
107
+ # Tutorial (opcional)
108
+ if video_tutorial:
109
+ tutorial_path = os.path.join(temp_dir, "tutorial_raw.mp4")
110
+ with open(tutorial_path, "wb") as f:
111
+ f.write(video_tutorial.read())
112
+ tutorial_mp4 = os.path.join(temp_dir, "tutorial.mp4")
113
+ subprocess.run([
114
+ "ffmpeg", "-i", tutorial_path, "-vf", "scale=720:1280,fps=30",
115
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
116
+ "-y", tutorial_mp4
117
+ ], check=True, stderr=subprocess.PIPE)
118
+
119
+ # Padronizar cortes 1280x720
120
+ cortes_names = []
121
+ for idx, corte in enumerate(cortes):
122
+ path_in = os.path.join(temp_dir, f"corte_in_{idx}.mp4")
123
+ path_out = os.path.join(temp_dir, f"corte_padrao_{idx}.mp4")
124
+ with open(path_in, "wb") as f:
125
+ f.write(corte.read())
126
+ subprocess.run([
127
+ "ffmpeg", "-i", path_in,
128
+ "-vf", "scale=1280:720:force_original_aspect_ratio=decrease",
129
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", path_out
130
+ ], check=True, stderr=subprocess.PIPE)
131
+ cortes_names.append(path_out)
132
+ # Cortar fundo para a duração final
133
+ fundo_cortado = os.path.join(temp_dir, "fundo_cortado.mp4")
134
+ subprocess.run([
135
+ "ffmpeg", "-i", fundo_path, "-t", str(duracao_final),
136
+ "-c:v", "libx264", "-preset", "ultrafast", "-y", fundo_cortado
137
+ ], check=True, stderr=subprocess.PIPE)
138
+
139
+ for n in range(num_videos_finais):
140
+ progresso.progress(10 + n * 5)
141
+ random.shuffle(cortes_names)
142
+ tempo_total = 0
143
+ cortes_prontos = []
144
+
145
+ while tempo_total < duracao_final:
146
+ for c in cortes_names:
147
+ dur = subprocess.run([
148
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
149
+ "-of", "default=noprint_wrappers=1:nokey=1", c
150
+ ], stdout=subprocess.PIPE).stdout.decode().strip()
151
+
152
+ try:
153
+ d = float(dur)
154
+ if d > duracao_corte:
155
+ ini = random.uniform(0, d - duracao_corte)
156
+ out = os.path.join(temp_dir, f"cut_{random.randint(1000,9999)}.mp4")
157
+ subprocess.run([
158
+ "ffmpeg", "-ss", str(ini), "-i", c, "-t", str(duracao_corte),
159
+ "-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
160
+ ], check=True, stderr=subprocess.PIPE)
161
+ cortes_prontos.append(out)
162
+ tempo_total += duracao_corte / velocidade_cortes
163
+ if tempo_total >= duracao_final:
164
+ break
165
+ except:
166
+ continue
167
+
168
+ # Concatenação dos cortes
169
+ lista = os.path.join(temp_dir, f"lista_{n}.txt")
170
+ with open(lista, "w") as f:
171
+ for c in cortes_prontos:
172
+ f.write(f"file '{c}'\n")
173
+
174
+ video_raw = os.path.join(temp_dir, f"video_raw_{n}.mp4")
175
+ subprocess.run([
176
+ "ffmpeg", "-f", "concat", "-safe", "0", "-i", lista,
177
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", video_raw
178
+ ], check=True, stderr=subprocess.PIPE)
179
+ progresso.progress(35 + n * 5)
180
+
181
+ # Filtros no vídeo principal (zoom, espelho, borda, etc.)
182
+ filtros_main = ["scale=720:1280:force_original_aspect_ratio=decrease"]
183
+ if zoom != 1.0:
184
+ filtros_main.append(f"scale=iw*{zoom}:ih*{zoom}")
185
+ filtros_main.append(f"setpts=PTS/{velocidade_cortes}")
186
+ if ativar_espelhar:
187
+ filtros_main.append("hflip")
188
+ if remover_borda and tamanho_borda > 0:
189
+ filtros_main.append(f"crop=in_w-{tamanho_borda*2}:in_h-{tamanho_borda*2}")
190
+ if ativar_filtro_cor:
191
+ filtros_main.append("eq=contrast=1.1:saturation=1.2")
192
+ if ativar_borda_personalizada:
193
+ cor_ffmpeg = f"0x{cor_borda.lstrip('#')}FF"
194
+ anim_map = {
195
+ "Nenhuma": "",
196
+ "Borda Pulsante": "enable='lt(mod(t,1),0.5)'",
197
+ "Cor Animada": "enable='lt(mod(t,1),0.5)'",
198
+ "Neon": "enable='lt(mod(t,0.5),0.25)'",
199
+ "Ondulada": "enable='gt(sin(t*3.14),0)'"
200
+ }
201
+ drawbox = f"drawbox=x=0:y=0:w=iw:h=ih:color={cor_ffmpeg}:t=5"
202
+ if anim_map[animacao_borda]:
203
+ drawbox += f":{anim_map[animacao_borda]}"
204
+ filtros_main.append(drawbox)
205
+
206
+ # Fundo com filtros
207
+ filtro_complex = f"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280"
208
+ if ativar_blur_fundo:
209
+ filtro_complex += f",boxblur={blur_strength}:1"
210
+ if ativar_sepia:
211
+ filtro_complex += ",colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131"
212
+ if ativar_granulado:
213
+ filtro_complex += ",noise=alls=20:allf=t+u"
214
+ if ativar_pb:
215
+ filtro_complex += ",hue=s=0.3"
216
+ if ativar_vignette:
217
+ filtro_complex += ",vignette"
218
+ filtro_complex += "[blur];[1:v]" + ",".join(filtros_main) + "[zoomed];[blur][zoomed]overlay=(W-w)/2:(H-h)/2[base]"
219
+
220
+ if ativar_texto and texto_personalizado.strip():
221
+ y_pos = "100" if posicao_texto == "Topo" else "(h-text_h)/2" if posicao_texto == "Centro" else "h-text_h-100"
222
+ enable = f":enable='lt(t\\,{segundos_texto})'" if duracao_texto == "Apenas primeiros segundos" else ""
223
+ texto_clean = texto_personalizado.replace(":", "\\:").replace("'", "\\'")
224
+ filtro_complex += f";[base]drawtext=text='{texto_clean}':fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:fontcolor={cor_texto}:fontsize={tamanho_texto}:shadowcolor={cor_sombra}:shadowx=3:shadowy=3:x=(w-text_w)/2:y={y_pos}{enable}[final]"
225
+ else:
226
+ filtro_complex += ";[base]null[final]"
227
+
228
+ video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
229
+ subprocess.run([
230
+ "ffmpeg", "-i", fundo_cortado, "-i", video_raw,
231
+ "-filter_complex", filtro_complex,
232
+ "-map", "[final]", "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
233
+ video_editado
234
+ ], check=True, stderr=subprocess.PIPE)
235
+
236
+ # Acelerar vídeo final
237
+ video_acelerado = os.path.join(temp_dir, f"video_acelerado_{n}.mp4")
238
+ subprocess.run([
239
+ "ffmpeg", "-y", "-i", video_editado, "-an",
240
+ "-filter:v", f"setpts=PTS/{velocidade_final}",
241
+ "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
242
+ video_acelerado
243
+ ], check=True, stderr=subprocess.PIPE)
244
+
245
+ progresso.progress(90)
246
+
247
+ # Tutorial no meio
248
+ video_final_raw = video_acelerado
249
+ if video_tutorial:
250
+ dur_proc = subprocess.run([
251
+ "ffprobe", "-v", "error", "-show_entries", "format=duration",
252
+ "-of", "default=noprint_wrappers=1:nokey=1", video_acelerado
253
+ ], stdout=subprocess.PIPE)
254
+ dur_f = float(dur_proc.stdout.decode().strip())
255
+ pt = dur_f / 2 if dur_f < 10 else random.uniform(5, dur_f - 5)
256
+ part1 = os.path.join(temp_dir, f"part1_{n}.mp4")
257
+ part2 = os.path.join(temp_dir, f"part2_{n}.mp4")
258
+ subprocess.run(["ffmpeg", "-i", video_acelerado, "-ss", "0", "-t", str(pt), "-c:v", "libx264", "-preset", "ultrafast", part1], check=True, stderr=subprocess.PIPE)
259
+ subprocess.run(["ffmpeg", "-i", video_acelerado, "-ss", str(pt), "-c:v", "libx264", "-preset", "ultrafast", part2], check=True, stderr=subprocess.PIPE)
260
+ final_txt = os.path.join(temp_dir, f"final_{n}.txt")
261
+ with open(final_txt, "w") as f:
262
+ f.write(f"file '{part1}'\nfile '{tutorial_mp4}'\nfile '{part2}'\n")
263
+ video_final_raw = os.path.join(temp_dir, f"video_final_raw_{n}.mp4")
264
+ subprocess.run(["ffmpeg", "-f", "concat", "-safe", "0", "-i", final_txt, "-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value), video_final_raw], check=True, stderr=subprocess.PIPE)