juntar / app.py
pcdoido2's picture
Update app.py
b4138bb verified
import streamlit as st
import subprocess
import os
import random
import tempfile
import shutil
import time
import base64
# ======================= CONFIG GERAL =======================
st.set_page_config(page_title="Shorts Generator", layout="centered")
# Abas laterais (igual ao primeiro app)
abas = ["🎬 Gerador", "🔐 Admin"]
pagina = st.sidebar.radio("Escolha uma página:", abas)
# Categorias e estrutura de pastas
CATEGORIAS = ["AVATAR WORLD", "BLOX FRUITS", "TOCA LIFE", "FC MOBILE"]
BASE_ASSETS = "assets"
for cat in CATEGORIAS:
os.makedirs(os.path.join(BASE_ASSETS, cat, "cortes"), exist_ok=True)
# ======================= PÁGINA GERADOR =======================
if pagina == "🎬 Gerador":
st.title("🎥 Shorts Generator - Simples")
st.markdown("Envie seus vídeos e/ou use cortes salvos por categoria para gerar conteúdo com efeitos e controle de duração!")
# Seleção de categoria para usar cortes salvos (opcional)
categoria_selecionada = st.selectbox(
"Escolha a categoria (opcional, para usar cortes já salvos):",
["Selecione..."] + CATEGORIAS
)
cortes_salvos_paths = []
if categoria_selecionada != "Selecione...":
path_cortes_salvos = os.path.join(BASE_ASSETS, categoria_selecionada, "cortes")
if os.path.isdir(path_cortes_salvos):
cortes_salvos_paths = [
os.path.join(path_cortes_salvos, f)
for f in os.listdir(path_cortes_salvos)
if f.lower().endswith(".mp4")
]
st.caption(f"Encontrados {len(cortes_salvos_paths)} cortes salvos em: {path_cortes_salvos}")
# Upload dos vídeos (MANTIDO)
cortes = st.file_uploader("Envie os vídeos de cortes", type=["mp4"], accept_multiple_files=True)
# Configurações (MANTIDO)
num_videos_finais = st.number_input("Quantos vídeos finais gerar?", min_value=1, max_value=10, value=1)
duracao_final = st.number_input("Duração final do vídeo (em segundos)", min_value=10, max_value=300, value=30)
# Min e Max duração dos cortes (MANTIDO)
duracao_min = st.slider("Duração mínima de cada corte (s)", 1, 30, 3)
duracao_max = st.slider("Duração máxima de cada corte (s)", 1, 60, 5)
if duracao_min > duracao_max:
st.warning("⚠️ A duração mínima não pode ser maior que a máxima.")
# Corte lateral e zoom (MANTIDO)
corte_lateral = st.slider("Corte lateral (%)", 0, 50, 0, 5)
zoom = st.slider("Zoom Central (1.0 = normal)", 1.0, 2.0, 1.0, 0.1)
# Velocidades e qualidade (MANTIDO)
velocidade_cortes = st.slider("Velocidade dos cortes", 0.5, 2.0, 1.0, 0.1)
velocidade_final = st.slider("Velocidade final do vídeo", 0.5, 2.0, 1.0, 0.1)
crf_value = st.slider("Qualidade CRF (menor = melhor qualidade)", 18, 30, 18)
# Outros (MANTIDO)
st.write("### Outros")
ativar_espelhar = st.checkbox("Espelhar Vídeo", value=True)
ativar_filtro_cor = st.checkbox("Filtro de Cor (Contraste/Saturação)", value=True)
# Botão principal (MANTIDO, agora combinando uploads + salvos)
if st.button("Gerar Vídeo(s)"):
if not cortes and len(cortes_salvos_paths) == 0:
st.error("❌ Envie vídeos OU selecione uma categoria com cortes salvos para continuar.")
else:
with st.spinner('🎥 Seu vídeo está sendo gerado...'):
progresso = st.progress(0)
temp_dir = tempfile.mkdtemp()
try:
# === PREPARAR LISTA FINAL DE ARQUIVOS DE CORTE ===
cortes_names = []
# 1) Arquivos enviados (MANTIDO)
if cortes:
for idx, corte in enumerate(cortes):
nome = os.path.join(temp_dir, f"corte_{idx}_{random.randint(1000,9999)}.mp4")
with open(nome, "wb") as f:
f.write(corte.read())
cortes_names.append(nome)
# 2) Arquivos salvos na categoria (NOVO – usamos os caminhos diretamente)
if len(cortes_salvos_paths) > 0:
cortes_names.extend(cortes_salvos_paths)
if len(cortes_names) == 0:
st.error("❌ Não há cortes disponíveis após combinar uploads e salvos.")
shutil.rmtree(temp_dir)
st.stop()
progresso.progress(10)
for n in range(num_videos_finais):
cortes_prontos = []
random.shuffle(cortes_names)
progresso.progress(10 + int(20 * (n / num_videos_finais)))
tempo_total = 0
while tempo_total < duracao_final:
for c in cortes_names:
dur_proc = subprocess.run([
"ffprobe", "-v", "error", "-show_entries", "format=duration",
"-of", "default=noprint_wrappers=1:nokey=1", c
], stdout=subprocess.PIPE)
dur = dur_proc.stdout.decode().strip()
try:
d = float(dur)
dur_aleatoria = random.uniform(duracao_min, duracao_max)
if d > dur_aleatoria:
ini = random.uniform(0, d - dur_aleatoria)
out = os.path.join(temp_dir, f"cut_{n}_{random.randint(1000,9999)}.mp4")
subprocess.run([
"ffmpeg", "-ss", str(ini), "-i", c, "-t", str(dur_aleatoria),
"-an", "-c:v", "libx264", "-preset", "ultrafast", "-crf", "30", out
], check=True, stderr=subprocess.PIPE)
cortes_prontos.append(out)
tempo_total += dur_aleatoria / velocidade_cortes
if tempo_total >= duracao_final:
break
except:
continue
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, "-c:v", "libx264",
"-preset", "ultrafast", "-crf", "30", video_raw
], check=True, stderr=subprocess.PIPE)
progresso.progress(40 + int(20 * (n / num_videos_finais)))
filtros_main = []
if corte_lateral > 0:
fator = (100 - corte_lateral) / 100
filtros_main.append(f"crop=iw*{fator}:ih:(iw-iw*{fator})/2:0")
if zoom != 1.0:
filtros_main.append(f"scale=iw*{zoom}:ih*{zoom},crop=iw/{zoom}:ih/{zoom}")
filtros_main.append(f"setpts=PTS/{velocidade_cortes}")
if ativar_espelhar:
filtros_main.append("hflip")
if ativar_filtro_cor:
filtros_main.append("eq=contrast=1.1:saturation=1.2")
filtros_main.append("pad=ceil(iw/2)*2:ceil(ih/2)*2")
filtro_final = ",".join(filtros_main)
video_editado = os.path.join(temp_dir, f"video_editado_{n}.mp4")
subprocess.run([
"ffmpeg", "-i", video_raw, "-vf", filtro_final,
"-c:v", "libx264", "-preset", "ultrafast", "-crf", str(crf_value),
video_editado
], check=True, stderr=subprocess.PIPE)
video_final = f"video_final_{n}_{int(time.time())}.mp4"
subprocess.run([
"ffmpeg", "-i", video_editado, "-filter:v", f"setpts=PTS/{velocidade_final}",
"-t", str(duracao_final), "-c:v", "libx264", "-preset", "ultrafast",
"-crf", str(crf_value), "-map_metadata", "-1", "-movflags", "+faststart", video_final
], check=True, stderr=subprocess.PIPE)
progresso.progress(90 + int(10 * (n / num_videos_finais)))
st.video(video_final)
with open(video_final, "rb") as f:
st.download_button(f"📅 Baixar Vídeo {n+1}", f, file_name=video_final)
progresso.progress(100)
st.success("✅ Todos os vídeos foram gerados com sucesso!")
except subprocess.CalledProcessError as e:
st.error(f"Erro durante processamento: {e.stderr.decode()}")
except Exception as e:
st.error(f"Erro inesperado: {str(e)}")
finally:
shutil.rmtree(temp_dir)
# ======================= PÁGINA ADMIN =======================
elif pagina == "🔐 Admin":
st.title("🔐 Área de Administração (CORTES)")
# Mesma senha do seu primeiro código: base64 de "admin123"
SENHA_CODIFICADA = "YWRtaW4xMjM="
if "autenticado_admin" not in st.session_state:
st.session_state.autenticado_admin = False
if not st.session_state.autenticado_admin:
senha_input = st.text_input("Senha do Admin:", type="password")
if st.button("Entrar"):
if base64.b64encode(senha_input.encode()).decode() == SENHA_CODIFICADA:
st.session_state.autenticado_admin = True
st.success("✅ Acesso liberado!")
else:
st.error("❌ Senha incorreta.")
st.stop()
if not st.session_state.autenticado_admin:
st.stop()
# Seleção da categoria e upload de CORTES
cat_admin = st.selectbox("Categoria para salvar os cortes:", CATEGORIAS, key="cat_admin_upload")
pasta_admin = os.path.join(BASE_ASSETS, cat_admin, "cortes")
st.caption(f"Pasta de destino: `{pasta_admin}`")
arquivos_admin = st.file_uploader(
"Enviar arquivos de CORTES (.mp4) para esta categoria",
type=["mp4"], accept_multiple_files=True, key="admin_uploader"
)
if arquivos_admin:
with st.spinner("⏳ Processando e padronizando cortes..."):
for f in arquivos_admin:
ext = os.path.splitext(f.name)[1].lower()
nome_aleatorio = f"{random.randint(1000000, 9999999)}{ext}"
temp_in = os.path.join(tempfile.gettempdir(), f"adm_input_{nome_aleatorio}")
with open(temp_in, "wb") as out:
out.write(f.read())
final_out = os.path.join(pasta_admin, nome_aleatorio)
# Agora salvando o arquivo diretamente, sem padronizar
shutil.move(temp_in, final_out)
st.success("✅ Cortes enviados, padronizados e salvos com sucesso!")
# Listagem e exclusão
st.write("Arquivos existentes nesta categoria:")
try:
arquivos_existentes = sorted([f for f in os.listdir(pasta_admin) if f.lower().endswith(".mp4")])
except FileNotFoundError:
arquivos_existentes = []
if not arquivos_existentes:
st.info("Nenhum arquivo encontrado nesta categoria.")
else:
for arq in arquivos_existentes:
col1, col2 = st.columns([5, 1])
with col1:
st.markdown(f"`{arq}`")
with col2:
if st.button("🗑", key=f"del_{cat_admin}_{arq}"):
os.remove(os.path.join(pasta_admin, arq))
st.success(f"❌ Arquivo '{arq}' excluído.")
st.rerun()