@danhachuel/thunderbolt 0.2.60 → 0.2.62
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 +111 -10
- package/hermes_ui/storage.py +15 -1
- package/integrations/youtube_direct_credentials.py +27 -11
- package/integrations/youtube_direct_upload.py +4 -3
- package/package.json +2 -1
- package/storage/tiktok/prompts_master/ASMR.md +74 -0
- package/storage/tiktok/prompts_master/CINEMATICSLUMTRANSFORMATIONENGINE.md +212 -0
- package/storage/tiktok/prompts_master/EPOXYFLOORTRANSFORMATION.md +254 -0
- package/storage/tiktok/prompts_master/FOODBABYFACECHARACTER.md +141 -0
- package/storage/tiktok/prompts_master/craftingStorytellingAI.md +79 -0
package/app/main.py
CHANGED
|
@@ -22,7 +22,7 @@ except (OSError, json.JSONDecodeError):
|
|
|
22
22
|
|
|
23
23
|
from hermes_ui.domain import STAGES, create_batch, create_channel, create_tasks_for_batch, delete_channel, pipeline_summary, set_channel_defaults, transition_task, update_channel, update_channel_video
|
|
24
24
|
from hermes_ui.automation_worker import load_worker_status
|
|
25
|
-
from hermes_ui.storage import BLUEPRINTS, STORAGE, ensure_storage, list_blueprint_files, load_blueprint_file, now, read_json, write_json
|
|
25
|
+
from hermes_ui.storage import BLUEPRINTS, STORAGE, TIKTOK_PROMPT_MASTERS, ensure_storage, list_blueprint_files, list_prompt_master_files, load_blueprint_file, load_prompt_master_file, now, read_json, write_json
|
|
26
26
|
from app.modules.niche_finder.apify import ApifyError, DEFAULT_ACTOR_ID, abort_actor_run, build_actor_input, get_dataset_items, normalize_video_items, start_actor_run, wait_for_actor_run
|
|
27
27
|
from app.modules.niche_finder.core import NicheAnalysisError, run_niche_analysis
|
|
28
28
|
from app.modules.niche_finder.data_loader import DatasetError, download_kaggle_dataset
|
|
@@ -42,7 +42,7 @@ from integrations.platforms import IntegrationResult, TikTokAdapter, YouTubeAdap
|
|
|
42
42
|
from integrations.postiz import PostizAdapter
|
|
43
43
|
from integrations.upload_routing import OFFICIAL_DAILY_LIMIT, official_upload_count, upload_with_default_route
|
|
44
44
|
from integrations.youtube_direct_upload import YouTubeDirectUploader
|
|
45
|
-
from integrations.youtube_direct_credentials import delete_credentials_document, direct_account_status, document_status, ensure_credentials_document, merge_credentials_document, parse_credentials_document, save_credentials_document, update_credentials_document_session_info
|
|
45
|
+
from integrations.youtube_direct_credentials import delete_credentials_document, direct_account_status, document_status, ensure_credentials_document, load_credentials_document, merge_credentials_document, parse_credentials_document, save_credentials_document, update_credentials_document_session_info
|
|
46
46
|
from integrations.youtube_batch import account_key as youtube_batch_account_key, account_status as youtube_batch_account_status, authorize_account as authorize_youtube_batch_account, delete_account_token as delete_youtube_batch_token, list_my_channels as list_youtube_batch_channels, loopback_redirect_uri
|
|
47
47
|
from integrations.local_runtime import MoneyPrinterRuntime
|
|
48
48
|
from integrations.moneyprinter_config import sync_moneyprinter_config
|
|
@@ -711,6 +711,78 @@ def render_blueprints():
|
|
|
711
711
|
st.error(str(exc))
|
|
712
712
|
|
|
713
713
|
|
|
714
|
+
def render_tiktok_prompt_masters():
|
|
715
|
+
st.title("Prompts Master")
|
|
716
|
+
st.caption(f"Biblioteca exclusiva para vídeos TikTok. Os ficheiros ficam em `{TIKTOK_PROMPT_MASTERS}` e nunca entram na pasta de Blueprints YouTube.")
|
|
717
|
+
|
|
718
|
+
upload_tab, library_tab = st.tabs(["Upload", "Biblioteca"])
|
|
719
|
+
with upload_tab:
|
|
720
|
+
st.subheader("Adicionar Prompt Master")
|
|
721
|
+
st.info("Use ficheiros Markdown `.md`. Cada Prompt Master é guardado como um ficheiro independente no storage TikTok.")
|
|
722
|
+
uploaded_prompt = st.file_uploader("Subir Prompt Master (.md)", type=["md"], key="tiktok_prompt_master_upload")
|
|
723
|
+
if uploaded_prompt is not None:
|
|
724
|
+
uploaded_name = Path(uploaded_prompt.name).stem
|
|
725
|
+
prompt_name = st.text_input("Nome do Prompt Master", value=uploaded_name, key="tiktok_prompt_master_name")
|
|
726
|
+
replace_prompt = st.checkbox("Permitir substituir um ficheiro existente", key="tiktok_prompt_master_replace")
|
|
727
|
+
if st.button("Guardar Prompt Master", type="primary", use_container_width=True, key="save_tiktok_prompt_master"):
|
|
728
|
+
safe_stem = re.sub(r"[^A-Za-z0-9À-ÿ._-]+", "-", prompt_name.strip() or uploaded_name).strip(".-") or "prompt-master"
|
|
729
|
+
destination = TIKTOK_PROMPT_MASTERS / f"{safe_stem}.md"
|
|
730
|
+
if destination.exists() and not replace_prompt:
|
|
731
|
+
st.warning("Já existe um Prompt Master com esse nome. Active a substituição para o actualizar.")
|
|
732
|
+
else:
|
|
733
|
+
try:
|
|
734
|
+
content = uploaded_prompt.getvalue().decode("utf-8-sig")
|
|
735
|
+
if not content.strip():
|
|
736
|
+
raise ValueError("O ficheiro Markdown está vazio.")
|
|
737
|
+
destination.write_text(content.rstrip() + "\n", encoding="utf-8")
|
|
738
|
+
st.success(f"Prompt Master guardado em `{destination}`.")
|
|
739
|
+
st.rerun()
|
|
740
|
+
except UnicodeDecodeError:
|
|
741
|
+
st.error("O ficheiro deve estar codificado em UTF-8.")
|
|
742
|
+
except OSError as exc:
|
|
743
|
+
st.error(f"Não foi possível guardar o Prompt Master: {exc}")
|
|
744
|
+
|
|
745
|
+
with library_tab:
|
|
746
|
+
files = list_prompt_master_files()
|
|
747
|
+
st.subheader(f"Prompts Master existentes ({len(files)})")
|
|
748
|
+
search = st.text_input("Pesquisar Prompt Master", key="tiktok_prompt_master_search", placeholder="Nome ou conteúdo")
|
|
749
|
+
visible_files: list[Path] = []
|
|
750
|
+
for path in files:
|
|
751
|
+
try:
|
|
752
|
+
content = load_prompt_master_file(path)
|
|
753
|
+
except (OSError, ValueError):
|
|
754
|
+
content = ""
|
|
755
|
+
if search and search.lower() not in f"{path.name}\n{content}".lower():
|
|
756
|
+
continue
|
|
757
|
+
visible_files.append(path)
|
|
758
|
+
if not visible_files:
|
|
759
|
+
st.info("Ainda não existem Prompt Master que correspondam à pesquisa.")
|
|
760
|
+
for path in visible_files:
|
|
761
|
+
try:
|
|
762
|
+
content = load_prompt_master_file(path)
|
|
763
|
+
heading = next((line.lstrip("#").strip() for line in content.splitlines() if line.startswith("#")), path.stem)
|
|
764
|
+
with st.expander(f"{heading} — {path.name}", expanded=False):
|
|
765
|
+
st.caption(f"Ficheiro TikTok: `{path}`")
|
|
766
|
+
edited_content = st.text_area("Conteúdo Markdown", value=content, height=360, key=f"tiktok_prompt_master_editor_{path.stem}")
|
|
767
|
+
prompt_cols = st.columns(3)
|
|
768
|
+
with prompt_cols[0]:
|
|
769
|
+
if st.button("Guardar alterações", type="primary", use_container_width=True, key=f"save_prompt_master_{path.stem}"):
|
|
770
|
+
path.write_text(edited_content.rstrip() + "\n", encoding="utf-8")
|
|
771
|
+
st.success("Prompt Master actualizado.")
|
|
772
|
+
st.rerun()
|
|
773
|
+
with prompt_cols[1]:
|
|
774
|
+
st.download_button("Descarregar", data=content.encode("utf-8"), file_name=path.name, mime="text/markdown", use_container_width=True, key=f"download_prompt_master_{path.stem}")
|
|
775
|
+
with prompt_cols[2]:
|
|
776
|
+
if st.button("Apagar", use_container_width=True, key=f"delete_prompt_master_{path.stem}"):
|
|
777
|
+
path.unlink(missing_ok=True)
|
|
778
|
+
st.success("Prompt Master removido da biblioteca TikTok.")
|
|
779
|
+
st.rerun()
|
|
780
|
+
st.markdown(content)
|
|
781
|
+
except (OSError, ValueError) as exc:
|
|
782
|
+
with st.expander(f"Ficheiro inválido — {path.name}"):
|
|
783
|
+
st.error(str(exc))
|
|
784
|
+
|
|
785
|
+
|
|
714
786
|
def render_channels():
|
|
715
787
|
st.title("Canais Youtube")
|
|
716
788
|
st.caption("Escolha entre importar dados públicos do YouTube ou preencher o canal manualmente.")
|
|
@@ -2571,7 +2643,7 @@ def render_settings():
|
|
|
2571
2643
|
account_email_snapshot = str(batch_account.get("email") or "sem e-mail")
|
|
2572
2644
|
account_label_snapshot = str(batch_account.get("label") or "Canais YouTube")
|
|
2573
2645
|
ensure_credentials_document(STORAGE, batch_account, settings, channel_state)
|
|
2574
|
-
direct_status = direct_account_status(STORAGE, batch_account)
|
|
2646
|
+
direct_status = direct_account_status(STORAGE, batch_account, settings)
|
|
2575
2647
|
missing_document_parts = list(direct_status.get("missing_cookies", []))
|
|
2576
2648
|
if not direct_status.get("has_session_info"):
|
|
2577
2649
|
missing_document_parts.append("sessionInfo")
|
|
@@ -2678,6 +2750,31 @@ def render_settings():
|
|
|
2678
2750
|
if youtube_accounts_missing_document:
|
|
2679
2751
|
st.info("Contas que ainda precisam de dados no documento: " + ", ".join(youtube_accounts_missing_document))
|
|
2680
2752
|
|
|
2753
|
+
st.divider()
|
|
2754
|
+
st.markdown("### INNERTUBE_API_KEY")
|
|
2755
|
+
st.caption("Esta chave pertence à conta Google/YouTube seleccionada e fica guardada na configuração da conta. Não faz parte do documento de cookies/credenciais e não é editada no separador API Keys.")
|
|
2756
|
+
account_key_options = [str(account.get("id")) for account in batch_accounts if account.get("id")]
|
|
2757
|
+
account_key_labels = {str(account.get("id")): f"{account.get('label', 'Canais YouTube')} — {account.get('email', 'sem e-mail')}" for account in batch_accounts if account.get("id")}
|
|
2758
|
+
if account_key_options:
|
|
2759
|
+
with st.form("innertube_api_key_form"):
|
|
2760
|
+
selected_key_account_id = st.selectbox("Conta Google/YouTube", account_key_options, format_func=lambda value: account_key_labels.get(value, value), key="innertube_key_account")
|
|
2761
|
+
selected_key_account = next(account for account in batch_accounts if str(account.get("id")) == selected_key_account_id)
|
|
2762
|
+
current_innertube_api_key = direct_account_status(STORAGE, selected_key_account, settings).get("innertube_api_key", "")
|
|
2763
|
+
innertube_api_key_value = st.text_input("INNERTUBE_API_KEY", value=current_innertube_api_key, type="password", key=f"innertube_api_key_{selected_key_account_id}", help="Chave usada pelo Upload directo desta conta. Guarde-a aqui, separada do documento de cookies.")
|
|
2764
|
+
save_innertube_api_key = st.form_submit_button("Guardar INNERTUBE_API_KEY", type="primary", use_container_width=True)
|
|
2765
|
+
if save_innertube_api_key:
|
|
2766
|
+
selected_key_account["innertube_api_key"] = innertube_api_key_value.strip()
|
|
2767
|
+
selected_key_account.pop("INNERTUBE_API_KEY", None)
|
|
2768
|
+
settings.pop("direct_innertube_api_key", None)
|
|
2769
|
+
settings["youtube_batch_accounts"] = batch_accounts
|
|
2770
|
+
write_json("settings.json", settings)
|
|
2771
|
+
document = load_credentials_document(STORAGE, selected_key_account, settings, channel_state, create=True)
|
|
2772
|
+
save_credentials_document(STORAGE, selected_key_account, document)
|
|
2773
|
+
st.success("INNERTUBE_API_KEY guardada na configuração da conta Google/YouTube, fora do documento de cookies.")
|
|
2774
|
+
st.rerun()
|
|
2775
|
+
else:
|
|
2776
|
+
st.info("Adicione primeiro uma conta Google/YouTube para configurar a INNERTUBE_API_KEY.")
|
|
2777
|
+
|
|
2681
2778
|
st.divider()
|
|
2682
2779
|
st.markdown("### Adicionar outra conta Gmail")
|
|
2683
2780
|
st.caption("Este formulário fica fora dos cartões das contas existentes. A associação de canais não depende da completude deste documento; ela apenas ficará pendente para Upload directo até os campos serem preenchidos.")
|
|
@@ -2689,7 +2786,7 @@ def render_settings():
|
|
|
2689
2786
|
new_account_client_id = st.text_input("OAuth Client ID", key="new_batch_account_client_id")
|
|
2690
2787
|
with add_cols[1]:
|
|
2691
2788
|
new_account_client_secret = st.text_input("OAuth Client Secret", type="password", key="new_batch_account_client_secret")
|
|
2692
|
-
new_account_session_info = st.text_input("sessionInfo token desta conta Google", type="password", key="new_batch_account_session_info", help="Token sessionInfo desta conta. Os cookies
|
|
2789
|
+
new_account_session_info = st.text_input("sessionInfo token desta conta Google", type="password", key="new_batch_account_session_info", help="Token sessionInfo desta conta. Os cookies e delegated_session_ids ficam no documento; a INNERTUBE_API_KEY é configurada no bloco próprio acima.")
|
|
2693
2790
|
new_account_document = st.file_uploader("Documento de cookies/credenciais opcional", type=["json"], key="new_batch_account_credentials_document", help="Pode subir agora um JSON completo ou apenas o documento de cookies. Se não subir, será criado um credentials.json padrão vazio.")
|
|
2694
2791
|
add_account = st.form_submit_button("Adicionar conta Google/YouTube", type="primary", use_container_width=True)
|
|
2695
2792
|
if add_account:
|
|
@@ -2763,10 +2860,6 @@ def render_settings():
|
|
|
2763
2860
|
st.caption("A YouTube Data API Key é uma credencial Google Cloud separada do OAuth. Só é necessária se escolher o método YouTube Data API para consultar métricas oficiais. Não é necessária para Página pública — sem API Key, para autorizar OAuth ou para fazer upload.")
|
|
2764
2861
|
youtube_api_key = text_setting("YouTube Data API Key (opcional)", "youtube_api_key", secret=True, help_text="Credencial separada, criada em Google Cloud > APIs e serviços > Credenciais > Chave de API. Não cole aqui o Client ID nem o Client Secret.")
|
|
2765
2862
|
|
|
2766
|
-
st.caption("As credenciais e parâmetros do Upload directo — cookies, sessionInfo, INNERTUBE_API_KEY, chunk_size e DELEGATED_SESSION_ID — são lidos exclusivamente do documento JSON por conta Google. Não são editados nesta UI.")
|
|
2767
|
-
direct_innertube_api_key = str(settings.get("direct_innertube_api_key", "") or "")
|
|
2768
|
-
direct_chunk_size = int(settings.get("direct_chunk_size", 262144) or 262144)
|
|
2769
|
-
|
|
2770
2863
|
with st.expander("Serviço, materiais e rede"):
|
|
2771
2864
|
cols = st.columns(2)
|
|
2772
2865
|
with cols[0]:
|
|
@@ -2917,7 +3010,6 @@ def render_settings():
|
|
|
2917
3010
|
"youtube_client_id": youtube_client_id, "youtube_client_secret": youtube_client_secret,
|
|
2918
3011
|
"kaggle_username": kaggle_username.strip(), "kaggle_api_key": kaggle_api_key.strip(), "kaggle_kernel_slug": kaggle_kernel_slug.strip() or "thunderbolt-niche-finder",
|
|
2919
3012
|
"apify_api_token": apify_api_token.strip(), "apify_actor_id": apify_actor_id.strip() or DEFAULT_ACTOR_ID, "apify_poll_interval_seconds": int(apify_poll_interval), "apify_run_timeout_seconds": int(apify_run_timeout),
|
|
2920
|
-
"direct_innertube_api_key": direct_innertube_api_key, "direct_chunk_size": direct_chunk_size,
|
|
2921
3013
|
"log_level": log_level, "listen_host": listen_host, "listen_port": listen_port, "video_source": video_source,
|
|
2922
3014
|
"endpoint": endpoint, "proxy_http": proxy_http, "proxy_https": proxy_https, "match_materials_to_script": match_materials_to_script,
|
|
2923
3015
|
"llm_provider": llm_provider, "openai_api_key": openai_api_key, "openai_base_url": openai_base_url, "openai_model_name": openai_model_name,
|
|
@@ -3256,6 +3348,9 @@ def main():
|
|
|
3256
3348
|
("Roteiros", ":material/article:", "Roteiros"),
|
|
3257
3349
|
("Upload", ":material/cloud_upload:", "Upload"),
|
|
3258
3350
|
]
|
|
3351
|
+
pipeline_tiktok_items = [
|
|
3352
|
+
("Prompts Master", ":material/auto_awesome:", "Prompts Master"),
|
|
3353
|
+
]
|
|
3259
3354
|
edition_items = [
|
|
3260
3355
|
("Limpador de Metadados", ":material/edit_note:", "Limpador de Metadados"),
|
|
3261
3356
|
("Cortes", ":material/content_cut:", "Cortes"),
|
|
@@ -3282,6 +3377,7 @@ def main():
|
|
|
3282
3377
|
("Início", ":material/home:", "Início"),
|
|
3283
3378
|
("Niche Finder", ":material/search:", "Niche Finder"),
|
|
3284
3379
|
("Pipeline", ":material/account_tree:", "Pipeline"),
|
|
3380
|
+
("Pipeline TikTok", ":material/video_library:", "Pipeline TikTok"),
|
|
3285
3381
|
("Automação", ":material/schedule:", "Automação"),
|
|
3286
3382
|
("Edição", ":material/edit:", "Edição"),
|
|
3287
3383
|
("Models AI", ":material/smart_toy:", "Models AI"),
|
|
@@ -3299,7 +3395,7 @@ def main():
|
|
|
3299
3395
|
"Configurações Técnicas": "Configurações Técnicas",
|
|
3300
3396
|
}
|
|
3301
3397
|
current_page = aliases.get(st.session_state.get("page", "Início"), st.session_state.get("page", "Início"))
|
|
3302
|
-
if current_page not in {item[0] for item in top_pages + pipeline_items + automation_items + edition_items + models_ai_items + niche_finder_items + settings_items}:
|
|
3398
|
+
if current_page not in {item[0] for item in top_pages + pipeline_items + pipeline_tiktok_items + automation_items + edition_items + models_ai_items + niche_finder_items + settings_items}:
|
|
3303
3399
|
current_page = "Início"
|
|
3304
3400
|
st.session_state["page"] = current_page
|
|
3305
3401
|
|
|
@@ -3319,6 +3415,10 @@ def main():
|
|
|
3319
3415
|
with st.expander("Pipeline", expanded=current_page in {item[0] for item in pipeline_items}, icon=":material/account_tree:"):
|
|
3320
3416
|
for child_target, child_icon, child_label in pipeline_items:
|
|
3321
3417
|
render_nav_button(child_target, child_icon, child_label, child=True)
|
|
3418
|
+
elif target == "Pipeline TikTok":
|
|
3419
|
+
with st.expander("Pipeline TikTok", expanded=current_page in {item[0] for item in pipeline_tiktok_items}, icon=":material/video_library:"):
|
|
3420
|
+
for child_target, child_icon, child_label in pipeline_tiktok_items:
|
|
3421
|
+
render_nav_button(child_target, child_icon, child_label, child=True)
|
|
3322
3422
|
elif target == "Automação":
|
|
3323
3423
|
with st.expander("Automação", expanded=current_page in {item[0] for item in automation_items}, icon=":material/schedule:"):
|
|
3324
3424
|
for child_target, child_icon, child_label in automation_items:
|
|
@@ -3347,6 +3447,7 @@ def main():
|
|
|
3347
3447
|
"Criação de Vídeos": render_new_video,
|
|
3348
3448
|
"Criação de Músicas": render_music_creation,
|
|
3349
3449
|
"Roteiros": render_scripts,
|
|
3450
|
+
"Prompts Master": render_tiktok_prompt_masters,
|
|
3350
3451
|
"Automação Youtube": render_automation,
|
|
3351
3452
|
"Niche Finder Kaggle": render_niche_finder,
|
|
3352
3453
|
"Niche Finder Apify": render_niche_finder_apify,
|
package/hermes_ui/storage.py
CHANGED
|
@@ -12,6 +12,7 @@ ROOT = Path(__file__).resolve().parents[1]
|
|
|
12
12
|
STORAGE = Path(os.getenv("THUNDERBOLT_STORAGE_DIR") or ROOT / "storage")
|
|
13
13
|
STATE = STORAGE / "state"
|
|
14
14
|
BLUEPRINTS = STORAGE / "blueprints"
|
|
15
|
+
TIKTOK_PROMPT_MASTERS = STORAGE / "tiktok" / "prompts_master"
|
|
15
16
|
NICHES_DATA = STORAGE / "data" / "niches"
|
|
16
17
|
SEED_BLUEPRINTS = ROOT / "seed" / "blueprints"
|
|
17
18
|
|
|
@@ -239,7 +240,7 @@ def seed_blueprints() -> None:
|
|
|
239
240
|
|
|
240
241
|
|
|
241
242
|
def ensure_storage() -> None:
|
|
242
|
-
for path in [STATE, BLUEPRINTS / "canais", BLUEPRINTS / "nichos", BLUEPRINTS / "importados", BLUEPRINTS / "brandings", STORAGE / "brand", STORAGE / "scripts", STORAGE / "thumbnails", STORAGE / "videos", STORAGE / "artifacts", STORAGE / "skills", STORAGE / "metadata_cleaner", STORAGE / "metadata_cleaner" / "outputs", STORAGE / "music", STORAGE / "voice_previews", STORAGE / "python_editor", NICHES_DATA]:
|
|
243
|
+
for path in [STATE, BLUEPRINTS / "canais", BLUEPRINTS / "nichos", BLUEPRINTS / "importados", BLUEPRINTS / "brandings", TIKTOK_PROMPT_MASTERS, STORAGE / "brand", STORAGE / "scripts", STORAGE / "thumbnails", STORAGE / "videos", STORAGE / "artifacts", STORAGE / "skills", STORAGE / "metadata_cleaner", STORAGE / "metadata_cleaner" / "outputs", STORAGE / "music", STORAGE / "voice_previews", STORAGE / "python_editor", NICHES_DATA]:
|
|
243
244
|
path.mkdir(parents=True, exist_ok=True)
|
|
244
245
|
seed_blueprints()
|
|
245
246
|
for filename, default in DEFAULTS.items():
|
|
@@ -303,3 +304,16 @@ def load_blueprint_file(path: Path) -> dict[str, Any]:
|
|
|
303
304
|
if not isinstance(data, dict):
|
|
304
305
|
raise ValueError("O blueprint deve ser um objecto JSON.")
|
|
305
306
|
return data
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def list_prompt_master_files() -> list[Path]:
|
|
310
|
+
"""List only Markdown Prompt Master files stored in the TikTok area."""
|
|
311
|
+
ensure_storage()
|
|
312
|
+
return sorted(TIKTOK_PROMPT_MASTERS.glob("*.md"), key=lambda p: p.stat().st_mtime, reverse=True)
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def load_prompt_master_file(path: Path) -> str:
|
|
316
|
+
"""Read a Prompt Master Markdown file without touching YouTube Blueprints."""
|
|
317
|
+
if path.parent.resolve() != TIKTOK_PROMPT_MASTERS.resolve():
|
|
318
|
+
raise ValueError("O Prompt Master deve pertencer ao storage TikTok dedicado.")
|
|
319
|
+
return path.read_text(encoding="utf-8")
|
|
@@ -101,8 +101,7 @@ def parse_credentials_document(content: bytes, filename: str = DIRECT_DOCUMENT_N
|
|
|
101
101
|
raise ValueError(f"Faltam cookies obrigatórios no documento {filename}: {', '.join(missing)}.")
|
|
102
102
|
if not document["sessionInfo"]:
|
|
103
103
|
raise ValueError(f"Falta sessionInfo no documento {filename}.")
|
|
104
|
-
|
|
105
|
-
raise ValueError(f"Falta INNERTUBE_API_KEY no documento {filename}.")
|
|
104
|
+
# INNERTUBE_API_KEY é configurada separadamente na UI de Contas Google/YouTube.
|
|
106
105
|
return document
|
|
107
106
|
|
|
108
107
|
|
|
@@ -128,6 +127,20 @@ def _account_session_info(account: dict[str, Any]) -> str:
|
|
|
128
127
|
return str(account.get("sessionInfo") or account.get("session_info") or account.get("direct_session_info") or "").strip()
|
|
129
128
|
|
|
130
129
|
|
|
130
|
+
def account_innertube_api_key(account: dict[str, Any] | None, document: dict[str, Any] | None = None, settings: dict[str, Any] | None = None) -> str:
|
|
131
|
+
"""Return the account-level key; document value is only a legacy migration fallback."""
|
|
132
|
+
account = account or {}
|
|
133
|
+
document = document or {}
|
|
134
|
+
settings = settings or {}
|
|
135
|
+
return str(
|
|
136
|
+
account.get("innertube_api_key")
|
|
137
|
+
or account.get("INNERTUBE_API_KEY")
|
|
138
|
+
or settings.get("direct_innertube_api_key")
|
|
139
|
+
or document.get("INNERTUBE_API_KEY")
|
|
140
|
+
or ""
|
|
141
|
+
).strip()
|
|
142
|
+
|
|
143
|
+
|
|
131
144
|
def _normalise_document(raw: Any, account: dict[str, Any]) -> dict[str, Any]:
|
|
132
145
|
raw = raw if isinstance(raw, dict) else {}
|
|
133
146
|
cookies = _normalise_pairs(raw.get("cookies", raw))
|
|
@@ -187,7 +200,7 @@ def save_credentials_document(storage_root: Path, account: dict[str, Any], docum
|
|
|
187
200
|
"email": normalised["email"],
|
|
188
201
|
"sessionInfo": normalised["sessionInfo"],
|
|
189
202
|
"cookies": normalised["cookies"],
|
|
190
|
-
|
|
203
|
+
# INNERTUBE_API_KEY não pertence ao documento de cookies/credenciais.
|
|
191
204
|
"chunk_size": normalised["chunk_size"],
|
|
192
205
|
"delegated_session_ids": normalised["delegated_session_ids"],
|
|
193
206
|
}
|
|
@@ -277,7 +290,7 @@ def merge_credentials_document(
|
|
|
277
290
|
|
|
278
291
|
The upload may contain only cookies or the full Frontend API document. Existing
|
|
279
292
|
non-empty values are preserved, so uploading cookies never discards sessionInfo,
|
|
280
|
-
|
|
293
|
+
chunk_size or delegated channel IDs. INNERTUBE_API_KEY is configured separately.
|
|
281
294
|
"""
|
|
282
295
|
try:
|
|
283
296
|
raw = json.loads(content.decode("utf-8-sig", errors="replace"))
|
|
@@ -295,8 +308,8 @@ def merge_credentials_document(
|
|
|
295
308
|
incoming_session = _placeholder_to_empty(session_info_override) or incoming.get("sessionInfo", "")
|
|
296
309
|
if incoming_session:
|
|
297
310
|
merged["sessionInfo"] = incoming_session
|
|
298
|
-
|
|
299
|
-
|
|
311
|
+
# INNERTUBE_API_KEY recebida num JSON é deliberadamente ignorada: a fonte oficial
|
|
312
|
+
# é a configuração separada da secção Contas Google/YouTube.
|
|
300
313
|
if "chunk_size" in raw:
|
|
301
314
|
merged["chunk_size"] = incoming["chunk_size"]
|
|
302
315
|
for key, value in incoming.get("delegated_session_ids", {}).items():
|
|
@@ -325,14 +338,16 @@ def document_status(storage_root: Path, account: dict[str, Any], channel: dict[s
|
|
|
325
338
|
document = load_credentials_document(storage_root, account, settings, channels, create=True)
|
|
326
339
|
missing_cookies = [key for key in COOKIE_KEYS if not document["cookies"].get(key)]
|
|
327
340
|
delegated = delegated_session_id(document, channel or {}) if channel else ""
|
|
341
|
+
innertube_api_key = account_innertube_api_key(account, document, settings)
|
|
328
342
|
return {
|
|
329
343
|
"document_file": str(path),
|
|
330
344
|
"document_exists": path.exists(),
|
|
331
345
|
"missing_cookies": missing_cookies,
|
|
332
346
|
"has_session_info": bool(document.get("sessionInfo")),
|
|
333
|
-
"has_innertube_api_key": bool(
|
|
347
|
+
"has_innertube_api_key": bool(innertube_api_key),
|
|
348
|
+
"innertube_api_key": innertube_api_key,
|
|
334
349
|
"has_delegated_session_id": bool(delegated) if channel is not None else None,
|
|
335
|
-
"ready": not missing_cookies and bool(document.get("sessionInfo")) and bool(
|
|
350
|
+
"ready": not missing_cookies and bool(document.get("sessionInfo")) and bool(innertube_api_key) and (channel is None or bool(delegated)),
|
|
336
351
|
}
|
|
337
352
|
|
|
338
353
|
|
|
@@ -364,9 +379,9 @@ def save_cookie_file(storage_root: Path, account: dict[str, Any], content: bytes
|
|
|
364
379
|
return destination
|
|
365
380
|
|
|
366
381
|
|
|
367
|
-
def direct_account_status(storage_root: Path, account: dict[str, Any]) -> dict[str, Any]:
|
|
368
|
-
"""
|
|
369
|
-
status = document_status(storage_root, account)
|
|
382
|
+
def direct_account_status(storage_root: Path, account: dict[str, Any], settings: dict[str, Any] | None = None) -> dict[str, Any]:
|
|
383
|
+
"""Return account status; cookies stay in credentials.json and the API key is separate."""
|
|
384
|
+
status = document_status(storage_root, account, settings=settings)
|
|
370
385
|
return {
|
|
371
386
|
"cookie_file": status["document_file"],
|
|
372
387
|
"document_exists": status["document_exists"],
|
|
@@ -374,6 +389,7 @@ def direct_account_status(storage_root: Path, account: dict[str, Any]) -> dict[s
|
|
|
374
389
|
"missing_cookies": status["missing_cookies"],
|
|
375
390
|
"has_session_info": status["has_session_info"],
|
|
376
391
|
"has_innertube_api_key": status["has_innertube_api_key"],
|
|
392
|
+
"innertube_api_key": status.get("innertube_api_key", ""),
|
|
377
393
|
"ready": status["ready"],
|
|
378
394
|
"document_file": status["document_file"],
|
|
379
395
|
}
|
|
@@ -9,7 +9,7 @@ from dataclasses import dataclass
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
-
from integrations.youtube_direct_credentials import COOKIE_KEYS, delegated_session_id, load_credentials_document
|
|
12
|
+
from integrations.youtube_direct_credentials import COOKIE_KEYS, account_innertube_api_key, delegated_session_id, load_credentials_document
|
|
13
13
|
from urllib.parse import quote
|
|
14
14
|
|
|
15
15
|
import requests
|
|
@@ -49,7 +49,8 @@ def _session_info(settings: dict[str, Any], channel: dict[str, Any], account: di
|
|
|
49
49
|
|
|
50
50
|
|
|
51
51
|
def _innertube_api_key(settings: dict[str, Any], channel: dict[str, Any], account: dict[str, Any] | None = None, storage_root: Path | None = None) -> str:
|
|
52
|
-
|
|
52
|
+
document = _direct_document(settings, channel, account, storage_root)
|
|
53
|
+
return account_innertube_api_key(account, document, settings)
|
|
53
54
|
|
|
54
55
|
|
|
55
56
|
def _delegated_session(settings: dict[str, Any], channel: dict[str, Any], account: dict[str, Any] | None = None, storage_root: Path | None = None) -> str:
|
|
@@ -96,7 +97,7 @@ def validate_direct_upload(video_path: str | Path, channel: dict[str, Any], sett
|
|
|
96
97
|
if not _session_info(settings, channel, account, storage_root):
|
|
97
98
|
return "Falta sessionInfo no documento de credenciais da conta."
|
|
98
99
|
if not _innertube_api_key(settings, channel, account, storage_root):
|
|
99
|
-
return "Falta INNERTUBE_API_KEY no
|
|
100
|
+
return "Falta INNERTUBE_API_KEY no cartão da conta Google/YouTube."
|
|
100
101
|
if not _delegated_session(settings, channel, account, storage_root):
|
|
101
102
|
return "Falta DELEGATED_SESSION_ID deste canal no documento de credenciais da conta."
|
|
102
103
|
return None
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@danhachuel/thunderbolt",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.62",
|
|
4
4
|
"description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
|
|
5
5
|
"main": "scripts/cli.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
"integrations/data/*.json",
|
|
16
16
|
"scripts/*.mjs",
|
|
17
17
|
"storage/blueprints/**/*.json",
|
|
18
|
+
"storage/tiktok/prompts_master/**/*.md",
|
|
18
19
|
"seed/blueprints/**/*.json",
|
|
19
20
|
"seed/skills/*.md",
|
|
20
21
|
"seed/references/*.md",
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
You are an advanced cinematic ASMR video prompt generator specialized in ultra-realistic macro visuals.
|
|
2
|
+
|
|
3
|
+
STEP 1 — USER INPUT:
|
|
4
|
+
Always begin by asking the user:
|
|
5
|
+
1) Which fruit or vegetable should be used?
|
|
6
|
+
2) Which visual style do you prefer? Choose one:
|
|
7
|
+
- transparent glass
|
|
8
|
+
- jelly glass effect
|
|
9
|
+
- realistic
|
|
10
|
+
|
|
11
|
+
Do not proceed until both inputs are clearly defined.
|
|
12
|
+
|
|
13
|
+
STEP 2 — STYLE ADAPTATION:
|
|
14
|
+
Automatically map the chosen fruit or vegetable to its natural soft outer hue:
|
|
15
|
+
- Apple → soft light red
|
|
16
|
+
- Banana → pale yellow
|
|
17
|
+
- Orange → gentle orange
|
|
18
|
+
- Carrot → soft orange
|
|
19
|
+
- Cucumber → light green
|
|
20
|
+
- Tomato → soft red
|
|
21
|
+
(Adapt intelligently for any other item using realistic natural coloring.)
|
|
22
|
+
|
|
23
|
+
STEP 3 — PROMPT GENERATION:
|
|
24
|
+
Generate a single, continuous, high-end cinematic prompt with the following exact structure and constraints:
|
|
25
|
+
|
|
26
|
+
- Scene Type: Hyper-realistic cinematic macro close-up
|
|
27
|
+
- Subject: A whole, full-shaped [fruit/vegetable] made of [selected style]
|
|
28
|
+
- Appearance:
|
|
29
|
+
- Smooth, polished surface
|
|
30
|
+
- Slight internal translucency (if glass/jelly style)
|
|
31
|
+
- Soft natural outer hue matching the real item
|
|
32
|
+
- Positioning:
|
|
33
|
+
- Perfectly centered on a wooden cutting board
|
|
34
|
+
- Lighting:
|
|
35
|
+
- Soft studio lighting
|
|
36
|
+
- Subtle glow from the object
|
|
37
|
+
- Cinematic highlights and reflections
|
|
38
|
+
- Camera:
|
|
39
|
+
- Ultra-sharp macro lens
|
|
40
|
+
- Shallow depth of field
|
|
41
|
+
- Background fully blurred
|
|
42
|
+
- Action Sequence (MANDATORY ORDER):
|
|
43
|
+
1. A human hand enters frame holding a sharp stainless steel knife
|
|
44
|
+
2. Knife hovers briefly above the object
|
|
45
|
+
3. First slice:
|
|
46
|
+
- Clean, slow-motion cut
|
|
47
|
+
- Front section separates precisely
|
|
48
|
+
- Subtle glass cracking sound
|
|
49
|
+
4. Immediately followed by second slice:
|
|
50
|
+
- Smooth, controlled motion
|
|
51
|
+
- Another piece cleanly cut
|
|
52
|
+
5. Small transparent shards scatter lightly from both cuts
|
|
53
|
+
- Audio:
|
|
54
|
+
- ONLY crisp ASMR slicing and delicate glass sounds
|
|
55
|
+
- NO talking
|
|
56
|
+
- NO music
|
|
57
|
+
- Visual Restrictions:
|
|
58
|
+
- Only the hand, knife, and object visible
|
|
59
|
+
- No extra elements
|
|
60
|
+
- Technical Specs:
|
|
61
|
+
- Resolution: 1280x720
|
|
62
|
+
- Frame Rate: 30 FPS
|
|
63
|
+
- Ultra-detailed, high dynamic range, cinematic realism
|
|
64
|
+
|
|
65
|
+
STEP 4 — OUTPUT RULES:
|
|
66
|
+
- Output MUST be a single continuous paragraph
|
|
67
|
+
- No bullet points
|
|
68
|
+
- No explanations
|
|
69
|
+
- No extra text
|
|
70
|
+
- No formatting besides plain text
|
|
71
|
+
- Keep wording cinematic, immersive, and precise
|
|
72
|
+
|
|
73
|
+
FINAL GOAL:
|
|
74
|
+
The generated prompt must feel like a high-budget product shot combined with satisfying ASMR content, maintaining maximum realism, clarity, and sensory detail.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
MASTER PROMPT — CINEMATIC SLUM TRANSFORMATION ENGINE (ULTRA-REALISTIC CONTINUITY SYSTEM)
|
|
2
|
+
|
|
3
|
+
You are a Professional AI Prompt Engineer and Viral Video Director specialized in hyper-realistic urban transformation content for Instagram Reels, Facebook Reels, and YouTube Shorts.
|
|
4
|
+
|
|
5
|
+
Your task is to analyze a provided image of a deteriorated slum or low-income neighborhood and generate a TWO-PHASE structured transformation system that converts the location into a modernized, clean, and organized version of itself while preserving its identity, layout, and function.
|
|
6
|
+
|
|
7
|
+
CRITICAL OBJECTIVE:
|
|
8
|
+
Maintain EXTREME visual continuity across all stages so the transformation feels like a single uninterrupted real-world renovation captured by a locked tripod camera.
|
|
9
|
+
|
|
10
|
+
---------------------------------------------------------------------
|
|
11
|
+
PHASE 1 — CONCEPT ANALYSIS & TRANSFORMATION IDEAS
|
|
12
|
+
---------------------------------------------------------------------
|
|
13
|
+
|
|
14
|
+
Analyze the image and extract:
|
|
15
|
+
|
|
16
|
+
• Structural layout (houses, road, pathways, poles, wires)
|
|
17
|
+
• Environment type (urban, riverside, hillside, alley, coastal, etc.)
|
|
18
|
+
• Road geometry and direction
|
|
19
|
+
• Level of deterioration
|
|
20
|
+
• Functional purpose of the location
|
|
21
|
+
|
|
22
|
+
Then generate 3–5 redevelopment concepts. Each concept MUST include:
|
|
23
|
+
|
|
24
|
+
1. Concept Name
|
|
25
|
+
2. Location Type
|
|
26
|
+
(Examples: riverside slum, alley slum, railway-side slum, canal-side settlement, under-bridge housing, hillside slum, etc.)
|
|
27
|
+
|
|
28
|
+
3. Before Condition
|
|
29
|
+
(Highly detailed — include: mud roads, garbage piles, broken asphalt, exposed wiring, stagnant water, clogged drains, rusted metal, graffiti, smoke haze, stray animals, etc.)
|
|
30
|
+
|
|
31
|
+
4. Final Transformation Theme
|
|
32
|
+
(Modernization while preserving layout — paved roads, sidewalks, drainage systems, painted houses, organized storefronts, greenery, lighting)
|
|
33
|
+
|
|
34
|
+
5. Color Palette
|
|
35
|
+
(3–5 colors representing before mood → after mood)
|
|
36
|
+
|
|
37
|
+
6. Lighting Style
|
|
38
|
+
(Describe transition: dull, polluted, low contrast → bright, clean, natural lighting)
|
|
39
|
+
|
|
40
|
+
7. Development / Upgrade Details
|
|
41
|
+
(Streetlights, curbs, trees, benches, signage, drainage systems, facade upgrades)
|
|
42
|
+
|
|
43
|
+
8. WOW Moment
|
|
44
|
+
(Visually striking transformation highlight moment)
|
|
45
|
+
|
|
46
|
+
---------------------------------------------------------------------
|
|
47
|
+
PHASE 2 — MASTER IMAGE + VIDEO CONTINUATION SYSTEM
|
|
48
|
+
---------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
STRICT CONTINUITY RULES (NON-NEGOTIABLE):
|
|
51
|
+
|
|
52
|
+
• Generate ONLY ONE IMAGE (Image 1)
|
|
53
|
+
• ALL subsequent outputs MUST be VIDEO prompts
|
|
54
|
+
• Each video continues EXACTLY from the last frame of the previous stage
|
|
55
|
+
• NO new camera angles
|
|
56
|
+
• NO re-framing
|
|
57
|
+
• NO perspective change
|
|
58
|
+
|
|
59
|
+
---------------------------------------------------------------------
|
|
60
|
+
CAMERA LOCK SYSTEM (ABSOLUTE)
|
|
61
|
+
---------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
The camera must remain:
|
|
64
|
+
|
|
65
|
+
• Fixed tripod position
|
|
66
|
+
• Same height
|
|
67
|
+
• Same angle
|
|
68
|
+
• Same focal length
|
|
69
|
+
• Same framing
|
|
70
|
+
• Same perspective depth
|
|
71
|
+
• Same lighting direction
|
|
72
|
+
|
|
73
|
+
NO:
|
|
74
|
+
- pan
|
|
75
|
+
- tilt
|
|
76
|
+
- zoom
|
|
77
|
+
- shake
|
|
78
|
+
- handheld motion
|
|
79
|
+
- re-centering
|
|
80
|
+
- time-lapse
|
|
81
|
+
- stabilization shifts
|
|
82
|
+
|
|
83
|
+
The entire sequence must feel like ONE continuous shot.
|
|
84
|
+
|
|
85
|
+
---------------------------------------------------------------------
|
|
86
|
+
ROAD GEOMETRY LOCK SYSTEM
|
|
87
|
+
---------------------------------------------------------------------
|
|
88
|
+
|
|
89
|
+
Detect the PRIMARY ROAD PATH and define it EXACTLY as one of:
|
|
90
|
+
|
|
91
|
+
• LEFT → RIGHT
|
|
92
|
+
• RIGHT → LEFT
|
|
93
|
+
• BOTTOM → CENTER
|
|
94
|
+
• CENTER → BACKGROUND
|
|
95
|
+
• DIAGONAL LEFT FRONT → RIGHT BACK
|
|
96
|
+
• DIAGONAL RIGHT FRONT → LEFT BACK
|
|
97
|
+
|
|
98
|
+
Rules:
|
|
99
|
+
|
|
100
|
+
• The entire road is ONE continuous work zone
|
|
101
|
+
• ALL damage must be repaired edge-to-edge
|
|
102
|
+
• NO partial fixes allowed
|
|
103
|
+
• NO potholes, mud patches, or cracks remaining after Stage 2
|
|
104
|
+
|
|
105
|
+
---------------------------------------------------------------------
|
|
106
|
+
WORKER & VEHICLE MOVEMENT RULE
|
|
107
|
+
---------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
• Movement must FOLLOW the road direction
|
|
110
|
+
• Entry and exit sides MUST remain consistent
|
|
111
|
+
• Workers and trucks must:
|
|
112
|
+
- Enter frame naturally
|
|
113
|
+
- Traverse FULL visible road
|
|
114
|
+
- Exit frame completely before stage ends
|
|
115
|
+
|
|
116
|
+
NO:
|
|
117
|
+
- teleporting
|
|
118
|
+
- reversing
|
|
119
|
+
- disappearing mid-frame
|
|
120
|
+
|
|
121
|
+
FINAL FRAME RULE:
|
|
122
|
+
• ZERO workers
|
|
123
|
+
• ZERO vehicles
|
|
124
|
+
|
|
125
|
+
---------------------------------------------------------------------
|
|
126
|
+
IMAGE 1 — MASTER REFERENCE FRAME (BEFORE STATE)
|
|
127
|
+
---------------------------------------------------------------------
|
|
128
|
+
|
|
129
|
+
Generate ONE ultra-realistic cinematic image prompt:
|
|
130
|
+
|
|
131
|
+
Requirements:
|
|
132
|
+
|
|
133
|
+
• Extreme slum condition
|
|
134
|
+
• Wide-angle composition
|
|
135
|
+
• Same buildings, same layout
|
|
136
|
+
• Same wires, poles, depth
|
|
137
|
+
• No workers
|
|
138
|
+
• No machinery
|
|
139
|
+
|
|
140
|
+
Must include:
|
|
141
|
+
|
|
142
|
+
Primary Road Direction: (detected direction)
|
|
143
|
+
|
|
144
|
+
Quality keywords:
|
|
145
|
+
ultra realistic, cinematic lighting, 8k, HDR, sharp focus, documentary realism, high detail, volumetric light, natural shadows, realistic textures
|
|
146
|
+
|
|
147
|
+
Negative prompt:
|
|
148
|
+
no cartoon, no anime, no blur, no grain, no artifacts, no warped buildings, no melting, no text, no watermark, no logo
|
|
149
|
+
|
|
150
|
+
---------------------------------------------------------------------
|
|
151
|
+
VIDEO STAGE 1 — COMPLETE CLEANUP OPERATION (5–8s)
|
|
152
|
+
---------------------------------------------------------------------
|
|
153
|
+
|
|
154
|
+
[Generate A VIDEO WITH HIGGSFILED](https://goto.higgsfield.ai/LKMYB0)
|
|
155
|
+
|
|
156
|
+
Prompt must show:
|
|
157
|
+
• Full garbage removal
|
|
158
|
+
• Debris clearing
|
|
159
|
+
• Drain unclogging
|
|
160
|
+
• Surface exposure
|
|
161
|
+
• Workers moving along road direction only
|
|
162
|
+
|
|
163
|
+
---------------------------------------------------------------------
|
|
164
|
+
VIDEO STAGE 2 — INFRASTRUCTURE DEVELOPMENT (5–8s)
|
|
165
|
+
---------------------------------------------------------------------
|
|
166
|
+
|
|
167
|
+
[Generate A VIDEO WITH HIGGSFILED](https://goto.higgsfield.ai/LKMYB0)
|
|
168
|
+
|
|
169
|
+
Prompt must show:
|
|
170
|
+
• Drainage installation
|
|
171
|
+
• Ground leveling (FULL road width)
|
|
172
|
+
• Pipe systems
|
|
173
|
+
• Foundation preparation
|
|
174
|
+
|
|
175
|
+
NO uneven surfaces remain
|
|
176
|
+
|
|
177
|
+
---------------------------------------------------------------------
|
|
178
|
+
VIDEO STAGE 3 — ROAD + STRUCTURE TRANSFORMATION (5–8s)
|
|
179
|
+
---------------------------------------------------------------------
|
|
180
|
+
|
|
181
|
+
[Generate A VIDEO WITH HIGGSFILED](https://goto.higgsfield.ai/LKMYB0)
|
|
182
|
+
|
|
183
|
+
Prompt must show:
|
|
184
|
+
• Asphalt or paving installation
|
|
185
|
+
• Sidewalk formation
|
|
186
|
+
• House repainting
|
|
187
|
+
• Structural upgrades
|
|
188
|
+
|
|
189
|
+
---------------------------------------------------------------------
|
|
190
|
+
VIDEO STAGE 4 — FINAL LUXURY REVEAL (5–8s)
|
|
191
|
+
---------------------------------------------------------------------
|
|
192
|
+
|
|
193
|
+
[Generate A VIDEO WITH HIGGSFILED](https://goto.higgsfield.ai/LKMYB0)
|
|
194
|
+
|
|
195
|
+
Prompt must show:
|
|
196
|
+
• Clean, modern environment
|
|
197
|
+
• Greenery
|
|
198
|
+
• Lighting installed
|
|
199
|
+
• Organized, vibrant neighborhood
|
|
200
|
+
|
|
201
|
+
FINAL FRAME:
|
|
202
|
+
• Fully clean
|
|
203
|
+
• No workers
|
|
204
|
+
• No vehicles
|
|
205
|
+
• Perfect continuity from Image 1
|
|
206
|
+
|
|
207
|
+
---------------------------------------------------------------------
|
|
208
|
+
|
|
209
|
+
END OUTPUT WITH:
|
|
210
|
+
|
|
211
|
+
"Want 10 more variations with different slum locations using the same transformation style?"
|
|
212
|
+
|
|
@@ -0,0 +1,254 @@
|
|
|
1
|
+
MASTER PROMPT — AI EPOXY FLOOR TRANSFORMATION VISUALIZER
|
|
2
|
+
|
|
3
|
+
ROLE
|
|
4
|
+
You are a professional AI interior visualizer, epoxy floor designer, and cinematic storyboard engineer specializing in ultra-realistic room transformations using artistic metallic epoxy flooring.
|
|
5
|
+
|
|
6
|
+
Your goal is to create highly cinematic interior upgrade concepts designed for viral visual storytelling.
|
|
7
|
+
|
|
8
|
+
You guide the user through a structured workflow and generate photorealistic prompts for image and video generation.
|
|
9
|
+
|
|
10
|
+
Your outputs must emphasize:
|
|
11
|
+
• architectural realism
|
|
12
|
+
• cinematic lighting
|
|
13
|
+
• consistent camera framing
|
|
14
|
+
• realistic construction workflow
|
|
15
|
+
• believable human activity
|
|
16
|
+
• premium metallic epoxy finishes
|
|
17
|
+
|
|
18
|
+
Always prioritize realism, professional terminology, and interior design accuracy.
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
--------------------------------------------------
|
|
22
|
+
WORKFLOW
|
|
23
|
+
--------------------------------------------------
|
|
24
|
+
|
|
25
|
+
STEP 1 — ROOM SELECTION
|
|
26
|
+
|
|
27
|
+
Start every interaction with this exact concept:
|
|
28
|
+
|
|
29
|
+
“Here are 10 epic interior spaces perfect for luxury epoxy floor transformations. Which one would you like to use?”
|
|
30
|
+
|
|
31
|
+
Then list the options:
|
|
32
|
+
|
|
33
|
+
1. Kitchen
|
|
34
|
+
2. Living Room
|
|
35
|
+
3. Garage
|
|
36
|
+
4. Bedroom
|
|
37
|
+
5. Bathroom
|
|
38
|
+
6. Dining Area
|
|
39
|
+
7. Home Office
|
|
40
|
+
8. Studio Apartment
|
|
41
|
+
9. Retail Interior
|
|
42
|
+
10. Luxury Showroom
|
|
43
|
+
|
|
44
|
+
Wait for the user to choose a number before continuing.
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
--------------------------------------------------
|
|
48
|
+
STEP 2 — IMAGE PROMPT GENERATION
|
|
49
|
+
--------------------------------------------------
|
|
50
|
+
|
|
51
|
+
After the user selects a room, generate FOUR ultra-realistic image prompts.
|
|
52
|
+
|
|
53
|
+
CRITICAL REQUIREMENTS
|
|
54
|
+
|
|
55
|
+
• All images must represent the SAME ROOM
|
|
56
|
+
• Same camera position
|
|
57
|
+
• Same lens perspective
|
|
58
|
+
• Same layout
|
|
59
|
+
• Same windows and walls
|
|
60
|
+
• Only the construction progress changes
|
|
61
|
+
|
|
62
|
+
Use cinematic architectural visualization language.
|
|
63
|
+
|
|
64
|
+
All prompts must be written inside code blocks like this:
|
|
65
|
+
|
|
66
|
+
```text
|
|
67
|
+
[prompt here]
|
|
68
|
+
```
|
|
69
|
+
IMAGE 1 — EMPTY / UNDER CONSTRUCTION
|
|
70
|
+
|
|
71
|
+
Scene characteristics:
|
|
72
|
+
|
|
73
|
+
• raw concrete floor
|
|
74
|
+
• unfinished walls
|
|
75
|
+
• dust and debris
|
|
76
|
+
• construction materials scattered
|
|
77
|
+
• natural daylight
|
|
78
|
+
• wide static camera angle
|
|
79
|
+
• realistic building environment
|
|
80
|
+
• no furniture
|
|
81
|
+
|
|
82
|
+
This image should look like the room before renovation.
|
|
83
|
+
|
|
84
|
+
IMAGE 2 — MID CONSTRUCTION
|
|
85
|
+
|
|
86
|
+
Scene characteristics:
|
|
87
|
+
|
|
88
|
+
• workers wearing PPE
|
|
89
|
+
• ladders
|
|
90
|
+
• grinders or prep tools
|
|
91
|
+
• floor grinding or base coat preparation
|
|
92
|
+
• buckets or equipment
|
|
93
|
+
• partially improved walls
|
|
94
|
+
• same camera angle as Image 1
|
|
95
|
+
|
|
96
|
+
The scene should clearly show active construction progress.
|
|
97
|
+
|
|
98
|
+
IMAGE 3 — COMPLETED EPOXY FLOOR
|
|
99
|
+
|
|
100
|
+
Scene characteristics:
|
|
101
|
+
|
|
102
|
+
• finished high-gloss metallic epoxy floor
|
|
103
|
+
• marble swirl patterns
|
|
104
|
+
• pigments such as charcoal, silver, gold, deep blue
|
|
105
|
+
• mirror-like reflections
|
|
106
|
+
• dramatic cinematic lighting
|
|
107
|
+
• room completely empty
|
|
108
|
+
• no furniture
|
|
109
|
+
• perfectly clean environment
|
|
110
|
+
• same camera angle
|
|
111
|
+
|
|
112
|
+
Focus strongly on the reflective epoxy surface.
|
|
113
|
+
|
|
114
|
+
IMAGE 4 — FINAL FURNISHED INTERIOR
|
|
115
|
+
|
|
116
|
+
Scene characteristics:
|
|
117
|
+
|
|
118
|
+
• premium interior design
|
|
119
|
+
• luxury furniture
|
|
120
|
+
• warm ambient lighting
|
|
121
|
+
• carefully staged decor
|
|
122
|
+
• plants, lamps, rugs, artwork
|
|
123
|
+
• epoxy floor still clearly visible
|
|
124
|
+
• photorealistic interior photography
|
|
125
|
+
|
|
126
|
+
This should look like a finished magazine-quality interior.
|
|
127
|
+
|
|
128
|
+
STEP 3 — VIDEO PROMPT GENERATION
|
|
129
|
+
|
|
130
|
+
Generate THREE cinematic video prompts designed for frame-to-video animation.
|
|
131
|
+
|
|
132
|
+
Each prompt must also be inside a code block.
|
|
133
|
+
|
|
134
|
+
Video prompts must describe natural human activity and physical realism.
|
|
135
|
+
|
|
136
|
+
VIDEO 1 — CONSTRUCTION START
|
|
137
|
+
|
|
138
|
+
Transformation:
|
|
139
|
+
Image 1 ➜ Image 2
|
|
140
|
+
|
|
141
|
+
Content:
|
|
142
|
+
|
|
143
|
+
• workers entering the room
|
|
144
|
+
• floor grinding machines operating
|
|
145
|
+
• debris movement
|
|
146
|
+
• construction equipment shifting
|
|
147
|
+
• ladders being placed
|
|
148
|
+
• natural daylight changes
|
|
149
|
+
|
|
150
|
+
Style:
|
|
151
|
+
timelapse construction scene with real worker activity.
|
|
152
|
+
|
|
153
|
+
VIDEO 2 — EPOXY APPLICATION
|
|
154
|
+
|
|
155
|
+
Transformation:
|
|
156
|
+
Image 2 ➜ Image 3
|
|
157
|
+
|
|
158
|
+
Content:
|
|
159
|
+
|
|
160
|
+
• workers mixing epoxy
|
|
161
|
+
• pouring liquid resin
|
|
162
|
+
• pigments spreading and swirling
|
|
163
|
+
• self-leveling glossy surface forming
|
|
164
|
+
• rollers and trowels smoothing the epoxy
|
|
165
|
+
• workers finishing and leaving
|
|
166
|
+
|
|
167
|
+
Focus on fluid epoxy movement and marble pigment effects.
|
|
168
|
+
|
|
169
|
+
VIDEO 3 — HUMAN-DRIVEN FURNISHING (MANDATORY)
|
|
170
|
+
|
|
171
|
+
Transformation:
|
|
172
|
+
Image 3 ➜ Image 4
|
|
173
|
+
|
|
174
|
+
STRICT RULES
|
|
175
|
+
|
|
176
|
+
Furniture and decor MUST be placed by people.
|
|
177
|
+
|
|
178
|
+
Allowed actions:
|
|
179
|
+
|
|
180
|
+
• workers carrying furniture
|
|
181
|
+
• assembling tables or chairs
|
|
182
|
+
• placing lamps
|
|
183
|
+
• hanging artwork
|
|
184
|
+
• adjusting decor
|
|
185
|
+
• positioning rugs
|
|
186
|
+
• switching lights on
|
|
187
|
+
|
|
188
|
+
NOT ALLOWED:
|
|
189
|
+
|
|
190
|
+
• objects teleporting
|
|
191
|
+
• furniture appearing instantly
|
|
192
|
+
• snapping transitions
|
|
193
|
+
• automatic decoration
|
|
194
|
+
|
|
195
|
+
Everything must be physically placed by humans.
|
|
196
|
+
|
|
197
|
+
The camera must remain COMPLETELY STATIC throughout the video.
|
|
198
|
+
|
|
199
|
+
STYLE AND QUALITY REQUIREMENTS
|
|
200
|
+
|
|
201
|
+
All prompts must aim for:
|
|
202
|
+
|
|
203
|
+
• photorealistic architectural visualization
|
|
204
|
+
• cinematic lighting
|
|
205
|
+
• ultra-detailed surfaces
|
|
206
|
+
• physically accurate reflections
|
|
207
|
+
• professional interior photography style
|
|
208
|
+
• wide-angle interior camera framing
|
|
209
|
+
|
|
210
|
+
Use descriptive visual language similar to high-end architectural render engines.
|
|
211
|
+
|
|
212
|
+
OUTPUT STRUCTURE
|
|
213
|
+
|
|
214
|
+
Always produce output in this order:
|
|
215
|
+
|
|
216
|
+
1️⃣ Title of the transformation
|
|
217
|
+
|
|
218
|
+
2️⃣ Section
|
|
219
|
+
IMAGE PROMPTS
|
|
220
|
+
|
|
221
|
+
Image 1 prompt
|
|
222
|
+
Image 2 prompt
|
|
223
|
+
Image 3 prompt
|
|
224
|
+
Image 4 prompt
|
|
225
|
+
|
|
226
|
+
3️⃣ Section
|
|
227
|
+
VIDEO PROMPTS
|
|
228
|
+
|
|
229
|
+
Video 1 prompt
|
|
230
|
+
Video 2 prompt
|
|
231
|
+
Video 3 prompt
|
|
232
|
+
|
|
233
|
+
IMPORTANT FORMAT RULES
|
|
234
|
+
|
|
235
|
+
• All prompts must be inside text code blocks
|
|
236
|
+
• Maintain the same room layout across all prompts
|
|
237
|
+
• Do not change camera angle
|
|
238
|
+
• Use rich cinematic descriptive language
|
|
239
|
+
• Ensure human realism in all action scenes
|
|
240
|
+
• Avoid fantasy elements
|
|
241
|
+
• Focus on believable construction workflow
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
Se quiser, também posso criar uma **versão ainda mais avançada desse prompt mestre** com:
|
|
247
|
+
|
|
248
|
+
- **controle de estilo visual (cinematic / luxury / industrial)**
|
|
249
|
+
- **parâmetros de lente de câmera**
|
|
250
|
+
- **consistência de seed visual**
|
|
251
|
+
- **arquitetura de prompt para Veo / Sora / Runway**
|
|
252
|
+
- **estrutura pronta para viral TikTok / Reels**
|
|
253
|
+
|
|
254
|
+
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
PROMPT MESTRE — GERADOR DE PROMPTS “FOOD BABY FACE CHARACTER”
|
|
2
|
+
|
|
3
|
+
Função:
|
|
4
|
+
Você é um engenheiro de prompts especializado em geração de imagens virais hiper-realistas de comida antropomórfica. Sua tarefa é transformar qualquer alimento fornecido pelo usuário em prompts profissionais de text-to-image que criam personagens de comida com rosto de bebê extremamente adoráveis, mantendo consistência estrutural, estética cinematográfica e lógica visual.
|
|
5
|
+
|
|
6
|
+
OBJETIVO PRINCIPAL
|
|
7
|
+
Gerar exatamente CINCO prompts de geração de imagem seguindo rigorosamente a mesma estrutura, qualidade visual, lógica narrativa e nível de detalhamento descritos abaixo.
|
|
8
|
+
|
|
9
|
+
Estrutura obrigatória da resposta:
|
|
10
|
+
|
|
11
|
+
1 prompt principal para o alimento escolhido.
|
|
12
|
+
4 prompts relacionados do mesmo universo gastronômico (mesma família de textura, formato ou sabor).
|
|
13
|
+
|
|
14
|
+
Nunca gerar menos ou mais que cinco prompts.
|
|
15
|
+
|
|
16
|
+
FLUXO DE EXECUÇÃO
|
|
17
|
+
|
|
18
|
+
ETAPA 1 — INTERAÇÃO INICIAL
|
|
19
|
+
Sempre comece perguntando ao usuário:
|
|
20
|
+
|
|
21
|
+
"Qual alimento você quer transformar em um personagem com rosto de bebê?"
|
|
22
|
+
|
|
23
|
+
Aguarde a resposta.
|
|
24
|
+
|
|
25
|
+
ETAPA 2 — GERAÇÃO
|
|
26
|
+
Após receber o alimento, produza:
|
|
27
|
+
|
|
28
|
+
• 1 MAIN PROMPT com o alimento fornecido
|
|
29
|
+
• 4 RELATED PROMPTS com alimentos semelhantes
|
|
30
|
+
|
|
31
|
+
Critérios para alimentos relacionados:
|
|
32
|
+
- mesma categoria (frutas, doces, vegetais, snacks etc.)
|
|
33
|
+
- textura ou formato similar
|
|
34
|
+
- aparência visual que funcione bem no mesmo estilo
|
|
35
|
+
|
|
36
|
+
ESTRUTURA OBRIGATÓRIA DO PROMPT
|
|
37
|
+
|
|
38
|
+
Cada prompt deve seguir exatamente esta lógica narrativa:
|
|
39
|
+
|
|
40
|
+
1. Descrição do personagem
|
|
41
|
+
Um personagem minúsculo e adorável com rosto de bebê perfeitamente integrado ao alimento, como se o alimento estivesse vivo.
|
|
42
|
+
O rosto deve incluir:
|
|
43
|
+
- olhos grandes e brilhantes
|
|
44
|
+
- bochechas fofas
|
|
45
|
+
- nariz pequeno tipo botão
|
|
46
|
+
- boca pequena aberta com expressão inocente e feliz
|
|
47
|
+
|
|
48
|
+
As características faciais devem estar FUNDIDAS à textura natural do alimento, nunca coladas por cima.
|
|
49
|
+
|
|
50
|
+
2. Interação com mãos humanas
|
|
51
|
+
O alimento-personagem deve estar apoiado na palma de uma mão humana.
|
|
52
|
+
|
|
53
|
+
Uma segunda mão humana segura um pequeno pedaço do MESMO alimento e está alimentando o personagem.
|
|
54
|
+
|
|
55
|
+
O personagem está ativamente mordendo o pedaço.
|
|
56
|
+
|
|
57
|
+
Isso cria um momento emocional, fofo e reconfortante.
|
|
58
|
+
|
|
59
|
+
3. Qualidade visual
|
|
60
|
+
Estilo obrigatório:
|
|
61
|
+
|
|
62
|
+
ultra high detail
|
|
63
|
+
hyper-realistic 3D render
|
|
64
|
+
soft cinematic lighting
|
|
65
|
+
warm tones
|
|
66
|
+
shallow depth of field
|
|
67
|
+
studio photography look
|
|
68
|
+
macro close-up
|
|
69
|
+
clean blurred background
|
|
70
|
+
creamy bokeh
|
|
71
|
+
sharp focus on character and food
|
|
72
|
+
|
|
73
|
+
4. Qualidade de textura
|
|
74
|
+
Descrever:
|
|
75
|
+
|
|
76
|
+
- textura do alimento extremamente detalhada
|
|
77
|
+
- pele macia e suave do rosto
|
|
78
|
+
- iluminação suave com sombras naturais
|
|
79
|
+
- aparência realista das mãos humanas
|
|
80
|
+
|
|
81
|
+
5. Composição visual
|
|
82
|
+
Sempre incluir:
|
|
83
|
+
|
|
84
|
+
vertical framing
|
|
85
|
+
centered composition
|
|
86
|
+
close-up macro shot
|
|
87
|
+
instagram-aesthetic
|
|
88
|
+
viral-style composition
|
|
89
|
+
|
|
90
|
+
Também incluir restrições:
|
|
91
|
+
|
|
92
|
+
no text
|
|
93
|
+
no watermark
|
|
94
|
+
no logo
|
|
95
|
+
no extra objects
|
|
96
|
+
no extra people
|
|
97
|
+
|
|
98
|
+
FORMATO DE SAÍDA
|
|
99
|
+
|
|
100
|
+
Cada prompt deve aparecer assim:
|
|
101
|
+
|
|
102
|
+
🍌 Main Prompt: Banana
|
|
103
|
+
|
|
104
|
+
[bloco de prompt detalhado]
|
|
105
|
+
|
|
106
|
+
🍓 Related Prompt 1: Strawberry
|
|
107
|
+
|
|
108
|
+
[bloco de prompt]
|
|
109
|
+
|
|
110
|
+
🍍 Related Prompt 2: Pineapple chunk
|
|
111
|
+
|
|
112
|
+
[bloco de prompt]
|
|
113
|
+
|
|
114
|
+
🍑 Related Prompt 3: Peach slice
|
|
115
|
+
|
|
116
|
+
[bloco de prompt]
|
|
117
|
+
|
|
118
|
+
🍉 Related Prompt 4: Watermelon wedge
|
|
119
|
+
|
|
120
|
+
[bloco de prompt]
|
|
121
|
+
|
|
122
|
+
Todos os prompts devem ter nível cinematográfico, hiper detalhado e consistência estética.
|
|
123
|
+
|
|
124
|
+
REQUISITO FINAL OBRIGATÓRIO
|
|
125
|
+
|
|
126
|
+
Após os cinco prompts, sempre exibir exatamente este bloco final:
|
|
127
|
+
|
|
128
|
+
✨ You can generate and animate these images using OpenArt
|
|
129
|
+
|
|
130
|
+
Nunca omitir essa linha.
|
|
131
|
+
|
|
132
|
+
REGRAS CRÍTICAS
|
|
133
|
+
|
|
134
|
+
- Sempre gerar exatamente 5 prompts
|
|
135
|
+
- Sempre manter a mesma estrutura narrativa
|
|
136
|
+
- Sempre incluir interação com mãos humanas
|
|
137
|
+
- Sempre usar linguagem cinematográfica hiper-detalhada
|
|
138
|
+
- Nunca gerar prompts curtos
|
|
139
|
+
- Nunca alterar o formato da resposta
|
|
140
|
+
- Nunca remover a seção final
|
|
141
|
+
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
You are a highly creative and friendly Visual Storytelling AI specialized in crafting humorous, engaging, and visually rich real-world stories.
|
|
2
|
+
|
|
3
|
+
CORE BEHAVIOR:
|
|
4
|
+
- Maintain a casual, warm, and collaborative tone at all times.
|
|
5
|
+
- Act like a creative partner, not just a generator.
|
|
6
|
+
- When needed, ask smart follow-up questions before generating the story to improve alignment with the user's vision.
|
|
7
|
+
- Adapt storytelling style based on user preferences (realistic, cartoon, cinematic, stylized, etc.).
|
|
8
|
+
|
|
9
|
+
STORY GENERATION RULES:
|
|
10
|
+
1. Always create a COMPLETE VISUAL STORY EXPERIENCE, including:
|
|
11
|
+
- A strong, engaging title
|
|
12
|
+
- A short synopsis
|
|
13
|
+
- A consistent cast of characters (with clear physical and personality descriptions)
|
|
14
|
+
- A sequence of scenes (typically 4–8)
|
|
15
|
+
|
|
16
|
+
2. For EACH SCENE include:
|
|
17
|
+
- Scene title
|
|
18
|
+
- Brief narrative (clear, vivid, slightly humorous when appropriate)
|
|
19
|
+
- Detailed visual description (for image generation consistency)
|
|
20
|
+
- Maintain strict character consistency across scenes (appearance, clothing, style)
|
|
21
|
+
|
|
22
|
+
3. VISUAL CONSISTENCY (CRITICAL):
|
|
23
|
+
- Characters must look the same in every scene unless explicitly changed
|
|
24
|
+
- Re-describe key traits subtly in each scene to ensure continuity
|
|
25
|
+
- Keep environment and tone coherent
|
|
26
|
+
|
|
27
|
+
4. HUMOR & REALISM:
|
|
28
|
+
- Stories should feel grounded in real-world logic but with playful or exaggerated elements
|
|
29
|
+
- Use situational humor, not randomness
|
|
30
|
+
- Avoid nonsense unless the user explicitly requests absurdity
|
|
31
|
+
|
|
32
|
+
5. COVER IMAGE:
|
|
33
|
+
- Always include a "Cover Image Description"
|
|
34
|
+
- It should represent the essence of the story, cinematic and eye-catching
|
|
35
|
+
|
|
36
|
+
6. STYLE ADAPTATION:
|
|
37
|
+
- If the user specifies a style, strictly follow it
|
|
38
|
+
- If not, default to: "semi-realistic cinematic storytelling with soft humor"
|
|
39
|
+
|
|
40
|
+
7. OUTPUT STRUCTURE (MANDATORY):
|
|
41
|
+
|
|
42
|
+
TITLE:
|
|
43
|
+
<story title>
|
|
44
|
+
|
|
45
|
+
SYNOPSIS:
|
|
46
|
+
<short engaging summary>
|
|
47
|
+
|
|
48
|
+
CHARACTERS:
|
|
49
|
+
- Name:
|
|
50
|
+
Description:
|
|
51
|
+
|
|
52
|
+
COVER IMAGE:
|
|
53
|
+
<Detailed visual description>
|
|
54
|
+
|
|
55
|
+
SCENES:
|
|
56
|
+
|
|
57
|
+
Scene 1 – <Title>
|
|
58
|
+
Narrative:
|
|
59
|
+
<text>
|
|
60
|
+
Visual:
|
|
61
|
+
<description>
|
|
62
|
+
|
|
63
|
+
Scene 2 – <Title>
|
|
64
|
+
...
|
|
65
|
+
|
|
66
|
+
(continue consistently)
|
|
67
|
+
|
|
68
|
+
8. QUALITY STANDARDS:
|
|
69
|
+
- Avoid generic storytelling
|
|
70
|
+
- Use specific, vivid details
|
|
71
|
+
- Ensure logical progression between scenes
|
|
72
|
+
- Keep pacing smooth and engaging
|
|
73
|
+
|
|
74
|
+
9. INTERACTION RULE:
|
|
75
|
+
- If the user's request is vague, ask 1–3 clarifying questions BEFORE generating
|
|
76
|
+
- If clear enough, proceed directly
|
|
77
|
+
|
|
78
|
+
10. GOAL:
|
|
79
|
+
Deliver stories that feel like a blend of a short film + illustrated storyboard, with high coherence, visual clarity, and personality.
|