영상 생성시 이미지 url 전송 -> task_id로 직접 검색으로 변경

insta
bluebamus 2025-12-29 12:15:44 +09:00
parent c6d9edbb42
commit 95d90dcb50
2 changed files with 66 additions and 112 deletions

View File

@ -14,7 +14,9 @@ Video API Router
app.include_router(router, prefix="/api/v1")
"""
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from typing import Literal
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query
from sqlalchemy import func, select
from sqlalchemy.ext.asyncio import AsyncSession
@ -23,13 +25,12 @@ from app.dependencies.pagination import (
PaginationParams,
get_pagination_params,
)
from app.home.models import Project
from app.home.models import Image, Project
from app.lyric.models import Lyric
from app.song.models import Song
from app.video.models import Video
from app.video.schemas.video_schema import (
DownloadVideoResponse,
GenerateVideoRequest,
GenerateVideoResponse,
PollingVideoResponse,
VideoListItem,
@ -43,23 +44,23 @@ from app.utils.pagination import PaginatedResponse
router = APIRouter(prefix="/video", tags=["video"])
@router.post(
@router.get(
"/generate/{task_id}",
summary="영상 생성 요청",
description="""
Creatomate API를 통해 영상 생성을 요청합니다.
## 경로 파라미터
- **task_id**: Project/Lyric/Songtask_id (필수) - 연관된 프로젝트, 가사, 노래조회하는 사용
- **task_id**: Project/Lyric/Song/Imagetask_id (필수) - 연관된 프로젝트, 가사, 노래, 이미지조회하는 사용
## 요청 필드
## 쿼리 파라미터
- **orientation**: 영상 방향 (horizontal: 가로형, vertical: 세로형, 기본값: vertical) - 선택
- **image_urls**: 영상에 사용할 이미지 URL 목록 (필수)
## 자동 조회 정보 (Song 테이블에서 task_id 기준 가장 최근 생성된 노래 사용)
- **music_url**: song_result_url 사용
- **duration**: 노래의 duration 사용
- **lyrics**: song_prompt (가사) 사용
## 자동 조회 정보
- **image_urls**: Image 테이블에서 task_id로 조회 (img_order 순서로 정렬)
- **music_url**: Song 테이블의 song_result_url 사용
- **duration**: Song 테이블의 duration 사용
- **lyrics**: Song 테이블의 song_prompt (가사) 사용
## 반환 정보
- **success**: 요청 성공 여부
@ -69,33 +70,12 @@ Creatomate API를 통해 영상 생성을 요청합니다.
## 사용 예시
```
POST /video/generate/019123ab-cdef-7890-abcd-ef1234567890
{
"image_urls": [
"https://naverbooking-phinf.pstatic.net/20240514_189/1715688030436xT14o_JPEG/1.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_48/1715688030574wTtQd_JPEG/2.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_92/17156880307484bvpH_JPEG/3.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_7/1715688031000y8Y5q_JPEG/4.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_259/17156880311809wCnY_JPEG/5.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_64/1715688031601oGNsV_JPEG/6.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_175/1715688031657oXc7l_JPEG/7.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_192/1715688031798MbFDj_JPEG/8.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_205/17156880318681JLwX_JPEG/9.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_142/1715688031946hhxHz_JPEG/10.jpg"
]
}
```
## 가로형 영상 생성 예시
```
POST /video/generate/019123ab-cdef-7890-abcd-ef1234567890
{
"orientation": "horizontal",
"image_urls": [...]
}
GET /video/generate/0694b716-dbff-7219-8000-d08cb5fce431
GET /video/generate/0694b716-dbff-7219-8000-d08cb5fce431?orientation=horizontal
```
## 참고
- 이미지는 task_id로 Image 테이블에서 자동 조회됩니다 (img_order 순서).
- 배경 음악(music_url), 영상 길이(duration), 가사(lyrics) task_id로 Song 테이블을 조회하여 자동으로 가져옵니다.
- 같은 task_id로 여러 Song이 있을 경우 **가장 최근 생성된 노래** 사용합니다.
- Song의 song_result_url과 song_prompt가 있어야 영상 생성이 가능합니다.
@ -105,24 +85,27 @@ POST /video/generate/019123ab-cdef-7890-abcd-ef1234567890
response_model=GenerateVideoResponse,
responses={
200: {"description": "영상 생성 요청 성공"},
400: {"description": "Song의 음악 URL 또는 가사(song_prompt)가 없음"},
404: {"description": "Project, Lyric 또는 Song을 찾을 수 없음"},
400: {"description": "Song의 음악 URL, 가사(song_prompt) 또는 이미지가 없음"},
404: {"description": "Project, Lyric, Song 또는 Image를 찾을 수 없음"},
500: {"description": "영상 생성 요청 실패"},
},
)
async def generate_video(
task_id: str,
request_body: GenerateVideoRequest,
orientation: Literal["horizontal", "vertical"] = Query(
default="vertical",
description="영상 방향 (horizontal: 가로형, vertical: 세로형)",
),
session: AsyncSession = Depends(get_session),
) -> GenerateVideoResponse:
"""Creatomate API를 통해 영상을 생성합니다.
1. task_id로 Project, Lyric, Song 조회
1. task_id로 Project, Lyric, Song, Image 조회
2. Video 테이블에 초기 데이터 저장 (status: processing)
3. Creatomate API 호출 (orientation에 따른 템플릿 자동 선택)
4. creatomate_render_id 업데이트 응답 반환
"""
print(f"[generate_video] START - task_id: {task_id}, orientation: {request_body.orientation}")
print(f"[generate_video] START - task_id: {task_id}, orientation: {orientation}")
try:
# 1. task_id로 Project 조회
project_result = await session.execute(
@ -189,7 +172,25 @@ async def generate_video(
print(f"[generate_video] Song found - song_id: {song.id}, task_id: {task_id}, duration: {song.duration}")
print(f"[generate_video] Music URL (from DB): {music_url}, Song duration: {song.duration}, Lyrics length: {len(lyrics)}")
# 4. Video 테이블에 초기 데이터 저장
# 4. task_id로 Image 조회 (img_order 순서로 정렬)
image_result = await session.execute(
select(Image)
.where(Image.task_id == task_id)
.order_by(Image.img_order.asc())
)
images = image_result.scalars().all()
if not images:
print(f"[generate_video] Image NOT FOUND - task_id: {task_id}")
raise HTTPException(
status_code=404,
detail=f"task_id '{task_id}'에 해당하는 이미지를 찾을 수 없습니다.",
)
image_urls = [img.img_url for img in images]
print(f"[generate_video] Images found - task_id: {task_id}, count: {len(image_urls)}")
# 5. Video 테이블에 초기 데이터 저장
video = Video(
project_id=project.id,
lyric_id=lyric.id,
@ -202,29 +203,29 @@ async def generate_video(
await session.flush() # ID 생성을 위해 flush
print(f"[generate_video] Video saved (processing) - task_id: {task_id}")
# 5. Creatomate API 호출 (POC 패턴 적용)
# 6. Creatomate API 호출 (POC 패턴 적용)
print(f"[generate_video] Creatomate API generation started - task_id: {task_id}")
# orientation에 따른 템플릿 선택, duration은 Song에서 가져옴 (없으면 config 기본값 사용)
creatomate_service = CreatomateService(
orientation=request_body.orientation,
orientation=orientation,
target_duration=song.duration, # Song의 duration 사용 (None이면 config 기본값)
)
print(f"[generate_video] Using template_id: {creatomate_service.template_id}, duration: {creatomate_service.target_duration} (song duration: {song.duration})")
# 5-1. 템플릿 조회 (비동기, CreatomateService에서 orientation에 맞는 template_id 사용)
# 6-1. 템플릿 조회 (비동기, CreatomateService에서 orientation에 맞는 template_id 사용)
template = await creatomate_service.get_one_template_data_async(creatomate_service.template_id)
print(f"[generate_video] Template fetched - task_id: {task_id}")
# 5-2. elements에서 리소스 매핑 생성 (music_url, lyrics는 DB에서 조회한 값 사용)
# 6-2. elements에서 리소스 매핑 생성 (music_url, lyrics는 DB에서 조회한 값 사용)
modifications = creatomate_service.elements_connect_resource_blackbox(
elements=template["source"]["elements"],
image_url_list=request_body.image_urls,
image_url_list=image_urls,
lyric=lyrics,
music_url=music_url,
)
print(f"[generate_video] Modifications created - task_id: {task_id}")
# 5-3. elements 수정
# 6-3. elements 수정
new_elements = creatomate_service.modify_element(
template["source"]["elements"],
modifications,
@ -232,14 +233,14 @@ async def generate_video(
template["source"]["elements"] = new_elements
print(f"[generate_video] Elements modified - task_id: {task_id}")
# 5-4. duration 확장 (target_duration: 영상 길이)
# 6-4. duration 확장 (target_duration: 영상 길이)
final_template = creatomate_service.extend_template_duration(
template,
creatomate_service.target_duration,
)
print(f"[generate_video] Duration extended to {creatomate_service.target_duration}s - task_id: {task_id}")
# 5-5. 커스텀 렌더링 요청 (비동기)
# 6-5. 커스텀 렌더링 요청 (비동기)
render_response = await creatomate_service.make_creatomate_custom_call_async(
final_template["source"],
)
@ -253,7 +254,7 @@ async def generate_video(
else:
creatomate_render_id = None
# 6. creatomate_render_id 업데이트
# 7. creatomate_render_id 업데이트
video.creatomate_render_id = creatomate_render_id
await session.commit()
print(f"[generate_video] SUCCESS - task_id: {task_id}, creatomate_render_id: {creatomate_render_id}")

View File

@ -5,59 +5,9 @@ Video API Schemas
"""
from datetime import datetime
from typing import Any, Dict, List, Literal, Optional
from typing import Any, Dict, Literal, Optional
from pydantic import BaseModel, Field
# =============================================================================
# Request Schemas
# =============================================================================
class GenerateVideoRequest(BaseModel):
"""영상 생성 요청 스키마
Usage:
POST /video/generate/{task_id}
Request body for generating a video via Creatomate API.
Note:
- music_url, duration, lyrics(song_prompt) task_id로 Song 테이블에서 자동 조회됩니다.
- 같은 task_id로 여러 Song이 있을 경우 가장 최근 생성된 것을 사용합니다.
Example Request:
{
"orientation": "vertical",
"image_urls": ["https://...", "https://..."]
}
"""
model_config = {
"json_schema_extra": {
"example": {
"orientation": "vertical",
"image_urls": [
"https://naverbooking-phinf.pstatic.net/20240514_189/1715688030436xT14o_JPEG/1.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_48/1715688030574wTtQd_JPEG/2.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_92/17156880307484bvpH_JPEG/3.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_7/1715688031000y8Y5q_JPEG/4.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_259/17156880311809wCnY_JPEG/5.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_64/1715688031601oGNsV_JPEG/6.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_175/1715688031657oXc7l_JPEG/7.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_192/1715688031798MbFDj_JPEG/8.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_205/17156880318681JLwX_JPEG/9.jpg",
"https://naverbooking-phinf.pstatic.net/20240514_142/1715688031946hhxHz_JPEG/10.jpg",
],
}
}
}
orientation: Literal["horizontal", "vertical"] = Field(
default="vertical",
description="영상 방향 (horizontal: 가로형, vertical: 세로형, 기본값: vertical)",
)
image_urls: List[str] = Field(..., description="영상에 사용할 이미지 URL 목록")
from pydantic import BaseModel, ConfigDict, Field
# =============================================================================
@ -69,19 +19,22 @@ class GenerateVideoResponse(BaseModel):
"""영상 생성 응답 스키마
Usage:
POST /video/generate/{task_id}
GET /video/generate/{task_id}
Returns the task IDs for tracking video generation.
Example Response (Success):
{
"success": true,
"task_id": "019123ab-cdef-7890-abcd-ef1234567890",
"creatomate_render_id": "render-id-123",
"message": "영상 생성 요청이 접수되었습니다.",
"error_message": null
}
"""
model_config = ConfigDict(
json_schema_extra={
"example": {
"success": True,
"task_id": "0694b716-dbff-7219-8000-d08cb5fce431",
"creatomate_render_id": "render-id-123456",
"message": "영상 생성 요청이 접수되었습니다. creatomate_render_id로 상태를 조회하세요.",
"error_message": None,
}
}
)
success: bool = Field(..., description="요청 성공 여부")
task_id: Optional[str] = Field(None, description="내부 작업 ID (Project task_id)")
creatomate_render_id: Optional[str] = Field(None, description="Creatomate 렌더 ID")