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,"
+ ""
+ )
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"]