youmassa / app.py
pcdoido2's picture
Update app.py
669458b verified
import streamlit as st
import subprocess
import random
import os
import tempfile
import time
import base64
import uuid
import shutil
import datetime
import yt_dlp
st.set_page_config(page_title="🎬 Cortar e Embaralhar Vídeo", layout="centered")
st.title("🎬 Cortar e Embaralhar Vídeo")
st.markdown("**1️⃣ Envie um vídeo curto (máx 3 minutos)**")
video = st.file_uploader("Selecione um vídeo", type=["mp4"])
st.markdown("**🔗 Ou insira o link do TikTok**")
url = st.text_input("URL do vídeo TikTok")
# ----- ESTADO -----
if "processando" not in st.session_state:
st.session_state.processando = False
if "video_path" not in st.session_state:
st.session_state.video_path = None
if "tutorial_path" not in st.session_state:
st.session_state.tutorial_path = None
if "video_final" not in st.session_state:
st.session_state.video_final = None
# ----- BOTÃO PUXAR VÍDEO -----
if url and st.button("🔄 Puxar vídeo do TikTok"):
with st.spinner("Baixando vídeo do TikTok..."):
unique_id = str(uuid.uuid4())
saida_video = os.path.join(tempfile.gettempdir(), f'tiktok_video_{unique_id}.mp4')
ydl_opts = {
'outtmpl': saida_video,
'format': 'mp4',
'noplaylist': True
}
try:
with yt_dlp.YoutubeDL(ydl_opts) as ydl:
ydl.download([url])
if os.path.exists(saida_video):
st.session_state.video_path = saida_video
st.success("✅ Vídeo do TikTok baixado com sucesso!")
except Exception as e:
st.error(f"Erro ao baixar vídeo: {e}")
# ----- SE USOU UPLOAD -----
if video and not st.session_state.video_path:
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
tmp.write(video.read())
st.session_state.video_path = tmp.name
video_path = st.session_state.video_path
# ----- PLAYER DO VÍDEO ANTES DOS CORTES -----
if video_path and not st.session_state.processando:
st.subheader("🎥 Pré-visualização do vídeo original")
st.video(video_path)
st.markdown("**2️⃣ Configure a exclusão e os cortes**")
excluir_inicio = st.number_input("Excluir a partir do segundo", min_value=0, value=8, key="excluir_inicio")
excluir_fim = st.number_input("Excluir até o segundo", min_value=0, value=12, key="excluir_fim")
st.checkbox("✂️ Ativar cortes e embaralhamento aleatório", value=False, key="embaralhar_cortes")
if st.session_state.embaralhar_cortes:
corte_min = st.number_input("Tamanho mínimo do corte (segundos)", min_value=1, value=2, key="corte_min")
corte_max = st.number_input("Tamanho máximo do corte (segundos)", min_value=3, value=5, key="corte_max")
tutorial = st.file_uploader("Adicionar tutorial (opcional)", type=["mp4"])
if tutorial:
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp4") as tmp:
tmp.write(tutorial.read())
st.session_state.tutorial_path = tmp.name
with st.expander("⚙️ Opções avançadas (zoom, velocidade, música, espelhamento)"):
zoom = st.slider("Zoom (%)", 100, 300, 110, key="zoom")
velocidade_cortes = st.slider("Velocidade dos cortes", 0.8, 2.0, 1.0, key="velocidade_cortes")
velocidade_final = st.slider("Velocidade final do vídeo", 0.8, 2.0, 1.0, key="velocidade_final")
espelhar = st.checkbox("Espelhar vídeo final", value=False, key="espelhar")
musica = st.file_uploader("Adicionar música (opcional)", type=["mp3"])
if musica:
with tempfile.NamedTemporaryFile(delete=False, suffix=".mp3") as tmpmus:
tmpmus.write(musica.read())
st.session_state.musica_path = tmpmus.name
else:
st.session_state.musica_path = None
processar = st.button("Gerar novo vídeo")
if processar:
st.session_state.processando = True
st.session_state.video_final = None # Reset do vídeo final
if st.session_state.processando:
loader = st.empty()
barra = st.progress(0)
loader.markdown(
"""
<div style='display:flex; align-items:center; gap:10px; font-size:20px;'>
<span class="loader"></span> <strong>Processando...</strong>
</div>
<style>
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
""", unsafe_allow_html=True
)
status = st.empty()
status.markdown("🔎 Analisando duração e resolução do vídeo...")
barra.progress(5)
# Duração e resolução
duracao_cmd = [
"ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of", "default=noprint_wrappers=1:nokey=1", video_path
]
resultado = subprocess.run(duracao_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
dur = float(resultado.stdout.decode().strip())
dur = int(dur)
resolucao_cmd = [
"ffprobe", "-v", "error", "-select_streams", "v:0",
"-show_entries", "stream=width,height",
"-of", "csv=s=x:p=0", video_path
]
resultado = subprocess.run(resolucao_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
largura, altura = resultado.stdout.decode().strip().split("x")
largura = int(largura)
altura = int(altura)
excluir_inicio = st.session_state.excluir_inicio
excluir_fim = st.session_state.excluir_fim
velocidade_cortes = st.session_state.velocidade_cortes
barra.progress(10)
status.markdown("🧹 Removendo parte indesejada...")
with tempfile.TemporaryDirectory() as tmpdir:
parte1 = os.path.join(tmpdir, "parte1.mp4")
parte2 = os.path.join(tmpdir, "parte2.mp4")
video_base = os.path.join(tmpdir, "video_base.mp4")
if excluir_inicio > 0:
subprocess.call([
"ffmpeg", "-y", "-i", video_path,
"-t", str(excluir_inicio),
"-c", "copy",
parte1
])
if excluir_fim < dur:
subprocess.call([
"ffmpeg", "-y", "-i", video_path,
"-ss", str(excluir_fim),
"-c", "copy",
parte2
])
with open(os.path.join(tmpdir, "lista_remocao.txt"), "w") as f:
if os.path.exists(parte1):
f.write(f"file '{parte1}'\n")
if os.path.exists(parte2):
f.write(f"file '{parte2}'\n")
subprocess.call([
"ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i",
os.path.join(tmpdir, "lista_remocao.txt"),
"-c", "copy",
video_base
])
barra.progress(15)
status.markdown("✂️ Preparando cortes...")
cortes = []
usados = set()
if st.session_state.embaralhar_cortes:
corte_min = st.session_state.corte_min
corte_max = st.session_state.corte_max
# Duração nova (do vídeo já limpo)
resultado = subprocess.run([
"ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of",
"default=noprint_wrappers=1:nokey=1", video_base
], stdout=subprocess.PIPE)
dur_base = float(resultado.stdout.decode().strip())
pos = 0
while pos < dur_base:
tam_possivel = min(corte_max, dur_base - pos)
if tam_possivel < corte_min:
break
tamanho = random.randint(corte_min, int(tam_possivel))
fim_corte = pos + tamanho
if (pos, fim_corte) not in usados:
cortes.append((int(pos), int(fim_corte)))
usados.add((pos, fim_corte))
pos = fim_corte
random.shuffle(cortes)
else:
# Sem cortes — usa vídeo já limpo
cortes = [(0, dur)] # isso será ignorado e o vídeo_base usado direto mais abaixo
barra.progress(25)
arquivos_cortes = []
total_cortes = len(cortes)
for idx, (start, end) in enumerate(cortes):
saida = os.path.join(tmpdir, f"clip_{idx}.mp4")
subprocess.call([
"ffmpeg", "-y", "-i", video_base,
"-ss", str(start), "-to", str(end),
"-filter:v", f"setpts=PTS/{velocidade_cortes}",
"-an",
"-c:v", "libx264", "-preset", "veryfast",
saida
])
if os.path.exists(saida) and os.path.getsize(saida) > 1000:
arquivos_cortes.append(saida)
barra.progress(25 + int(((idx + 1) / total_cortes) * 30))
if not arquivos_cortes:
barra.empty()
loader.empty()
status.markdown("❌ Nenhum corte foi criado.")
st.stop()
barra.progress(60)
status.markdown("🔗 Unindo cortes...")
lista_txt = os.path.join(tmpdir, "lista_cortes.txt")
with open(lista_txt, "w") as f:
for arquivo in arquivos_cortes:
f.write(f"file '{arquivo}'\n")
video_unido = os.path.join(tmpdir, "video_unido.mp4")
subprocess.call([
"ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", lista_txt,
"-c:v", "libx264", "-preset", "veryfast",
video_unido
])
barra.progress(70)
status.markdown("🔍 Aplicando zoom e espelhamento no vídeo unido...")
video_processado = os.path.join(tmpdir, "video_processado.mp4")
zoom_factor = st.session_state.zoom / 100
filtro_zoom = f"scale=iw*{zoom_factor}:ih*{zoom_factor},crop={largura}:{altura}"
if st.session_state.espelhar:
filtro_zoom += ",hflip"
subprocess.call([
"ffmpeg", "-y", "-i", video_unido,
"-vf", filtro_zoom,
"-c:v", "libx264", "-preset", "veryfast",
video_processado
])
barra.progress(75)
status.markdown("📌 Inserindo tutorial (se houver)...")
if st.session_state.tutorial_path:
# Pega duração do vídeo processado
duracao_cmd = [
"ffprobe", "-v", "error", "-show_entries",
"format=duration", "-of",
"default=noprint_wrappers=1:nokey=1", video_processado
]
resultado = subprocess.run(duracao_cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
duracao_video = float(resultado.stdout.decode().strip())
# Escolhe posição aleatória entre 3s e final - 1s
pos_tutorial = random.uniform(3, duracao_video - 1)
parte1 = os.path.join(tmpdir, "parte1.mp4")
parte2 = os.path.join(tmpdir, "parte2.mp4")
subprocess.call([
"ffmpeg", "-y", "-i", video_processado,
"-t", str(pos_tutorial),
"-c", "copy",
parte1
])
subprocess.call([
"ffmpeg", "-y", "-i", video_processado,
"-ss", str(pos_tutorial),
"-c", "copy",
parte2
])
tutorial_pad = os.path.join(tmpdir, "tutorial_pad.mp4")
subprocess.call([
"ffmpeg", "-y", "-i", st.session_state.tutorial_path,
"-vf", f"scale={largura}:{altura}",
"-c:v", "libx264", "-preset", "veryfast",
"-c:a", "aac",
tutorial_pad
])
lista_final_txt = os.path.join(tmpdir, "lista_final.txt")
with open(lista_final_txt, "w") as f:
f.write(f"file '{parte1}'\n")
f.write(f"file '{tutorial_pad}'\n")
f.write(f"file '{parte2}'\n")
video_com_tutorial = os.path.join(tmpdir, "video_com_tutorial.mp4")
subprocess.call([
"ffmpeg", "-y", "-f", "concat", "-safe", "0", "-i", lista_final_txt,
"-c:v", "libx264", "-preset", "veryfast",
video_com_tutorial
])
else:
video_com_tutorial = video_processado
barra.progress(80)
status.markdown("🚀 Aplicando velocidade final...")
video_final_temp = os.path.join(tmpdir, "video_final_temp.mp4")
subprocess.call([
"ffmpeg", "-y", "-i", video_com_tutorial,
"-filter:v", f"setpts=PTS/{st.session_state.velocidade_final}",
"-c:v", "libx264", "-preset", "veryfast",
video_final_temp
])
video_final = os.path.join(tmpdir, "video_final.mp4")
if st.session_state.musica_path:
barra.progress(90)
status.markdown("🎵 Adicionando música...")
subprocess.call([
"ffmpeg", "-y", "-i", video_final_temp, "-i", st.session_state.musica_path,
"-shortest",
"-c:v", "copy",
"-c:a", "aac",
video_final
])
else:
os.rename(video_final_temp, video_final)
barra.progress(95)
status.markdown("📋 Adicionando metadata...")
marcas = {
"samsung": ["SM-S918B", "SM-A546E", "SM-M536B"],
"xiaomi": ["Redmi Note 12", "Poco X5", "Mi 11 Lite"],
"motorola": ["Moto G84", "Moto Edge 40", "Moto E13"],
"apple": ["iPhone 13", "iPhone 14 Pro", "iPhone SE"],
"google": ["Pixel 6", "Pixel 7a"]
}
marca = random.choice(list(marcas.keys()))
modelo = random.choice(marcas[marca])
software = f"{marca.capitalize()} Video Editor {random.randint(1,5)}.{random.randint(0,9)}"
latitude = round(random.uniform(-33.0, -2.0), 4)
longitude = round(random.uniform(-60.0, -35.0), 4)
location = f"{latitude:+.4f}{longitude:+.4f}/"
creation_time = (datetime.datetime.now() - datetime.timedelta(days=random.randint(0,5))).strftime("%Y-%m-%dT%H:%M:%SZ")
video_final_meta = os.path.join(tmpdir, "video_final_meta.mp4")
metadata_cmd = [
"ffmpeg", "-y", "-i", video_final,
"-metadata", "title=Video Shorts",
"-metadata", "comment=Captured with mobile device",
"-metadata", f"make={marca}",
"-metadata", f"model={modelo}",
"-metadata", f"software={software}",
"-metadata", f"creation_time={creation_time}",
"-metadata", f"location={location}",
"-c", "copy",
video_final_meta
]
subprocess.call(metadata_cmd)
barra.empty()
loader.empty()
status.empty()
barra.progress(100)
video_final_permanente = os.path.join(tempfile.gettempdir(), "video_final_final.mp4")
shutil.copy(video_final_meta, video_final_permanente)
st.session_state.video_final = video_final_permanente
# --- PLAYER DOS VÍDEOS E BOTÕES ---
if st.session_state.video_final:
st.success("✅ Vídeo criado com sucesso!")
load_players = st.empty()
load_players.markdown(
"""
<div style='display:flex; align-items:center; gap:10px; font-size:20px; margin-top:20px;'>
<span class="loader"></span> <strong>Aguarde, preparando os players...</strong>
</div>
<style>
.loader {
border: 4px solid #f3f3f3;
border-top: 4px solid #3498db;
border-radius: 50%;
width: 20px;
height: 20px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
""", unsafe_allow_html=True
)
time.sleep(1)
st.subheader("🔎 Comparação lado a lado")
st.markdown(f"""
<div style='display: flex; gap: 10px; justify-content: center; align-items: flex-start; flex-wrap: nowrap; overflow-x: auto;'>
<div style='flex: 1; min-width: 45%;'>
<strong>🎬 Original</strong><br>
<video controls style='width: 100%; height: auto;'>
<source src="data:video/mp4;base64,{base64.b64encode(open(video_path, "rb").read()).decode()}" type="video/mp4">
</video>
</div>
<div style='flex: 1; min-width: 45%;'>
<strong>🎬 Novo</strong><br>
<video controls style='width: 100%; height: auto;'>
<source src="data:video/mp4;base64,{base64.b64encode(open(st.session_state.video_final, "rb").read()).decode()}" type="video/mp4">
</video>
</div>
</div>
""", unsafe_allow_html=True)
load_players.empty()
with open(st.session_state.video_final, "rb") as f:
st.download_button("⬇️ Baixar vídeo", f, file_name="video_novo.mp4")
if st.button("🔄 Criar novo"):
for key in list(st.session_state.keys()):
del st.session_state[key]
st.experimental_rerun()
if st.button("🔄 Gerar novamente com os mesmos ajustes"):
st.session_state.processando = True
st.session_state.video_final = None