import streamlit as st import os import subprocess import tempfile import shutil import base64 import random import string from datetime import datetime, timedelta st.set_page_config(page_title="Turbo Spinner Shorts PRO", layout="centered") st.title("🎬 Turbo Spinner Shorts PRO (TikTok Safe)") # Diretório temporário temp_dir = tempfile.mkdtemp() uploaded_file = st.file_uploader("📤 Envie seu vídeo", type=["mp4", "mov", "avi", "mkv"]) def run_ffmpeg(cmd, label, output_file): progress_bar = st.progress(0, text=f"⏳ {label} em andamento...") process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) percent = 0 while process.poll() is None: if percent < 100: percent += 2 progress_bar.progress(percent, text=f"⏳ {label} {percent}%") process.wait() progress_bar.progress(100, text=f"✅ {label} finalizado!") return os.path.exists(output_file) and os.path.getsize(output_file) > 0 def make_download_button(file_path, label): with open(file_path, "rb") as f: b64 = base64.b64encode(f.read()).decode() href = f'⬇️ {label}' st.markdown(href, unsafe_allow_html=True) def random_string(n=6): return ''.join(random.choices(string.ascii_lowercase + string.digits, k=n)) def gerar_nome_saida(): agora = datetime.now() data = agora.strftime("%Y%m%d") hora = agora.strftime("%H%M%S") return f"lv_0_{data}{hora}.mp4" def get_duration(video_path): try: result = subprocess.run( ['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'default=noprint_wrappers=1:nokey=1', video_path], stdout=subprocess.PIPE, stderr=subprocess.STDOUT ) return float(result.stdout) except Exception: return 15.0 def gerar_microdrift(dur, base_speed=1.0): expr = f"{1/base_speed}*PTS" for _ in range(2): start = round(random.uniform(0, max(1, dur-2)), 2) end = round(start + random.uniform(0.5, 2), 2) if end > dur: end = dur factor = random.choice([0.98, 0.99, 1.01, 1.02]) expr = f"if(between(T,{start},{end}),{factor}*{expr},{expr})" return expr if uploaded_file is not None: input_path = os.path.join(temp_dir, uploaded_file.name) with open(input_path, "wb") as f: f.write(uploaded_file.getbuffer()) st.success("✅ Vídeo carregado com sucesso!") dur = get_duration(input_path) st.subheader("🚀 Funções Avançadas") opt_semantic_noise = st.checkbox("Injeção de Ruído Semântico") opt_dynamic_frame = st.checkbox("Molduras Dinâmicas") st.subheader("✂️ Corte do Vídeo") cut_start = st.slider( "Cortar do início (segundos)", 0.0, float(max(0.0, dur - 1)), 0.0, 0.1 ) cut_end = st.slider( "Cortar do final (segundos)", 0.0, float(max(0.0, dur - cut_start - 1)), 0.0, 0.1 ) st.subheader("🎵 Pitch Shift") enable_pitch = st.checkbox("Ativar Pitch Shift") pitch_value = st.slider( "Tom do áudio", 0.8, 1.2, 1.0, 0.01 ) res = st.radio("📱 Resolução final", ["720x1280", "1080x1920"], index=0) w, h = res.split("x") preset = st.radio("⚡ Escolha um preset:", ["Low", "Médio", "High"], index=1) preset_defaults = { "Low": { "speed_auto": False, "zoom_auto": False, "tilt": False, "eq": False, "bw": False, "sepia": False, "sharpen": False, "fade": False, "border": False }, "Médio": { "speed_auto": True, "zoom_auto": True, "tilt": False, "eq": True, "bw": False, "sepia": False, "sharpen": True, "fade": True, "border": False }, "High": { "speed_auto": True, "zoom_auto": True, "tilt": True, "eq": True, "bw": True, "sepia": True, "sharpen": True, "fade": True, "border": True } } defaults = preset_defaults[preset] st.subheader("🎨 Efeitos ativos") speed_val = st.slider("⏩ Velocidade base", 0.5, 2.0, 1.0, 0.05) opt_speed_auto = st.checkbox("Velocidade automática", value=defaults["speed_auto"]) zoom_val = st.slider("🔍 Zoom base", 1.0, 1.5, 1.0, 0.01) opt_zoom_auto = st.checkbox("Zoom automático", value=defaults["zoom_auto"]) opt_tilt = st.checkbox("Tilt (ângulo leve)", value=defaults["tilt"]) opt_eq = st.checkbox("Brilho / Contraste / Saturação", value=defaults["eq"]) opt_bw = st.checkbox("Filtro de desaturação leve", value=defaults["bw"]) opt_sepia = st.checkbox("Filtro sépia suave", value=defaults["sepia"]) opt_sharpen = st.checkbox("Sharpen (nitidez extra)", value=defaults["sharpen"]) opt_fade = st.checkbox("Fade in/out (máx 1s)", value=defaults["fade"]) opt_border = st.checkbox("Borda preta", value=defaults["border"]) hard_mode = st.checkbox("Ativar randomização HARD (reescrita de metadados)") if st.button("🎲 Gerar Spin"): out_name = gerar_nome_saida() out_path = os.path.join(temp_dir, out_name) first_pass = os.path.join(temp_dir, "first_pass.mp4") effects = [] expr = gerar_microdrift(dur, base_speed=speed_val) effects.append(f"setpts='{expr}'") if opt_zoom_auto: zoom_choice = round(random.uniform(1.01, 1.25), 2) effects.append(f"scale=iw*{zoom_choice}:ih*{zoom_choice},crop={w}:{h}") elif zoom_val > 1.0: effects.append(f"scale=iw*{zoom_val}:ih*{zoom_val},crop={w}:{h}") else: effects.append(f"scale={w}:{h}:force_original_aspect_ratio=increase,crop={w}:{h}") if opt_tilt: tilt = random.choice([1, 2, -1, -2]) effects.append(f"rotate={tilt}*PI/180:ow={w}:oh={h}:c=black") if opt_eq: effects.append( f"eq=brightness={round(random.uniform(-0.1,0.1),2)}:" f"contrast={round(random.uniform(0.9,1.1),2)}:" f"saturation={round(random.uniform(0.9,1.1),2)}" ) if opt_bw: effects.append("hue=s=0.7") if opt_sepia: effects.append("colorchannelmixer=.85:.3:.15:0:.3:.85:.15:0:.2:.3:.7") if opt_sharpen: effects.append("unsharp=5:5:1.0:5:5:0.0") if opt_fade: fade_in = min(1, dur * 0.1) fade_out = min(1, dur * 0.1) effects.append( f"fade=t=in:st=0:d={fade_in}," f"fade=t=out:st={max(dur-fade_out,0)}:d={fade_out}" ) if opt_border: effects.append(f"pad={w}:{h}:(ow-iw)/2:(oh-ih)/2:black") effects.append("setsar=1") # 🔥 Injeção de ruído semântico if opt_semantic_noise: effects.append("noise=alls=3:allf=t") effects.append( f"eq=brightness={round(random.uniform(-0.02,0.02),3)}:" f"contrast={round(random.uniform(0.98,1.02),3)}:" f"saturation={round(random.uniform(0.98,1.02),3)}" ) crop_w = int(w) - random.randint(0, 3) crop_h = int(h) - random.randint(0, 3) effects.append(f"crop={crop_w}:{crop_h}") # 🎨 Molduras dinâmicas if opt_dynamic_frame: emoji = random.choice(["🔥", "⚡", "✨", "👀", "🚀"]) x_pos = random.randint(20, int(w) - 80) y_pos = random.randint(20, int(h) - 80) effects.append( f"drawtext=text='{emoji}':" f"x={x_pos}:y={y_pos}:" f"fontsize=40:" f"fontcolor=white@0.3" ) effects.append(f"pad={w}:{h}:(ow-iw)/2:(oh-ih)/2:black@0.3") vf = ",".join(effects) final_duration = dur - cut_start - cut_end if final_duration <= 0: st.error("❌ Corte inválido.") st.stop() if enable_pitch and pitch_value != 1.0: af_filter = f"asetrate=48000*{pitch_value},aresample=48000" else: af_filter = "aresample=48000" cmd1 = [ "ffmpeg", "-y", "-ss", str(cut_start), "-i", input_path, "-t", str(final_duration), "-vf", vf, "-af", af_filter, "-c:v", "libx264", "-pix_fmt", "yuv420p", "-preset", "fast", "-crf", "23", "-c:a", "aac", "-b:a", "128k", "-shortest", "-vsync", "vfr", first_pass ] success1 = run_ffmpeg(cmd1, "Efeitos", first_pass) if not success1: st.error("❌ Erro no processamento inicial.") else: if not hard_mode: shutil.move(first_pass, out_path) st.success("🎉 Spin gerado com sucesso!") st.video(out_path) make_download_button(out_path, "Download Spin") else: crf = str(random.randint(18, 26)) encoder_name = random_string(12) cmd2 = [ "ffmpeg", "-y", "-i", first_pass, "-map_metadata", "-1", "-c:v", "libx264", "-preset", "fast", "-crf", crf, "-c:a", "aac", "-movflags", "faststart", "-metadata", f"title={random_string(10)}", "-metadata", f"encoder={encoder_name}", out_path ] success2 = run_ffmpeg(cmd2, "Finalizando Spin", out_path) if success2: st.success("🎉 Spin gerado com randomização HARD!") st.video(out_path) make_download_button(out_path, "Download Spin") else: st.error("❌ Erro no passo final.") if st.button("🗑️ Limpar temporários"): shutil.rmtree(temp_dir) st.warning("Arquivos temporários foram apagados.")