@danhachuel/thunderbolt 0.3.63 → 0.3.65
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/MANUAL-INSTALACAO.md +7 -5
- package/README.md +2 -2
- package/app/main.py +15 -2
- package/hermes_ui/pipeline_worker.py +70 -3
- package/integrations/moneyprinter_config.py +7 -0
- package/package.json +1 -1
package/MANUAL-INSTALACAO.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Este manual descreve a instalação local da UI Thunderbolt, baseada no MoneyPrinterTurbo, utilizando o pacote npm `@danhachuel/thunderbolt`. O fluxo recomendado instala automaticamente o ambiente Python, as dependências da aplicação, as dependências do MoneyPrinterTurbo, o Streamlit e o suporte FFmpeg através de `imageio-ffmpeg`.
|
|
4
4
|
|
|
5
|
-
> **Versão deste manual:** 0.3.
|
|
5
|
+
> **Versão deste manual:** 0.3.65
|
|
6
6
|
> **Pacote npm:** `@danhachuel/thunderbolt`
|
|
7
7
|
> **Porta padrão da UI:** `localhost:3030`
|
|
8
8
|
> **Repositório:** [github.com/DanHachuel/thunderbolt](https://github.com/DanHachuel/thunderbolt)
|
|
@@ -128,13 +128,13 @@ Execute:
|
|
|
128
128
|
Windows PowerShell ou MobaXterm:
|
|
129
129
|
|
|
130
130
|
```powershell
|
|
131
|
-
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
131
|
+
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.65 install
|
|
132
132
|
```
|
|
133
133
|
|
|
134
134
|
Linux/macOS:
|
|
135
135
|
|
|
136
136
|
```bash
|
|
137
|
-
npx --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
137
|
+
npx --yes --prefer-online @danhachuel/thunderbolt@0.3.65 install
|
|
138
138
|
```
|
|
139
139
|
|
|
140
140
|
A instalação normal é **segura para actualizações**: preserva `storage`, Blueprints, Brandings, configurações e artefactos do utilizador. Remove apenas `.venv`, o clone técnico do MoneyPrinterTurbo e dependências que serão recriadas. Uma pasta antiga sem dados do utilizador, como `C:\Users\<utilizador>\AppData\Local\hermes` da tentativa incompleta, pode ser removida; uma pasta antiga que contenha Blueprints, Brandings ou storage é preservada e apenas avisada no terminal. Feche processos Python, Node, Streamlit e MobaXterm que estejam a usar as pastas antes de executar.
|
|
@@ -142,7 +142,7 @@ A instalação normal é **segura para actualizações**: preserva `storage`, Bl
|
|
|
142
142
|
Se quiser apagar absolutamente tudo de forma intencional, use o comando destrutivo separado:
|
|
143
143
|
|
|
144
144
|
```powershell
|
|
145
|
-
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
145
|
+
npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.65 install --purge-data
|
|
146
146
|
```
|
|
147
147
|
|
|
148
148
|
O parâmetro `--purge-data` apaga Blueprints, Brandings, configurações, storage e artefactos locais. Não o use numa actualização normal.
|
|
@@ -397,6 +397,8 @@ A área **Voz, TTS e música — Azure Speech, restantes serviços e Suno** est
|
|
|
397
397
|
|
|
398
398
|
Na criação de vídeo, abra **Configurações de áudio** e escolha **Upload** em **Modo de narração**. Use **Ficheiro de narração** para seleccionar o áudio, clique em **Guardar áudio de narração** e confirme a pré-visualização. O Thunderbolt valida que o ficheiro existe antes de criar a tarefa e encaminha o caminho ao MoneyPrinterTurbo com `--custom-audio-file`; este argumento é necessário porque o motor não persiste automaticamente o caminho carregado. São aceites ficheiros `.mp3`, `.wav`, `.m4a`, `.aac`, `.flac` e `.ogg`, guardados no storage local em `voiceovers`.
|
|
399
399
|
|
|
400
|
+
O selector **Voiceover Service** oferece **Azure Speech SDK V2** e **Azure TTS V1**. Quando existe **Azure Speech key + região**, a opção V2 é preferida e o worker marca internamente a voz com `-V2`, activando o SDK Azure Speech e evitando o stream `edge_tts`. Tarefas antigas que ainda tenham `Azure TTS V1` guardado também são migradas para V2 quando essas credenciais estão configuradas. Sem credenciais Azure, o V1 continua disponível como fallback sem key; nesse caso o timeout interno do stream passa a 90 segundos para tolerar scripts longos e redes lentas. Um erro de TTS identifica explicitamente **Azure Speech / edge_tts API**, a etapa **Narração TTS** e os providers envolvidos.
|
|
401
|
+
|
|
400
402
|
As notificações novas são verificadas automaticamente na sessão activa e aparecem como pop-ups no canto inferior direito, mesmo quando está aberta outra página. O pop-up não marca o registo como lido: a aba **Notificações** continua a ser o centro persistente para consultar o histórico, gerir preferências e marcar eventos como lidos. Cada ID é apresentado no máximo uma vez por sessão do navegador.
|
|
401
403
|
|
|
402
404
|
| Provider | Configuração mínima | Pool suportado |
|
|
@@ -556,7 +558,7 @@ Ao abrir a página, o Thunderbolt não prepara dados públicos, não descarrega
|
|
|
556
558
|
|
|
557
559
|
Os parâmetros da UI são número de clusters entre 2 e 10, suporte mínimo entre 0,01 e 0,50, país, engagement, intervalo de datas e tags, todos dentro da área principal da aba. O núcleo normaliza os dados, calcula engagement, aplica filtros, faz transformação logarítmica e standardização, executa K-Means e calcula itemsets/regras com FP-Growth. Não são apresentados resultados até ao primeiro clique em **Analisar Nichos**; o mesmo botão aplica alterações posteriores aos filtros. Os resultados são DataFrames de clusters, itemsets frequentes, regras de associação e dados analisados; o gráfico de dispersão é criado nativamente com Plotly.
|
|
558
560
|
|
|
559
|
-
As dependências adicionais — `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub` — são instaladas pelo procedimento normal de `npx`. Em instalações existentes, execute novamente `npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.
|
|
561
|
+
As dependências adicionais — `scikit-learn`, `mlxtend`, `plotly`, `seaborn`, `matplotlib` e `kagglehub` — são instaladas pelo procedimento normal de `npx`. Em instalações existentes, execute novamente `npx.cmd --yes --prefer-online @danhachuel/thunderbolt@0.3.65 install`; o instalador detecta e reutiliza o que já estiver válido.
|
|
560
562
|
|
|
561
563
|
### Niche Finder Apify
|
|
562
564
|
|
package/README.md
CHANGED
|
@@ -44,7 +44,7 @@ Em **Pipeline Vídeos > Criação de Vídeos** e **Automação Youtube**, a opç
|
|
|
44
44
|
|
|
45
45
|
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 exclusivamente os cartões activos FAL AI, KIE AI e Agnes AI do pool 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.
|
|
46
46
|
|
|
47
|
-
Quando uma etapa falha, a tarefa, a notificação e a página **Configurações > Logs** guardam e mostram sempre a coluna **API/Provider**, o serviço, a rota e, quando aplicável, os campos de configuração em falta. No caso do MoneyPrinterTurbo, os marcadores `LLM_PROVIDER`, `MISSING` e `INVALID` são convertidos em attribution legível; por exemplo, um erro pode indicar simultaneamente **OpenAI / NVIDIA NIM API** e **Pexels API**, em vez de apresentar apenas a mensagem genérica de credenciais adicionais. Registos históricos sem metadata são identificados explicitamente como anteriores à attribution estruturada.
|
|
47
|
+
Quando uma etapa falha, a tarefa, a notificação e a página **Configurações > Logs** guardam e mostram sempre a coluna **API/Provider**, o serviço, a rota e, quando aplicável, os campos de configuração em falta. No caso do MoneyPrinterTurbo, os marcadores `LLM_PROVIDER`, `MISSING` e `INVALID` são convertidos em attribution legível; por exemplo, um erro pode indicar simultaneamente **OpenAI / NVIDIA NIM API** e **Pexels API**, em vez de apresentar apenas a mensagem genérica de credenciais adicionais. Os timeouts `azure_tts_v1`/`edge_tts` são identificados como **Azure Speech / edge_tts API**. Quando há Azure Speech key e região, o worker encaminha a voz para o SDK Azure Speech V2; sem essas credenciais, o fallback edge_tts usa um timeout interno de 90 segundos. O worker invoca o helper com `--` antes das flags MoneyPrinterTurbo, porque `mpt_agent.py` reserva esses argumentos para a CLI filha. Registos históricos sem metadata são identificados explicitamente como anteriores à attribution estruturada.
|
|
48
48
|
|
|
49
49
|
## Upload-Post — publicação para múltiplas plataformas
|
|
50
50
|
|
|
@@ -87,7 +87,7 @@ No topo da área principal da aplicação existe o menu nativo de idioma no padr
|
|
|
87
87
|
|
|
88
88
|
A UI suporta os temas **Dark** e **Light** através do menu nativo de três pontos do Streamlit, no local original do toolbar. Não existe um selector Theme adicional dentro da página. A configuração distribuída em `.streamlit/config.toml` disponibiliza as variantes nomeadas **Dark** e **Light**, e o menu nativo continua responsável por alternar entre os modos, seguindo o padrão do [MoneyPrinterTurbo](https://github.com/harry0703/MoneyPrinterTurbo). O CSS próprio do Thunderbolt usa cores semânticas, `currentColor` e `color-mix` para acompanhar o tema activo, sem alterar a posição nem a funcionalidade do toolbar, do botão Deploy e do menu principal.
|
|
89
89
|
|
|
90
|
-
## Navegação da UI 0.3.
|
|
90
|
+
## Navegação da UI 0.3.65
|
|
91
91
|
|
|
92
92
|
A barra lateral mantém os níveis principais, nesta ordem: **Início**, **Niche Finder**, **Pipeline**, **Pipeline TikTok**, **Automação**, **Edição**, **AI Influencers** e **Configurações**. **Pipeline Vídeos** é expansível e contém **Criação de Vídeos**, **Backlog Vídeos**, **Roteiros**, **Thumbnails** e **Upload**. **Pipeline Música** é expansível e contém **Criação de Músicas** e **Upload Música**. **Automação** também é expansível e contém **Automação Youtube**. **Edição** é expansível e contém **Limpador de Metadados**, **Cortes**, **Editor Python** e **Download Mídia**, nessa ordem. **AI Influencers** é expansível e contém **Personagens**, **Geração de Conteúdo IA**, **Redes Sociais**, **Tutorial Meta** e **Tutorial Supabase**, nessa ordem. A página Geração de Conteúdo IA divide-se em **Imagens**, **Vídeos** e **Motion Control**; a subaba Motion Control permanece vazia nesta release.
|
|
93
93
|
**Niche Finder** é expansível e contém **Niche Finder Kaggle** e **Niche Finder Apify**. **Configurações** é expansível e contém **Canais Youtube**, **Blueprints Youtube**, **MCP**, **Contas Google**, **Notificações**, **Logs** e **Configuração API**, nessa ordem. O Início reúne o dashboard e as filas do Pipeline, sem botões de acções rápidas.
|
package/app/main.py
CHANGED
|
@@ -164,7 +164,7 @@ VIDEO_CONCATENATION_OPTIONS = ["Random Concatenation (Recommended)", "Sequential
|
|
|
164
164
|
VIDEO_TRANSITION_OPTIONS = ["None", "Fade", "Dissolve"]
|
|
165
165
|
VIDEO_ENCODER_OPTIONS = ["Default (Recommended)", "H.264", "H.265"]
|
|
166
166
|
VOICEOVER_MODE_OPTIONS = ["Auto", "Upload", "None"]
|
|
167
|
-
VOICEOVER_SERVICE_OPTIONS = ["Azure TTS V1"]
|
|
167
|
+
VOICEOVER_SERVICE_OPTIONS = ["Azure Speech SDK V2", "Azure TTS V1"]
|
|
168
168
|
VOICEOVER_VOLUME_OPTIONS = ["20%", "40%", "60%", "80%", "100%"]
|
|
169
169
|
VOICEOVER_SPEED_OPTIONS = ["0.5x", "0.75x", "1.0x", "1.25x", "1.5x", "2.0x"]
|
|
170
170
|
BACKGROUND_MUSIC_SOURCE_OPTIONS = ["Ficheiro existente", "Carregar ficheiro", "Criar via Suno API", "Random Background Music", "Sem música"]
|
|
@@ -1046,7 +1046,20 @@ def render_video_generation_settings(
|
|
|
1046
1046
|
if stored_voiceover_value and Path(stored_voiceover_value).is_file():
|
|
1047
1047
|
st.audio(stored_voiceover_value)
|
|
1048
1048
|
settings["voiceover_file"] = stored_voiceover_value
|
|
1049
|
-
|
|
1049
|
+
configured_voice_settings = read_json("settings.json", {})
|
|
1050
|
+
configured_azure_voice = bool(
|
|
1051
|
+
str(configured_voice_settings.get("azure_speech_key") or "").strip()
|
|
1052
|
+
and str(configured_voice_settings.get("azure_speech_region") or "").strip()
|
|
1053
|
+
)
|
|
1054
|
+
voice_service_default = "Azure Speech SDK V2" if configured_azure_voice else "Azure TTS V1"
|
|
1055
|
+
voice_service_index = VOICEOVER_SERVICE_OPTIONS.index(voice_service_default)
|
|
1056
|
+
settings["voiceover_service"] = st.selectbox(
|
|
1057
|
+
"Voiceover Service",
|
|
1058
|
+
VOICEOVER_SERVICE_OPTIONS,
|
|
1059
|
+
index=voice_service_index,
|
|
1060
|
+
key=f"{prefix}_voiceover_service",
|
|
1061
|
+
help="Azure Speech SDK V2 usa a key/região configuradas e não depende do stream edge_tts. Azure TTS V1 usa edge_tts e pode funcionar sem key Azure.",
|
|
1062
|
+
)
|
|
1050
1063
|
if channel is not None:
|
|
1051
1064
|
channel_id = str(channel.get("id") or channel.get("name") or "")
|
|
1052
1065
|
channel_voice = str(channel.get("default_voice") or channel.get("voice") or "").strip()
|
|
@@ -324,6 +324,16 @@ def _failure_attribution(
|
|
|
324
324
|
provider_code = str(markers["helper_provider"] or "").strip().casefold()
|
|
325
325
|
combined = f"{output} {error}".casefold()
|
|
326
326
|
|
|
327
|
+
if stage == "video" and any(marker in combined for marker in ("edge_tts", "edge tts", "azure_tts_v1", "azure speech")):
|
|
328
|
+
return {
|
|
329
|
+
"failure_api": "Azure Speech / edge_tts API",
|
|
330
|
+
"failure_provider": "azure_speech, edge_tts",
|
|
331
|
+
"failure_service": "Narração TTS",
|
|
332
|
+
"failure_route": route,
|
|
333
|
+
"failure_config_fields": "",
|
|
334
|
+
"failure_stage": stage,
|
|
335
|
+
}
|
|
336
|
+
|
|
327
337
|
if stage == "video" and missing:
|
|
328
338
|
api_labels: list[str] = []
|
|
329
339
|
provider_values: list[str] = []
|
|
@@ -567,7 +577,45 @@ def _mpt_video_language(value: Any) -> str:
|
|
|
567
577
|
return raw if re.fullmatch(r"[A-Za-z]{2,3}(?:-[A-Za-z]{2,4})?", raw) else ""
|
|
568
578
|
|
|
569
579
|
|
|
570
|
-
def
|
|
580
|
+
def _uses_azure_speech_sdk_v2(
|
|
581
|
+
generation_settings: dict[str, Any],
|
|
582
|
+
settings: dict[str, Any] | None = None,
|
|
583
|
+
) -> bool:
|
|
584
|
+
service = str(generation_settings.get("voiceover_service") or "").strip().casefold()
|
|
585
|
+
if service in {
|
|
586
|
+
"azure speech sdk v2",
|
|
587
|
+
"azure speech",
|
|
588
|
+
"azure tts v2",
|
|
589
|
+
"azure speech sdk",
|
|
590
|
+
}:
|
|
591
|
+
return True
|
|
592
|
+
if service in {"azure tts v1", "edge tts", "edge_tts"}:
|
|
593
|
+
configured = settings or {}
|
|
594
|
+
return bool(
|
|
595
|
+
str(configured.get("azure_speech_key") or "").strip()
|
|
596
|
+
and str(configured.get("azure_speech_region") or "").strip()
|
|
597
|
+
)
|
|
598
|
+
configured = settings or {}
|
|
599
|
+
return bool(
|
|
600
|
+
str(configured.get("azure_speech_key") or "").strip()
|
|
601
|
+
and str(configured.get("azure_speech_region") or "").strip()
|
|
602
|
+
)
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def _azure_speech_v2_voice_name(value: str) -> str:
|
|
606
|
+
"""Mark a normal Azure voice for MPT's Azure Speech SDK V2 branch."""
|
|
607
|
+
voice = str(value or "").strip()
|
|
608
|
+
if not voice or ":" in voice or voice.casefold() == "no-voice":
|
|
609
|
+
return voice
|
|
610
|
+
if re.search(r"-v2(?:-|$)", voice, flags=re.IGNORECASE):
|
|
611
|
+
return voice
|
|
612
|
+
match = re.search(r"-(Female|Male)$", voice, flags=re.IGNORECASE)
|
|
613
|
+
if match:
|
|
614
|
+
return f"{voice[:match.start()]}-V2{voice[match.start():]}"
|
|
615
|
+
return f"{voice}-V2"
|
|
616
|
+
|
|
617
|
+
|
|
618
|
+
def _moneyprinter_cli_args(task: dict[str, Any], route: str, settings: dict[str, Any] | None = None) -> list[str]:
|
|
571
619
|
"""Build the explicit MPT CLI contract for the stock Pexels/Pixabay route."""
|
|
572
620
|
generation_settings = task.get("generation_settings") if isinstance(task.get("generation_settings"), dict) else {}
|
|
573
621
|
args: list[str] = ["--video-source", route]
|
|
@@ -606,6 +654,8 @@ def _moneyprinter_cli_args(task: dict[str, Any], route: str) -> list[str]:
|
|
|
606
654
|
if voice_mode == "none" or voice_mode == "upload":
|
|
607
655
|
args.extend(["--voice-name", "no-voice"])
|
|
608
656
|
elif voice:
|
|
657
|
+
if _uses_azure_speech_sdk_v2(generation_settings, settings):
|
|
658
|
+
voice = _azure_speech_v2_voice_name(voice)
|
|
609
659
|
args.extend(["--voice-name", voice])
|
|
610
660
|
volume = generation_settings.get("voiceover_volume")
|
|
611
661
|
speed = generation_settings.get("voiceover_speed")
|
|
@@ -678,9 +728,26 @@ def _run_video_helper(task: dict[str, Any]) -> Path:
|
|
|
678
728
|
command = ["uv", "run", "--no-project", "--python", "3.11", "python", "mpt_agent.py"]
|
|
679
729
|
if configured_root:
|
|
680
730
|
command.extend(["--root", str(configured_root)])
|
|
681
|
-
command.extend(["--subject", subject])
|
|
731
|
+
command.extend(["--subject", subject, "--"])
|
|
682
732
|
generation_settings = task.get("generation_settings") if isinstance(task.get("generation_settings"), dict) else {}
|
|
683
|
-
|
|
733
|
+
if _uses_azure_speech_sdk_v2(generation_settings, settings) and str(generation_settings.get("voiceover_mode") or "").strip().casefold() not in {"none", "upload"}:
|
|
734
|
+
missing_voice_config = [
|
|
735
|
+
field for field, value in (
|
|
736
|
+
("azure_speech_key", settings.get("azure_speech_key")),
|
|
737
|
+
("azure_speech_region", settings.get("azure_speech_region")),
|
|
738
|
+
) if not str(value or "").strip()
|
|
739
|
+
]
|
|
740
|
+
if missing_voice_config:
|
|
741
|
+
message = "Azure Speech SDK V2 foi seleccionado, mas faltam credenciais de voz."
|
|
742
|
+
metadata = _failure_attribution(task, settings, "video", error=message)
|
|
743
|
+
metadata.update({
|
|
744
|
+
"failure_api": "Azure Speech API",
|
|
745
|
+
"failure_provider": "azure_speech",
|
|
746
|
+
"failure_service": "Azure Speech SDK V2",
|
|
747
|
+
"failure_config_fields": ", ".join(missing_voice_config),
|
|
748
|
+
})
|
|
749
|
+
raise PipelineError(_failure_message(message, metadata), failure_metadata=metadata)
|
|
750
|
+
command.extend(_moneyprinter_cli_args(task, route, settings=settings))
|
|
684
751
|
if str(generation_settings.get("voiceover_mode") or "").strip().casefold() == "upload":
|
|
685
752
|
voiceover_file = Path(str(generation_settings.get("voiceover_file") or "").strip()).expanduser()
|
|
686
753
|
if not str(voiceover_file) or not voiceover_file.is_file() or voiceover_file.stat().st_size <= 0:
|
|
@@ -116,6 +116,13 @@ def build_moneyprinter_config(settings: dict[str, Any], existing: dict[str, Any]
|
|
|
116
116
|
for settings_key, config_key in app_map.items():
|
|
117
117
|
if settings_key in settings:
|
|
118
118
|
app[config_key] = settings[settings_key]
|
|
119
|
+
# Edge TTS V1 is retained as a keyless fallback, but its default 30-second
|
|
120
|
+
# stream deadline is too short for longer scripts or slow networks.
|
|
121
|
+
try:
|
|
122
|
+
edge_tts_timeout = float(settings.get("edge_tts_timeout", 90))
|
|
123
|
+
except (TypeError, ValueError):
|
|
124
|
+
edge_tts_timeout = 90.0
|
|
125
|
+
app["edge_tts_timeout"] = max(30, int(edge_tts_timeout))
|
|
119
126
|
# A UI can store the canonical mapping while older installations still
|
|
120
127
|
# expose one legacy field per source. Export every supported source as a
|
|
121
128
|
# TOML array so MoneyPrinterTurbo can rotate keys without CSV parsing.
|
package/package.json
CHANGED