@danhachuel/thunderbolt 0.6.0 → 0.6.2

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 CHANGED
@@ -703,9 +703,14 @@ def generate_video_content_for_ui(
703
703
  blueprint: dict[str, Any] | None = None,
704
704
  ) -> dict[str, Any]:
705
705
  """Generate the subject, script and English keywords in one MoneyPrinter-style action."""
706
- subject = str(subject or "").strip()
706
+ entered_subject = str(subject or "").strip()
707
+ subject = entered_subject
707
708
  topic_result: dict[str, Any] | None = None
708
- if not subject:
709
+ if entered_subject:
710
+ # Um Video Subject escrito pelo utilizador é sempre o tema editorial;
711
+ # nunca o substituímos por um tópico aleatório gerado pela IA.
712
+ subject = entered_subject
713
+ else:
709
714
  selected_blueprint = blueprint if isinstance(blueprint, dict) else blueprint_for_channel(channel)
710
715
  topic_result = generate_topic_for_channel(settings, channel, selected_blueprint)
711
716
  subject = str(topic_result.get("topic") or "").strip()
@@ -1311,6 +1316,45 @@ def render_channel_videos(channel: dict) -> None:
1311
1316
  render_channel_video_editor(video, channel_id)
1312
1317
 
1313
1318
 
