@danhachuel/thunderbolt 0.7.37 → 0.7.38
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/app/main.py +3 -3
- package/hermes_ui/domain.py +3 -3
- package/hermes_ui/pipeline_worker.py +2 -1
- package/hermes_ui/thumbnail_blueprints.py +28 -10
- package/package.json +1 -1
- package/seed/blueprints/thumbnails/Tiktok_Generic_Thumbnail_Blueprint.md +181 -0
- /package/seed/blueprints/thumbnails/{Generic_Thumbnail_Blueprint.md → Youtube_Generic_Thumbnail_Blueprint.md} +0 -0
package/app/main.py
CHANGED
|
@@ -2155,7 +2155,7 @@ def render_thumbnail_blueprints():
|
|
|
2155
2155
|
script = st.selectbox("Blueprint de roteiro associado", blueprint_ids, index=blueprint_ids.index(current_script) if current_script in blueprint_ids else 0, format_func=lambda item: blueprint_labels.get(item, item or "Sem Blueprint padrão"), key=f"thumbnail_script_blueprint_channel_{channel_id}")
|
|
2156
2156
|
with cols[2]:
|
|
2157
2157
|
if st.button("Guardar par", key=f"thumbnail_blueprint_save_{channel_id}", width="stretch"):
|
|
2158
|
-
if thumb
|
|
2158
|
+
if thumb in {"Youtube_Generic_Thumbnail_Blueprint", "Tiktok_Generic_Thumbnail_Blueprint"} and script:
|
|
2159
2159
|
st.error("Not Allowed to Associate, System Use Only")
|
|
2160
2160
|
else:
|
|
2161
2161
|
update_channel(channel_id, {"thumbnail_blueprint_id": thumb, "default_thumbnail_blueprint_id": thumb, "blueprint_id": script, "default_blueprint_id": script})
|
|
@@ -6508,7 +6508,7 @@ def render_automation():
|
|
|
6508
6508
|
st.link_button("Abrir canal", channel_url, type="primary", width="content")
|
|
6509
6509
|
with header_cols[2]:
|
|
6510
6510
|
st.markdown("**Thumbnail Blueprint**")
|
|
6511
|
-
st.caption(str(paired_thumbnail.get("name") or "
|
|
6511
|
+
st.caption(str(paired_thumbnail.get("name") or "Youtube_Generic_Thumbnail_Blueprint"))
|
|
6512
6512
|
with header_cols[3]:
|
|
6513
6513
|
st.markdown("**Idioma do roteiro**")
|
|
6514
6514
|
st.caption(video_language_label(normalize_video_language(channel.get("language") or "pt")))
|
|
@@ -6561,7 +6561,7 @@ def render_automation():
|
|
|
6561
6561
|
"format": automation_format,
|
|
6562
6562
|
})
|
|
6563
6563
|
set_channel_defaults(channel_id, automation_blueprint, automation_voice)
|
|
6564
|
-
paired_id = str(paired_thumbnail.get("id") or "
|
|
6564
|
+
paired_id = str(paired_thumbnail.get("id") or "Youtube_Generic_Thumbnail_Blueprint")
|
|
6565
6565
|
update_channel(channel_id, {"thumbnail_blueprint_id": paired_id, "default_thumbnail_blueprint_id": paired_id})
|
|
6566
6566
|
st.success("Agendamento guardado.")
|
|
6567
6567
|
st.rerun()
|
package/hermes_ui/domain.py
CHANGED
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from .notifications import record_notification
|
|
9
9
|
from .storage import StorageIntegrityError, append_json, now, read_json, update_json, write_json
|
|
10
|
-
from .thumbnail_blueprints import
|
|
10
|
+
from .thumbnail_blueprints import thumbnail_blueprint_for_channel
|
|
11
11
|
|
|
12
12
|
STAGES = ["niche", "blueprint", "brand", "topic", "script", "title", "keywords", "video", "thumbnail_prompt", "thumbnail", "upload"]
|
|
13
13
|
# Ordem executada pelo worker. Cada etapa só é executada quando o seu artefacto
|
|
@@ -242,7 +242,7 @@ def create_tasks_for_batch(batch: dict[str, Any]) -> list[dict[str, Any]]:
|
|
|
242
242
|
"generation_settings": generation_settings,
|
|
243
243
|
"blueprint_id": payload.get("blueprint_id") or channel.get("default_blueprint_id") or channel.get("blueprint_id", ""),
|
|
244
244
|
"blueprint_name": payload.get("blueprint_name", ""),
|
|
245
|
-
"thumbnail_blueprint_id": payload.get("thumbnail_blueprint_id") or channel.get("default_thumbnail_blueprint_id") or channel.get("thumbnail_blueprint_id") or
|
|
245
|
+
"thumbnail_blueprint_id": payload.get("thumbnail_blueprint_id") or channel.get("default_thumbnail_blueprint_id") or channel.get("thumbnail_blueprint_id") or thumbnail_blueprint_for_channel({**channel, "thumbnail_blueprint_id": ""}, payload.get("format", options.get("format", ""))).get("id", ""),
|
|
246
246
|
"voice": payload.get("voice") or channel.get("default_voice") or channel.get("voice", ""),
|
|
247
247
|
"automation_on": bool(channel.get("automation_on", False)),
|
|
248
248
|
"automation_time": channel.get("automation_time", "00:00"),
|
|
@@ -525,7 +525,7 @@ def _refresh_task_channel_video_settings(task: dict[str, Any]) -> None:
|
|
|
525
525
|
channel.get("default_thumbnail_blueprint_id")
|
|
526
526
|
or channel.get("thumbnail_blueprint_id")
|
|
527
527
|
or task.get("thumbnail_blueprint_id")
|
|
528
|
-
or
|
|
528
|
+
or thumbnail_blueprint_for_channel({**channel, "thumbnail_blueprint_id": ""}, task.get("format", "")).get("id", "")
|
|
529
529
|
)
|
|
530
530
|
task["blueprint_id"] = channel.get("default_blueprint_id") or channel.get("blueprint_id") or task.get("blueprint_id", "")
|
|
531
531
|
if channel.get("style_wide"):
|
|
@@ -1353,7 +1353,8 @@ def _run_task(task: dict[str, Any]) -> dict[str, Any]:
|
|
|
1353
1353
|
blueprint = _blueprint_for_channel(channel)
|
|
1354
1354
|
if not blueprint and (task.get("blueprint_id") or task.get("blueprint_name")):
|
|
1355
1355
|
blueprint = {"id": str(task.get("blueprint_id") or ""), "name": str(task.get("blueprint_name") or task.get("blueprint_id") or "")}
|
|
1356
|
-
|
|
1356
|
+
visual_format = task.get("format") or ("portrait" if str(task.get("platform") or "").casefold() in {"tiktok", "instagram"} else "wide")
|
|
1357
|
+
visual_blueprint = thumbnail_blueprint_for_channel(channel, visual_format)
|
|
1357
1358
|
if visual_blueprint.get("content"):
|
|
1358
1359
|
blueprint = {**blueprint, "thumbnail_blueprint_rules": visual_blueprint["content"]}
|
|
1359
1360
|
route = _normalise_video_route(task, settings)
|
|
@@ -7,9 +7,12 @@ from pathlib import Path
|
|
|
7
7
|
from typing import Any, Mapping
|
|
8
8
|
|
|
9
9
|
from .provider_routing import route_llm_json
|
|
10
|
-
from .storage import BLUEPRINTS, atomic_write, list_blueprint_files, load_blueprint_file
|
|
10
|
+
from .storage import BLUEPRINTS, SEED_THUMBNAIL_BLUEPRINTS, atomic_write, list_blueprint_files, load_blueprint_file
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
HORIZONTAL_GENERIC_THUMBNAIL_BLUEPRINT_ID = "Youtube_Generic_Thumbnail_Blueprint"
|
|
13
|
+
VERTICAL_GENERIC_THUMBNAIL_BLUEPRINT_ID = "Tiktok_Generic_Thumbnail_Blueprint"
|
|
14
|
+
# Backwards-compatible name for callers that only need the landscape default.
|
|
15
|
+
GENERIC_THUMBNAIL_BLUEPRINT_ID = HORIZONTAL_GENERIC_THUMBNAIL_BLUEPRINT_ID
|
|
13
16
|
GENERIC_ASSOCIATION_ERROR = "Not Allowed to Associate, System Use Only"
|
|
14
17
|
|
|
15
18
|
PROMPT_MASTER = '''You are a forensic YouTube thumbnail analyst. Build a reusable Thumbnail Blueprint from the reference channel videos below.
|
|
@@ -38,26 +41,41 @@ def resolve_thumbnail_blueprint(identifier: Any) -> dict[str, Any]:
|
|
|
38
41
|
wanted = str(identifier or "").strip()
|
|
39
42
|
if not wanted:
|
|
40
43
|
return {}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
if wanted == "Generic_Thumbnail_Blueprint":
|
|
45
|
+
wanted = HORIZONTAL_GENERIC_THUMBNAIL_BLUEPRINT_ID
|
|
46
|
+
folders = (BLUEPRINTS / "thumbnails", SEED_THUMBNAIL_BLUEPRINTS)
|
|
47
|
+
for folder in folders:
|
|
48
|
+
for path in folder.glob("*.md"):
|
|
49
|
+
if path.stem == wanted or path.name == wanted:
|
|
50
|
+
return {"id": path.stem, "name": path.stem, "path": str(path), "content": path.read_text(encoding="utf-8")}
|
|
45
51
|
return {"id": wanted, "name": wanted}
|
|
46
52
|
|
|
47
53
|
|
|
48
|
-
def
|
|
54
|
+
def _generic_thumbnail_blueprint_id(format_value: Any = "", platform: Any = "") -> str:
|
|
55
|
+
raw = str(format_value or "").strip().casefold()
|
|
56
|
+
if raw in {"portrait", "vertical", "shorts", "9:16", "tiktok", "reels"}:
|
|
57
|
+
return VERTICAL_GENERIC_THUMBNAIL_BLUEPRINT_ID
|
|
58
|
+
platform_raw = str(platform or "").strip().casefold()
|
|
59
|
+
if platform_raw in {"tiktok", "instagram", "instagram reels", "shorts"} and not raw:
|
|
60
|
+
return VERTICAL_GENERIC_THUMBNAIL_BLUEPRINT_ID
|
|
61
|
+
return HORIZONTAL_GENERIC_THUMBNAIL_BLUEPRINT_ID
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def thumbnail_blueprint_for_channel(channel: Mapping[str, Any], format_value: Any = "") -> dict[str, Any]:
|
|
49
65
|
direct = channel.get("default_thumbnail_blueprint_id") or channel.get("thumbnail_blueprint_id")
|
|
50
66
|
if direct:
|
|
51
67
|
return resolve_thumbnail_blueprint(direct)
|
|
52
68
|
script_id = str(channel.get("default_blueprint_id") or channel.get("blueprint_id") or "").strip()
|
|
53
69
|
pairs = _pair_state()
|
|
54
|
-
return resolve_thumbnail_blueprint(
|
|
70
|
+
return resolve_thumbnail_blueprint(
|
|
71
|
+
pairs.get(script_id, "") or _generic_thumbnail_blueprint_id(format_value, channel.get("platform"))
|
|
72
|
+
)
|
|
55
73
|
|
|
56
74
|
|
|
57
75
|
def thumbnail_blueprint_for_blueprint(blueprint_id: Any) -> dict[str, Any]:
|
|
58
76
|
"""Resolve the visual pair for a script Blueprint, falling back to Generic."""
|
|
59
77
|
paired_id = _pair_state().get(str(blueprint_id or "").strip(), "")
|
|
60
|
-
return resolve_thumbnail_blueprint(paired_id or
|
|
78
|
+
return resolve_thumbnail_blueprint(paired_id or HORIZONTAL_GENERIC_THUMBNAIL_BLUEPRINT_ID)
|
|
61
79
|
|
|
62
80
|
|
|
63
81
|
def _pair_state() -> dict[str, str]:
|
|
@@ -70,7 +88,7 @@ def _pair_state() -> dict[str, str]:
|
|
|
70
88
|
|
|
71
89
|
|
|
72
90
|
def save_thumbnail_blueprint_pair(thumbnail_id: str, blueprint_id: str) -> None:
|
|
73
|
-
if str(thumbnail_id)
|
|
91
|
+
if str(thumbnail_id) in {HORIZONTAL_GENERIC_THUMBNAIL_BLUEPRINT_ID, VERTICAL_GENERIC_THUMBNAIL_BLUEPRINT_ID} and str(blueprint_id):
|
|
74
92
|
raise ValueError(GENERIC_ASSOCIATION_ERROR)
|
|
75
93
|
pairs = _pair_state()
|
|
76
94
|
if blueprint_id:
|
package/package.json
CHANGED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
🔒 TRAVA DE ESTILO — NÃO NEGOCIÁVEL
|
|
2
|
+
[Um parágrafo descrevendo o estilo visual geral do canal: ex.: "Estilo limpo, minimalista, com iluminação high‑key, cores sólidas e composição centrada no rosto/produto, otimizado para consumo em tela vertical de celular."]
|
|
3
|
+
|
|
4
|
+
❌ Não permitido (elementos que o canal nunca usa, com base nas capas observadas):
|
|
5
|
+
|
|
6
|
+
[ex.: Sem texto no rodapé da tela, sem rostos cortados, sem cores saturadas, etc.]
|
|
7
|
+
|
|
8
|
+
[Liste todas as proibições observadas]
|
|
9
|
+
|
|
10
|
+
📱 ZONAS SEGURAS (VERTICAL 9:16) — OBRIGATÓRIO
|
|
11
|
+
A capa é feita para 1080 × 1920 px. A UI do TikTok/Reels cobre partes da imagem — nada essencial pode ficar ali.
|
|
12
|
+
|
|
13
|
+
text
|
|
14
|
+
0 px ─────────────────────────────
|
|
15
|
+
┌────────────────────┐
|
|
16
|
+
│ ZONA MORTA TOPO │ ~150 px (barra de progresso / "Seguindo")
|
|
17
|
+
├───────────────┬────┤
|
|
18
|
+
│ │ UI │
|
|
19
|
+
│ ÁREA SEGURA │ DI │ ~220 px (curtir, comentar,
|
|
20
|
+
│ texto + │ RE │ compartilhar, som, avatar)
|
|
21
|
+
│ rosto/produto│ │
|
|
22
|
+
│ │ │
|
|
23
|
+
├───────────────┴────┤
|
|
24
|
+
│ ZONA MORTA BASE │ ~480 px (legenda, @usuário,
|
|
25
|
+
│ │ música, CTA, barra de navegação)
|
|
26
|
+
└────────────────────┘
|
|
27
|
+
1920 px ────────────────────────────
|
|
28
|
+
Regras rígidas de zona segura:
|
|
29
|
+
|
|
30
|
+
Margem superior: 150 px livres
|
|
31
|
+
|
|
32
|
+
Margem inferior: 480 px livres
|
|
33
|
+
|
|
34
|
+
Margem direita: 220 px livres
|
|
35
|
+
|
|
36
|
+
Margem esquerda: 80 px livres
|
|
37
|
+
|
|
38
|
+
Área útil real: aproximadamente 780 × 1290 px (canto superior esquerdo em 80,150)
|
|
39
|
+
|
|
40
|
+
Recorte do grid do perfil (3:4):
|
|
41
|
+
|
|
42
|
+
O TikTok e o Instagram cortam a capa em 3:4 no grid do perfil.
|
|
43
|
+
|
|
44
|
+
O núcleo da mensagem (rosto + 2–4 palavras) deve caber dentro do recorte central 1080 × 1440 px (y entre 240 e 1680).
|
|
45
|
+
|
|
46
|
+
Teste sempre: "se essa capa for cortada em 3:4, ainda entendo a mensagem?"
|
|
47
|
+
|
|
48
|
+
🧍♂️ ENQUADRAMENTO E POSE
|
|
49
|
+
Sujeito principal: [descreva o elemento visual principal – pessoa, produto, cena, etc.]
|
|
50
|
+
|
|
51
|
+
Regras de enquadramento:
|
|
52
|
+
|
|
53
|
+
[ex.: Sujeito centralizado horizontalmente, ocupando o terço médio do quadro]
|
|
54
|
+
|
|
55
|
+
[ex.: Rosto/produto nunca abaixo de y = 1400 px]
|
|
56
|
+
|
|
57
|
+
[Adicione regras]
|
|
58
|
+
|
|
59
|
+
Ângulo de câmera: [baixo, alto, nível dos olhos, etc.]
|
|
60
|
+
|
|
61
|
+
Corte (cropping): [quanto do sujeito preenche o quadro — em vertical, o sujeito costuma ocupar 50–70% da altura útil]
|
|
62
|
+
|
|
63
|
+
Energia / ação: [estático, dinâmico, expressivo, ação congelada, etc.]
|
|
64
|
+
|
|
65
|
+
Expressão / leitura à distância: [a emoção precisa ser legível a 2 metros de distância do celular]
|
|
66
|
+
|
|
67
|
+
🎨 FUNDO E ILUMINAÇÃO
|
|
68
|
+
Ambiente de fundo: [estúdio, externo, escuro, cor sólida, etc.]
|
|
69
|
+
|
|
70
|
+
Iluminação: [high‑key, low‑key, natural, dramática, etc.]
|
|
71
|
+
|
|
72
|
+
Efeitos: [bokeh, flare, fumaça, glow, etc.]
|
|
73
|
+
|
|
74
|
+
Profundidade: [profundidade de campo rasa, foco profundo, etc.]
|
|
75
|
+
|
|
76
|
+
Ruído visual: [nível de detalhe, poluído vs. limpo — em vertical, prefira fundos simples para não competir com o texto]
|
|
77
|
+
|
|
78
|
+
🧭 IDENTIFICADORES / SÍMBOLOS DO CANAL
|
|
79
|
+
[Descreva símbolos recorrentes que comunicam contexto instantaneamente — bandeiras, logos, ícones específicos, rostos recorrentes, cores-assinatura. Se não houver, escreva explicitamente: "O canal não utiliza identificadores simbólicos recorrentes."]
|
|
80
|
+
|
|
81
|
+
🚨 ELEMENTO DE ATENÇÃO VISUAL
|
|
82
|
+
[Descreva se o canal usa setas, círculos, zooms, emojis, contornos — especifique cor, forma, posição e regras de uso. Em vertical, o elemento deve ficar acima da zona morta inferior. Se o canal NÃO usa, escreva explicitamente: "O canal não utiliza elementos de atenção visual."]
|
|
83
|
+
|
|
84
|
+
📝 ESTILO DE TEXTO
|
|
85
|
+
Estrutura:
|
|
86
|
+
|
|
87
|
+
[Número de linhas / faixas de texto e posicionamento — ex.: bloco de 2 linhas no terço superior, alinhado à esquerda]
|
|
88
|
+
|
|
89
|
+
[Hierarquia — ex.: headline grande + sub‑headline menor]
|
|
90
|
+
|
|
91
|
+
Tipografia:
|
|
92
|
+
|
|
93
|
+
[Família, peso, caixa — ex.: sans‑serif bold, CAIXA ALTA, contorno grosso]
|
|
94
|
+
|
|
95
|
+
Hierarquia de cor:
|
|
96
|
+
|
|
97
|
+
[Cor de fundo do bloco, cor do texto de cada camada, contorno/sombra]
|
|
98
|
+
|
|
99
|
+
Tamanho mínimo legível:
|
|
100
|
+
|
|
101
|
+
Fonte nunca menor que 90 px de altura de caixa (em 1080 px de largura)
|
|
102
|
+
|
|
103
|
+
Contraste alto obrigatório (texto claro sobre fundo escuro ou vice‑versa)
|
|
104
|
+
|
|
105
|
+
Cobertura de texto: [% aproximado da capa ocupada por texto — ex.: faixa superior ocupando 25%]
|
|
106
|
+
|
|
107
|
+
Regra do mudo: a capa precisa funcionar com o som desligado.
|
|
108
|
+
|
|
109
|
+
🧠 PSICOLOGIA DO TEXTO
|
|
110
|
+
[Descreva os gatilhos emocionais usados nas headlines — urgência, curiosidade, choque, números, perguntas, etc.]
|
|
111
|
+
|
|
112
|
+
Exemplos de palavras/frases observadas:
|
|
113
|
+
|
|
114
|
+
[ex.: "Você não vai acreditar", "Destruiu", "Novo", "Por quê", "Ninguém fala disso"]
|
|
115
|
+
|
|
116
|
+
Restrição de comprimento: 2–4 palavras. Máximo 5. Texto longo morre em tela vertical.
|
|
117
|
+
|
|
118
|
+
🎯 REGRAS DE COMPOSIÇÃO
|
|
119
|
+
Aplicação da regra dos terços: [onde os elementos-chave são posicionados no quadro vertical]
|
|
120
|
+
|
|
121
|
+
Hierarquia focal: [ordem de importância visual — sujeito, texto, elemento de atenção, fundo]
|
|
122
|
+
|
|
123
|
+
Espaço negativo: [como as áreas vazias são usadas — em vertical, o espaço negativo é o que evita que o texto seja engolido pela UI]
|
|
124
|
+
|
|
125
|
+
Controle de distração: [o que é evitado para manter o foco — ex.: nada de detalhes no canto inferior direito]
|
|
126
|
+
|
|
127
|
+
Mobile‑first: [garantir leitura em tela de 5–6 polegadas, com brilho ambiente alto]
|
|
128
|
+
|
|
129
|
+
📐 FORMATO E QUALIDADE
|
|
130
|
+
Proporção:
|
|
131
|
+
9:16 (vertical)
|
|
132
|
+
|
|
133
|
+
Resolução:
|
|
134
|
+
1080 × 1920 px (mínimo) — 1440 × 2560 px (ideal / futuro‑proof)
|
|
135
|
+
|
|
136
|
+
Compatibilidade:
|
|
137
|
+
TikTok · Instagram Reels · YouTube Shorts · Kwai — mesma capa, sem reenquadramento
|
|
138
|
+
|
|
139
|
+
Estilo de imagem:
|
|
140
|
+
[Fotorrealista / ilustrado / misto — descreva o nível de realismo]
|
|
141
|
+
|
|
142
|
+
Nitidez:
|
|
143
|
+
[Clareza esperada — ex.: detalhes nítidos no sujeito, fundo levemente suave]
|
|
144
|
+
|
|
145
|
+
Restrições:
|
|
146
|
+
|
|
147
|
+
[Liste restrições técnicas observadas — sem marca d'água, sem blur, sem iluminação chapada, sem texto cortado pela UI, etc.]
|
|
148
|
+
|
|
149
|
+
[Nenhum elemento essencial fora da ÁREA SEGURA definida acima]
|
|
150
|
+
|
|
151
|
+
🧠 OBJETIVO FINAL
|
|
152
|
+
Objetivo primário:
|
|
153
|
+
Maximizar CTR / taxa de retenção nos 3 primeiros segundos para [nicho do canal — ex.: reviews de tecnologia, finanças pessoais, true crime, etc.] em formato vertical.
|
|
154
|
+
|
|
155
|
+
Gatilhos emocionais:
|
|
156
|
+
[Emoções-alvo — ex.: curiosidade, medo, surpresa, excitação, indignação]
|
|
157
|
+
|
|
158
|
+
Público:
|
|
159
|
+
[Descreva o espectador-alvo — ex.: entusiastas de tech, investidores, público 18–34]
|
|
160
|
+
|
|
161
|
+
A capa deve comunicar instantaneamente:
|
|
162
|
+
[Mensagem central — ex.: "Uma revelação chocante", "Um produto imperdível", "Você está fazendo isso errado"]
|
|
163
|
+
|
|
164
|
+
Teste de aprovação (checklist):
|
|
165
|
+
|
|
166
|
+
□ Mensagem compreendida em menos de 1 segundo
|
|
167
|
+
□ Nada importante abaixo de y = 1440 px
|
|
168
|
+
□ Nada importante à direita de x = 860 px
|
|
169
|
+
□ Funciona no recorte 3:4 do grid do perfil
|
|
170
|
+
□ Funciona com o som desligado
|
|
171
|
+
□ Legível em tela de celular a 2 metros de distância
|
|
172
|
+
🧾 FORMATO DE ENTRADA FINAL
|
|
173
|
+
TÍTULO DO VÍDEO / TEMA:
|
|
174
|
+
"[Insira o título real do vídeo ou o tópico do roteiro aqui]"
|
|
175
|
+
|
|
176
|
+
⚙️ INSTRUÇÃO FINAL DE SISTEMA
|
|
177
|
+
Usando o estilo travado definido acima, gere uma capa vertical para TikTok/Instagram Reels que siga rigorosamente todas as regras composicionais, textuais e visuais.
|
|
178
|
+
|
|
179
|
+
Extraia automaticamente 2–4 palavras do TÍTULO DO VÍDEO fornecido para servir como headline e posicione-a de acordo com as regras de ESTILO DE TEXTO e de ZONAS SEGURAS (nada abaixo de y = 1440 px, nada à direita de x = 860 px).
|
|
180
|
+
|
|
181
|
+
A saída deve ser uma capa pronta para upload em 9:16, mínimo 1080 × 1920 px, replicando a identidade visual exata do canal — válida simultaneamente para TikTok, Reels e Shorts, e legível também no recorte 3:4 do grid do perfil. [Especifique se o logo deve ser incluído ou não — ex.: "SEM LOGO" ou "INCLUIR LOGO DO CANAL no canto superior esquerdo, dentro da área segura".]
|
|
File without changes
|