@danhachuel/thunderbolt 0.6.80 → 0.6.81
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/influencers_ui.py +14 -12
- package/hermes_ui/media_generation.py +3 -3
- package/package.json +1 -1
package/app/influencers_ui.py
CHANGED
|
@@ -28,7 +28,6 @@ from hermes_ui.media_generation import (
|
|
|
28
28
|
generate_motion_control_video,
|
|
29
29
|
generate_ugc_product_video,
|
|
30
30
|
generate_video_for_card,
|
|
31
|
-
upload_kie_file,
|
|
32
31
|
validate_motion_control_file,
|
|
33
32
|
)
|
|
34
33
|
from hermes_ui.creative_generation import generate_ugc_segment_prompts
|
|
@@ -349,8 +348,12 @@ def _workflow_owner(repository: Any) -> str:
|
|
|
349
348
|
return ensure_standalone_content_owner(repository)
|
|
350
349
|
|
|
351
350
|
|
|
352
|
-
def _workflow_provider_cards(settings: Mapping[str, Any]) -> list[dict[str, Any]]:
|
|
353
|
-
|
|
351
|
+
def _workflow_provider_cards(settings: Mapping[str, Any], *, provider: str | None = "kie_ai") -> list[dict[str, Any]]:
|
|
352
|
+
cards = media_cards_for_pool(settings, "video")
|
|
353
|
+
if provider is None:
|
|
354
|
+
return cards
|
|
355
|
+
normalized_provider = str(provider).strip().lower()
|
|
356
|
+
return [card for card in cards if str(card.get("provider") or "").strip().lower() == normalized_provider]
|
|
354
357
|
|
|
355
358
|
|
|
356
359
|
def render_motion_control(settings: dict[str, Any]) -> None:
|
|
@@ -421,18 +424,17 @@ def render_motion_control(settings: dict[str, Any]) -> None:
|
|
|
421
424
|
|
|
422
425
|
|
|
423
426
|
def render_ugc_products(settings: dict[str, Any]) -> None:
|
|
424
|
-
"""Render the local product-image to
|
|
427
|
+
"""Render the local product-image to video workflow without Telegram or social publishing."""
|
|
425
428
|
st.title("UGC Products")
|
|
426
|
-
st.caption("Crie um anúncio UGC a partir de uma imagem de produto e de um roteiro local. O workflow gera dois clips
|
|
429
|
+
st.caption("Crie um anúncio UGC a partir de uma imagem de produto e de um roteiro local. O workflow gera dois clips de vídeo com o provider e modelo seleccionados e junta-os com FFmpeg no storage local.")
|
|
427
430
|
repository = _repository(settings)
|
|
428
431
|
if repository is None:
|
|
429
432
|
return
|
|
430
|
-
video_cards =
|
|
433
|
+
video_cards = _workflow_provider_cards(settings, provider=None)
|
|
431
434
|
if not video_cards:
|
|
432
|
-
st.warning("Active e configure
|
|
435
|
+
st.warning("Active e configure pelo menos um provider de vídeo no pool de vídeo em Configuração API > API Keys > Imagem e Video IA.")
|
|
433
436
|
return
|
|
434
437
|
image_cards, _ = _provider_options(settings, "image")
|
|
435
|
-
image_cards = [card for card in image_cards if str(card.get("provider") or "").strip().lower() == "nano_banana"]
|
|
436
438
|
with st.form("influencer_ugc_products_form"):
|
|
437
439
|
product_upload = st.file_uploader("Imagem do produto", type=["jpg", "jpeg", "png", "webp"], key="ugc_products_image")
|
|
438
440
|
script = st.text_area("Roteiro de vídeo", height=180, placeholder="Escreva a demonstração, falas e acções. Para controlar os dois clips manualmente, separe-os com uma linha contendo ---.", key="ugc_products_script")
|
|
@@ -444,7 +446,7 @@ def render_ugc_products(settings: dict[str, Any]) -> None:
|
|
|
444
446
|
elif improve_image:
|
|
445
447
|
st.info("Não existe um provider activo no pool de imagem; será usada a imagem original.")
|
|
446
448
|
card_options = [str(card.get("id") or "") for card in video_cards]
|
|
447
|
-
provider_id = st.selectbox("Provider
|
|
449
|
+
provider_id = st.selectbox("Provider / modelo de vídeo", card_options, format_func=lambda value: _provider_label(next(card for card in video_cards if str(card.get("id")) == value)), key="ugc_products_video_provider")
|
|
448
450
|
generate = st.form_submit_button("Gerar UGC Product", type="primary", use_container_width=True)
|
|
449
451
|
if generate:
|
|
450
452
|
if product_upload is None:
|
|
@@ -467,15 +469,15 @@ def render_ugc_products(settings: dict[str, Any]) -> None:
|
|
|
467
469
|
"prompt": script,
|
|
468
470
|
"caption": "",
|
|
469
471
|
"provider": card.get("provider"),
|
|
470
|
-
"model": str(card.get("model") or "
|
|
472
|
+
"model": str(card.get("model") or ""),
|
|
471
473
|
"platform": "",
|
|
472
474
|
"state": "running",
|
|
473
475
|
"metadata": {"workflow": "ugc_products", "product_image_path": str(product_path), "effective_image_path": str(image_path), "segment_prompts": prompts, "telegram": False, "social_publish": False},
|
|
474
476
|
}
|
|
475
477
|
)
|
|
476
|
-
|
|
478
|
+
image_url = _image_input({"stored_path": str(image_path), "mime_type": mimetypes.guess_type(image_path.name)[0] or "image/jpeg"})
|
|
479
|
+
with st.spinner("A gerar os dois clips com o provider seleccionado e a juntá-los localmente…"):
|
|
477
480
|
try:
|
|
478
|
-
image_url = upload_kie_file(image_path, card, upload_path="thunderbolt/ugc-products")
|
|
479
481
|
output, task_ids = generate_ugc_product_video(settings, card, image_url=image_url, prompts=prompts)
|
|
480
482
|
repository.update_content(record["id"], {"state": "completed", "artifact_path": str(output), "provider_request_id": ",".join(task_ids), "metadata": {"workflow": "ugc_products", "product_image_path": str(product_path), "effective_image_path": str(image_path), "segment_prompts": prompts, "task_ids": task_ids, "telegram": False, "social_publish": False}})
|
|
481
483
|
record_notification("influencer_content_completed", "UGC Products concluído", "O vídeo UGC Product foi gerado, unido e guardado localmente.", metadata={"content_id": record["id"], "workflow": "ugc_products", "provider": card.get("provider")}, dedupe_key=f"influencer-workflow:{record['id']}:completed")
|
|
@@ -1102,7 +1102,7 @@ def generate_ugc_product_video(
|
|
|
1102
1102
|
prompts: list[str],
|
|
1103
1103
|
output_path: Path | None = None,
|
|
1104
1104
|
) -> tuple[Path, list[str]]:
|
|
1105
|
-
"""Generate two 8-second
|
|
1105
|
+
"""Generate two 8-second clips with the selected video card and concatenate them locally."""
|
|
1106
1106
|
clean_prompts = [str(item or "").strip() for item in prompts if str(item or "").strip()]
|
|
1107
1107
|
if len(clean_prompts) != 2:
|
|
1108
1108
|
raise MediaGenerationError("UGC Products requer exactamente dois prompts de segmento.")
|
|
@@ -1111,6 +1111,6 @@ def generate_ugc_product_video(
|
|
|
1111
1111
|
segment_paths = [destination.with_name(f"{destination.stem}-segment-{index + 1}.mp4") for index in range(2)]
|
|
1112
1112
|
task_ids: list[str] = []
|
|
1113
1113
|
for prompt, segment_path in zip(clean_prompts, segment_paths):
|
|
1114
|
-
|
|
1115
|
-
task_ids.append(
|
|
1114
|
+
generate_video_for_card(settings, card, prompt, image_url=image_url, output_path=segment_path)
|
|
1115
|
+
task_ids.append(segment_path.stem)
|
|
1116
1116
|
return concatenate_video_files(segment_paths, destination), task_ids
|
package/package.json
CHANGED