@danhachuel/thunderbolt 0.7.53 → 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.
- package/app/main.py +146 -40
- 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)
|
|
@@ -3326,16 +3404,33 @@ def render_channels():
|
|
|
3326
3404
|
channels = [channel for channel in read_json("channels.json", []) if is_youtube_channel_record(channel)]
|
|
3327
3405
|
st.divider()
|
|
3328
3406
|
st.subheader(f"Canais Youtube cadastrados ({len(channels)})")
|
|
3329
|
-
|
|
3330
|
-
|
|
3331
|
-
|
|
3332
|
-
|
|
3333
|
-
|
|
3407
|
+
filter_cols = st.columns([1, 1], gap="small")
|
|
3408
|
+
with filter_cols[0]:
|
|
3409
|
+
youtube_channel_search = st.text_input(
|
|
3410
|
+
"Pesquisar nomes dos canais YouTube",
|
|
3411
|
+
key="youtube_registered_channels_search",
|
|
3412
|
+
placeholder="Digite o nome do canal",
|
|
3413
|
+
)
|
|
3414
|
+
with filter_cols[1]:
|
|
3415
|
+
youtube_country_options = [""] + list(COUNTRY_OPTIONS)
|
|
3416
|
+
youtube_country_filter = st.selectbox(
|
|
3417
|
+
"Filtrar por País",
|
|
3418
|
+
youtube_country_options,
|
|
3419
|
+
format_func=lambda value: value or "Todos os países",
|
|
3420
|
+
key="youtube_registered_country_filter",
|
|
3421
|
+
)
|
|
3422
|
+
if st.button("Filtrar", type="primary", width="content", key="youtube_registered_country_filter_apply"):
|
|
3423
|
+
st.session_state["youtube_registered_country_filter_applied"] = youtube_country_filter
|
|
3424
|
+
applied_country = str(st.session_state.get("youtube_registered_country_filter_applied") or "").strip()
|
|
3334
3425
|
visible_registered_channels = [
|
|
3335
3426
|
channel for channel in channels
|
|
3336
3427
|
if not youtube_channel_search.strip()
|
|
3337
3428
|
or youtube_channel_search.strip().casefold() in str(channel.get("name") or "").casefold()
|
|
3338
3429
|
]
|
|
3430
|
+
visible_registered_channels = [
|
|
3431
|
+
channel for channel in visible_registered_channels
|
|
3432
|
+
if not applied_country or str(channel.get("country") or "").strip() == applied_country
|
|
3433
|
+
]
|
|
3339
3434
|
if not channels:
|
|
3340
3435
|
st.info("Nenhum canal YouTube cadastrado.")
|
|
3341
3436
|
return
|
|
@@ -3392,17 +3487,34 @@ def render_channels():
|
|
|
3392
3487
|
delete_key = f"delete_pending_{channel_id}"
|
|
3393
3488
|
with st.expander("Detalhes e configuração do canal", expanded=False):
|
|
3394
3489
|
calculated_time, calculated_source, calculated_words = _channel_average_video_time(channel)
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
if
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
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
|
+
|
|
3403
3515
|
action_cols = st.columns(2)
|
|
3404
3516
|
with action_cols[0]:
|
|
3405
|
-
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"):
|
|
3406
3518
|
st.session_state[edit_key] = True
|
|
3407
3519
|
st.rerun()
|
|
3408
3520
|
with action_cols[1]:
|
|
@@ -3425,31 +3537,6 @@ def render_channels():
|
|
|
3425
3537
|
if st.button("Cancelar", key=f"cancel_delete_{channel_id}"):
|
|
3426
3538
|
st.session_state.pop(delete_key, None)
|
|
3427
3539
|
st.rerun()
|
|
3428
|
-
if st.session_state.get(edit_key):
|
|
3429
|
-
render_channel_edit_form(channel, youtube_account_ids, youtube_account_labels, youtube_accounts_by_id)
|
|
3430
|
-
else:
|
|
3431
|
-
summary = channel_blueprint_summary(channel)
|
|
3432
|
-
render_channel_thumbnail_blueprint_panel(channel, compact=True)
|
|
3433
|
-
channel_language = language_label(channel.get("language") or "pt")
|
|
3434
|
-
block_cols = st.columns(4, gap="small")
|
|
3435
|
-
with block_cols[0]:
|
|
3436
|
-
st.markdown(f"**Blueprint Padrão**\n\n{summary['name']}")
|
|
3437
|
-
if st.button("Editar Blueprint", key=f"edit_prompts_{channel_id}", width="stretch"):
|
|
3438
|
-
st.session_state[edit_key] = True
|
|
3439
|
-
st.rerun()
|
|
3440
|
-
with block_cols[1]:
|
|
3441
|
-
st.markdown(f"**Nicho**\n\n{channel_niche_label(channel)}")
|
|
3442
|
-
if st.button("Editar Nicho", key=f"edit_niche_{channel_id}", width="stretch"):
|
|
3443
|
-
st.session_state[edit_key] = True
|
|
3444
|
-
st.rerun()
|
|
3445
|
-
with block_cols[2]:
|
|
3446
|
-
st.markdown(f"**Narrador/Voz Padrão**\n\n{summary['voice'] or 'Sem voz padrão'}")
|
|
3447
|
-
if st.button("Configurar Narrador/Voz", key=f"edit_voice_{channel_id}", width="stretch"):
|
|
3448
|
-
st.session_state[edit_key] = True
|
|
3449
|
-
st.rerun()
|
|
3450
|
-
with block_cols[3]:
|
|
3451
|
-
st.markdown(f"**Idioma**\n\n{channel_language}")
|
|
3452
|
-
|
|
3453
3540
|
channel_account_ids = list(youtube_account_ids)
|
|
3454
3541
|
current_channel_account_id = str(channel.get("google_account_id", ""))
|
|
3455
3542
|
if current_channel_account_id and current_channel_account_id not in channel_account_ids:
|
|
@@ -3497,6 +3584,7 @@ def render_channels():
|
|
|
3497
3584
|
st.success("Conta Google e DELEGATED_SESSION_ID individual do canal guardados.")
|
|
3498
3585
|
st.rerun()
|
|
3499
3586
|
|
|
3587
|
+
render_channel_video_defaults(channel)
|
|
3500
3588
|
render_channel_audio_subtitle_defaults(channel)
|
|
3501
3589
|
render_channel_videos(channel)
|
|
3502
3590
|
|
|
@@ -6480,7 +6568,24 @@ def render_automation():
|
|
|
6480
6568
|
if not channels:
|
|
6481
6569
|
st.info("Nenhum canal cadastrado para configurar.")
|
|
6482
6570
|
with st.expander("Canais cadastrados", expanded=False):
|
|
6483
|
-
|
|
6571
|
+
automation_country_options = [""] + list(COUNTRY_OPTIONS)
|
|
6572
|
+
automation_filter_cols = st.columns([1, 1], gap="small")
|
|
6573
|
+
with automation_filter_cols[0]:
|
|
6574
|
+
automation_country_filter = st.selectbox(
|
|
6575
|
+
"Filtrar por País",
|
|
6576
|
+
automation_country_options,
|
|
6577
|
+
format_func=lambda value: value or "Todos os países",
|
|
6578
|
+
key="youtube_automation_country_filter",
|
|
6579
|
+
)
|
|
6580
|
+
with automation_filter_cols[1]:
|
|
6581
|
+
if st.button("Filtrar", type="primary", width="content", key="youtube_automation_country_filter_apply"):
|
|
6582
|
+
st.session_state["youtube_automation_country_filter_applied"] = automation_country_filter
|
|
6583
|
+
applied_automation_country = str(st.session_state.get("youtube_automation_country_filter_applied") or "").strip()
|
|
6584
|
+
filtered_automation_channels = [
|
|
6585
|
+
channel for channel in channels
|
|
6586
|
+
if not applied_automation_country or str(channel.get("country") or "").strip() == applied_automation_country
|
|
6587
|
+
]
|
|
6588
|
+
for channel in filtered_automation_channels:
|
|
6484
6589
|
channel_id = channel["id"]
|
|
6485
6590
|
with st.container(border=True):
|
|
6486
6591
|
blueprint_ids, blueprint_labels, current_blueprint, voice_options, current_voice = channel_default_options(channel)
|
|
@@ -6497,6 +6602,7 @@ def render_automation():
|
|
|
6497
6602
|
with header_cols[1]:
|
|
6498
6603
|
st.write(f"**{channel.get('name', 'Sem nome')}**")
|
|
6499
6604
|
st.caption(channel.get("handle") or channel.get("url") or "sem URL")
|
|
6605
|
+
st.caption(f"País: {channel.get('country') or 'Não definido'}")
|
|
6500
6606
|
channel_url = str(channel.get("url") or "").strip()
|
|
6501
6607
|
if not channel_url:
|
|
6502
6608
|
channel_id_or_handle = str(channel.get("youtube_channel_id") or channel.get("youtube_id") or channel.get("handle") or "").strip()
|
package/package.json
CHANGED