diff --git a/apps/api/app/api/routes/ai_avatar_render.py b/apps/api/app/api/routes/ai_avatar_render.py index 988e1e19e..3277ccb45 100644 --- a/apps/api/app/api/routes/ai_avatar_render.py +++ b/apps/api/app/api/routes/ai_avatar_render.py @@ -321,4 +321,4 @@ def finalize_render_job( ) from exc except Exception as exc: logger.error("渲染任务finalize失败: job_id=%s err=%s", job_id, exc, exc_info=True) - raise HTTPException(status_code=500, detail=f"保存到成片库失败: {str(exc)}") + raise HTTPException(status_code=500, detail=f"保存到成片库失败: {str(exc)}") from exc diff --git a/apps/api/app/schemas/lipsync.py b/apps/api/app/schemas/lipsync.py index 48b5aded5..3f0e838f0 100644 --- a/apps/api/app/schemas/lipsync.py +++ b/apps/api/app/schemas/lipsync.py @@ -82,8 +82,9 @@ class CreateLipsyncJobRequest(BaseModel): if not video.startswith(("http://", "https://")): raise ValueError("video_url 必须是 HTTP/HTTPS URL") lower = video.lower().split("?")[0] - if not lower.endswith(".mp4"): - raise ValueError("video_url 仅支持 MP4 格式") + allowed_video_exts = (".mp4", ".mov", ".m4v", ".webm", ".avi", ".mkv", ".3gp") + if not any(lower.endswith(ext) for ext in allowed_video_exts): + raise ValueError("video_url 格式不支持,仅支持: " + ", ".join(allowed_video_exts)) has_audio = bool((self.audio_url or "").strip()) has_tts = bool((self.voice_id or "").strip()) and bool((self.script_text or "").strip()) diff --git a/tests/unit/test_ai_avatar_emotion_tts_lipsync.py b/tests/unit/test_ai_avatar_emotion_tts_lipsync.py index 0da0cb187..22132f59e 100644 --- a/tests/unit/test_ai_avatar_emotion_tts_lipsync.py +++ b/tests/unit/test_ai_avatar_emotion_tts_lipsync.py @@ -262,8 +262,8 @@ def test_smart_cover_selects_best_frame_and_persists(): score_patch.assert_called_once() # 验证使用了增大的轮询参数 call_kwargs = mk.extract_frames.call_args - assert call_kwargs.kwargs.get("poll_interval") == 1.0 or call_kwargs[1].get("poll_interval") == 1.0 - assert call_kwargs.kwargs.get("max_poll_attempts") == 15 or call_kwargs[1].get("max_poll_attempts") == 15 + assert call_kwargs.kwargs.get("poll_interval") == 2.0 or call_kwargs[1].get("poll_interval") == 2.0 + assert call_kwargs.kwargs.get("max_poll_attempts") == 30 or call_kwargs[1].get("max_poll_attempts") == 30 def test_smart_cover_returns_empty_when_mediakit_unavailable(): @@ -334,8 +334,8 @@ def test_extract_frames_uses_extended_poll_params(): cov.select_best_cover_frame("https://other/avatar.mp4", max_frames=3) call_kwargs = mk.extract_frames.call_args - assert call_kwargs.kwargs.get("poll_interval") == 1.0 or call_kwargs[1].get("poll_interval") == 1.0 - assert call_kwargs.kwargs.get("max_poll_attempts") == 15 or call_kwargs[1].get("max_poll_attempts") == 15 + assert call_kwargs.kwargs.get("poll_interval") == 2.0 or call_kwargs[1].get("poll_interval") == 2.0 + assert call_kwargs.kwargs.get("max_poll_attempts") == 30 or call_kwargs[1].get("max_poll_attempts") == 30 assert call_kwargs.kwargs.get("max_retries") == 1 or call_kwargs[1].get("max_retries") == 1 diff --git a/tests/unit/test_lipsync_routes.py b/tests/unit/test_lipsync_routes.py index 5a7fdfd65..9bfbcc735 100644 --- a/tests/unit/test_lipsync_routes.py +++ b/tests/unit/test_lipsync_routes.py @@ -91,16 +91,27 @@ class TestSchemaValidation: assert req.voice_id == "longxiaochun_v3" assert req.script_text == "大家好,欢迎来到直播间" - def test_invalid_video_url_not_mp4(self): + def test_invalid_video_url_unsupported_ext(self): from app.schemas.lipsync import CreateLipsyncJobRequest - with pytest.raises(ValueError, match="MP4"): + # 不支持的扩展名(.txt)应报错 + with pytest.raises(ValueError, match="格式不支持"): CreateLipsyncJobRequest( - video_url="https://example.com/video.mov", + video_url="https://example.com/video.txt", voice_id="longxiaochun_v3", script_text="测试文本", ) + def test_mov_video_url_accepted(self): + from app.schemas.lipsync import CreateLipsyncJobRequest + + # .MOV 是 iPhone 拍摄的常见容器,h264 编码可直接被 MediaKit 处理 + req = CreateLipsyncJobRequest( + video_url="https://example.com/video.mov", + audio_url="https://example.com/audio.mp3", + ) + assert req.video_url.endswith(".mov") + def test_invalid_video_url_empty(self): from app.schemas.lipsync import CreateLipsyncJobRequest