1319
+ def render_channel_audio_subtitle_defaults(channel: dict) -> None:
1320
+ """Render and persist the audio/subtitle defaults used by channel automations."""
1321
+ channel_id = str(channel.get("id") or "")
1322
+ subtitle_font = str(channel.get("default_subtitle_font") or SUBTITLE_FONT_OPTIONS[0])
1323
+ subtitle_position = str(channel.get("default_subtitle_position") or SUBTITLE_POSITION_OPTIONS[0])
1324
+ music_source = str(channel.get("default_background_music_source") or "Sem música")
1325
+ music_volume = str(channel.get("default_background_music_volume") or "20%")
1326
+ with st.expander("Configurações de legendas", expanded=False):
1327
+ subtitle_cols = st.columns(2)
1328
+ with subtitle_cols[0]:
1329
+ enable_subtitles = st.checkbox("Enable Subtitles", value=bool(channel.get("default_enable_subtitles", True)), key=f"channel_default_subtitles_{channel_id}")
1330
+ selected_font = st.selectbox("Font", SUBTITLE_FONT_OPTIONS, index=SUBTITLE_FONT_OPTIONS.index(subtitle_font) if subtitle_font in SUBTITLE_FONT_OPTIONS else 0, key=f"channel_default_subtitle_font_{channel_id}")
1331
+ selected_position = st.selectbox("Position", SUBTITLE_POSITION_OPTIONS, index=SUBTITLE_POSITION_OPTIONS.index(subtitle_position) if subtitle_position in SUBTITLE_POSITION_OPTIONS else 0, key=f"channel_default_subtitle_position_{channel_id}")
1332
+ subtitle_color = st.color_picker("Color", str(channel.get("default_subtitle_color") or "#FFFFFF"), key=f"channel_default_subtitle_color_{channel_id}")
1333
+ subtitle_background = st.checkbox("Background", value=bool(channel.get("default_subtitle_background", True)), key=f"channel_default_subtitle_background_{channel_id}")
1334
+ subtitle_background_color = st.color_picker("Background Color", str(channel.get("default_subtitle_background_color") or "#000000"), key=f"channel_default_subtitle_background_color_{channel_id}")
1335
+ subtitle_rounded_background = st.checkbox("Rounded Background", value=bool(channel.get("default_subtitle_rounded_background", False)), key=f"channel_default_subtitle_rounded_background_{channel_id}")
1336
+ with subtitle_cols[1]:
1337
+ subtitle_font_size = st.slider("Font Size", min_value=12, max_value=96, value=int(channel.get("default_subtitle_font_size") or 60), key=f"channel_default_subtitle_font_size_{channel_id}")
1338
+ subtitle_outline = st.color_picker("Outline", str(channel.get("default_subtitle_outline") or "#000000"), key=f"channel_default_subtitle_outline_{channel_id}")
1339
+ subtitle_outline_width = st.slider("Outline Width", min_value=0.0, max_value=5.0, value=float(channel.get("default_subtitle_outline_width") or 1.5), step=0.25, key=f"channel_default_subtitle_outline_width_{channel_id}")
1340
+ if st.button("Guardar configurações de legendas", type="primary", key=f"save_channel_subtitle_defaults_{channel_id}", use_container_width=True):
1341
+ update_channel(channel_id, {
1342
+ "default_enable_subtitles": bool(enable_subtitles), "default_subtitle_font": selected_font, "default_subtitle_position": selected_position,
1343
+ "default_subtitle_color": subtitle_color, "default_subtitle_background": bool(subtitle_background), "default_subtitle_background_color": subtitle_background_color,
1344
+ "default_subtitle_rounded_background": bool(subtitle_rounded_background), "default_subtitle_font_size": int(subtitle_font_size),
1345
+ "default_subtitle_outline": subtitle_outline, "default_subtitle_outline_width": float(subtitle_outline_width),
1346
+ })
1347
+ st.success("Default de legendas guardado para este canal.")
1348
+ st.rerun()
1349
+ with st.expander("Configurações de Audio", expanded=False):
1350
+ selected_music_source = st.selectbox("Fonte da música de fundo", BACKGROUND_MUSIC_SOURCE_OPTIONS, index=BACKGROUND_MUSIC_SOURCE_OPTIONS.index(music_source) if music_source in BACKGROUND_MUSIC_SOURCE_OPTIONS else BACKGROUND_MUSIC_SOURCE_OPTIONS.index("Sem música"), key=f"channel_default_music_source_{channel_id}")
1351
+ selected_music_volume = st.selectbox("Volume da música de fundo", BACKGROUND_MUSIC_VOLUME_OPTIONS, index=BACKGROUND_MUSIC_VOLUME_OPTIONS.index(music_volume) if music_volume in BACKGROUND_MUSIC_VOLUME_OPTIONS else 2, key=f"channel_default_music_volume_{channel_id}")
1352
+ if st.button("Guardar configurações de Audio", type="primary", key=f"save_channel_audio_defaults_{channel_id}", use_container_width=True):
1353
+ update_channel(channel_id, {"default_background_music_source": selected_music_source, "default_background_music_volume": selected_music_volume})
1354
+ st.success("Default de Audio guardado para este canal.")
1355
+ st.rerun()
1356
+
1357
+
1314
1358
  def render_channel_edit_form(channel: dict, youtube_account_ids: list[str], youtube_account_labels: dict[str, str], youtube_accounts_by_id: dict[str, dict[str, Any]]) -> None:
1315
1359
  channel_id = str(channel["id"])
1316
1360
  blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
@@ -2341,6 +2385,7 @@ def render_tiktok_channels():
2341
2385
  update_channel(channel_id, {"name": edit_name.strip(), "url": edit_url.strip(), "handle": edit_handle.strip(), "language": edit_language, "niche": edit_niche.strip(), "reference_channels": [edit_niche.strip()] if edit_niche.strip() else [], "default_voice": edit_voice, "voice": edit_voice, "style_wide": channel_video_source_storage(edit_source), "video_aspect_ratio": edit_aspect, "automation_on": edit_automation, "automation_time": edit_time.strip(), "description": edit_description.strip(), "platform": "tiktok"})
2342
2386
  st.session_state.pop(f"tiktok_edit_{channel_id}", None)
2343
2387
  st.rerun()
2388
+ render_channel_audio_subtitle_defaults(channel)
2344
2389
  with st.expander("Últimos 10 vídeos publicados", expanded=False):
2345
2390
  recent_videos = channel_videos_for(channel, limit=10)
2346
2391
  if not recent_videos:
