@danhachuel/thunderbolt 0.4.27 → 0.4.29

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 CHANGED
@@ -4055,6 +4055,9 @@ def _thumbnail_editor_context(record: dict[str, Any]) -> tuple[dict[str, Any], d
4055
4055
  blueprint = blueprint_for_channel(channel)
4056
4056
  if not blueprint and record.get("blueprint_id"):
4057
4057
  blueprint = {"id": record.get("blueprint_id"), "name": record.get("blueprint_name") or record.get("blueprint_id")}
4058
+ thumbnail_blueprint = thumbnail_blueprint_for_channel({**channel, "thumbnail_blueprint_id": record.get("thumbnail_blueprint_id") or channel.get("thumbnail_blueprint_id", "")})
4059
+ if thumbnail_blueprint.get("content"):
4060
+ blueprint = {**blueprint, "thumbnail_blueprint_rules": thumbnail_blueprint["content"]}
4058
4061
  return channel, blueprint
4059
4062
 
4060
4063
 
@@ -4141,7 +4144,9 @@ def render_thumbnails():
4141
4144
  ):
4142
4145
  try:
4143
4146
  with st.spinner("A gerar a imagem com Nano Banana…"):
4144
- _task, generated_path = generate_thumbnail_for_task(task_id, settings)
4147
+ channel, _blueprint = _thumbnail_editor_context(record)
4148
+ thumbnail_blueprint = thumbnail_blueprint_for_channel({**channel, "thumbnail_blueprint_id": record.get("thumbnail_blueprint_id") or channel.get("thumbnail_blueprint_id", "")})
4149
+ _task, generated_path = generate_thumbnail_for_task(task_id, settings, thumbnail_blueprint=thumbnail_blueprint)
4145
4150
  record_notification(
4146
4151
  "thumbnail_generation_completed",
4147
4152
  f"Thumbnail gerada: {record['title']}",
@@ -235,8 +235,8 @@ def _image_request(card: dict[str, Any], prompt: str) -> Any:
235
235
  constrained_prompt = _append_generation_constraints(
236
236
  prompt,
237
237
  kind="image",
238
- aspect_ratio=str(card.get("aspect_ratio") or ""),
239
- size=str(card.get("image_size") or ""),
238
+ aspect_ratio="16:9",
239
+ size="1280x720 minimum",
240
240
  )
241
241
  if style == "cloudflare":
242
242
  return requests.post(endpoint, headers=_headers(card), json={"prompt": constrained_prompt}, timeout=180)
@@ -278,8 +278,8 @@ def generate_image_for_card(
278
278
  _append_generation_constraints(
279
279
  prompt,
280
280
  kind="image",
281
- aspect_ratio=str(card.get("aspect_ratio") or ""),
282
- size=str(card.get("image_size") or ""),
281
+ aspect_ratio="16:9",
282
+ size="1280x720 minimum",
283
283
  ),
284
284
  topic=topic,
285
285
  variant_index=variant_index,
@@ -168,8 +168,10 @@ def generate_thumbnail_image(
168
168
  )
169
169
 
170
170
  model = str(settings.get("gemini_image_model") or DEFAULT_GEMINI_IMAGE_MODEL).strip()
171
- aspect_ratio = str(settings.get("gemini_image_aspect_ratio") or DEFAULT_ASPECT_RATIO).strip()
172
- image_size = str(settings.get("gemini_image_size") or DEFAULT_IMAGE_SIZE).strip()
171
+ # YouTube thumbnails are never square: do not allow a legacy/provider setting
172
+ # to override the required horizontal canvas.
173
+ aspect_ratio = DEFAULT_ASPECT_RATIO
174
+ image_size = DEFAULT_IMAGE_SIZE
173
175
  request_input: str | list[dict[str, str]] = effective_prompt
174
176
  if reference_image:
175
177
  request_input = [
@@ -10,8 +10,9 @@ from typing import Any
10
10
  from .creative_generation import generate_thumbnail_prompt
11
11
  from .media_generation import generate_image_from_pool
12
12
  from .media_providers import media_cards_for_pool
13
- from .storage import STORAGE, now, read_json, update_json
13
+ from .storage import STORAGE, ensure_storage, now, read_json, update_json
14
14
  from .thumbnail_generation import ThumbnailGenerationError, generate_thumbnail_image
15
+ from .thumbnail_blueprints import thumbnail_blueprint_for_channel
15
16
 
16
17
 
17
18
  def _generate_image_with_pool(
@@ -97,6 +98,7 @@ def normalize_thumbnail_task(task: dict[str, Any]) -> dict[str, Any]:
97
98
  "channel_name": str(task.get("channel_name") or "Canal sem nome").strip(),
98
99
  "blueprint_id": str(task.get("blueprint_id") or "").strip(),
99
100
  "blueprint_name": str(task.get("blueprint_name") or "").strip(),
101
+ "thumbnail_blueprint_id": str(task.get("thumbnail_blueprint_id") or "").strip(),
100
102
  "status": str(task.get("thumbnail_status") or "not_generated"),
101
103
  "prompt": str(prompt or "").strip(),
102
104
  "image_path": _as_path(image_path),
@@ -242,7 +244,11 @@ def _update_thumbnail_task(task_id: str, callback: Any) -> dict[str, Any]:
242
244
  return update_json("tasks.json", [], mutate)
243
245
 
244
246
 
245
- def generate_thumbnail_for_task(task_id: str, settings: dict[str, Any]) -> tuple[dict[str, Any], Path]:
247
+ def generate_thumbnail_for_task(
248
+ task_id: str,
249
+ settings: dict[str, Any],
250
+ thumbnail_blueprint: dict[str, Any] | None = None,
251
+ ) -> tuple[dict[str, Any], Path]:
246
252
  """Generate an image from the task's current prompt and persist it on the task."""
247
253
  tasks = read_json("tasks.json", [])
248
254
  if not isinstance(tasks, list):
@@ -251,9 +257,13 @@ def generate_thumbnail_for_task(task_id: str, settings: dict[str, Any]) -> tuple
251
257
  if not record["prompt"]:
252
258
  raise ThumbnailGenerationError("A thumbnail não tem um prompt de imagem para gerar.")
253
259
  _archive_image(str(task_id), record.get("image_path"))
260
+ visual_prompt = record["prompt"]
261
+ rules = str((thumbnail_blueprint or {}).get("content") or "").strip()
262
+ if rules:
263
+ visual_prompt = f"{visual_prompt}\n\nTHUMBNAIL BLUEPRINT — FOLLOW THESE VISUAL RULES:\n{rules}"
254
264
  image_path = _generate_image_with_pool(
255
265
  settings,
256
- record["prompt"],
266
+ visual_prompt,
257
267
  topic=record["title"] or record["topic"],
258
268
  variant_index=record["variant_index"],
259
269
  lettering_text=record.get("thumbnail_text") or "",
@@ -281,12 +291,19 @@ def regenerate_thumbnail_prompt(
281
291
  topic = record["topic"] or record["title"]
282
292
  if not topic:
283
293
  raise ThumbnailGenerationError("A tarefa não tem tópico para refazer o prompt da thumbnail.")
294
+ ensure_storage()
295
+ visual_blueprint = thumbnail_blueprint_for_channel(
296
+ {**(channel or {}), "thumbnail_blueprint_id": record.get("thumbnail_blueprint_id") or (channel or {}).get("thumbnail_blueprint_id", "")}
297
+ )
298
+ effective_blueprint = {**(blueprint or {})}
299
+ if visual_blueprint.get("content"):
300
+ effective_blueprint["thumbnail_blueprint_rules"] = visual_blueprint["content"]
284
301
  variant = generate_thumbnail_prompt(
285
302
  settings,
286
303
  channel or {},
287
304
  topic,
288
305
  current_prompt=record["prompt"],
289
- blueprint=blueprint,
306
+ blueprint=effective_blueprint,
290
307
  language=language,
291
308
  )
292
309
  updated = _update_thumbnail_task(
@@ -435,4 +452,3 @@ __all__ = [
435
452
  "regenerate_thumbnail_prompt_and_image",
436
453
  "upload_thumbnail_image",
437
454
  ]
438
-
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.4.27",
3
+ "version": "0.4.29",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",