@danhachuel/thunderbolt 0.7.54 → 0.7.55

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.
Files changed (2) hide show
  1. package/app/main.py +105 -34
  2. package/package.json +1 -1
package/app/main.py CHANGED
@@ -1572,6 +1572,84 @@ def render_channel_audio_subtitle_defaults(channel: dict) -> None:
1572
1572
  st.rerun()
1573
1573
 
1574
1574
 
1575
+ def render_channel_video_defaults(channel: dict) -> None:
1576
+ """Render and persist the video defaults used by this channel."""
1577
+ channel_id = str(channel.get("id") or "")
1578
+ source = channel_video_source_value(channel.get("default_video_source") or channel.get("style_wide"))
1579
+ aspect_ratio = str(channel.get("default_video_aspect_ratio") or channel.get("video_aspect_ratio") or "Landscape 16:9")
1580
+ encoder = str(channel.get("default_video_encoder") or VIDEO_ENCODER_OPTIONS[0])
1581
+ maximum_clip_duration = int(channel.get("default_maximum_clip_duration") or 10)
1582
+ videos_per_run = int(channel.get("default_videos_per_run") or 1)
1583
+ concatenation_mode = str(channel.get("default_video_concatenation_mode") or VIDEO_CONCATENATION_OPTIONS[0])
1584
+ transition_mode = str(channel.get("default_video_transition_mode") or VIDEO_TRANSITION_OPTIONS[0])
1585
+ with st.expander("Configurações de vídeo", expanded=False):
1586
+ video_cols = st.columns(2)
1587
+ with video_cols[0]:
1588
+ selected_source = st.selectbox(
1589
+ "Fonte do vídeo",
1590
+ WIDE_STYLE_OPTIONS,
1591
+ index=WIDE_STYLE_OPTIONS.index(source) if source in WIDE_STYLE_OPTIONS else 0,
1592
+ key=f"channel_default_video_source_{channel_id}",
1593
+ )
1594
+ selected_aspect_ratio = st.selectbox(
1595
+ "Proporção do vídeo",
1596
+ CHANNEL_ASPECT_RATIO_OPTIONS,
1597
+ index=CHANNEL_ASPECT_RATIO_OPTIONS.index(aspect_ratio) if aspect_ratio in CHANNEL_ASPECT_RATIO_OPTIONS else 0,
1598
+ key=f"channel_default_video_aspect_ratio_{channel_id}",
1599
+ )
1600
+ selected_concatenation = st.selectbox(
1601
+ "Video Concatenation Mode",
1602
+ VIDEO_CONCATENATION_OPTIONS,
1603
+ index=VIDEO_CONCATENATION_OPTIONS.index(concatenation_mode) if concatenation_mode in VIDEO_CONCATENATION_OPTIONS else 0,
1604
+ key=f"channel_default_video_concatenation_{channel_id}",
1605
+ )
1606
+ selected_transition = st.selectbox(
1607
+ "Video Transition Mode",
1608
+ VIDEO_TRANSITION_OPTIONS,
1609
+ index=VIDEO_TRANSITION_OPTIONS.index(transition_mode) if transition_mode in VIDEO_TRANSITION_OPTIONS else 0,
1610
+ key=f"channel_default_video_transition_{channel_id}",
1611
+ )
1612
+ with video_cols[1]:
1613
+ selected_maximum_clip_duration = st.selectbox(
1614
+ "Maximum Clip Duration (seconds)",
1615
+ [3, 5, 8, 10, 15],
1616
+ index=[3, 5, 8, 10, 15].index(maximum_clip_duration) if maximum_clip_duration in [3, 5, 8, 10, 15] else 3,
1617
+ key=f"channel_default_maximum_clip_duration_{channel_id}",
1618
+ )
1619
+ selected_videos_per_run = st.selectbox(
1620
+ "Videos per Run",
1621
+ list(range(1, 11)),
1622
+ index=videos_per_run - 1 if 1 <= videos_per_run <= 10 else 0,
1623
+ key=f"channel_default_videos_per_run_{channel_id}",
1624
+ )
1625
+ selected_encoder = st.selectbox(
1626
+ "Video Encoder",
1627
+ VIDEO_ENCODER_OPTIONS,
1628
+ index=VIDEO_ENCODER_OPTIONS.index(encoder) if encoder in VIDEO_ENCODER_OPTIONS else 0,
1629
+ key=f"channel_default_video_encoder_{channel_id}",
1630
+ )
1631
+ match_visuals = st.checkbox(
1632
+ "Match Visuals to Script Order",
1633
+ value=bool(channel.get("default_match_visuals_to_script_order", False)),
1634
+ key=f"channel_default_match_visuals_{channel_id}",
1635
+ )
1636
+ if st.button("Guardar configurações de vídeo", type="primary", key=f"save_channel_video_defaults_{channel_id}", width="stretch"):
1637
+ update_channel(channel_id, {
1638
+ "default_video_source": channel_video_source_storage(selected_source),
1639
+ "default_video_aspect_ratio": selected_aspect_ratio,
1640
+ "default_video_concatenation_mode": selected_concatenation,
1641
+ "default_video_transition_mode": selected_transition,
1642
+ "default_maximum_clip_duration": int(selected_maximum_clip_duration),
1643
+ "default_videos_per_run": int(selected_videos_per_run),
1644
+ "default_video_encoder": selected_encoder,
1645
+ "default_match_visuals_to_script_order": bool(match_visuals),
1646
+ "style_wide": channel_video_source_storage(selected_source),
1647
+ "video_aspect_ratio": selected_aspect_ratio,
1648
+ })
1649
+ st.success("Default de vídeo guardado para este canal.")
1650
+ st.rerun()
1651
+
1652
+
1575
1653
  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:
1576
1654
  channel_id = str(channel["id"])
1577
1655
  blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
@@ -3409,17 +3487,34 @@ def render_channels():
3409
3487
  delete_key = f"delete_pending_{channel_id}"
3410
3488
  with st.expander("Detalhes e configuração do canal", expanded=False):
3411
3489
  calculated_time, calculated_source, calculated_words = _channel_average_video_time(channel)
3412
- average_video_time = st.text_input("Tempo Medio de Video", value=channel_video_time_value(channel), key=f"youtube_channel_average_video_time_{channel_id}", help=f"Referência: {calculated_words or 0} palavras · {calculated_source}.")
3413
- if st.button("Guardar tempo", key=f"save_youtube_channel_average_video_time_{channel_id}", width="stretch"):
3414
- if not valid_hhmm(average_video_time):
3415
- st.error("Use o formato MM:SS, por exemplo 12:00.")
3416
- else:
3417
- update_channel(channel_id, {"average_video_time": average_video_time.strip() or DEFAULT_AVERAGE_VIDEO_TIME, "average_video_word_count": words_from_channel_time(average_video_time)})
3418
- st.success("Tempo médio do canal YouTube guardado.")
3419
- st.rerun()
3490
+ with st.expander("Tempo Medio de Video", expanded=False):
3491
+ average_video_time = st.text_input("Tempo Medio de Video", value=channel_video_time_value(channel), key=f"youtube_channel_average_video_time_{channel_id}", help=f"Referência: {calculated_words or 0} palavras · {calculated_source}.")
3492
+ if st.button("Guardar Tempo Medio de Video", key=f"save_youtube_channel_average_video_time_{channel_id}", type="primary", width="stretch"):
3493
+ if not valid_hhmm(average_video_time):
3494
+ st.error("Use o formato MM:SS, por exemplo 12:00.")
3495
+ else:
3496
+ update_channel(channel_id, {"average_video_time": average_video_time.strip() or DEFAULT_AVERAGE_VIDEO_TIME, "average_video_word_count": words_from_channel_time(average_video_time)})
3497
+ st.success("Tempo médio do canal YouTube guardado.")
3498
+ st.rerun()
3499
+ if st.session_state.get(edit_key):
3500
+ render_channel_edit_form(channel, youtube_account_ids, youtube_account_labels, youtube_accounts_by_id)
3501
+ else:
3502
+ summary = channel_blueprint_summary(channel)
3503
+ render_channel_thumbnail_blueprint_panel(channel, compact=True)
3504
+ channel_language = language_label(channel.get("language") or "pt")
3505
+ block_cols = st.columns(4, gap="small")
3506
+ with block_cols[0]:
3507
+ st.markdown(f"**Blueprint Padrão**\n\n{summary['name']}")
3508
+ with block_cols[1]:
3509
+ st.markdown(f"**Nicho**\n\n{channel_niche_label(channel)}")
3510
+ with block_cols[2]:
3511
+ st.markdown(f"**Narrador/Voz Padrão**\n\n{summary['voice'] or 'Sem voz padrão'}")
3512
+ with block_cols[3]:
3513
+ st.markdown(f"**Idioma**\n\n{channel_language}")
3514
+
3420
3515
  action_cols = st.columns(2)