@@ -3032,6 +3077,7 @@ def render_channels():
3032
3077
  st.success("Conta Google e DELEGATED_SESSION_ID individual do canal guardados.")
3033
3078
  st.rerun()
3034
3079
 
3080
+ render_channel_audio_subtitle_defaults(channel)
3035
3081
  render_channel_videos(channel)
3036
3082
 
3037
3083
 
@@ -53,6 +53,18 @@ def create_channel(name: str, url: str = "", metadata: dict[str, Any] | None = N
53
53
  "style_wide": "pexels",
54
54
  "voice": "",
55
55
  "default_voice": "",
56
+ "default_enable_subtitles": True,
57
+ "default_subtitle_font": "MicrosoftYaHeiBold.ttc",
58
+ "default_subtitle_position": "Bottom (Recommended)",
59
+ "default_subtitle_color": "#FFFFFF",
60
+ "default_subtitle_background": True,
61
+ "default_subtitle_background_color": "#000000",
62
+ "default_subtitle_rounded_background": False,
63
+ "default_subtitle_font_size": 60,
64
+ "default_subtitle_outline": "#000000",
65
+ "default_subtitle_outline_width": 1.5,
66
+ "default_background_music_source": "Sem música",
67
+ "default_background_music_volume": "20%",
56
68
  "delegated_session_id": "",
57
69
  "automation_on": False,
58
70
  "automation_time": "00:00",
@@ -164,6 +176,19 @@ def create_tasks_for_batch(batch: dict[str, Any]) -> list[dict[str, Any]]:
164
176
  if thumbnail_path:
165
177
  artifacts.setdefault("thumbnail", thumbnail_path)
166
178
  initial_stage = "topic" if pending_creative else "script"
179
+ channel_generation_defaults = {
180
+ key.removeprefix("default_"): channel[key]
181
+ for key in (
182
+ "default_enable_subtitles", "default_subtitle_font", "default_subtitle_position", "default_subtitle_color",
183
+ "default_subtitle_background", "default_subtitle_background_color", "default_subtitle_rounded_background",
184
+ "default_subtitle_font_size", "default_subtitle_outline", "default_subtitle_outline_width",
185
+ "default_background_music_source", "default_background_music_volume",
186
+ )
187
+ if key in channel
188
+ }
189
+ configured_generation_settings = options.get("generation_settings") if isinstance(options.get("generation_settings"), dict) else {}
190
+ payload_generation_settings = payload.get("generation_settings") if isinstance(payload.get("generation_settings"), dict) else {}
191
+ generation_settings = {**channel_generation_defaults, **configured_generation_settings, **payload_generation_settings}
167
192
  task = {
168
193
  "id": make_id("video"),
169
194
  "batch_id": batch["id"],
@@ -183,7 +208,7 @@ def create_tasks_for_batch(batch: dict[str, Any]) -> list[dict[str, Any]]:
183
208
  "music_path": payload.get("music_path", options.get("music_path", "")),
184
209
  "music_source": payload.get("music_source", options.get("music_source", "")),
185
210
  "background_mode": payload.get("background_mode", options.get("background_mode", "stock")),
186
- "generation_settings": payload.get("generation_settings", options.get("generation_settings", {})),
211
+ "generation_settings": generation_settings,
187
212
  "blueprint_id": payload.get("blueprint_id") or channel.get("default_blueprint_id") or channel.get("blueprint_id", ""),
188
213
  "blueprint_name": payload.get("blueprint_name", ""),
189
214
  "thumbnail_blueprint_id": payload.get("thumbnail_blueprint_id") or channel.get("default_thumbnail_blueprint_id") or channel.get("thumbnail_blueprint_id") or GENERIC_THUMBNAIL_BLUEPRINT_ID,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.6.00",
3
+ "version": "0.6.02",
4
4
  "description": "Thunderbolt — interface local para operação de canais faceless e motor MoneyPrinterTurbo",
5
5
  "license": "MIT",
6
6
  "main": "scripts/cli.mjs",