youmassa / app.py
pcdoido2's picture
Update app.py
669458b verified
Raw
History Blame Contribute Delete
17.4 kB
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