|
@@ -5,6 +5,7 @@ YouTube 平台实现
|
|
|
"""
|
|
"""
|
|
|
|
|
|
|
|
import json
|
|
import json
|
|
|
|
|
+import logging
|
|
|
import re
|
|
import re
|
|
|
import time
|
|
import time
|
|
|
from typing import Any, Dict, List, Optional
|
|
from typing import Any, Dict, List, Optional
|
|
@@ -13,11 +14,13 @@ import httpx
|
|
|
|
|
|
|
|
from agent.tools.models import ToolResult
|
|
from agent.tools.models import ToolResult
|
|
|
from agent.tools.builtin.content.quality import get_post_evaluator
|
|
from agent.tools.builtin.content.quality import get_post_evaluator
|
|
|
-from agent.tools.utils.image import build_image_grid, encode_base64, load_images
|
|
|
|
|
|
|
+from agent.tools.builtin.content.collage import render_image_collage
|
|
|
from agent.tools.builtin.content.registry import (
|
|
from agent.tools.builtin.content.registry import (
|
|
|
PlatformDef, ParamSpec, register_platform,
|
|
PlatformDef, ParamSpec, register_platform,
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+logger = logging.getLogger(__name__)
|
|
|
|
|
+
|
|
|
CRAWLER_BASE_URL = "http://crawler.aiddit.com/crawler"
|
|
CRAWLER_BASE_URL = "http://crawler.aiddit.com/crawler"
|
|
|
DEFAULT_TIMEOUT = 60.0
|
|
DEFAULT_TIMEOUT = 60.0
|
|
|
|
|
|
|
@@ -198,8 +201,8 @@ async def search(
|
|
|
}
|
|
}
|
|
|
video["_quality_score"] = eval_res["total_score"]
|
|
video["_quality_score"] = eval_res["total_score"]
|
|
|
video["_quality_grade"] = eval_res["grade"]
|
|
video["_quality_grade"] = eval_res["grade"]
|
|
|
- except Exception:
|
|
|
|
|
- pass
|
|
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ logger.debug("YouTube quality evaluation failed: %s", exc)
|
|
|
|
|
|
|
|
summary_item = {
|
|
summary_item = {
|
|
|
"index": idx,
|
|
"index": idx,
|
|
@@ -282,8 +285,8 @@ async def detail(post: Dict[str, Any], extras: Optional[Dict[str, Any]] = None)
|
|
|
inner2 = inner.get("data", {})
|
|
inner2 = inner.get("data", {})
|
|
|
if isinstance(inner2, dict):
|
|
if isinstance(inner2, dict):
|
|
|
captions_text = inner2.get("content")
|
|
captions_text = inner2.get("content")
|
|
|
- except Exception:
|
|
|
|
|
- pass
|
|
|
|
|
|
|
+ except Exception as exc:
|
|
|
|
|
+ logger.debug("YouTube caption retrieval failed: %s", exc)
|
|
|
|
|
|
|
|
# ── 3) 视频文件下载(用户显式 extras.download_video=True 时才跑) ──
|
|
# ── 3) 视频文件下载(用户显式 extras.download_video=True 时才跑) ──
|
|
|
video_path = None
|
|
video_path = None
|
|
@@ -375,7 +378,7 @@ async def detail(post: Dict[str, Any], extras: Optional[Dict[str, Any]] = None)
|
|
|
if transcript_text and transcript_text != captions_text:
|
|
if transcript_text and transcript_text != captions_text:
|
|
|
memory_parts.append("transcript")
|
|
memory_parts.append("transcript")
|
|
|
if detail_error:
|
|
if detail_error:
|
|
|
- memory_parts.append(f"degraded(detail backend down)")
|
|
|
|
|
|
|
+ memory_parts.append("degraded(detail backend down)")
|
|
|
memory_extra = f" with {'+'.join(memory_parts)}" if memory_parts else ""
|
|
memory_extra = f" with {'+'.join(memory_parts)}" if memory_parts else ""
|
|
|
|
|
|
|
|
title = video_info.get("title") or post.get("title") or content_id
|
|
title = video_info.get("title") or post.get("title") or content_id
|
|
@@ -391,56 +394,22 @@ async def detail(post: Dict[str, Any], extras: Optional[Dict[str, Any]] = None)
|
|
|
async def _build_video_collage(videos: List[Dict[str, Any]]) -> Optional[str]:
|
|
async def _build_video_collage(videos: List[Dict[str, Any]]) -> Optional[str]:
|
|
|
urls, titles = [], []
|
|
urls, titles = [], []
|
|
|
for video in videos:
|
|
for video in videos:
|
|
|
- thumb = None
|
|
|
|
|
- if "thumbnails" in video and isinstance(video["thumbnails"], list) and video["thumbnails"]:
|
|
|
|
|
- thumb = video["thumbnails"][0].get("url")
|
|
|
|
|
- elif "thumbnail" in video:
|
|
|
|
|
- thumb = video.get("thumbnail")
|
|
|
|
|
- elif "cover_url" in video:
|
|
|
|
|
- thumb = video.get("cover_url")
|
|
|
|
|
-
|
|
|
|
|
- if thumb:
|
|
|
|
|
- urls.append(thumb)
|
|
|
|
|
- base_title = video.get("title", "")
|
|
|
|
|
|
|
+ thumbnail = None
|
|
|
|
|
+ if isinstance(video.get("thumbnails"), list) and video["thumbnails"]:
|
|
|
|
|
+ thumbnail = video["thumbnails"][0].get("url")
|
|
|
|
|
+ thumbnail = thumbnail or video.get("thumbnail") or video.get("cover_url")
|
|
|
|
|
+ if thumbnail:
|
|
|
|
|
+ urls.append(thumbnail)
|
|
|
|
|
+ title = video.get("title", "")
|
|
|
score = video.get("_quality_score")
|
|
score = video.get("_quality_score")
|
|
|
- if score is not None:
|
|
|
|
|
- title_with_score = f"[{score}分] {base_title}"
|
|
|
|
|
- else:
|
|
|
|
|
- title_with_score = base_title
|
|
|
|
|
- titles.append(title_with_score)
|
|
|
|
|
-
|
|
|
|
|
- if not urls:
|
|
|
|
|
- return None
|
|
|
|
|
|
|
+ titles.append(f"[{score}分] {title}" if score is not None else title)
|
|
|
|
|
|
|
|
- loaded = await load_images(urls)
|
|
|
|
|
- valid_images, valid_labels = [], []
|
|
|
|
|
- for (_, img), title in zip(loaded, titles):
|
|
|
|
|
- if img is not None:
|
|
|
|
|
- valid_images.append(img)
|
|
|
|
|
- valid_labels.append(title)
|
|
|
|
|
-
|
|
|
|
|
- if not valid_images:
|
|
|
|
|
- return None
|
|
|
|
|
-
|
|
|
|
|
- grid = build_image_grid(images=valid_images, labels=valid_labels)
|
|
|
|
|
- import io
|
|
|
|
|
- buf = io.BytesIO()
|
|
|
|
|
- grid.save(buf, format="PNG")
|
|
|
|
|
- img_bytes = buf.getvalue()
|
|
|
|
|
-
|
|
|
|
|
- try:
|
|
|
|
|
- from agent.tools.builtin.file.image_cdn import _upload_bytes_to_oss
|
|
|
|
|
- import hashlib
|
|
|
|
|
-
|
|
|
|
|
- md5_hash = hashlib.md5(img_bytes).hexdigest()[:12]
|
|
|
|
|
- filename = f"youtube_collage_{md5_hash}.png"
|
|
|
|
|
- cdn_url = await _upload_bytes_to_oss(img_bytes, filename)
|
|
|
|
|
- return {"type": "url", "url": cdn_url}
|
|
|
|
|
- except Exception as e:
|
|
|
|
|
- import logging
|
|
|
|
|
- logging.getLogger(__name__).warning("Failed to upload youtube collage to CDN: %s", e)
|
|
|
|
|
- b64, _ = encode_base64(grid, format="PNG")
|
|
|
|
|
- return {"type": "base64", "media_type": "image/png", "data": b64}
|
|
|
|
|
|
|
+ return await render_image_collage(
|
|
|
|
|
+ urls,
|
|
|
|
|
+ labels=titles,
|
|
|
|
|
+ filename_prefix="youtube_collage",
|
|
|
|
|
+ logger=logger,
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
|
|
|
|
|
# ── 注册 ──
|
|
# ── 注册 ──
|