@danhachuel/thunderbolt 0.4.10 → 0.4.11
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/README.md +1 -1
- package/app/main.py +23 -3
- package/hermes_ui/material_sources.py +45 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ A migração é baseada no workflow público [AI Agents A-Z — episódio 35](ht
|
|
|
44
44
|
|
|
45
45
|
## Pipeline de vídeo — fontes e ordem de execução
|
|
46
46
|
|
|
47
|
-
Em **Pipeline Vídeos > Criação de Vídeos** e **Automação Youtube**, a opção **Pexels/Pixabay** usa a rota stock do [MoneyPrinterTurbo](https://github.com/harry0703/MoneyPrinterTurbo): as keywords do roteiro são encaminhadas para a pesquisa da fonte seleccionada, os clips são descarregados e reutilizados localmente, e o motor faz a composição com MoviePy/FFmpeg, respeitando proporção, duração máxima, concatenação, transições, correspondência visual ao roteiro, narração, legendas e música de fundo. As API keys de Pexels e Pixabay são exportadas para o `config.toml` do motor e a fonte efectiva é encaminhada por tarefa, sem depender apenas da fonte global guardada nas configurações.
|
|
47
|
+
Em **Pipeline Vídeos > Criação de Vídeos** e **Automação Youtube**, a opção **Pexels/Pixabay** usa a rota stock do [MoneyPrinterTurbo](https://github.com/harry0703/MoneyPrinterTurbo): as keywords do roteiro são encaminhadas para a pesquisa da fonte seleccionada, os clips são descarregados e reutilizados localmente, e o motor faz a composição com MoviePy/FFmpeg, respeitando proporção, duração máxima, concatenação, transições, correspondência visual ao roteiro, narração, legendas e música de fundo. As API keys de Pexels e Pixabay são exportadas para o `config.toml` do motor e a fonte efectiva é encaminhada por tarefa, sem depender apenas da fonte global guardada nas configurações. Em **Configurações > Configuração API > API Keys > Imagem e Video Montagem/MoviePy**, cada cartão possui o campo **Prioridade**: o menor número aparece primeiro e a ordenação fica guardada no storage local.
|
|
48
48
|
|
|
49
49
|
A ordem persistida da criação é **Tema → Script → Título → Keywords opcional → Vídeo → Prompt Thumbnail em JSON → Thumbnail → Upload**. O vídeo é materializado antes do prompt e da imagem da thumbnail; uma falha posterior de thumbnail não invalida um MP4 já pronto. **Full IA** é uma rota separada e usa o pool de vídeo configurável com **FAL AI, KIE AI, Agnes AI, Nano Banana, Replicate AI, Pollinations.ai, Hugging Face Inference API, InferencePort Proxy e HeyGen**, respeitando apenas cartões activos que declarem capacidade de vídeo. **Apenas Música** não chama a pipeline de vídeo nem tenta gerar thumbnail: reutiliza o áudio local/Suno já descarregado e deixa-o pronto para a integração de upload musical.
|
|
50
50
|
|
package/app/main.py
CHANGED
|
@@ -5617,7 +5617,7 @@ def _render_material_source_card(settings: dict[str, Any], cards: list[dict[str,
|
|
|
5617
5617
|
_render_credential_status("" if is_local else card.get("api_key"), local=is_local, required=not is_local)
|
|
5618
5618
|
card_form = nullcontext() if embedded else st.form(f"material_source_card_form_{card_id}")
|
|
5619
5619
|
with card_form:
|
|
5620
|
-
content_cols = st.columns(
|
|
5620
|
+
content_cols = st.columns([1.4, 1.25, 0.85])
|
|
5621
5621
|
with content_cols[0]:
|
|
5622
5622
|
if is_local:
|
|
5623
5623
|
st.caption("Esta fonte não usa API key.")
|
|
@@ -5631,6 +5631,16 @@ def _render_material_source_card(settings: dict[str, Any], cards: list[dict[str,
|
|
|
5631
5631
|
value=active_card_id == card_id,
|
|
5632
5632
|
key=f"material_card_{card_id}_selected",
|
|
5633
5633
|
)
|
|
5634
|
+
with content_cols[2]:
|
|
5635
|
+
priority = st.number_input(
|
|
5636
|
+
"Prioridade",
|
|
5637
|
+
min_value=1,
|
|
5638
|
+
max_value=999,
|
|
5639
|
+
value=max(1, int(card.get("priority", index + 1))),
|
|
5640
|
+
step=1,
|
|
5641
|
+
help="1 é o primeiro provider da fila. Em caso de falha elegível, os providers seguintes são considerados por ordem crescente.",
|
|
5642
|
+
key=f"material_card_{card_id}_priority",
|
|
5643
|
+
)
|
|
5634
5644
|
if not is_local:
|
|
5635
5645
|
_render_api_test_control(
|
|
5636
5646
|
settings,
|
|
@@ -5640,7 +5650,12 @@ def _render_material_source_card(settings: dict[str, Any], cards: list[dict[str,
|
|
|
5640
5650
|
)
|
|
5641
5651
|
save_card = st.form_submit_button("Salvar", type="primary", use_container_width=True, key=f"material_card_{card_id}_save")
|
|
5642
5652
|
if save_card:
|
|
5643
|
-
cards[index] = {
|
|
5653
|
+
cards[index] = {
|
|
5654
|
+
**card,
|
|
5655
|
+
"api_key": str(api_key or "").strip(),
|
|
5656
|
+
"enabled": bool(enabled),
|
|
5657
|
+
"priority": max(1, int(priority)),
|
|
5658
|
+
}
|
|
5644
5659
|
selected_id = card_id if selected and enabled else active_card_id
|
|
5645
5660
|
_persist_material_source_cards(settings, cards, selected_id)
|
|
5646
5661
|
st.success(f"Fonte {definition['label']} guardada.")
|
|
@@ -5668,7 +5683,12 @@ def render_material_source_api_keys(settings: dict[str, Any], *, embedded: bool
|
|
|
5668
5683
|
)
|
|
5669
5684
|
add_source_clicked = st.form_submit_button("Configurar Nova Fonte de Materiais", type="primary", use_container_width=True, key="add_material_source_card") if embedded else st.button("Configurar Nova Fonte de Materiais", type="primary", use_container_width=True, key="add_material_source_card")
|
|
5670
5685
|
if add_source_clicked:
|
|
5671
|
-
|
|
5686
|
+
new_card = new_material_card(provider_to_add, card_id=f"material-{provider_to_add}-{uuid.uuid4().hex[:8]}")
|
|
5687
|
+
new_card["priority"] = max(
|
|
5688
|
+
(int(item.get("priority", index + 1)) for index, item in enumerate(cards)),
|
|
5689
|
+
default=0,
|
|
5690
|
+
) + 1
|
|
5691
|
+
cards.append(new_card)
|
|
5672
5692
|
_persist_material_source_cards(settings, cards, str(settings.get("material_active_card_id") or ""))
|
|
5673
5693
|
st.rerun()
|
|
5674
5694
|
|
|
@@ -6,6 +6,7 @@ from uuid import uuid4
|
|
|
6
6
|
|
|
7
7
|
MATERIAL_CARDS_KEY = "material_source_cards"
|
|
8
8
|
MATERIAL_ACTIVE_CARD_KEY = "material_active_card_id"
|
|
9
|
+
DEFAULT_MATERIAL_PRIORITY = 1
|
|
9
10
|
|
|
10
11
|
MATERIAL_SOURCE_CATALOG: tuple[dict[str, str], ...] = (
|
|
11
12
|
{"code": "pexels", "label": "Pexels", "description": "Banco de vídeos e imagens para materiais da pipeline.", "legacy_key": "pexels_api_keys"},
|
|
@@ -43,15 +44,29 @@ def material_source_definition(source: Any) -> dict[str, str] | None:
|
|
|
43
44
|
return _SOURCE_BY_CODE.get(str(source or "").strip().lower())
|
|
44
45
|
|
|
45
46
|
|
|
46
|
-
def _new_card(
|
|
47
|
+
def _new_card(
|
|
48
|
+
provider: str,
|
|
49
|
+
api_key: str = "",
|
|
50
|
+
*,
|
|
51
|
+
card_id: str | None = None,
|
|
52
|
+
priority: int = DEFAULT_MATERIAL_PRIORITY,
|
|
53
|
+
) -> dict[str, Any]:
|
|
47
54
|
return {
|
|
48
55
|
"id": card_id or f"material-{provider}-{uuid4().hex[:8]}",
|
|
49
56
|
"provider": provider,
|
|
50
57
|
"api_key": str(api_key or "").strip(),
|
|
51
58
|
"enabled": True,
|
|
59
|
+
"priority": max(1, int(priority)),
|
|
52
60
|
}
|
|
53
61
|
|
|
54
62
|
|
|
63
|
+
def _normalise_priority(value: Any, fallback: int = DEFAULT_MATERIAL_PRIORITY) -> int:
|
|
64
|
+
try:
|
|
65
|
+
return max(1, int(value))
|
|
66
|
+
except (TypeError, ValueError):
|
|
67
|
+
return max(1, int(fallback or DEFAULT_MATERIAL_PRIORITY))
|
|
68
|
+
|
|
69
|
+
|
|
55
70
|
def normalize_material_card(card: Any, index: int = 0) -> dict[str, Any]:
|
|
56
71
|
raw = card if isinstance(card, dict) else {}
|
|
57
72
|
provider = str(raw.get("provider") or raw.get("source") or "pexels").strip().lower()
|
|
@@ -63,9 +78,25 @@ def normalize_material_card(card: Any, index: int = 0) -> dict[str, Any]:
|
|
|
63
78
|
"provider": provider,
|
|
64
79
|
"api_key": str(raw.get("api_key") or raw.get("key") or "").strip() if provider != "local" else "",
|
|
65
80
|
"enabled": bool(raw.get("enabled", True)),
|
|
81
|
+
"priority": _normalise_priority(raw.get("priority", index + 1), index + 1),
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
|
|
85
|
+
def _ordered_cards(cards: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
|
86
|
+
indexed = [(index, normalize_material_card(card, index)) for index, card in enumerate(cards)]
|
|
87
|
+
indexed.sort(key=lambda pair: (pair[1].get("priority", pair[0] + 1), pair[0]))
|
|
88
|
+
return [card for _index, card in indexed]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def material_source_cards(settings: dict[str, Any], *, enabled_only: bool = False) -> list[dict[str, Any]]:
|
|
92
|
+
"""Return material providers in stable priority order for the MoviePy queue."""
|
|
93
|
+
migrated, _ = ensure_material_source_cards(settings)
|
|
94
|
+
cards = [dict(item) for item in migrated.get(MATERIAL_CARDS_KEY, [])]
|
|
95
|
+
if enabled_only:
|
|
96
|
+
cards = [card for card in cards if card.get("enabled", True)]
|
|
97
|
+
return cards
|
|
98
|
+
|
|
99
|
+
|
|
69
100
|
def _cards_key_mapping(cards: list[dict[str, Any]]) -> dict[str, list[str]]:
|
|
70
101
|
mapping = {item["code"]: [] for item in MATERIAL_SOURCE_CATALOG}
|
|
71
102
|
for index, raw_card in enumerate(cards):
|
|
@@ -94,12 +125,23 @@ def ensure_material_source_cards(settings: dict[str, Any]) -> tuple[dict[str, An
|
|
|
94
125
|
cards: list[dict[str, Any]] = []
|
|
95
126
|
for item in MATERIAL_SOURCE_CATALOG:
|
|
96
127
|
for key in material_api_keys(settings, item["code"], _ignore_cards=True):
|
|
97
|
-
cards.append(
|
|
128
|
+
cards.append(
|
|
129
|
+
_new_card(
|
|
130
|
+
item["code"],
|
|
131
|
+
key,
|
|
132
|
+
card_id=f"material-{item['code']}-{len(cards)}",
|
|
133
|
+
priority=len(cards) + 1,
|
|
134
|
+
)
|
|
135
|
+
)
|
|
98
136
|
if not cards:
|
|
99
137
|
selected = selected_material_source(settings)
|
|
100
138
|
cards.append(_new_card(selected if selected in _SOURCE_BY_CODE else "pexels", card_id="material-default-0"))
|
|
101
139
|
changed = True
|
|
102
140
|
|
|
141
|
+
ordered_cards = _ordered_cards(cards)
|
|
142
|
+
if ordered_cards != cards:
|
|
143
|
+
changed = True
|
|
144
|
+
cards = ordered_cards
|
|
103
145
|
settings[MATERIAL_CARDS_KEY] = cards
|
|
104
146
|
active_id = str(settings.get(MATERIAL_ACTIVE_CARD_KEY) or "").strip()
|
|
105
147
|
valid_ids = {str(card["id"]) for card in cards}
|
|
@@ -137,6 +179,7 @@ def apply_material_source_cards_to_settings(
|
|
|
137
179
|
normalized_cards = [normalize_material_card(item, index) for index, item in enumerate(cards)]
|
|
138
180
|
if not normalized_cards:
|
|
139
181
|
normalized_cards = [_new_card("pexels", card_id="material-default-0")]
|
|
182
|
+
normalized_cards = _ordered_cards(normalized_cards)
|
|
140
183
|
settings[MATERIAL_CARDS_KEY] = normalized_cards
|
|
141
184
|
selected_card = next((card for card in normalized_cards if str(card["id"]) == str(active_card_id)), None)
|
|
142
185
|
if selected_card is None:
|
package/package.json
CHANGED