3421
3516
  with action_cols[0]:
3422
- if st.button("Editar", key=f"edit_channel_button_{channel_id}", width="stretch"):
3517
+ if st.button("Editar", key=f"edit_channel_button_{channel_id}", type="primary", width="stretch"):
3423
3518
  st.session_state[edit_key] = True
3424
3519
  st.rerun()
3425
3520
  with action_cols[1]:
@@ -3442,31 +3537,6 @@ def render_channels():
3442
3537
  if st.button("Cancelar", key=f"cancel_delete_{channel_id}"):
3443
3538
  st.session_state.pop(delete_key, None)
3444
3539
  st.rerun()
3445
- if st.session_state.get(edit_key):
3446
- render_channel_edit_form(channel, youtube_account_ids, youtube_account_labels, youtube_accounts_by_id)
3447
- else:
3448
- summary = channel_blueprint_summary(channel)
3449
- render_channel_thumbnail_blueprint_panel(channel, compact=True)
3450
- channel_language = language_label(channel.get("language") or "pt")
3451
- block_cols = st.columns(4, gap="small")
3452
- with block_cols[0]:
3453
- st.markdown(f"**Blueprint Padrão**\n\n{summary['name']}")
3454
- if st.button("Editar Blueprint", key=f"edit_prompts_{channel_id}", width="stretch"):
3455
- st.session_state[edit_key] = True
3456
- st.rerun()
3457
- with block_cols[1]:
3458
- st.markdown(f"**Nicho**\n\n{channel_niche_label(channel)}")
3459
- if st.button("Editar Nicho", key=f"edit_niche_{channel_id}", width="stretch"):
3460
- st.session_state[edit_key] = True
3461
- st.rerun()
3462
- with block_cols[2]:
3463
- st.markdown(f"**Narrador/Voz Padrão**\n\n{summary['voice'] or 'Sem voz padrão'}")
3464
- if st.button("Configurar Narrador/Voz", key=f"edit_voice_{channel_id}", width="stretch"):
3465
- st.session_state[edit_key] = True
3466
- st.rerun()
3467
- with block_cols[3]:
3468
- st.markdown(f"**Idioma**\n\n{channel_language}")
3469
-
3470
3540
  channel_account_ids = list(youtube_account_ids)
3471
3541
  current_channel_account_id = str(channel.get("google_account_id", ""))
3472
3542
  if current_channel_account_id and current_channel_account_id not in channel_account_ids:
@@ -3514,6 +3584,7 @@ def render_channels():
3514
3584
  st.success("Conta Google e DELEGATED_SESSION_ID individual do canal guardados.")
3515
3585
  st.rerun()
3516
3586
 
3587
+ render_channel_video_defaults(channel)
3517
3588
  render_channel_audio_subtitle_defaults(channel)
3518
3589
  render_channel_videos(channel)
3519
3590
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@danhachuel/thunderbolt",
3
- "version": "0.7.54",
3
+ "version": "0.7.55",
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",