Update app.py
Browse files
app.py
CHANGED
@@ -305,96 +305,112 @@ if st.button("Gerar Vídeo(s)"):
|
|
305 |
"-of", "default=noprint_wrappers=1:nokey=1", video_final_raw
|
306 |
], stdout=subprocess.PIPE)
|
307 |
dur_video_real = float(dur_proc.stdout.decode().strip())
|
308 |
-
|
309 |
-
|
310 |
-
|
311 |
-
|
312 |
-
|
313 |
-
|
314 |
-
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
"Nenhuma": "",
|
325 |
-
"Borda Pulsante": "enable='lt(mod(t,1),0.5)'",
|
326 |
-
"Cor Animada": "enable='lt(mod(t,1),0.5)'",
|
327 |
-
"Neon": "enable='lt(mod(t,0.5),0.25)'",
|
328 |
-
"Ondulada": "enable='gt(sin(t*3.14),0)'"
|
329 |
-
}
|
330 |
-
drawbox = f"drawbox=x=0:y=0:w=iw:h=ih:color={cor_ffmpeg}:t=5"
|
331 |
-
if anim_map[animacao_borda]:
|
332 |
-
drawbox += f":{anim_map[animacao_borda]}"
|
333 |
-
filtros_main.append(drawbox)
|
334 |
-
|
335 |
-
# Fundo com filtros
|
336 |
-
filtro_complex = f"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280"
|
337 |
-
if ativar_blur_fundo:
|
338 |
-
filtro_complex += f",boxblur={blur_strength}:1"
|
339 |
-
if ativar_sepia:
|
340 |
-
filtro_complex += ",colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131"
|
341 |
-
if ativar_granulado:
|
342 |
-
filtro_complex += ",noise=alls=20:allf=t+u"
|
343 |
-
if ativar_pb:
|
344 |
-
filtro_complex += ",hue=s=0.3"
|
345 |
-
if ativar_vignette:
|
346 |
-
filtro_complex += ",vignette"
|
347 |
-
filtro_complex += "[blur];[1:v]" + ",".join(filtros_main) + "[zoomed];[blur][zoomed]overlay=(W-w)/2:(H-h)/2[base]"
|
348 |
-
|
349 |
-
if ativar_texto and texto_personalizado.strip():
|
350 |
-
y_pos = "100" if posicao_texto == "Topo" else "(h-text_h)/2" if posicao_texto == "Centro" else "h-text_h-100"
|
351 |
-
enable = f":enable='lt(t\\,{segundos_texto})'" if duracao_texto == "Apenas primeiros segundos" else ""
|
352 |
-
texto_clean = texto_personalizado.replace(":", "\\:").replace("'", "\\'")
|
353 |
-
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]"
|
354 |
else:
|
355 |
-
|
356 |
-
|
357 |
-
|
358 |
-
|
359 |
-
|
360 |
-
"-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
|
375 |
-
|
376 |
-
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
383 |
-
|
384 |
-
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
|
398 |
-
|
399 |
-
|
400 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
305 |
"-of", "default=noprint_wrappers=1:nokey=1", video_final_raw
|
306 |
], stdout=subprocess.PIPE)
|
307 |
dur_video_real = float(dur_proc.stdout.decode().strip())
|
308 |
+
if musica:
|
309 |
+
musica_path = os.path.join(temp_dir, "musica_original.mp3")
|
310 |
+
with open(musica_path, "wb") as f:
|
311 |
+
f.write(musica.read())
|
312 |
+
musica_cortada = os.path.join(temp_dir, f"musica_cortada_{n}.aac")
|
313 |
+
subprocess.run([
|
314 |
+
"ffmpeg", "-i", musica_path, "-ss", "0", "-t", str(dur_video_real),
|
315 |
+
"-vn", "-acodec", "aac", "-y", musica_cortada
|
316 |
+
], check=True, stderr=subprocess.PIPE)
|
317 |
+
final_name = f"video_final_{n}_{int(time.time())}.mp4"
|
318 |
+
subprocess.run([
|
319 |
+
"ffmpeg", "-i", video_final_raw, "-i", musica_cortada,
|
320 |
+
"-map", "0:v:0", "-map", "1:a:0",
|
321 |
+
"-c:v", "copy", "-c:a", "aac",
|
322 |
+
"-shortest", final_name
|
323 |
+
], check=True, stderr=subprocess.PIPE)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
else:
|
325 |
+
final_name = f"video_final_{n}_{int(time.time())}.mp4"
|
326 |
+
shutil.copy(video_final_raw, final_name)
|
327 |
+
|
328 |
+
# 🔒 APLICAR ANTI-FLOP NO FINAL
|
329 |
+
if ativar_antiflop:
|
330 |
+
st.info(f"🔒 Aplicando Anti-Flop no vídeo {n+1}...")
|
331 |
+
|
332 |
+
# ➡️ Anti-Flop filtros:
|
333 |
+
encoder_name = f"{fingerprint}Cam_{random.randint(1000,9999)}"
|
334 |
+
antiflop_out = f"antiflop_{n}_{int(time.time())}.mp4"
|
335 |
+
|
336 |
+
vf_af = (
|
337 |
+
f"scale=iw*{zoom_af}:ih*{zoom_af},"
|
338 |
+
f"crop=iw/{zoom_af}:ih/{zoom_af},"
|
339 |
+
f"eq=brightness={brilho_af}:contrast={contraste_af}:saturation={saturacao_af},"
|
340 |
+
f"noise=alls={ruido_af}:allf=t,"
|
341 |
+
f"rotate={rotacao_af}*PI/180:[email protected],"
|
342 |
+
"scale=trunc(iw/2)*2:trunc(ih/2)*2" # Garante resolução par
|
343 |
+
)
|
344 |
+
if usar_marca:
|
345 |
+
vf_af += f",drawtext=text='{encoder_name}':[email protected]:x=10:y=10:fontsize=24"
|
346 |
+
if usar_glitch:
|
347 |
+
vf_af += ",blackframe=1:0"
|
348 |
+
if transicoes:
|
349 |
+
effects = [
|
350 |
+
"fade=t=in:st=0:d=0.5",
|
351 |
+
"fade=t=out:st=3:d=0.5",
|
352 |
+
"zoompan=z='min(pzoom+0.0015,1.5)':d=1",
|
353 |
+
"drawbox=x=0:y=0:w=iw:h=ih:color=white@0.02:t=fill"
|
354 |
+
]
|
355 |
+
vf_af += "," + random.choice(effects)
|
356 |
+
|
357 |
+
af = f"asetrate=44100*{pitch_af},aresample=44100"
|
358 |
+
profile = "baseline" if fingerprint in ["Android", "Xiaomi"] else "main"
|
359 |
+
if fingerprint == "iPhone":
|
360 |
+
profile = "high"
|
361 |
+
level = "4.0" if fingerprint == "Samsung" else "3.1"
|
362 |
+
ar = "44100" if fingerprint in ["iPhone", "Xiaomi"] else "48000"
|
363 |
+
|
364 |
+
subprocess.run([
|
365 |
+
'ffmpeg', '-i', final_name,
|
366 |
+
'-vf', vf_af,
|
367 |
+
'-af', af,
|
368 |
+
'-r', str(fps_af),
|
369 |
+
'-c:v', 'libx264', '-profile:v', profile, '-level', level,
|
370 |
+
'-b:v', f"{bitrate_af}k", '-ar', ar,
|
371 |
+
'-preset', 'fast', '-tune', 'zerolatency',
|
372 |
+
'-movflags', '+faststart',
|
373 |
+
antiflop_out
|
374 |
+
], check=True)
|
375 |
+
|
376 |
+
videos_gerados.append(antiflop_out)
|
377 |
+
st.video(antiflop_out)
|
378 |
+
with open(antiflop_out, "rb") as f:
|
379 |
+
st.download_button(f"📥 Baixar vídeo {n+1} Anti-Flop", f, file_name=antiflop_out)
|
380 |
+
else:
|
381 |
+
videos_gerados.append(final_name)
|
382 |
+
st.video(final_name)
|
383 |
+
with open(final_name, "rb") as f:
|
384 |
+
st.download_button(f"📥 Baixar vídeo {n+1}", f, file_name=final_name)
|
385 |
+
|
386 |
+
progresso.progress(100)
|
387 |
+
st.success("✅ Todos os vídeos foram gerados com sucesso!")
|
388 |
+
|
389 |
+
except subprocess.CalledProcessError as e:
|
390 |
+
st.error(f"❌ Erro ao gerar vídeo:\n\n{e.stderr.decode(errors='ignore')}")
|
391 |
+
|
392 |
+
finally:
|
393 |
+
shutil.rmtree(temp_dir)
|
394 |
+
|
395 |
+
# ➡️ Fora do try-finally: Botão "Baixar Todos os Vídeos"
|
396 |
+
if videos_gerados:
|
397 |
+
st.markdown("### 📥 Baixar Todos os Vídeos")
|
398 |
+
download_links = ""
|
399 |
+
for v in videos_gerados:
|
400 |
+
download_links += f"<a href='{v}' download='{v}'></a>\n"
|
401 |
+
|
402 |
+
st.markdown(
|
403 |
+
f"""
|
404 |
+
<button onclick="baixarTodos()">📥 Baixar Todos</button>
|
405 |
+
<script>
|
406 |
+
function baixarTodos() {{
|
407 |
+
const links = Array.from(document.querySelectorAll('a[download]'));
|
408 |
+
for (let l of links) {{
|
409 |
+
l.click();
|
410 |
+
}}
|
411 |
+
}}
|
412 |
+
</script>
|
413 |
+
{download_links}
|
414 |
+
""",
|
415 |
+
unsafe_allow_html=True
|
416 |
+
)
|