@danhachuel/thunderbolt 0.7.58 → 0.7.60
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 +1 -1
- package/hermes_ui/domain.py +16 -2
- package/hermes_ui/video_length.py +1 -1
- package/integrations/platforms.py +56 -0
- package/package.json +1 -1
package/app/main.py
CHANGED
|
@@ -1578,7 +1578,7 @@ def render_channel_video_defaults(channel: dict) -> None:
|
|
|
1578
1578
|
source = channel_video_source_value(channel.get("default_video_source") or channel.get("style_wide"))
|
|
1579
1579
|
aspect_ratio = str(channel.get("default_video_aspect_ratio") or channel.get("video_aspect_ratio") or "Landscape 16:9")
|
|
1580
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
|
|
1581
|
+
maximum_clip_duration = int(channel.get("default_maximum_clip_duration") or 5)
|
|
1582
1582
|
videos_per_run = int(channel.get("default_videos_per_run") or 1)
|
|
1583
1583
|
concatenation_mode = str(channel.get("default_video_concatenation_mode") or VIDEO_CONCATENATION_OPTIONS[0])
|
|
1584
1584
|
transition_mode = str(channel.get("default_video_transition_mode") or VIDEO_TRANSITION_OPTIONS[0])
|
package/hermes_ui/domain.py
CHANGED
|
@@ -97,6 +97,12 @@ def create_channel(name: str, url: str = "", metadata: dict[str, Any] | None = N
|
|
|
97
97
|
}
|
|
98
98
|
if metadata:
|
|
99
99
|
channel.update({k: v for k, v in metadata.items() if k in channel and k != "id"})
|
|
100
|
+
if str(channel.get("platform") or "").strip().casefold() == "youtube":
|
|
101
|
+
channel.setdefault("default_maximum_clip_duration", 5)
|
|
102
|
+
channel.setdefault("default_video_aspect_ratio", "Landscape 16:9")
|
|
103
|
+
channel.setdefault("default_enable_subtitles", True)
|
|
104
|
+
channel.setdefault("default_background_music_source", "Sem música")
|
|
105
|
+
channel.setdefault("average_video_time", "20:00")
|
|
100
106
|
channels = read_json("channels.json", [])
|
|
101
107
|
channels.append(channel)
|
|
102
108
|
write_json("channels.json", channels)
|
|
@@ -208,10 +214,15 @@ def create_tasks_for_batch(batch: dict[str, Any]) -> list[dict[str, Any]]:
|
|
|
208
214
|
"default_subtitle_font_size": 60,
|
|
209
215
|
"default_subtitle_outline": "#000000",
|
|
210
216
|
"default_subtitle_outline_width": 1.5,
|
|
211
|
-
|
|
212
|
-
|
|
217
|
+
"default_background_music_source": "Sem música",
|
|
218
|
+
"default_background_music_volume": "20%",
|
|
213
219
|
}.items()
|
|
214
220
|
}
|
|
221
|
+
if str(channel.get("platform") or "").strip().casefold() == "youtube":
|
|
222
|
+
channel_generation_defaults.update({
|
|
223
|
+
"maximum_clip_duration": channel.get("default_maximum_clip_duration", 5),
|
|
224
|
+
"video_aspect_ratio": channel.get("default_video_aspect_ratio", "Landscape 16:9"),
|
|
225
|
+
})
|
|
215
226
|
configured_generation_settings = options.get("generation_settings") if isinstance(options.get("generation_settings"), dict) else {}
|
|
216
227
|
payload_generation_settings = payload.get("generation_settings") if isinstance(payload.get("generation_settings"), dict) else {}
|
|
217
228
|
generation_settings = {**channel_generation_defaults, **configured_generation_settings, **payload_generation_settings}
|
|
@@ -519,6 +530,9 @@ def _refresh_task_channel_video_settings(task: dict[str, Any]) -> None:
|
|
|
519
530
|
}
|
|
520
531
|
for setting_name, (channel_key, default) in channel_defaults.items():
|
|
521
532
|
generation_settings[setting_name] = channel.get(channel_key, default)
|
|
533
|
+
if str(channel.get("platform") or "").strip().casefold() == "youtube":
|
|
534
|
+
generation_settings["maximum_clip_duration"] = channel.get("default_maximum_clip_duration", 5)
|
|
535
|
+
generation_settings["video_aspect_ratio"] = channel.get("default_video_aspect_ratio", "Landscape 16:9")
|
|
522
536
|
task["generation_settings"] = generation_settings
|
|
523
537
|
task["voice"] = channel.get("default_voice") or channel.get("voice") or task.get("voice", "")
|
|
524
538
|
task["thumbnail_blueprint_id"] = (
|
|
@@ -6,7 +6,7 @@ from pathlib import Path
|
|
|
6
6
|
from typing import Any
|
|
7
7
|
|
|
8
8
|
WORDS_PER_MINUTE = 150
|
|
9
|
-
DEFAULT_AVERAGE_VIDEO_TIME = "
|
|
9
|
+
DEFAULT_AVERAGE_VIDEO_TIME = "20:00"
|
|
10
10
|
DEFAULT_TIKTOK_AVERAGE_VIDEO_TIME = "01:20"
|
|
11
11
|
AVERAGE_VIDEO_TIME_KEY = "average_video_time"
|
|
12
12
|
AVERAGE_VIDEO_WORD_COUNT_KEY = "average_video_word_count"
|
|
@@ -248,6 +248,45 @@ def _public_feed_data(channel_id: str, headers: dict[str, str]) -> dict[str, Any
|
|
|
248
248
|
return {}
|
|
249
249
|
|
|
250
250
|
|
|
251
|
+
def _public_page_videos(document: str, channel_id: str, limit: int) -> list[dict[str, Any]]:
|
|
252
|
+
"""Extract public video cards from ytInitialData as an RSS fallback."""
|
|
253
|
+
initial_data = _extract_json_assignment(document, "ytInitialData") or {}
|
|
254
|
+
videos: list[dict[str, Any]] = []
|
|
255
|
+
|
|
256
|
+
def visit(value: Any) -> None:
|
|
257
|
+
if len(videos) >= limit:
|
|
258
|
+
return
|
|
259
|
+
if isinstance(value, dict):
|
|
260
|
+
renderer = value.get("videoRenderer")
|
|
261
|
+
if isinstance(renderer, dict):
|
|
262
|
+
video_id = str(renderer.get("videoId") or "").strip()
|
|
263
|
+
if video_id:
|
|
264
|
+
title = _text_from_node(renderer.get("title")) or "Vídeo sem título"
|
|
265
|
+
thumbnails = ((renderer.get("thumbnail") or {}).get("thumbnails") or [])
|
|
266
|
+
thumbnail = str(thumbnails[-1].get("url", "")) if thumbnails and isinstance(thumbnails[-1], dict) else ""
|
|
267
|
+
videos.append({
|
|
268
|
+
"id": f"youtube_{video_id}",
|
|
269
|
+
"youtube_video_id": video_id,
|
|
270
|
+
"channel_id": channel_id,
|
|
271
|
+
"title": title,
|
|
272
|
+
"published_at": _text_from_node(renderer.get("publishedTimeText")),
|
|
273
|
+
"updated_at": "",
|
|
274
|
+
"url": f"https://www.youtube.com/watch?v={video_id}",
|
|
275
|
+
"thumbnail_url": thumbnail,
|
|
276
|
+
"source": "youtube_public_page",
|
|
277
|
+
"status": "publicado",
|
|
278
|
+
})
|
|
279
|
+
return
|
|
280
|
+
for child in value.values():
|
|
281
|
+
visit(child)
|
|
282
|
+
elif isinstance(value, list):
|
|
283
|
+
for child in value:
|
|
284
|
+
visit(child)
|
|
285
|
+
|
|
286
|
+
visit(initial_data)
|
|
287
|
+
return videos[:limit]
|
|
288
|
+
|
|
289
|
+
|
|
251
290
|
def fetch_channel_videos_public(channel_ref: str | dict[str, Any], limit: int = 10) -> "IntegrationResult":
|
|
252
291
|
"""Fetch the latest public channel videos from YouTube's Atom feed without an API key."""
|
|
253
292
|
if isinstance(channel_ref, dict):
|
|
@@ -281,6 +320,23 @@ def fetch_channel_videos_public(channel_ref: str | dict[str, Any], limit: int =
|
|
|
281
320
|
response.raise_for_status()
|
|
282
321
|
root = ET.fromstring(response.text)
|
|
283
322
|
except (requests.RequestException, ET.ParseError, ValueError) as exc:
|
|
323
|
+
for page_url in _public_page_candidates(f"https://www.youtube.com/channel/{channel_id}"):
|
|
324
|
+
try:
|
|
325
|
+
page_response = requests.get(
|
|
326
|
+
page_url,
|
|
327
|
+
headers={**headers, "Accept": "text/html,application/xhtml+xml"},
|
|
328
|
+
timeout=12,
|
|
329
|
+
)
|
|
330
|
+
page_response.raise_for_status()
|
|
331
|
+
except requests.RequestException:
|
|
332
|
+
continue
|
|
333
|
+
fallback_videos = _public_page_videos(page_response.text or "", channel_id, max(1, min(10, int(limit or 10))))
|
|
334
|
+
if fallback_videos:
|
|
335
|
+
return IntegrationResult(
|
|
336
|
+
True,
|
|
337
|
+
f"{len(fallback_videos)} vídeo(s) público(s) carregado(s) pela página do canal sem API Key.",
|
|
338
|
+
{"channel_id": channel_id, "videos": fallback_videos},
|
|
339
|
+
)
|
|
284
340
|
return IntegrationResult(False, f"Não foi possível carregar os vídeos públicos do canal: {exc}", {"channel_id": channel_id, "videos": []})
|
|
285
341
|
namespace = {
|
|
286
342
|
"yt": "http://www.youtube.com/xml/schemas/2015",
|
package/package.json
CHANGED