Spaces:
Sleeping
Sleeping
| 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("<div style='background-color:#F0F0FF;padding:10px;border-radius:8px'>", 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("</div>", unsafe_allow_html=True) | |
| # Upload de múltiplos vídeos ou imagens de fundo | |
| st.markdown("<div style='background-color:#FFF0F0;padding:10px;border-radius:8px;margin-top:15px;'>", 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("</div>", 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(""" | |
| <style> | |
| .file-box { | |
| border: 1px solid #ccc; | |
| padding: 10px; | |
| margin-bottom: 10px; | |
| border-radius: 5px; | |
| background-color: #f9f9f9; | |
| } | |
| .file-name { | |
| font-size: 16px; | |
| font-weight: bold; | |
| } | |
| </style> | |
| """, 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("<div class='file-box'>", unsafe_allow_html=True) | |
| st.markdown(f"<div class='file-name'>{file}</div>", 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("</div>", unsafe_allow_html=True) | |