ganhos / app.py
pcdoido2's picture
Update app.py
ee975b1 verified
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'<a href="data:file/mp4;base64,{b64}" download="{os.path.basename(file_path)}">⬇️ {label}</a>'
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.")