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( """
Processando...
""", 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( """
Aguarde, preparando os players...
""", unsafe_allow_html=True ) time.sleep(1) st.subheader("🔎 Comparação lado a lado") st.markdown(f"""
🎬 Original
🎬 Novo
""", 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