feat(phase7): add generated video download endpoint
CI/CD Pipeline / Code Quality Check (push) Failing after 46s
CI/CD Pipeline / Run Tests (push) Has been skipped
CI/CD Pipeline / Build Summary (push) Has been skipped

This commit is contained in:
Xiaoxia AI
2026-06-18 20:33:14 +08:00
parent 938ba71869
commit bd078438c0
5 changed files with 67 additions and 8 deletions
+22 -2
View File
@@ -1,9 +1,17 @@
from fastapi import APIRouter, Depends, HTTPException
from app.dependencies import get_generated_video_repository
from app.schemas.generated_video import GeneratedVideoResponse, ListGeneratedVideosResponse
from app.schemas.generated_video import (
GeneratedVideoDownloadUrlResponse,
GeneratedVideoResponse,
ListGeneratedVideosResponse,
)
from packages.adapters.sqlalchemy_impl import SQLAlchemyGeneratedVideoRepository
from packages.application import GetGeneratedVideoUseCase, ListGeneratedVideosUseCase
from packages.application import (
GetGeneratedVideoDownloadUrlUseCase,
GetGeneratedVideoUseCase,
ListGeneratedVideosUseCase,
)
router = APIRouter()
@@ -59,3 +67,15 @@ def get_generated_video(
height=item.height,
fps=item.fps,
)
@router.get("/{video_id}/download-url", response_model=GeneratedVideoDownloadUrlResponse)
def get_generated_video_download_url(
video_id: str,
generated_video_repository: SQLAlchemyGeneratedVideoRepository = Depends(get_generated_video_repository),
) -> GeneratedVideoDownloadUrlResponse:
use_case = GetGeneratedVideoDownloadUrlUseCase(generated_video_repository)
download_url = use_case.execute(video_id)
if download_url is None:
raise HTTPException(status_code=404, detail=f"GeneratedVideo {video_id} not found")
return GeneratedVideoDownloadUrlResponse(video_id=video_id, download_url=download_url)
+5
View File
@@ -16,5 +16,10 @@ class GeneratedVideoResponse(BaseModel):
fps: float
class GeneratedVideoDownloadUrlResponse(BaseModel):
video_id: str
download_url: str
class ListGeneratedVideosResponse(BaseModel):
items: list[GeneratedVideoResponse]
+7 -5
View File
@@ -2,7 +2,7 @@
**Phase**: Phase 7 - 核心视频剪辑业务
**状态**: 🔄 进行中
**最后更新**: 2026-06-18 20:11 GMT+8
**最后更新**: 2026-06-18 20:31 GMT+8
---
@@ -50,6 +50,7 @@
- [x] GeneratedVideo 主线骨架已落地
- [x] 生成任务创建后自动触发 worker
- [x] 生成结果最小闭环测试已落地
- [x] 生成结果下载地址接口已落地
- [ ] 前端主链路联调完成
### 4. 本轮已完成的具体验证
@@ -58,7 +59,7 @@
- [x] `tests/integration/test_upload_pipeline.py` 通过
- [x] `tests/integration/test_classification_pipeline.py` 通过
- [x] `tests/integration/test_projects.py` 通过
- [x] `tests/integration/test_generation_pipeline.py` 通过(含生成结果最小闭环)
- [x] `tests/integration/test_generation_pipeline.py` 通过(含生成结果最小闭环、下载地址查询
- [x] 素材与生成主线相关目录编译检查通过
---
@@ -97,6 +98,7 @@
- [x] ClassificationJob 与 Asset 元数据更新链路第一轮打通
- [x] GenerationTask / GeneratedVideo 主线骨架已补齐
- [x] API / Adapter / Worker 的生成结果流第一轮联调
- [x] GeneratedVideo 查询 / 下载地址第一轮打通
- [ ] 测试补齐与回归验证
### Step 3:保留 Agent 体系设计,等待 runtime 修复后再恢复实跑
@@ -107,7 +109,7 @@
## 五、当前判断
**当前 Phase 7 已完成素材前半主链打通,并把生成链推进到“最小可运行闭环”,整体仍保持在既定规则内推进。**
**当前 Phase 7 已完成素材前半主链打通,并把生成链推进到“最小可运行闭环 + 结果查询/下载接口可用”,整体仍保持在既定规则内推进。**
当前执行策略是:
- 暂停 Agent 实跑
@@ -125,7 +127,7 @@
- 答:Phase 7 - 核心视频剪辑业务
2. **当前 Phase 主要在做什么?**
- 答:已切入 Phase 7 第一批业务开发,当前已完成素材前半主链收口,并把生成链推进到最小可运行闭环,正在持续提交与 CI/CD 验证
- 答:已切入 Phase 7 第一批业务开发,当前已完成素材前半主链收口,并把生成链推进到最小可运行闭环与结果查询/下载可用,正在持续提交与 CI/CD 验证
3. **当前最重要的阻塞点是什么?**
- 答:OpenClaw 子 Agent runtime 暂不稳定,因此暂停 Agent 实跑;另外数据库字段命名仍有历史包袱,但已通过映射兼容,不阻断主线开发
@@ -152,4 +154,4 @@
---
**状态结论**Phase 7 未跑偏,已暂停 Agent 实跑并切回主会话直开;当前素材前半主链已打通,生成链已进入最小可运行闭环,现继续通过提交与 CI/CD 验证推进。
**状态结论**Phase 7 未跑偏,已暂停 Agent 实跑并切回主会话直开;当前素材前半主链已打通,生成链已进入最小可运行闭环且结果查询/下载可用,现继续通过提交与 CI/CD 验证推进。
+11
View File
@@ -30,3 +30,14 @@ class ListGeneratedVideosByTaskUseCase:
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
+22 -1
View File
@@ -1,6 +1,6 @@
from datetime import datetime, timezone
from packages.application import CreateGenerationTaskCommand, CreateGenerationTaskUseCase
from packages.application import CreateGenerationTaskCommand, CreateGenerationTaskUseCase, GetGeneratedVideoDownloadUrlUseCase
from packages.domain import GeneratedVideo, GenerationTaskStatus
@@ -121,3 +121,24 @@ def test_generation_pipeline_smoke():
assert len(videos) == 1
assert videos[0].project_id == "proj-1"
assert videos[0].generation_task_id == task.id
def test_get_generated_video_download_url():
video_repo = DummyGeneratedVideoRepository()
video = GeneratedVideo.create(
workspace_id="ws-1",
project_id="proj-1",
generation_task_id="task-1",
name="task-1.mp4",
file_url="https://example.invalid/generated/task-1.mp4",
file_size=2048,
duration=12.5,
width=1920,
height=1080,
fps=25.0,
)
video_repo.create(video)
use_case = GetGeneratedVideoDownloadUrlUseCase(video_repo)
assert use_case.execute(video.id) == "https://example.invalid/generated/task-1.mp4"
assert use_case.execute("missing") is None