From c796ada84fe93286fbd6d26f300d4d32a2952875 Mon Sep 17 00:00:00 2001 From: lk-eternal Date: Sun, 31 Aug 2025 21:13:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0=E8=A7=86=E9=A2=91?= =?UTF-8?q?=E5=88=86=E6=9E=90=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E6=8F=90=E7=A4=BA=E8=AF=8D=E6=A8=A1=E6=9D=BF=EF=BC=8C=E6=8F=90?= =?UTF-8?q?=E5=8D=87=E5=A4=8D=E5=88=BB=E9=A1=B9=E7=9B=AE=E7=9A=84=E7=81=B5?= =?UTF-8?q?=E6=B4=BB=E6=80=A7=E5=92=8C=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + Dockerfile | 4 +- src/api/dependencies.py | 4 +- src/api/routes/projects.py | 8 +++- src/application/schemas/project.py | 1 + .../use_cases/project_use_cases.py | 5 ++- src/domain/services/ai_service.py | 3 +- src/infrastructure/external/gemini_client.py | 39 +++---------------- .../external/openrouter_client.py | 6 +-- 9 files changed, 27 insertions(+), 44 deletions(-) diff --git a/.gitignore b/.gitignore index f1edbe5..687774b 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,4 @@ Thumbs.db # Temporary files *.tmp *.temp +.qiniu_pythonsdk_hostscache.json \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e5a7ba2..e3dfa0f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -43,5 +43,5 @@ EXPOSE 8000 HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \ CMD curl -f http://localhost:8000/docs || exit 1 -# 启动命令 -CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "src"] \ No newline at end of file +# 启动命令 - 使用多worker提升并发性能 +CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--app-dir", "src", "--workers", "4", "--loop", "uvloop"] \ No newline at end of file diff --git a/src/api/dependencies.py b/src/api/dependencies.py index 7e96bff..463c00a 100644 --- a/src/api/dependencies.py +++ b/src/api/dependencies.py @@ -90,8 +90,8 @@ class AIServiceImpl(AIService): async def generate_video(self, frame_image_bytes: bytes, shot_prompt: str): return await self.gemini_client.generate_video(frame_image_bytes, shot_prompt) - async def analyze_video(self, video_url: str): - return await self.gemini_client.analyze_video(video_url) + async def analyze_video(self, video_url: str, prompt_template: str): + return await self.gemini_client.analyze_video(video_url, prompt_template) class StorageServiceImpl(StorageService): diff --git a/src/api/routes/projects.py b/src/api/routes/projects.py index 99294a4..fb59a28 100644 --- a/src/api/routes/projects.py +++ b/src/api/routes/projects.py @@ -434,8 +434,14 @@ async def replicate_from_video( ): """一键复刻:从视频URL生成项目、素材和分镜""" try: + # 视频分析提示词模板不需要特定的占位符变量,因为视频内容直接传给AI模型 + # 这里可以添加其他模板格式验证逻辑,如果需要的话 + # 调用业务逻辑 - result = await project_use_cases.replicate_from_video(request.video_url) + result = await project_use_cases.replicate_from_video( + request.video_url, + request.prompt_template + ) project = result["project"] assets = result["assets"] diff --git a/src/application/schemas/project.py b/src/application/schemas/project.py index 835c0b0..f011024 100644 --- a/src/application/schemas/project.py +++ b/src/application/schemas/project.py @@ -157,6 +157,7 @@ class ComposeVideoResponse(BaseModel): class VideoReplicateRequest(BaseModel): """一键复刻请求模式""" video_url: str = Field(..., description="要复刻的视频URL") + prompt_template: str = Field(..., description="视频分析提示词模板") # 更新引用 diff --git a/src/application/use_cases/project_use_cases.py b/src/application/use_cases/project_use_cases.py index fc65fd6..64d459a 100644 --- a/src/application/use_cases/project_use_cases.py +++ b/src/application/use_cases/project_use_cases.py @@ -590,12 +590,13 @@ class ProjectUseCases: logger.error(f"生成视频失败: {e}") raise ValueError(f"生成视频失败: {e}") - async def replicate_from_video(self, video_url: str) -> Dict[str, Any]: + async def replicate_from_video(self, video_url: str, prompt_template: str) -> Dict[str, Any]: """ 一键复刻:从视频URL生成项目、素材和分镜 Args: video_url: 要复刻的视频URL + prompt_template: 视频分析提示词模板 Returns: 包含project、assets、storyboards的字典 @@ -605,7 +606,7 @@ class ProjectUseCases: # 1. 使用Gemini分析视频内容 logger.info("正在分析视频内容...") - analysis_result = await self.ai_service.analyze_video(video_url) + analysis_result = await self.ai_service.analyze_video(video_url, prompt_template) if not analysis_result: raise ValueError("视频分析失败") diff --git a/src/domain/services/ai_service.py b/src/domain/services/ai_service.py index 88a6457..10f1340 100644 --- a/src/domain/services/ai_service.py +++ b/src/domain/services/ai_service.py @@ -110,12 +110,13 @@ class AIService(ABC): pass @abstractmethod - async def analyze_video(self, video_url: str) -> Optional[Dict[str, Any]]: + async def analyze_video(self, video_url: str, prompt_template: str) -> Optional[Dict[str, Any]]: """ 分析视频内容,提取关键素材帧和分镜关键帧 Args: video_url: 视频URL + prompt_template: 视频分析提示词模板 Returns: 分析结果字典,包含key_assets_frames和key_storyboard_frames diff --git a/src/infrastructure/external/gemini_client.py b/src/infrastructure/external/gemini_client.py index d979ec4..d05e3c6 100644 --- a/src/infrastructure/external/gemini_client.py +++ b/src/infrastructure/external/gemini_client.py @@ -11,6 +11,7 @@ from google.genai import types from ..config import settings from ..utils import safe_json_loads from .key_pool_manager import key_pool_manager +from ..services.template_service import TemplateService from loguru import logger import ssl import urllib3 @@ -217,7 +218,7 @@ class GeminiClient: # 调用Veo-3.0 API生成视频 operation = self._current_client.models.generate_videos( - model="veo-3.0-fast-generate-preview", #veo-3.0-generate-preview + model="veo-3.0-generate-preview", #veo-3.0-fast-generate-preview prompt=video_prompt, image=image_input ) @@ -252,12 +253,13 @@ class GeminiClient: logger.error(f"Veo-3.0视频生成失败: {e}") raise e - async def analyze_video(self, video_url: str) -> Optional[Dict[str, Any]]: + async def analyze_video(self, video_url: str, prompt_template: str) -> Optional[Dict[str, Any]]: """ 分析视频内容,提取关键素材帧和分镜关键帧 Args: video_url: 视频URL + prompt_template: 视频分析提示词模板 Returns: 分析结果字典,包含key_assets_frames和key_storyboard_frames @@ -308,37 +310,8 @@ class GeminiClient: return myfile - # 构建分析提示词 - analysis_prompt = """ - 请仔细分析这个视频,并返回以下JSON格式的结果: - { - "title": "为这个视频生成一个简洁有吸引力的标题,不超过20个字符", - "script": "根据视频内容生成的完整剧本,包含对话、动作、场景描述等", - "key_assets_frames": [ - { - "timestamp": "HH:MM:SS", - "name": "素材名称", - "description": "素材描述", - "tags": ["标签1", "标签2"] - } - ], - "key_storyboard_frames": [ - { - "timestamp": "HH:MM:SS", - "frame_prompt": "该帧画面描述", - "shot_prompt": "该关键帧到下一关键帧之间的剧情描述" - } - ] - } - - 要求: - 1. title: 根据视频主题和内容生成简洁有吸引力的标题,要能概括视频核心内容,不超过20个字符 - 2. script: 根据视频内容生成完整的剧本,包含场景描述、角色对话、动作指导等,要生动详细 - 3. key_assets_frames: 提取3-5个关键素材帧,包含视觉元素如角色、场景、道具、动物等 - 4. key_storyboard_frames: 提取分镜关键帧,约每8秒一帧 - 5. timestamp格式必须是HH:MM:SS - 6. 确保返回的是有效的JSON格式,不要包含其他文字 - """ + # 直接使用提示词模板作为分析提示词 + analysis_prompt = prompt_template myfile = self._execute_with_retry(_upload_and_analyze) diff --git a/src/infrastructure/external/openrouter_client.py b/src/infrastructure/external/openrouter_client.py index 8091f63..69ff05a 100644 --- a/src/infrastructure/external/openrouter_client.py +++ b/src/infrastructure/external/openrouter_client.py @@ -21,7 +21,7 @@ class OpenRouterClient: async def generate_text( self, prompt: str, - model: str = "anthropic/claude-3.5-sonnet" + model: str = "google/gemini-2.5-pro" ) -> Optional[str]: """ 生成文本内容 @@ -66,7 +66,7 @@ class OpenRouterClient: self, prompt_template: str, script_or_idea: str, - model: str = "anthropic/claude-3.5-sonnet" + model: str = "google/gemini-2.5-pro" ) -> Optional[Dict[str, Any]]: """ 生成完整剧本和素材信息 @@ -107,7 +107,7 @@ class OpenRouterClient: text: str, source_lang: str = "zh", target_lang: str = "en", - model: str = "anthropic/claude-3.5-sonnet" + model: str = "google/gemini-2.5-pro" ) -> Optional[str]: """ 翻译文本