Files
xiaoxia-saas/packages/application/generated_videos.py
T
CI Bot 1f8de776e8 feat: 成片中心后端升级
- 成片列表分页接口(支持按项目/状态/复核状态筛选)
- 封面图自动生成(视频生成后ffmpeg抽首帧+OSS上传)
- 复核状态流转接口(pending_review/approved/rejected)
- 批量下载异步任务(打包zip+OSS上传+状态查询)
- 18个新增单测全绿,20个已有单测保持通过

新增文件:
- apps/api/app/api/routes/videos.py - 成片中心API
- apps/api/app/schemas/video_center.py - Schema定义
- apps/worker/video_processing/thumbnail_generator.py - 缩略图生成
- apps/worker/worker_app/tasks/batch_download.py - 批量下载任务
- tests/unit/test_video_center_backend.py - 单元测试

修改文件:
- packages/ports/generated_video_repository.py - 新增4个接口方法
- packages/adapters/sqlalchemy_impl/generated_video_repository.py - 实现4个方法
- packages/application/generated_videos.py - 新增3个UseCase
- packages/application/__init__.py - 导出新类
- apps/api/app/api/router.py - 注册videos路由
- apps/worker/video_processing/dedup_helpers.py - 集成缩略图生成
- apps/worker/worker_app/celery_app.py - 注册batch_download任务
2026-07-14 09:17:09 +08:00

90 lines
3.4 KiB
Python
Executable File

from __future__ import annotations
from packages.domain import GeneratedVideo
from packages.ports.generated_video_repository import GeneratedVideoRepository
class ListGeneratedVideosUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(self, project_id: str) -> list[GeneratedVideo]:
if not project_id.strip():
raise ValueError("project_id 不能为空")
return self.generated_video_repository.list_by_project(project_id.strip())
class ListGeneratedVideosPaginatedUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(
self,
*,
project_id: str | None = None,
status: str | None = None,
review_status: str | None = None,
page: int = 1,
page_size: int = 20,
) -> tuple[list[GeneratedVideo], int]:
if page < 1:
page = 1
if page_size < 1 or page_size > 100:
page_size = 20
return self.generated_video_repository.list_paginated(
project_id=project_id,
status=status,
review_status=review_status,
page=page,
page_size=page_size,
)
class GetGeneratedVideoUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(self, video_id: str) -> GeneratedVideo | None:
return self.generated_video_repository.get(video_id)
class ListGeneratedVideosByTaskUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(self, generation_task_id: str) -> list[GeneratedVideo]:
if not generation_task_id.strip():
raise ValueError("generation_task_id 不能为空")
return self.generated_video_repository.list_by_generation_task(generation_task_id.strip())
class GetGeneratedVideoDownloadUrlUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(self, video_id: str) -> str | None:
item = self.generated_video_repository.get(video_id)
if item is None:
return None
return item.file_url
class UpdateVideoReviewStatusUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(self, video_id: str, review_status: str) -> GeneratedVideo | None:
if not video_id.strip():
raise ValueError("video_id 不能为空")
if review_status not in ("pending_review", "approved", "rejected"):
raise ValueError(f"无效的 review_status: {review_status}")
return self.generated_video_repository.update_review_status(video_id.strip(), review_status)
class GetVideosByIdsUseCase:
def __init__(self, generated_video_repository: GeneratedVideoRepository):
self.generated_video_repository = generated_video_repository
def execute(self, video_ids: list[str]) -> list[GeneratedVideo]:
return self.generated_video_repository.get_by_ids(video_ids)