@danhachuel/thunderbolt 0.7.57 → 0.7.58
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.
|
@@ -7,7 +7,7 @@ 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, SEED_THUMBNAIL_BLUEPRINTS, atomic_write, list_blueprint_files, load_blueprint_file
|
|
10
|
+
from .storage import BLUEPRINTS, SEED_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
13
|
VERTICAL_GENERIC_THUMBNAIL_BLUEPRINT_ID = "Tiktok_Generic_Thumbnail_Blueprint"
|
|
@@ -72,7 +72,7 @@ def thumbnail_blueprint_for_channel(channel: Mapping[str, Any], format_value: An
|
|
|
72
72
|
script_id = str(channel.get("default_blueprint_id") or channel.get("blueprint_id") or "").strip()
|
|
73
73
|
pairs = _pair_state()
|
|
74
74
|
return resolve_thumbnail_blueprint(
|
|
75
|
-
|
|
75
|
+
_paired_thumbnail_id(script_id, pairs) or _generic_thumbnail_blueprint_id(format_value, channel.get("platform"))
|
|
76
76
|
)
|
|
77
77
|
|
|
78
78
|
|
|
@@ -118,6 +118,9 @@ def thumbnail_aspect_ratio_for_channel_task(
|
|
|
118
118
|
if not selected and direct and direct not in generic_ids:
|
|
119
119
|
selected = resolve_thumbnail_blueprint(direct)
|
|
120
120
|
if selected.get("content") and direct not in generic_ids:
|
|
121
|
+
explicit_ratio = _explicit_blueprint_aspect_ratio(selected.get("content"))
|
|
122
|
+
if explicit_ratio:
|
|
123
|
+
return explicit_ratio
|
|
121
124
|
return "9:16" if _contains_vertical_rules(selected.get("content")) else "16:9"
|
|
122
125
|
|
|
123
126
|
platform = str(channel.get("platform") or task.get("platform") or "").strip().casefold()
|
|
@@ -130,21 +133,60 @@ def _contains_vertical_rules(value: Any) -> bool:
|
|
|
130
133
|
return bool(re.search(r"\b9\s*:\s*16\b|\bvertical\b|\bportrait\b", str(value or ""), flags=re.IGNORECASE))
|
|
131
134
|
|
|
132
135
|
|
|
136
|
+
def _explicit_blueprint_aspect_ratio(value: Any) -> str:
|
|
137
|
+
"""Read the authoritative FORMAT/QUALITY ratio before contextual wording.
|
|
138
|
+
|
|
139
|
+
Finance and other landscape blueprints may mention a vertical third or
|
|
140
|
+
vertical positioning in their composition rules. Those words must not
|
|
141
|
+
override an explicit ``Aspect ratio: 16:9`` declaration.
|
|
142
|
+
"""
|
|
143
|
+
content = str(value or "")
|
|
144
|
+
format_sections = re.findall(
|
|
145
|
+
r"(?is)(?:FORMAT\s*(?:&|AND)?\s*QUALITY|FORMATO\s*(?:E|&)\s*QUALIDADE)(.*?)(?=\n#{1,6}\s|\Z)",
|
|
146
|
+
content,
|
|
147
|
+
)
|
|
148
|
+
search_area = "\n".join(format_sections) if format_sections else content
|
|
149
|
+
ratio_match = re.search(r"\b(16\s*:\s*9|9\s*:\s*16)\b", search_area)
|
|
150
|
+
if not ratio_match:
|
|
151
|
+
return ""
|
|
152
|
+
return "16:9" if ratio_match.group(1).replace(" ", "") == "16:9" else "9:16"
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _normalised_pair_key(value: Any) -> str:
|
|
156
|
+
return re.sub(r"[^a-z0-9]+", " ", str(value or "").casefold()).strip()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def _paired_thumbnail_id(blueprint_id: Any, pairs: Mapping[str, str]) -> str:
|
|
160
|
+
wanted = str(blueprint_id or "").strip()
|
|
161
|
+
if not wanted:
|
|
162
|
+
return ""
|
|
163
|
+
if wanted in pairs:
|
|
164
|
+
return str(pairs[wanted] or "")
|
|
165
|
+
wanted_key = _normalised_pair_key(wanted)
|
|
166
|
+
for key, value in pairs.items():
|
|
167
|
+
if _normalised_pair_key(key) == wanted_key:
|
|
168
|
+
return str(value or "")
|
|
169
|
+
return ""
|
|
170
|
+
|
|
171
|
+
|
|
133
172
|
def thumbnail_blueprint_for_blueprint(blueprint_id: Any, format_value: Any = "") -> dict[str, Any]:
|
|
134
173
|
"""Resolve the visual pair for a script Blueprint, falling back to Generic."""
|
|
135
|
-
paired_id =
|
|
174
|
+
paired_id = _paired_thumbnail_id(blueprint_id, _pair_state())
|
|
136
175
|
if isinstance(paired_id, list):
|
|
137
176
|
paired_id = paired_id[0] if paired_id else ""
|
|
138
177
|
return resolve_thumbnail_blueprint(paired_id or _generic_thumbnail_blueprint_id(format_value))
|
|
139
178
|
|
|
140
179
|
|
|
141
180
|
def _pair_state() -> dict[str, str]:
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
181
|
+
result: dict[str, str] = {}
|
|
182
|
+
for path in (SEED_BLUEPRINTS / "thumbnail_blueprint_pairs.json", BLUEPRINTS / "thumbnail_blueprint_pairs.json"):
|
|
183
|
+
try:
|
|
184
|
+
value = __import__("json").loads(path.read_text(encoding="utf-8"))
|
|
185
|
+
except (OSError, ValueError, TypeError):
|
|
186
|
+
continue
|
|
187
|
+
if isinstance(value, dict):
|
|
188
|
+
result.update({str(k): str(v) for k, v in value.items()})
|
|
189
|
+
return result
|
|
148
190
|
|
|
149
191
|
|
|
150
192
|
def thumbnail_blueprint_associations() -> dict[str, str]:
|
|
@@ -39,6 +39,9 @@ def infer_thumbnail_aspect_ratio(
|
|
|
39
39
|
) -> str:
|
|
40
40
|
"""Resolve the requested thumbnail orientation without trusting provider defaults."""
|
|
41
41
|
blueprint_content = str((blueprint or {}).get("content") or "")
|
|
42
|
+
explicit_ratio = _explicit_blueprint_aspect_ratio(blueprint_content)
|
|
43
|
+
if explicit_ratio:
|
|
44
|
+
return explicit_ratio
|
|
42
45
|
source = f"{blueprint_content}\n{str(prompt or '')}"
|
|
43
46
|
if re.search(r"\b9\s*:\s*16\b|\bvertical\b|\bportrait\b", source, flags=re.IGNORECASE):
|
|
44
47
|
return "9:16"
|
|
@@ -47,6 +50,19 @@ def infer_thumbnail_aspect_ratio(
|
|
|
47
50
|
return fallback if fallback in {"16:9", "9:16"} else DEFAULT_ASPECT_RATIO
|
|
48
51
|
|
|
49
52
|
|
|
53
|
+
def _explicit_blueprint_aspect_ratio(value: Any) -> str:
|
|
54
|
+
content = str(value or "")
|
|
55
|
+
sections = re.findall(
|
|
56
|
+
r"(?is)(?:FORMAT\s*(?:&|AND)?\s*QUALITY|FORMATO\s*(?:E|&)\s*QUALIDADE)(.*?)(?=\n#{1,6}\s|\Z)",
|
|
57
|
+
content,
|
|
58
|
+
)
|
|
59
|
+
area = "\n".join(sections) if sections else content
|
|
60
|
+
match = re.search(r"\b(16\s*:\s*9|9\s*:\s*16)\b", area)
|
|
61
|
+
if not match:
|
|
62
|
+
return ""
|
|
63
|
+
return "16:9" if match.group(1).replace(" ", "") == "16:9" else "9:16"
|
|
64
|
+
|
|
65
|
+
|
|
50
66
|
def normalize_thumbnail_bytes(image_bytes: bytes, aspect_ratio: str = DEFAULT_ASPECT_RATIO) -> bytes:
|
|
51
67
|
"""Return a non-distorted JPEG in the requested thumbnail orientation."""
|
|
52
68
|
try:
|
package/package.json
CHANGED