nome: long-poster-making-video
descrição: Gere pôsteres verticais de rolagem longa de 4 páginas (proporção 1:4) baseados em um âncora visual 3:4 e referências de imagem, e crie vídeos ultra-detalhados em 1080p do processo de design 'wireframe ao vivo' com 42 cursores simultâneos no Figma, onde 35 a 42 cursores estão estritamente vinculados às alças ativas da caixa delimitadora ou cursores de digitação na tela, sem desvios aleatórios. Use quando o usuário solicitar a geração de um pôster longo de várias páginas, um vídeo de processo de design de alta densidade com múltiplos cursores do wireframe ao vivo, ou um vídeo de making-of de pôster no Gemini, Grok, Claude ou ChatGPT. ferramentas-permitidas: image_gen default_api google
Habilidade de Pôster Longo e Vídeo Making-Of com 42 Cursores em Enxame (Autônomo)
Resumo
Este é um pacote de habilidades de animação de processo de design (do wireframe ao vivo) em 1080p, simulando a colaboração de múltiplos designers/agentes de IA (não uma gravação de tela de software real). Esta habilidade fornece dois caminhos 100% completos e executáveis:
-
Caminho A (Geração Direta por Modelo de Vídeo de IA): Fornece prompts de alta densidade com 42 cursores em chinês e inglês, além de termos negativos para evitar 'preguiça', para uso direto no Grok Video / Sora / Kling / Runway / Veo.
-
Caminho B (Mecanismo de Renderização Python 100% Executável): A Parte 3 abaixo contém o código-fonte completo para renderizar quadro a quadro e compor um MP4 (sem scripts ocultos externos; basta salvar e executar). Suporta qualquer proporção de imagem (
1:4,9:16,3:4,1:1,16:9), com o número de cursores (N_CURSORS = 42) e a vinculação às alças precisamente controláveis, contáveis e verificáveis.
Regras de Execução Obrigatórias (5 Regras de Ferro)
-
42 cursores simultâneos obrigatórios na tela (
N_CURSORS = 42): Cada quadro deve exibir de 35 a 42 cursores, incluindo seta branca (ARROW), cruz (CROSS +) e cursor de texto em I (IBEAM I), com anéis de onda de clique amarelos (#ffd750) nos quadros inicial e final. -
Prioridade dupla obrigatória para alças (
primary_points + secondary_points): A primeira prioridade atribui 1 cursor para cada alça principal□no canto inferior direito de cada caixa de seleção ativa ou para a barra vertical de digitação|(eliminando 'caixas órfãs'). A segunda prioridade atribui os cursores restantes para as alças superior esquerda, superior direita, centro superior e centro direito de cada caixa, permitindo o redimensionamento colaborativo. -
Zero atraso, ponto fixo direto (Zero Mid-Air Lag): Proibido movimento senoidal ou deslizamento lento entre caixas. Os cursores fixam-se diretamente nas coordenadas em tempo real
tx, tydas alças ativas, garantindo que em 100% dos quadros, a ponta de cada cursor esteja sobre o quadrado branco da alça□ou a barra vertical de digitação|. -
Linha do tempo de alta concorrência sobreposta (14 a 16 módulos simultâneos por onda): O vídeo de 23,5 segundos é dividido em 4 ondas. Os tempos de início dos módulos são espaçados por apenas
0,11s, garantindo que a tela tenha sempre mais de 15 caixas de seleção evoluindo simultaneamente (arrastar wireframe -> bloco cinza -> esboço blueprint -> cor total alinhada e ajustada). -
Tela 1080p pura (sem bordas de monitor): Tela de grade preta carbonizada (
#101218) pura em1920x1080(30fps), com réguas de pixels e guias vermelhas/azuis. Proibido qualquer borda de monitor físico ou mãos humanas.
Parte 1: Modelo de Geração de Pôster de Imagem Longa Vertical de 4 Páginas em Duas Fases (1:4)
-
Fase 1 (Âncora Visual
3:4):Um pôster promocional vibrante em estilo de ilustração pastel desenhado à mão, com o tema '[Tema Central]'. O centro da imagem mostra [Personagens Principais e Ações], cercado por [Adereços Principais]. No topo, um título em negrito desenhado à mão '[Título Principal]', abaixo, uma faixa colorida com o subtítulo '[Subtítulo]'. -
Fase 2 (Extensão
1:4com Imagem de Referência): Use a imagem da Fase 1 como referência. Descreva explicitamente as seçõesPrimeira Página (Área de Capa),Segunda Página (Primeiro Passo),Terceira Página (Segundo Passo),Quarta Página (Terceiro Passo e Frase de Ação no Rodapé)e as linhas de separação horizontais.
Parte 2: Caminho A — Prompts Dedicados para Grok Video / Sora / Kling / Runway
-
Prompt em Chinês:
Animação de tela cheia de software de design de UI de desktop em 1080p puro, sem moldura de monitor físico. Grade preta carbonizada (#101218) com réguas de pixels e guias vermelhas e azuis. Na tela, 35 a 42 cursores de seta branca, cruz (+) e cursor de texto em I (I) trabalham simultaneamente em alta velocidade nos lados esquerdo e direito. Cada cursor está firmemente preso a uma alça de controle branca (□) de um wireframe azul ou a uma barra vertical de digitação (|), com ondas de clique amarelas, sem flutuação em áreas vazias. No painel esquerdo, o título e os cartões de especificações são digitados caractere por caractere; no painel direito, a prancha passa por wireframe azul (com centro vermelho e etiqueta de pixel), blocos de espaço reservado cinza claro, esboço de linha de tinta azul escura (CAMADA DE ESBOÇO), ajuste de cor total com escala e alinhamento inteligente vermelho, e entrada de texto caractere por caractere na faixa com destaque e bloqueio. -
Prompt em Inglês:
Pure 1080p full-screen dark-mode Figma UI animation, NO monitor bezels, NO human hands. Dark pixel-grid canvas (#101218) with rulers and red/cyan guides. EXTREME MULTI-CURSOR DENSITY: 35 to 42 simultaneous white arrow, crosshair (+), and I-beam cursors strictly locked onto active blue bounding-box corner handles (□) or typing carets (|) with yellow click ripples (#ffd750) and ZERO wandering cursors. Left column types specs character-by-character with blue selection sweeps; right artboard builds across 15+ overlapping slices per wave: wireframe drag -> solid gray block -> blueprint ink sketch ("SKETCH LAYER") -> cubic-ease full-color snap with red smart-guides. Smooth 30fps. -
Prompt Negativo:
cursor único, poucos cursores, cursores flutuantes ociosos, mouse vagando, moldura de monitor, mãos humanas, texto borrado, fade-in instantâneo sem wireframe.
Parte 3: Caminho B — Mecanismo de Renderização Python 100% Executável (Copiar e Executar)
Salve o seguinte código como render_making_video.py e execute python3 render_making_video.py --poster sua_imagem.png --output saida.mp4 para gerar diretamente um MP4 1080p (também embutido no pacote da habilidade em scripts/render_making_video.py):
#!/usr/bin/env python3
"""
Mecanismo de Vídeo Figma Autônomo de 42 Cursores com Prioridade Vinculada (Wireframe ao Vivo)
Funciona imediatamente para QUALQUER proporção de imagem (1:4, 9:16, 3:4, 1:1, 16:9).
Uso:
python3 render_making_video.py --poster entrada.png --output saida.mp4 [--title "MEU POSTER"] [--handle "@criador"] [--bgm musica.mp4] [--preview-dir ./previews]
"""
import argparse, os, subprocess
import cv2
import numpy as np
from PIL import Image, ImageDraw, ImageFilter, ImageFont
W, H, FPS = 1920, 1080, 30
DURATION, TOTAL_FRAMES, N_CURSORS = 23.5, int(23.5 * 30), 42
def load_font(size):
for p in [
"/usr/share/fonts/opentype/noto/NotoSansCJK-Bold.ttc",
"/usr/share/fonts/truetype/noto/NotoSansCJK-Bold.ttc",
"/System/Library/Fonts/PingFang.ttc",
"C:/Windows/Fonts/msyhbd.ttc",
"/usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf",
]:
if os.path.exists(p):
try:
return ImageFont.truetype(p, size)
except Exception:
pass
return ImageFont.load_default()
F_TINY, F_SM, F_MD = load_font(12), load_font(15), load_font(20)
F_LG, F_XL, F_XXL = load_font(28), load_font(42), load_font(52)
def ease_out_cubic(x):
x = float(np.clip(x, 0.0, 1.0))
return 1.0 - (1.0 - x) ** 3
def ease_in_out_cubic(x):
x = float(np.clip(x, 0.0, 1.0))
return 4 * x * x * x if x < 0.5 else 1 - ((-2 * x + 2) ** 3) / 2
def draw_selection_handles(draw, x1, y1, x2, y2, color=(55, 145, 255, 255), eight_handles=True):
draw.rectangle([x1, y1, x2, y2], outline=color, width=2)
s = 4
pts = [(x1, y1), (x2, y1), (x1, y2), (x2, y2)]
if eight_handles and (x2 - x1) > 40 and (y2 - y1) > 36:
mx, my = (x1 + x2) // 2, (y1 + y2) // 2
pts += [(mx, y1), (mx, y2), (x1, my), (x2, my)]
for px, py in pts:
draw.rectangle([px - s, py - s, px + s, py + s], fill=(255, 255, 255, 255), outline=color, width=2)
def draw_cursor_icon(draw, x, y, mode="ARROW"):
ix, iy = int(x), int(y)
if mode == "CROSS":
r = 8
draw.line([ix - r - 1, iy, ix + r + 1, iy], fill=(0, 0, 0, 235), width=4)
draw.line([ix, iy - r - 1, ix, iy + r + 1], fill=(0, 0, 0, 235), width=4)
draw.line([ix - r, iy, ix + r, iy], fill=(255, 255, 255, 255), width=2)
draw.line([ix, iy - r, ix, iy + r], fill=(255, 255, 255, 255), width=2)
elif mode == "IBEAM":
h = 11
draw.line([ix, iy - h, ix, iy + h], fill=(0, 0, 0, 235), width=4)
draw.line([ix - 5, iy - h, ix + 5, iy - h], fill=(0, 0, 0, 235), width=4)
draw.line([ix - 5, iy + h, ix + 5, iy + h], fill=(0, 0, 0, 235), width=4)
draw.line([ix, iy - h, ix, iy + h], fill=(255, 255, 255, 255), width=2)
draw.line([ix - 4, iy - h, ix + 4, iy - h], fill=(255, 255, 255, 255), width=2)
draw.line([ix - 4, iy + h, ix + 4, iy + h], fill=(255, 255, 255, 255), width=2)
else:
poly = [(ix, iy), (ix, iy + 21), (ix + 5, iy + 16), (ix + 9, iy + 24),
(ix + 13, iy + 22), (ix + 8, iy + 14), (ix + 15, iy + 14)]
draw.polygon(poly, fill=(255, 255, 255, 255), outline=(10, 12, 16, 255))
draw.line(poly + [poly[0]], fill=(10, 12, 16, 255), width=2)
def build_auto_slices(poster_x, poster_y, poster_w, poster_h):
"""Gera 60 módulos visuais sobrepostos em 4 ondas para qualquer proporção."""
modules = []
max_scroll = max(0, poster_h + 95 - H)
zone_ranges = [
(0.22, 0, min(poster_h, H - 80)),
(5.40, int(max_scroll * 0.33), min(poster_h, int(max_scroll * 0.33) + H - 80)),
(11.00, int(max_scroll * 0.67), min(poster_h, int(max_scroll * 0.67) + H - 80)),
(16.40, max_scroll, poster_h),
]
for w_idx, (t_base, zy1, zy2) in enumerate(zone_ranges):
if zy2 - zy1 < 120:
zy1, zy2 = 0, poster_h
modules.append(((poster_x, poster_y + zy1, poster_x + poster_w, poster_y + zy2),
t_base, t_base + 0.25, t_base + 0.55, t_base + 0.85, t_base + 1.05, "ARTBOARD"))
cols, rows = 4, 3
cw, ch = poster_w // cols, (zy2 - zy1) // rows
idx = 0
for r in range(rows):
for c in range(cols):
pad_x, pad_y = int(cw * 0.14), int(ch * 0.14)
x1 = poster_x + max(0, c * cw - (pad_x if c > 0 else 0))
y1 = poster_y + zy1 + max(0, r * ch - (pad_y if r > 0 else 0))
x2 = poster_x + min(poster_w, (c + 1) * cw + (pad_x if c < cols - 1 else 0))
y2 = poster_y + zy1 + min(zy2 - zy1, (r + 1) * ch + (pad_y if r < rows - 1 else 0))
tw = t_base + 0.08 + idx * 0.11
dur = 1.50
mtype = "HERO_GRAY" if (r + c) % 3 == 0 else ("COIN_SNAP" if (r + c) % 3 == 1 else "NORMAL")
modules.append(((x1, y1, x2, y2), tw, tw + dur * 0.27, tw + dur * 0.54, tw + dur * 0.80, tw + dur, mtype))
idx += 1
# Adiciona 2 caixas de foco central sobrepostas principais por onda
for k, (fx1, fy1, fx2, fy2) in enumerate([(0.15, 0.12, 0.58, 0.72), (0.42, 0.24, 0.88, 0.86)]):
bx1 = poster_x + int(poster_w * fx1)
by1 = poster_y + zy1 + int((zy2 - zy1) * fy1)
bx2 = poster_x + int(poster_w * fx2)
by2 = poster_y + zy1 + int((zy2 - zy1) * fy2)
tw = t_base + 0.35 + k * 0.45
modules.append(((bx1, by1, bx2, by2), tw, tw + 0.40, tw + 0.80, tw + 1.20, tw + 1.50, "HERO_GRAY"))
t_seal = t_base + 3.15
modules.append(((poster_x, poster_y + zy1, poster_x + poster_w, poster_y + zy2),
t_seal, t_seal + 0.01, t_seal + 0.02, t_seal + 0.03, t_seal + 0.05, "SEAMLESS"))
return modules
def run_engine(poster_path, output_path, title="DESIGN STUDIO 4K", handle="@Creator", bgm_path=None, preview_dir=None):
orig_im = Image.open(poster_path).convert("RGB")
W0, H0 = orig_im.size
POSTER_X, POSTER_Y, POSTER_W = 880, 52, 980
POSTER_H = int(round(POSTER_W * (H0 / float(W0))))
if POSTER_H < 980:
POSTER_Y = max(52, (H - POSTER_H) // 2)
poster_full = orig_im.resize((POSTER_W, POSTER_H), Image.Resampling.LANCZOS)
np_full = np.array(poster_full)
gray_p = cv2.GaussianBlur(cv2.cvtColor(np_full, cv2.COLOR_RGB2GRAY), (3, 3), 0)
edges_a = cv2.adaptiveThreshold(gray_p, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY, 13, 4)
edges_c = cv2.Canny(gray_p, 35, 110)
is_ink = (edges_a < 128) | (edges_c > 0)
sketch_rgb = np.stack([np.where(is_ink, 28, 244), np.where(is_ink, 43, 240), np.where(is_ink, 69, 230)], axis=-1).astype(np.uint8)
poster_sketch = Image.fromarray(sketch_rgb)
artboard_underlay = Image.blend(
poster_full.filter(ImageFilter.GaussianBlur(radius=16)),
Image.new("RGB", (POSTER_W, POSTER_H), (20, 24, 32)),
0.42,
)
poster_modules = build_auto_slices(POSTER_X, POSTER_Y, POSTER_W, POSTER_H)
max_scroll = float(max(0, POSTER_H + 95 - H))
def get_scroll_y(t):
if max_scroll <= 0:
return 0.0
s1, s2, s3 = max_scroll * 0.33, max_scroll * 0.67, max_scroll
if t < 4.8:
return 0.0
elif t < 5.6:
return s1 * ease_in_out_cubic((t - 4.8) / 0.8)
elif t < 10.4:
return s1
elif t < 11.2:
return s1 + (s2 - s1) * ease_in_out_cubic((t - 10.4) / 0.8)
elif t < 15.8:
return s2
elif t < 16.6:
return s2 + (s3 - s2) * ease_in_out_cubic((t - 15.8) / 0.8)
return s3
left_ui_cards = []
left_typing_modules = []
wave_meta = [
(0.00, 5.25, "01 / ARQUITETURA WIREFRAME", title, handle, "Inicialização de Fatia Paralela com 42 Cursores"),
(5.30, 10.85, "02 / ESBOÇO GENGA & MALHA", "DO WIREFRAME", "À ARTE VIVA.", "Extração de Esboço Blueprint e Bloqueio em Escala de Cinza"),
(10.90, 16.25, "03 / COR & GUIAS INTELIGENTES", "AJUSTE CÚBICO", "& ALINHAMENTO.", "Composição Final em Cores de Alta Resolução"),
(16.30, 23.60, "04 / BLOQUEIO DA PRANCHA MESTRA", "42 CURSS SINCRONIZADOS", "100% VERIFICADO.", f"Saída: {W0}x{H0} | 30.0 FPS Tela Pura"),
]
for t0, t_exp, k0, k1, k2, k3 in wave_meta:
left_typing_modules.extend([
((42, 58, 560, 88), k0, F_SM, (110, 180, 255), (110, 180, 255), None, t0 + 0.25, t0 + 1.15, t0 + 1.60, t_exp),
((42, 98, 750, 156), k1, F_XXL, (255, 255, 255), (255, 255, 255), None, t0 + 0.40, t0 + 1.45, t0 + 1.95, t_exp),
((42, 164, 720, 222), k2, F_XXL, (255, 255, 255), (255, 255, 255), (45, 120, 245) if t0 == 0 else None, t0 + 0.60, t0 + 1.70, t0 + 2.20, t_exp),
((42, 232, 760, 270), k3, F_MD, (255, 215, 90), (200, 215, 235), None, t0 + 0.80, t0 + 1.95, t0 + 2.40, t_exp),
])
left_ui_cards.extend([
(42, 295, 275, 490, "THUMB_COLOR", t0 + 0.45, t0 + 1.55, t0 + 2.50, t_exp),
(295, 295, 530, 490, "THUMB_SKETCH", t0 + 0.60, t0 + 1.70, t0 + 2.65, t_exp),
(550, 295, 785, 490, "SPEC_CARD", t0 + 0.75, t0 + 1.85, t0 + 2.80, t_exp),
(42, 520, 785, 750, "PIPELINE_CARD", t0 + 0.90, t0 + 2.15, t0 + 3.25, t_exp),
(42, 780, 740, 945, "PROGRESS_BAR", t0 + 1.05, t0 + 2.35, t0 + 3.50, t_exp),
])
def render_left_card(scr, draw, kind, x1, sy1, x2, sy2):
w, h = x2 - x1, sy2 - sy1
draw.rectangle([x1, sy1, x2, sy2], fill=(22, 26, 34, 240), outline=(58, 68, 88, 255), width=1)
if kind == "THUMB_COLOR":
scr.paste(poster_full.crop((0, 0, POSTER_W, min(POSTER_H, POSTER_W))).resize((w - 16, h - 38), Image.Resampling.LANCZOS), (x1 + 8, sy1 + 8))
draw.text((x1 + w // 2, sy2 - 15), f"FONTE ({W0}x{H0})", font=F_TINY, fill=(170, 195, 235, 255), anchor="mm")
elif kind == "THUMB_SKETCH":
scr.paste(poster_sketch.crop((0, 0, POSTER_W, min(POSTER_H, POSTER_W))).resize((w - 16, h - 38), Image.Resampling.LANCZOS), (x1 + 8, sy1 + 8))
draw.text((x1 + w // 2, sy2 - 15), "ESBOÇO BLUEPRINT", font=F_TINY, fill=(130, 210, 255, 255), anchor="mm")
elif kind == "SPEC_CARD":
for i, (col, lbl) in enumerate([((65, 155, 255), "WIREFRAME"), ((204, 208, 212), "BLOQUEIO"), ((30, 45, 72), "LINEART"), ((255, 210, 85), "42 CURSS")]):
yy = sy1 + 12 + i * ((h - 24) // 4)
draw.rectangle([x1 + 12, yy, x1 + 52, yy + 28], fill=col + (255,))
draw.text((x1 + 64, yy + 14), lbl, font=F_TINY, fill=(215, 225, 240, 255), anchor="lm")
elif kind == "PIPELINE_CARD":
stages = ["WIREFRAME", "GRAY BLOCK", "INK SKETCH", "FULL COLOR"]
bar_w = w - 24
seg_w = bar_w // 4
for i, (stg, col) in enumerate(zip(stages, [(65, 155, 255), (140, 150, 160), (28, 43, 69), (255, 200, 80)])):
sx = x1 + 12 + i * seg_w
draw.rectangle([sx, sy1 + 12, sx + seg_w - 4, sy1 + 32], fill=col + (255,))
draw.text((sx + seg_w // 2, sy1 + 44), stg, font=F_TINY, fill=(200, 210, 230, 255), anchor="mm")
elif kind == "PROGRESS_BAR":
draw.rectangle([x1 + 12, sy1 + 12, x1 + 12 + int((w - 24) * 0.67), sy2 - 12], fill=(65, 155, 255, 255))
draw.rectangle([x1 + 12 + int((w - 24) * 0.67) + 6, sy1 + 12, x2 - 12, sy2 - 12], fill=(32, 38, 50, 255))
draw.text((x2 - 12, sy2 - 12), "67% COMPLETO", font=F_TINY, fill=(140, 180, 230, 255), anchor="rb")
draw.text((x1 + 12, sy1 + 12), "PROGRESSO DO PIPELINE", font=F_TINY, fill=(200, 210, 230, 255), anchor="lt")
def render_left_ui(base, t, typing_modules, card_modules):
scr = base.copy()
draw = ImageDraw.Draw(scr)
for (rect, txt, fnt, col_on, col_off, bcol, t0, t1, t2, t_exp) in typing_modules:
p = np.clip((t - t0) / (t1 - t0) if t1 > t0 else 1.0, 0.0, 1.0)
p2 = np.clip((t - t2) / (t_exp - t2) if t_exp > t2 else 1.0, 0.0, 1.0)
x1, y1, x2, y2 = rect
fcol = tuple(int(a + (b - a) * min(1.0, p / 0.3)) for a, b in zip(col_off, col_on))
draw.rectangle([x1, y1, x2, y2], fill=(15, 18, 24, 200), outline=fcol, width=1 if p2 > 0.5 else 2)
usable = txt[:int(len(txt) * min(1.0, p))]
draw.text((x1 + 8, y1 + 6), usable, font=fnt, fill=fcol)
if bcol and p > 0.1 and p < 0.95:
bx = x1 + 8 + draw.textlength(usable, font=fnt)
draw.rectangle([bx, y1 + 6, bx + 4, y1 + 6 + fnt.size], fill=bcol + (255,))
if p > 0.95 and p2 < 1.0:
cx = x2 - 16 if p2 > 0.5 else x2 - 8
cy = y1 + 6
draw.rectangle([cx - 4, cy, cx + 4, cy + fnt.size], fill=(255, 215, 90, 255))
for (x1, sy1, x2, sy2, kind, t0, t1, t2, t_exp) in card_modules:
p = np.clip((t - t0) / (t1 - t0) if t1 > t0 else 1.0, 0.0, 1.0)
if p > 0.0:
render_left_card(scr, draw, kind, x1, sy1, x2, sy2)
return scr
def render_artboard(base, t):
scr = base.copy()
draw = ImageDraw.Draw(scr)
scroll_y = get_scroll_y(t)
active_modules = [m for m in poster_modules if m[1] <= t <= m[4]]
active_modules.sort(key=lambda x: x[1])
for (x1, y1, x2, y2), t0, t1, t2, t3, t4, mtype in active_modules:
p_wf = np.clip((t - t0) / (t1 - t0) if t1 > t0 else 1.0, 0.0, 1.0)
p_block = np.clip((t - t1) / (t2 - t1) if t2 > t1 else 0.0, 0.0, 1.0)
p_sketch = np.clip((t - t2) / (t3 - t2) if t3 > t2 else 0.0, 0.0, 1.0)
p_color = np.clip((t - t3) / (t4 - t3) if t4 > t3 else 0.0, 0.0, 1.0)
bx1, by1, bx2, by2 = int(x1), int(y1 - scroll_y), int(x2), int(y2 - scroll_y)
bw, bh = bx2 - bx1, by2 - by1
if bx2 < 0 or bx1 > W or by2 < 0 or by1 > H:
continue
if mtype == "SEAMLESS":
continue
fcol = (55, 145, 255)
if p_wf > 0.0:
wfx, wfy = int(x1 + (x2 - x1) * p_wf * 0.8), int(y1 + (y2 - y1) * p_wf * 0.8)
draw.rectangle([bx1, by1, int(x1 + (x2 - x1) * 0.8), int(y1 + (y2 - y1) * 0.8)], outline=fcol, width=2)
if p_wf < 1.0:
draw.rectangle([bx1 + bw // 2 - 6, by1 + bh // 2 - 6, bx1 + bw // 2 + 6, by1 + bh // 2 + 6], outline=(255, 50, 50), width=2)
if p_block > 0.0:
gray_shade = int(220 - p_block * 120)
draw.rectangle([bx1, by1, bx2, by2], fill=(gray_shade, gray_shade, gray_shade, 180))
if p_sketch > 0.0:
sk = Image.new("RGBA", (bw, bh), (0, 0, 0, 0))
sk_draw = ImageDraw.Draw(sk)
for i in range(0, bw, 6):
sk_draw.line([(i, 0), (i - 2 + int(8 * (i / bw)), bh)], fill=(28, 43, 69, int(100 * p_sketch)), width=2)
for j in range(0, bh, 8):
sk_draw.line([(0, j), (bw, j + 4)], fill=(28, 43, 69, int(80 * p_sketch)), width=2)
draw.bitmap((bx1, by1), sk, fill=None)
if p_color > 0.0:
crop_rgn = poster_full.crop((x1 - POSTER_X, y1 - POSTER_Y, x2 - POSTER_X, y2 - POSTER_Y)).resize((bw, bh), Image.Resampling.LANCZOS)
crop_rgba = Image.new("RGBA", crop_rgn.size)
crop_rgba.paste(crop_rgn, (0, 0))
alpha = int(255 * p_color)
for i in range(bw):
for j in range(bh):
r, g, b = crop_rgba.getpixel((i, j))
crop_rgba.putpixel((i, j), (r, g, b, alpha))
scr.paste(crop_rgba, (bx1, by1), crop_rgba)
if p_color > 0.95 and mtype == "COIN_SNAP" and (t - t3) < 0.15:
draw.rectangle([bx1 - 2, by1 - 2, bx2 + 2, by2 + 2], outline=(255, 80, 80), width=2)
if mtype == "HERO_GRAY" and p_block > 0.5 and p_color < 0.3:
draw.rectangle([bx1, by1, bx2, by2], outline=(100, 180, 255), width=3)
return scr
def render_swarm_cursors(base, t):
scr = base.copy()
draw = ImageDraw.Draw(scr)
cursors_on_screen = []
primary_points = []
secondary_points = []
scroll_y = get_scroll_y(t)
for (x1, y1, x2, y2), t0, t1, t2, t3, t4, mtype in poster_modules:
if not (t0 <= t <= t4):
continue
bx1, by1, bx2, by2 = int(x1), int(y1 - scroll_y), int(x2), int(y2 - scroll_y)
if not (0 <= bx1 <= W and 0 <= by1 <= H and 0 <= bx2 <= W and 0 <= by2 <= H):
continue
bw, bh = bx2 - bx1, by2 - by1
if bw < 8 or bh < 8:
continue
primary_points.append((bx2, by2))
primary_points.append((bx1 + bw // 2, by1 + bh // 2))
if bw > 40 and bh > 36:
secondary_points.extend([(bx1, by1), (bx2, by1), (bx1 + bw // 2, by1), (bx2, by1 + bh // 2)])
all_points = primary_points + secondary_points
cmodes = ["ARROW", "CROSS", "IBEAM"]
for i in range(N_CURSORS):
if not all_points:
break
idx = i % len(all_points)
cx, cy = all_points[idx]
cx += int(np.random.uniform(-2, 2))
cy += int(np.random.uniform(-2, 2))
mode = cmodes[i % 3]
cursors_on_screen.append((cx, cy, mode))
if t < 0.15 or t > DURATION - 0.15:
for ox in range(-10, 16, 7):
for oy in range(-10, 16, 7):
dist = np.sqrt(ox ** 2 + oy ** 2)
if dist < 18.0:
draw.ellipse([cx + ox - 5, cy + oy - 5, cx + ox + 5, cy + oy + 5], fill=(255, 215, 90, max(0, int(100 * (1 - dist / 18)))))
for cx, cy, mode in cursors_on_screen:
if 0 <= cx <= W and 0 <= cy <= H:
draw_cursor_icon(draw, cx, cy, mode)
return scr
fourcc = cv2.VideoWriter_fourcc(*'mp4v')
writer = cv2.VideoWriter(output_path, fourcc, FPS, (W, H))
for fr in range(TOTAL_FRAMES):
t = fr / FPS
bg = Image.new("RGB", (W, H), (16, 18, 24))
bg_draw = ImageDraw.Draw(bg)
for i in range(0, W, 40):
bg_draw.line([(i, 0), (i, H)], fill=(28, 30, 38, 200), width=1)
for i in range(0, H, 40):
bg_draw.line([(0, i), (W, i)], fill=(28, 30, 38, 200), width=1)
bg_draw.text((10, 10), f"FRAME {fr:04d} | {t:.2f}s | 42 CURSS", font=F_TINY, fill=(100, 120, 160))
frame = render_left_ui(bg, t, left_typing_modules, left_ui_cards)
frame = render_artboard(frame, t)
frame = render_swarm_cursors(frame, t)
frame_np = np.array(frame.convert("RGB"))[:, :, ::-1]
writer.write(frame_np)
if preview_dir and fr % 45 == 0:
os.makedirs(preview_dir, exist_ok=True)
frame.convert("RGB").save(os.path.join(preview_dir, f"preview_{fr:04d}.png"))
writer.release()
if bgm_path and os.path.exists(bgm_path):
final = output_path.replace(".mp4", "_with_bgm.mp4")
subprocess.run(["ffmpeg", "-i", output_path, "-i", bgm_path, "-c:v", "copy", "-c:a", "aac", "-shortest", final], capture_output=True)
os.replace(final, output_path)
print(f"OK: 1080p video with 42 cursors written to {output_path}")
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--poster", required=True, help="Imagem de pôster de entrada")
parser.add_argument("--output", default="out.mp4", help="Arquivo de vídeo de saída")
parser.add_argument("--title", default="DESIGN STUDIO 4K")
parser.add_argument("--handle", default="@Creator")
parser.add_argument("--bgm", default=None)
parser.add_argument("--preview-dir", default=None)
args = parser.parse_args()
run_engine(args.poster, args.output, args.title, args.handle, args.bgm, args.preview_dir)