|
import streamlit as st |
|
import subprocess |
|
import os |
|
import random |
|
import tempfile |
|
import shutil |
|
import time |
|
import base64 |
|
|
|
|
|
st.set_page_config(page_title="Shorts Generator", layout="centered") |
|
|
|
|
|
abas = ["🎬 Gerador", "🔐 Admin"] |
|
pagina = st.sidebar.radio("Escolha uma página:", abas) |
|
|
|
|
|
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) |
|
|
|
|
|
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!") |
|
|
|
|
|
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}") |
|
|
|
|
|
cortes = st.file_uploader("Envie os vídeos de cortes", type=["mp4"], accept_multiple_files=True) |
|
|
|
|
|
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) |
|
|
|
|
|
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 = st.slider("Corte lateral (%)", 0, 50, 0, 5) |
|
zoom = st.slider("Zoom Central (1.0 = normal)", 1.0, 2.0, 1.0, 0.1) |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
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: |
|
|
|
cortes_names = [] |
|
|
|
|
|
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) |
|
|
|
|
|
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) |
|
|
|
|
|
elif pagina == "🔐 Admin": |
|
st.title("🔐 Área de Administração (CORTES)") |
|
|
|
|
|
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() |
|
|
|
|
|
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) |
|
|
|
|
|
shutil.move(temp_in, final_out) |
|
|
|
st.success("✅ Cortes enviados, padronizados e salvos com sucesso!") |
|
|
|
|
|
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() |
|
|