diff --git a/packages/shared/ai_service.py b/packages/shared/ai_service.py index 32014d23d..c4a02a0fa 100755 --- a/packages/shared/ai_service.py +++ b/packages/shared/ai_service.py @@ -323,9 +323,16 @@ def _call_ai_cover_service( } if cover_type == "manual" and frame_time is not None: + svg_placeholder = ( + "data:image/svg+xml," + "" + "" + "手动选帧" + "" + ) return { "type": "manual", - "image_url": f"/api/v1/assets/placeholder/cover?time={frame_time}", + "image_url": svg_placeholder, "frame_time": frame_time, } @@ -368,12 +375,20 @@ def _call_ai_cover_service( except Exception as e: logger.exception("MediaKit 抽帧失败,降级到 stub: %s", str(e)) - # 降级:stub 行为 + # 降级:stub 行为 - 返回 SVG data URI 占位图 logger.info("使用 stub 封面: plan_id=%s", plan_id) time.sleep(0.3) + svg_placeholder = ( + "data:image/svg+xml," + "" + "" + "封面生成中" + "请配置 MediaKit API Key" + "" + ) return { "type": "ai_frame", - "image_url": f"/api/v1/assets/placeholder/cover?plan={plan_id}", + "image_url": svg_placeholder, "frame_time": round(random.uniform(1.0, 10.0), 1), "confidence": round(random.uniform(0.80, 0.98), 2), } diff --git a/tests/unit/test_mediakit_cover.py b/tests/unit/test_mediakit_cover.py index 49864e136..22193e67e 100755 --- a/tests/unit/test_mediakit_cover.py +++ b/tests/unit/test_mediakit_cover.py @@ -185,7 +185,7 @@ class TestAICoverService: # 应该降级到 stub assert result["type"] == "ai_frame" - assert "placeholder" in result["image_url"] + assert result["image_url"].startswith("data:image/svg+xml,") def test_call_ai_cover_without_video_url_fallback(self): """没有视频 URL 时使用 stub.""" @@ -199,7 +199,7 @@ class TestAICoverService: ) assert result["type"] == "ai_frame" - assert "placeholder" in result["image_url"] + assert result["image_url"].startswith("data:image/svg+xml,") def test_call_ai_cover_upload_type(self): """upload 类型直接返回.""" @@ -247,7 +247,7 @@ class TestAICoverService: ) assert result["type"] == "ai_frame" - assert "placeholder" in result["image_url"] + assert result["image_url"].startswith("data:image/svg+xml,") @patch("packages.shared.mediakit_client.get_mediakit_client") def test_call_ai_cover_empty_frames_fallback(self, mock_get_client): @@ -267,7 +267,7 @@ class TestAICoverService: ) assert result["type"] == "ai_frame" - assert "placeholder" in result["image_url"] + assert result["image_url"].startswith("data:image/svg+xml,") class TestGenerateCover: diff --git a/tests/unit/test_shared_ai_service.py b/tests/unit/test_shared_ai_service.py index f46002dbf..7603a90bb 100755 --- a/tests/unit/test_shared_ai_service.py +++ b/tests/unit/test_shared_ai_service.py @@ -431,7 +431,8 @@ class TestAiCoverService: result = _call_ai_cover_service("plan1", ["a1"], "manual", frame_time=5.5) assert result["type"] == "manual" assert result["frame_time"] == 5.5 - assert "5.5" in result["image_url"] + assert result["image_url"].startswith("data:image/svg+xml,") + assert "手动选帧" in result["image_url"] def test_cover_type_ai_frame(self): with patch("shared.ai_service.time.sleep"): @@ -440,7 +441,8 @@ class TestAiCoverService: assert result["type"] == "ai_frame" assert result["frame_time"] == 5.0 assert result["confidence"] == 0.9 - assert "plan1" in result["image_url"] + assert result["image_url"].startswith("data:image/svg+xml,") + assert "封面生成中" in result["image_url"] def test_cover_type_ai_regenerate(self): with patch("shared.ai_service.time.sleep"): @@ -451,3 +453,20 @@ class TestAiCoverService: with patch("shared.ai_service.time.sleep"): result = _call_ai_cover_service("plan1", ["a1"], "ai_frame") assert 1.0 <= result["frame_time"] <= 10.0 + + def test_stub_returns_svg_data_uri(self): + """stub 降级返回 SVG data URI,不含任何后端 API 路径.""" + with patch("shared.ai_service.time.sleep"): + result = _call_ai_cover_service("plan1", ["a1"], "ai_frame") + assert result["image_url"].startswith("data:image/svg+xml,") + assert "/api/v1/" not in result["image_url"] + assert "1080" in result["image_url"] + assert "1920" in result["image_url"] + + def test_manual_stub_returns_svg_data_uri(self): + """manual 模式返回 SVG data URI 占位图.""" + with patch("shared.ai_service.time.sleep"): + result = _call_ai_cover_service("plan1", ["a1"], "manual", frame_time=3.0) + assert result["image_url"].startswith("data:image/svg+xml,") + assert "/api/v1/" not in result["image_url"] + assert "手动选帧" in result["image_url"]