import streamlit as st
import subprocess
import os
import random
import tempfile
import shutil
import time
st.set_page_config(page_title="TikTok Video Generator - PRO", layout="centered")
pagina = st.sidebar.radio("Escolha uma página:", ["🎬 Gerador de Vídeo", "📂 Gerenciador de Arquivos"])
if pagina == "🎬 Gerador de Vídeo":
st.title("🎥 TikTok Video Generator - PRO")
st.markdown("Envie seus vídeos e gere conteúdo com efeitos, zoom, texto, música e filtros!")
# Uploads dos vídeos de cortes (principal)
st.markdown("
", unsafe_allow_html=True)
st.subheader("🎬 Envie os vídeos principais (cortes)")
cortes = st.file_uploader("Vídeos de cortes", type=["mp4"], accept_multiple_files=True)
st.markdown("
", unsafe_allow_html=True)
# Upload de múltiplos vídeos ou imagens de fundo
st.markdown("", unsafe_allow_html=True)
st.subheader("🌄 Envie os vídeos ou imagens de fundo (opcional)")
videos_fundo = st.file_uploader("Vídeos ou Imagens de Fundo", type=["mp4", "mov", "jpg", "jpeg", "png"], accept_multiple_files=True)
st.markdown("
", unsafe_allow_html=True)
# Uploads adicionais (múltiplos tutoriais agora)
videos_tutorial = st.file_uploader("📎 Tutoriais (opcional)", type="mp4", accept_multiple_files=True)
musica = st.file_uploader("🎵 Música (opcional, MP3)", type=["mp3"])
num_videos_finais = st.number_input("Quantos vídeos gerar?", 1, 10, 1)
duracao_final = st.number_input("Duração final (s)", 10, 300, 30)
st.write("## Configuração dos cortes")
# BOTÃO ALEATORIZAR CONFIGURAÇÕES
aleatorizar = st.checkbox("🎲 Aleatorizar configurações por vídeo")
duracao_corte_min = st.slider("Duração mínima de cada corte (s)", 1, 10, 3)
duracao_corte_max = st.slider("Duração máxima de cada corte (s)", duracao_corte_min + 1, 20, 5)
zoom = st.slider("Zoom no vídeo principal", 1.0, 2.0, 1.0, 0.1)
blur_strength = st.slider("Blur no fundo", 1, 50, 10)
velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
velocidade_final = st.slider("Velocidade final", 0.5, 2.0, 1.0, 0.1)
crf_value = st.slider("Qualidade CRF", 18, 30, 23)
st.write("## Texto no Vídeo")
ativar_texto = st.checkbox("Ativar texto", value=False)
if ativar_texto:
texto_personalizado = st.text_input("Texto (emojis permitidos)", "🔥 Siga para mais!")
posicao_texto = st.selectbox("Posição do texto", ["Topo", "Centro", "Base"])
duracao_texto = st.radio("Duração do texto", ["Vídeo todo", "Apenas primeiros segundos"])
segundos_texto = 5
if duracao_texto == "Apenas primeiros segundos":
segundos_texto = st.slider("Segundos", 1, 30, 5)
cor_texto = st.color_picker("Cor do texto", "#FFFF00")
cor_sombra = st.color_picker("Cor da sombra", "#000000")
tamanho_texto = st.slider("Tamanho da fonte", 20, 100, 60, step=2)
st.write("## Filtros no fundo")
ativar_blur_fundo = st.checkbox("Ativar blur no fundo", value=True)
ativar_sepia = st.checkbox("Sépia", False)
ativar_granulado = st.checkbox("Granulado", False)
ativar_pb = st.checkbox("Preto e branco", False)
ativar_vignette = st.checkbox("Vignette", False)
with st.expander("🎨 Filtros Avançados"):
ativar_brilho = st.checkbox("Brilho extra", False)
ativar_contraste = st.checkbox("Contraste forte (cinemático)", False)
ativar_colorboost = st.checkbox("Cores intensas", False)
ativar_azul = st.checkbox("Filtro Azul Frio", False)
ativar_quente = st.checkbox("Filtro Quente (laranja)", False)
ativar_desaturar = st.checkbox("Desaturação parcial", False)
ativar_vhs = st.checkbox("Efeito VHS Leve", False)
st.write("## Outros efeitos")
ativar_espelhar = st.checkbox("Espelhar vídeo", True)
ativar_filtro_cor = st.checkbox("Ajuste de cor", True)
remover_borda = st.checkbox("Remover borda do vídeo")
tamanho_borda = st.slider("Tamanho da borda (px)", 0, 200, 0, 5)
ativar_borda_personalizada = st.checkbox("Borda personalizada", False)
if ativar_borda_personalizada:
cor_borda = st.color_picker("Cor da borda", "#FF0000")
animacao_borda = st.selectbox("Animação da borda", ["Nenhuma", "Borda Pulsante", "Cor Animada", "Neon", "Ondulada"])
st.write("## Deseja salvar os vídeos no gerenciador de arquivos?")
salvar_no_gerenciador = st.checkbox("Salvar ao invés de oferecer download")
CATEGORIAS = ["AVATAR WORLD", "BLOX FRUITS", "TOCA LIFE"]
categoria_selecionada = None
if salvar_no_gerenciador:
categoria_selecionada = st.radio("Escolha a categoria:", CATEGORIAS, index=0)
os.makedirs(categoria_selecionada, exist_ok=True)
if st.button("Gerar Vídeo(s)"):
if not cortes:
st.error("❌ Envie os vídeos de cortes.")
else:
with st.spinner("🎬 Processando..."):
progresso = st.progress(0)
temp_dir = tempfile.mkdtemp()
try:
# ---------- SALVAR CORTES ----------
cortes_names = []
for idx, corte in enumerate(cortes):
path_in = os.path.join(temp_dir, f"corte_in_{idx}.mp4")
path_out = os.path.join(temp_dir, f"corte_padrao_{idx}.mp4")
with open(path_in, "wb") as f:
f.write(corte.read())
subprocess.run([
"ffmpeg", "-i", path_in,
"-vf", "scale=1280:720:force_original_aspect_ratio=decrease,scale=trunc(iw/2)*2:trunc(ih/2)*2",
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", path_out
], check=True, stderr=subprocess.PIPE)
cortes_names.append(path_out)
# ---------- SALVAR TUTORIAIS ----------
tutorials_salvos = []
if videos_tutorial:
for idx, tut in enumerate(videos_tutorial):
t_path = os.path.join(temp_dir, f"tutorial_salvo_{idx}.mp4")
with open(t_path, "wb") as f:
f.write(tut.read())
tutorials_salvos.append(t_path)
# ---------- SALVAR FUNDOS ----------
fundos_salvos = []
if videos_fundo:
for idx, fundo in enumerate(videos_fundo):
fundo_ext = os.path.splitext(fundo.name)[-1].lower()
fundo_path = os.path.join(temp_dir, f"fundo_salvo_{idx}{fundo_ext}")
with open(fundo_path, "wb") as f:
f.write(fundo.read())
fundos_salvos.append(fundo_path)
# ---------- LOOP PARA GERAR VÍDEOS ----------
for n in range(num_videos_finais):
progresso.progress(5 + n * 5)
random.shuffle(cortes_names)
tempo_total = 0
cortes_prontos = []
cortes_usados = []
# ---------- APLICAR OU SORTEAR CONFIGURAÇÕES ----------
if aleatorizar and num_videos_finais > 1:
duracao_corte_min_n = random.randint(1, 5)
duracao_corte_max_n = random.randint(duracao_corte_min_n + 1, 8)
zoom_n = round(random.uniform(1.0, 1.5), 2)
blur_strength_n = random.randint(5, 30)
velocidade_cortes_n = round(random.uniform(0.8, 1.5), 2)
velocidade_final_n = round(random.uniform(0.8, 1.3), 2)
# ----- FILTROS -----
todos_filtros = [
"blur_fundo", "sepia", "granulado", "pb", "vignette",
"brilho", "contraste", "colorboost", "azul", "quente", "desaturar", "vhs"
]
qtd_filtros = random.randint(1, 4)
filtros_escolhidos = random.sample(todos_filtros, qtd_filtros)
ativar_blur_fundo_n = "blur_fundo" in filtros_escolhidos
ativar_sepia_n = "sepia" in filtros_escolhidos
ativar_granulado_n = "granulado" in filtros_escolhidos
ativar_pb_n = "pb" in filtros_escolhidos
ativar_vignette_n = "vignette" in filtros_escolhidos
ativar_brilho_n = "brilho" in filtros_escolhidos
ativar_contraste_n = "contraste" in filtros_escolhidos
ativar_colorboost_n = "colorboost" in filtros_escolhidos
ativar_azul_n = "azul" in filtros_escolhidos
ativar_quente_n = "quente" in filtros_escolhidos
ativar_desaturar_n = "desaturar" in filtros_escolhidos
ativar_vhs_n = "vhs" in filtros_escolhidos
# ----- OUTROS EFEITOS -----
ativar_espelhar_n = random.choice([True, False])
# Corrigido: só aleatoriza borda se ativada
if remover_borda:
remover_borda_n = random.choice([True, False])
tamanho_borda_n = random.randint(0, 50)
else:
remover_borda_n = False
tamanho_borda_n = 0
else:
duracao_corte_min_n = duracao_corte_min
duracao_corte_max_n = duracao_corte_max
zoom_n = zoom
blur_strength_n = blur_strength
velocidade_cortes_n = velocidade_cortes
velocidade_final_n = velocidade_final
ativar_blur_fundo_n = ativar_blur_fundo
ativar_sepia_n = ativar_sepia
ativar_granulado_n = ativar_granulado
ativar_pb_n = ativar_pb
ativar_vignette_n = ativar_vignette
ativar_brilho_n = ativar_brilho
ativar_contraste_n = ativar_contraste
ativar_colorboost_n = ativar_colorboost
ativar_azul_n = ativar_azul
ativar_quente_n = ativar_quente
ativar_desaturar_n = ativar_desaturar
ativar_vhs_n = ativar_vhs
ativar_espelhar_n = ativar_espelhar
remover_borda_n = remover_borda
tamanho_borda_n = tamanho_borda
# ---------- ESCOLHER FUNDO ALEATÓRIO ----------
if fundos_salvos:
fundo_path = random.choice(fundos_salvos)
fundo_ext = os.path.splitext(fundo_path)[-1].lower()
fundo_convertido = os.path.join(temp_dir, f"fundo_convertido_{n}.mp4")
if fundo_ext in [".mp4", ".mov"]:
subprocess.run([
"ffmpeg", "-i", fundo_path, "-vf",
"scale='if(gt(iw/ih,720/1280),max(720,iw),-2)':'if(gt(iw/ih,720/1280),-2,max(1280,ih))',"
"scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,fps=30",
"-t", str(duracao_final), "-preset", "ultrafast", "-crf", "25",
fundo_convertido
], check=True, stderr=subprocess.PIPE)
else:
movimento = "scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280,"
subprocess.run([
"ffmpeg", "-loop", "1", "-i", fundo_path,
"-c:v", "libx264", "-t", str(duracao_final),
"-pix_fmt", "yuv420p",
"-vf", movimento + "fps=30",
"-preset", "ultrafast", "-crf", "25",
fundo_convertido
], check=True, stderr=subprocess.PIPE)
else:
fundo_convertido = os.path.join(temp_dir, f"fundo_preto_{n}.mp4")
subprocess.run([
"ffmpeg", "-f", "lavfi", "-i", "color=black:s=720x1280:d=600",
"-t", str(duracao_final),
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "25",
fundo_convertido
], check=True, stderr=subprocess.PIPE)
# ---------- ESCOLHER TUTORIAL ALEATÓRIO ----------
if tutorials_salvos:
tutorial_path = random.choice(tutorials_salvos)
tutorial_mp4 = os.path.join(temp_dir, f"tutorial_conv_{n}.mp4")
subprocess.run([
"ffmpeg", "-i", tutorial_path, "-vf", "scale=720:1280,fps=30",
"-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
"-y", tutorial_mp4
], check=True, stderr=subprocess.PIPE)
else:
tutorial_mp4 = None
# ---------- GERAR CORTES CURTOS COM DURAÇÃO ALEATÓRIA ----------
tentativas = 0
max_tentativas = 100
while tempo_total < duracao_final and tentativas < max_tentativas:
tentativas += 1
random.shuffle(cortes_names)
for c in cortes_names:
dur = subprocess.run([
"ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", c
], stdout=subprocess.PIPE).stdout.decode().strip()
try:
d = float(dur)
if d < duracao_corte_min_n + 0.5:
continue
duracao_aleatoria = random.uniform(duracao_corte_min_n, min(d, duracao_corte_max_n))
ini = random.uniform(0, d - duracao_aleatoria)
repetido = False
for usado in cortes_usados:
mesmo_video = c == usado[0]
sobreposicao = abs(ini - usado[1]) < 0.5
if mesmo_video and sobreposicao:
repetido = True
break
if repetido:
continue
out = os.path.join(temp_dir, f"cut_{random.randint(1000,9999)}.mp4")
subprocess.run([
"ffmpeg", "-ss", str(ini), "-i", c, "-t", str(duracao_aleatoria),
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
"-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
], check=True, stderr=subprocess.PIPE)
cortes_prontos.append(out)
cortes_usados.append((c, ini, duracao_aleatoria))
tempo_total += duracao_aleatoria / velocidade_cortes_n
if tempo_total >= duracao_final:
break
except:
continue
if tempo_total >= duracao_final:
break
lista = os.path.join(temp_dir, f"lista_{n}.txt")
with open(lista, "w") as f:
for c in cortes_prontos:
f.write(f"file '{c}'\n")
video_raw = os.path.join(temp_dir, f"video_raw_{n}.mp4")
subprocess.run([
"ffmpeg", "-f", "concat", "-safe", "0", "-i", lista,
"-vf", "scale=trunc(iw/2)*2:trunc(ih/2)*2",
"-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", video_raw
], check=True, stderr=subprocess.PIPE)
# ---------- FILTROS E TEXTO ----------
filtros_main = ["scale=720:1280:force_original_aspect_ratio=decrease"]
if zoom_n != 1.0:
filtros_main.append(f"scale=iw*{zoom_n}:ih*{zoom_n}")
filtros_main.append(f"setpts=PTS/{velocidade_cortes_n}")
if ativar_espelhar_n:
filtros_main.append("hflip")
if remover_borda_n and tamanho_borda_n > 0:
filtros_main.append(f"crop=in_w-{tamanho_borda_n*2}:in_h-{tamanho_borda_n*2}")
if ativar_filtro_cor:
filtros_main.append("eq=contrast=1.1:saturation=1.2")
filtros_main.append("scale=trunc(iw/2)*2:trunc(ih/2)*2")
if ativar_borda_personalizada:
cor_ffmpeg = f"0x{cor_borda.lstrip('#')}FF"
drawbox = f"drawbox=x=0:y=0:w=iw:h=ih:color={cor_ffmpeg}:t=5"
filtros_main.append(drawbox)
filtro_complex = (
f"[0:v]scale=720:1280:force_original_aspect_ratio=increase,crop=720:1280"
)
if ativar_blur_fundo_n:
filtro_complex += f",boxblur={blur_strength_n}:1"
if ativar_sepia_n:
filtro_complex += ",colorchannelmixer=.393:.769:.189:0:.349:.686:.168:0:.272:.534:.131"
if ativar_granulado_n:
filtro_complex += ",noise=alls=20:allf=t+u"
if ativar_pb_n:
filtro_complex += ",hue=s=0.3"
if ativar_vignette_n:
filtro_complex += ",vignette"
if ativar_brilho_n:
filtro_complex += ",eq=brightness=0.05"
if ativar_contraste_n:
filtro_complex += ",eq=contrast=1.3"
if ativar_colorboost_n:
filtro_complex += ",eq=saturation=1.5"
if ativar_azul_n:
filtro_complex += ",colorbalance=bs=0.5"
if ativar_quente_n:
filtro_complex += ",colorbalance=rs=0.3"
if ativar_desaturar_n:
filtro_complex += ",hue=s=0.5"
if ativar_vhs_n:
filtro_complex += ",noise=alls=10:allf=t+u,format=yuv420p"
filtro_complex += "[blur];"
filtro_complex += f"[1:v]{','.join(filtros_main)}[zoomed];"
filtro_complex += "[blur][zoomed]overlay=(W-w)/2:(H-h)/2[base]"
if ativar_texto and texto_personalizado.strip():
y_pos = "100" if posicao_texto == "Topo" else "(h-text_h)/2" if posicao_texto == "Centro" else "h-text_h-100"
enable = f":enable='lt(t\\,{segundos_texto})'" if duracao_texto == "Apenas primeiros segundos" else ""
texto_clean = texto_personalizado.replace(":", "\\:").replace("'", "\\'")
filtro_complex += f";[base]drawtext=text='{texto_clean}':"
filtro_complex += (
f"fontfile=/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf:"
f"fontcolor={cor_texto}:fontsize={tamanho_texto}:"
f"shadowcolor={cor_sombra}:shadowx=3:shadowy=3:"
f"x=(w-text_w)/2:y={y_pos}{enable}[final]"
)
else:
filtro_complex += ";[base]null[final]"
video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
subprocess.run([
"ffmpeg", "-i", fundo_convertido, "-i", video_raw,
"-filter_complex", filtro_complex,
"-map", "[final]",
"-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
video_editado
], check=True, stderr=subprocess.PIPE)
# ---------- ACELERAR VÍDEO ----------
video_acelerado = os.path.join(temp_dir, f"video_acelerado_{n}.mp4")
subprocess.run([
"ffmpeg", "-y", "-i", video_editado, "-an",
"-filter:v", f"setpts=PTS/{velocidade_final_n}",
"-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
video_acelerado
], check=True, stderr=subprocess.PIPE)
video_final_raw = video_acelerado
# ---------- INSERIR TUTORIAL ----------
if tutorial_mp4:
dur_proc = subprocess.run([
"ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", video_acelerado
], stdout=subprocess.PIPE)
dur_f = float(dur_proc.stdout.decode().strip())
pt = dur_f / 2 if dur_f < 10 else random.uniform(5, dur_f - 5)
part1 = os.path.join(temp_dir, f"part1_{n}.mp4")
part2 = os.path.join(temp_dir, f"part2_{n}.mp4")
subprocess.run([
"ffmpeg", "-i", video_acelerado, "-ss", "0", "-t", str(pt),
"-c:v", "libx264", "-preset", "ultrafast", part1
], check=True, stderr=subprocess.PIPE)
subprocess.run([
"ffmpeg", "-i", video_acelerado, "-ss", str(pt),
"-c:v", "libx264", "-preset", "ultrafast", part2
], check=True, stderr=subprocess.PIPE)
final_txt = os.path.join(temp_dir, f"final_{n}.txt")
with open(final_txt, "w") as f:
f.write(f"file '{part1}'\nfile '{tutorial_mp4}'\nfile '{part2}'\n")
video_final_raw = os.path.join(temp_dir, f"video_final_raw_{n}.mp4")
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)
# ---------- ADICIONAR MÚSICA ----------
dur_proc = subprocess.run([
"ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", video_final_raw
], stdout=subprocess.PIPE)
dur_video_real = float(dur_proc.stdout.decode().strip())
if musica:
musica_path = os.path.join(temp_dir, "musica_original.mp3")
with open(musica_path, "wb") as f:
f.write(musica.read())
musica_cortada = os.path.join(temp_dir, f"musica_cortada_{n}.aac")
subprocess.run([
"ffmpeg", "-i", musica_path, "-ss", "0", "-t", str(dur_video_real),
"-vn", "-acodec", "aac", "-y", musica_cortada
], check=True, stderr=subprocess.PIPE)
final_name = f"video_final_{n}_{int(time.time())}.mp4"
subprocess.run([
"ffmpeg", "-i", video_final_raw, "-i", musica_cortada,
"-map", "0:v:0", "-map", "1:a:0",
"-c:v", "copy", "-c:a", "aac",
"-shortest", final_name
], check=True, stderr=subprocess.PIPE)
else:
final_name = f"video_final_{n}_{int(time.time())}.mp4"
shutil.copy(video_final_raw, final_name)
# ---------- DOWNLOAD OU SALVAR ----------
if salvar_no_gerenciador and categoria_selecionada:
destino = os.path.join("uploaded_files", categoria_selecionada, final_name)
os.makedirs(os.path.join("uploaded_files", categoria_selecionada), exist_ok=True)
shutil.move(final_name, destino)
st.success(f"✅ Vídeo {n+1} salvo na categoria '{categoria_selecionada}'.")
else:
st.video(final_name)
with open(final_name, "rb") as f:
st.download_button(f"📥 Baixar vídeo {n+1}", f, file_name=final_name)
progresso.progress(100)
st.success("✅ Todos os vídeos foram gerados com sucesso!")
except subprocess.CalledProcessError as e:
st.error(f"❌ Erro ao gerar vídeo:\n\n{e.stderr.decode(errors='ignore')}")
finally:
shutil.rmtree(temp_dir)
elif pagina == "📂 Gerenciador de Arquivos":
st.header("📂 Vídeos Salvos por Categoria")
BASE_FOLDER = "uploaded_files"
CATEGORIES = ["AVATAR WORLD", "BLOX FRUITS", "TOCA LIFE"]
st.markdown("""
""", unsafe_allow_html=True)
for categoria in CATEGORIES:
folder = os.path.join(BASE_FOLDER, categoria)
os.makedirs(folder, exist_ok=True)
arquivos = os.listdir(folder)
st.subheader(f"📁 {categoria}")
if not arquivos:
st.info("Nenhum vídeo salvo nessa categoria.")
else:
for file in arquivos:
file_path = os.path.join(folder, file)
st.markdown("", unsafe_allow_html=True)
st.markdown(f"
{file}
", unsafe_allow_html=True)
assistir = st.button(f"▶ Assistir {file}", key=f"assistir_{categoria}_{file}")
if assistir:
st.video(file_path)
col1, col2, col3 = st.columns(3)
with col1:
with open(file_path, "rb") as f_obj:
st.download_button("⬇ Download", f_obj, file_name=file, key=f"down_{categoria}_{file}")
with col2:
if st.button("🗑 Excluir", key=f"delete_{categoria}_{file}"):
os.remove(file_path)
st.success(f"Arquivo '{file}' excluído.")
st.rerun()
with col3:
with open(file_path, "rb") as f_obj:
if st.download_button("⬇ Baixar & Apagar", f_obj, file_name=file, key=f"down_del_{categoria}_{file}"):
os.remove(file_path)
st.success(f"Arquivo '{file}' baixado e excluído.")
st.rerun()
st.markdown("
", unsafe_allow_html=True)