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)