fix(cover): 封面生成前校验视频URL可达性,避免MediaKit超时120秒 (#1356)
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m41s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m14s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m42s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m36s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m40s
CI/CD Pipeline / Unit Tests (push) Successful in 10m23s
CI/CD Pipeline / Integration Tests (push) Successful in 4m27s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m30s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m5s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m46s
CI/CD Pipeline / Canary Release to Production (push) Failing after 815h33m54s
CI/CD Pipeline / CI Gate (push) Failing after 815h39m39s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 815h40m23s
CI/CD Pipeline / Build Production Web Image (push) Failing after 815h40m23s
CI/CD Pipeline / Production Browser E2E (push) Failing after 815h40m22s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 815h50m45s
CI/CD Pipeline / PR Build Web Image (push) Failing after 815h50m46s
CI/CD Pipeline / PR Build API Image (push) Failing after 815h50m47s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 815h50m49s
CI/CD Pipeline / Deploy Production (push) Failing after 816h13m54s
CI/CD Pipeline / Build Production API Image (push) Failing after 816h13m55s
CI/CD Pipeline / Frontend Lint (push) Failing after 816h24m19s
CI/CD Pipeline / Frontend Unit Tests (push) Successful in 1m41s
CI/CD Pipeline / Validate - Migration (alembic) (push) Successful in 2m14s
CI/CD Pipeline / Validate - Type Check (mypy) (push) Successful in 2m42s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 2m42s
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 4m36s
CI/CD Pipeline / Validate - Code Quality (push) Successful in 6m40s
CI/CD Pipeline / Unit Tests (push) Successful in 10m23s
CI/CD Pipeline / Integration Tests (push) Successful in 4m27s
CI/CD Pipeline / Build Staging API Image (push) Successful in 12m35s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m30s
CI/CD Pipeline / ACR Image Cleanup (push) Successful in 38s
CI/CD Pipeline / Staging E2E Tests (push) Failing after 1m5s
CI/CD Pipeline / Staging API Integration Tests (push) Successful in 2m46s
CI/CD Pipeline / Canary Release to Production (push) Failing after 815h33m54s
CI/CD Pipeline / CI Gate (push) Failing after 815h39m39s
CI/CD Pipeline / Build Production Worker Image (push) Failing after 815h40m23s
CI/CD Pipeline / Build Production Web Image (push) Failing after 815h40m23s
CI/CD Pipeline / Production Browser E2E (push) Failing after 815h40m22s
CI/CD Pipeline / PR Build Worker Image (push) Failing after 815h50m45s
CI/CD Pipeline / PR Build Web Image (push) Failing after 815h50m46s
CI/CD Pipeline / PR Build API Image (push) Failing after 815h50m47s
CI/CD Pipeline / Check if frontend-only change (push) Failing after 815h50m49s
CI/CD Pipeline / Deploy Production (push) Failing after 816h13m54s
CI/CD Pipeline / Build Production API Image (push) Failing after 816h13m55s
CI/CD Pipeline / Frontend Lint (push) Failing after 816h24m19s
Co-authored-by: xiaoxia <dev@xiaoxiajianji.com> Co-committed-by: xiaoxia <dev@xiaoxiajianji.com>
This commit was merged in pull request #1356.
This commit is contained in:
@@ -13,6 +13,8 @@ import random
|
||||
import time
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
import requests as http_requests
|
||||
|
||||
from packages.domain.config_schemas import DEFAULT_EDIT_PLAN_CONFIG
|
||||
from packages.shared.ai_client import get_doubao_client
|
||||
|
||||
@@ -398,6 +400,26 @@ def _call_ai_cover_service(
|
||||
|
||||
client = get_mediakit_client()
|
||||
if client.is_available:
|
||||
# 先检查视频 URL 是否可访问,避免 MediaKit 下载失败后超时
|
||||
try:
|
||||
head_resp = http_requests.head(primary_video_url, timeout=10, allow_redirects=True)
|
||||
if head_resp.status_code != 200:
|
||||
logger.error(
|
||||
"封面视频URL不可访问: plan_id=%s url=%s status=%d",
|
||||
plan_id,
|
||||
primary_video_url,
|
||||
head_resp.status_code,
|
||||
)
|
||||
raise RuntimeError(
|
||||
f"封面生成失败: 预览视频URL不可访问 (HTTP {head_resp.status_code})。"
|
||||
f"请重新生成预览视频后再试。"
|
||||
)
|
||||
except http_requests.RequestException as e:
|
||||
logger.error("封面视频URL连通性检查失败: plan_id=%s url=%s error=%s", plan_id, primary_video_url, e)
|
||||
raise RuntimeError(
|
||||
f"封面生成失败: 无法访问预览视频 ({e.__class__.__name__})。" f"请重新生成预览视频后再试。"
|
||||
) from e
|
||||
|
||||
try:
|
||||
logger.info("调用 MediaKit 抽帧: plan_id=%s video=%s", plan_id, primary_video_url[:80])
|
||||
frames = client.extract_frames(
|
||||
|
||||
@@ -142,9 +142,13 @@ class TestMediaKitClient:
|
||||
class TestAICoverService:
|
||||
"""AI 封面服务测试."""
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_with_mediakit_success(self, mock_get_client):
|
||||
def test_call_ai_cover_with_mediakit_success(self, mock_get_client, mock_head):
|
||||
"""MediaKit 抽帧成功."""
|
||||
# Mock HEAD request to verify URL is accessible
|
||||
mock_head.return_value.status_code = 200
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_client.extract_frames.return_value = [{"image_url": "https://example.com/frame.jpg", "timestamp": 3.5}]
|
||||
@@ -166,9 +170,34 @@ class TestAICoverService:
|
||||
|
||||
mock_client.extract_frames.assert_called_once()
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_with_mediakit_failure_raises(self, mock_get_client):
|
||||
def test_call_ai_cover_video_url_unreachable(self, mock_get_client, mock_head):
|
||||
"""视频 URL 不可访问时抛出 RuntimeError."""
|
||||
# Mock HEAD request to return 404
|
||||
mock_head.return_value.status_code = 404
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_get_client.return_value = mock_client
|
||||
|
||||
from packages.shared.ai_service import _call_ai_cover_service
|
||||
|
||||
with pytest.raises(RuntimeError, match="预览视频URL不可访问"):
|
||||
_call_ai_cover_service(
|
||||
plan_id="plan-123",
|
||||
asset_ids=["asset-1"],
|
||||
cover_type="ai_frame",
|
||||
primary_video_url="https://example.com/nonexistent.mp4",
|
||||
)
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_with_mediakit_failure_raises(self, mock_get_client, mock_head):
|
||||
"""MediaKit 失败时抛出 RuntimeError(不再降级到 stub)."""
|
||||
# Mock HEAD request to return 200 (URL is accessible, but MediaKit fails)
|
||||
mock_head.return_value.status_code = 200
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_client.extract_frames.side_effect = Exception("API error")
|
||||
@@ -243,9 +272,12 @@ class TestAICoverService:
|
||||
primary_video_url="https://example.com/video.mp4",
|
||||
)
|
||||
|
||||
@patch("packages.shared.ai_service.http_requests.head")
|
||||
@patch("packages.shared.mediakit_client.get_mediakit_client")
|
||||
def test_call_ai_cover_empty_frames_raises(self, mock_get_client):
|
||||
def test_call_ai_cover_empty_frames_raises(self, mock_get_client, mock_head):
|
||||
"""MediaKit 返回空帧列表时抛出 RuntimeError(不再降级)."""
|
||||
mock_head.return_value.status_code = 200 # URL accessible
|
||||
|
||||
mock_client = Mock()
|
||||
mock_client.is_available = True
|
||||
mock_client.extract_frames.return_value = []
|
||||
|
||||
Reference in New Issue
Block a user