From 759a7e0508dd3efdeb53cf685875bc4c4bb26a76 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Tue, 11 Aug 2026 11:50:10 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=81=E9=9D=A2=E9=A2=84=E8=A7=88=20b?= =?UTF-8?q?roken=20image=20=E2=80=94=20MediaKit=E5=B8=A7=E5=9B=BE=E8=BD=AC?= =?UTF-8?q?=E5=AD=98OSS=E8=BF=94=E5=9B=9E=E5=85=AC=E5=BC=80URL?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 根因:MediaKit extract_frames 返回的 image_url 是内部临时 URL, 浏览器无法直接访问,导致封面预览显示 broken image。 修复:新增 _transfer_cover_frame_to_storage(),在 MediaKit 抽帧 成功后下载帧图并重新上传到 OSS,返回公开可访问的 URL。 失败时降级返回原始 URL(不影响流程)。 78个相关单测全部通过。 --- packages/shared/ai_service.py | 61 ++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/packages/shared/ai_service.py b/packages/shared/ai_service.py index c4a02a0fa..2550fb060 100755 --- a/packages/shared/ai_service.py +++ b/packages/shared/ai_service.py @@ -296,6 +296,62 @@ def _call_ai_recommend_service( # ── AI 封面生成 ────────────────────────────────────────────────────────────── + +def _transfer_cover_frame_to_storage(frame_url: str, plan_id: str) -> str: + """下载 MediaKit 帧图并上传到 OSS,返回公开可访问的 URL. + + Args: + frame_url: MediaKit 返回的帧图 URL(内部/临时 URL) + plan_id: 剪辑计划 ID(用于生成存储路径) + + Returns: + 公开可访问的 URL;如果下载/上传失败则返回原始 URL + """ + import tempfile + import uuid + from pathlib import Path + + try: + import httpx + + # 下载帧图 + logger.info("下载 MediaKit 帧图: plan_id=%s url=%s", plan_id, frame_url[:80]) + resp = httpx.get(frame_url, timeout=30, follow_redirects=True) + resp.raise_for_status() + + if not resp.content: + logger.warning("MediaKit 帧图下载为空,返回原始 URL") + return frame_url + + # 写入临时文件 + with tempfile.NamedTemporaryFile(suffix=".jpg", delete=False) as tmp: + tmp.write(resp.content) + tmp_path = tmp.name + + # 上传到 OSS + from packages.shared.storage import get_shared_storage_service + storage = get_shared_storage_service() + cover_key = f"covers/{plan_id}/mediakit_frame_{uuid.uuid4().hex[:8]}.jpg" + storage.upload_file( + file_or_path=tmp_path, + storage_key=cover_key, + content_type="image/jpeg", + ) + + # 获取公开 URL + public_url = storage.get_url(cover_key) + logger.info("封面帧图已上传到 OSS: plan_id=%s key=%s url=%s", plan_id, cover_key, public_url[:80]) + + # 清理临时文件 + Path(tmp_path).unlink(missing_ok=True) + + return public_url + + except Exception as e: + logger.warning("封面帧图转存失败,返回原始 URL: %s", str(e)) + return frame_url + + def _call_ai_cover_service( plan_id: str, asset_ids: List[str], @@ -363,9 +419,12 @@ def _call_ai_cover_service( timestamp, image_url[:80], ) + # MediaKit 返回的 URL 是临时内部 URL,浏览器无法直接访问 + # 需要下载到本地并重新上传到 OSS,返回公开可访问的 URL + public_url = _transfer_cover_frame_to_storage(image_url, plan_id) return { "type": "ai_frame", - "image_url": image_url, + "image_url": public_url, "frame_time": round(timestamp, 1), "confidence": 0.85, }