lyric 생성 시점에 subtitle 생성하도록 DB 및 코드 변경
parent
7da6ab6ec0
commit
395b4dbbfb
|
|
@ -300,6 +300,12 @@ class MarketingIntel(Base):
|
|||
comment="마케팅 인텔리전스 결과물",
|
||||
)
|
||||
|
||||
subtitle : Mapped[dict[str, Any]] = mapped_column(
|
||||
JSON,
|
||||
nullable=True,
|
||||
comment="자막 정보 생성 결과물",
|
||||
)
|
||||
|
||||
created_at: Mapped[datetime] = mapped_column(
|
||||
DateTime,
|
||||
nullable=False,
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ from app.lyric.schemas.lyric import (
|
|||
LyricListItem,
|
||||
LyricStatusResponse,
|
||||
)
|
||||
from app.lyric.worker.lyric_task import generate_lyric_background
|
||||
from app.lyric.worker.lyric_task import generate_lyric_background, generate_subtitle_background
|
||||
from app.utils.chatgpt_prompt import ChatgptService
|
||||
from app.utils.logger import get_logger
|
||||
from app.utils.pagination import PaginatedResponse, get_paginated
|
||||
|
|
@ -351,7 +351,7 @@ async def generate_lyric(
|
|||
# ========== Step 4: 백그라운드 태스크 스케줄링 ==========
|
||||
step4_start = time.perf_counter()
|
||||
logger.debug(f"[generate_lyric] Step 4: 백그라운드 태스크 스케줄링...")
|
||||
|
||||
orientation = request_body.orientation
|
||||
background_tasks.add_task(
|
||||
generate_lyric_background,
|
||||
task_id=task_id,
|
||||
|
|
@ -359,6 +359,12 @@ async def generate_lyric(
|
|||
lyric_input_data=lyric_input_data,
|
||||
lyric_id=lyric.id,
|
||||
)
|
||||
|
||||
background_tasks.add_task(
|
||||
generate_subtitle_background,
|
||||
orientation = orientation,
|
||||
task_id=task_id
|
||||
)
|
||||
|
||||
step4_elapsed = (time.perf_counter() - step4_start) * 1000
|
||||
logger.debug(f"[generate_lyric] Step 4 완료 ({step4_elapsed:.1f}ms)")
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ Lyric API Schemas
|
|||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import Optional, Literal
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
|
@ -42,7 +42,8 @@ class GenerateLyricRequest(BaseModel):
|
|||
"region": "군산",
|
||||
"detail_region_info": "군산 신흥동 말랭이 마을",
|
||||
"language": "Korean",
|
||||
"m_id" : 1
|
||||
"m_id" : 1,
|
||||
"orientation" : "vertical"
|
||||
}
|
||||
"""
|
||||
|
||||
|
|
@ -54,7 +55,8 @@ class GenerateLyricRequest(BaseModel):
|
|||
"region": "군산",
|
||||
"detail_region_info": "군산 신흥동 말랭이 마을",
|
||||
"language": "Korean",
|
||||
"m_id" : 1
|
||||
"m_id" : 1,
|
||||
"orientation" : "vertical"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
|
@ -68,7 +70,11 @@ class GenerateLyricRequest(BaseModel):
|
|||
language: str = Field(
|
||||
default="Korean",
|
||||
description="가사 출력 언어 (Korean, English, Chinese, Japanese, Thai, Vietnamese)",
|
||||
)
|
||||
),
|
||||
orientation: Literal["horizontal", "vertical"] = Field(
|
||||
default="vertical",
|
||||
description="영상 방향 (horizontal: 가로형, vertical: 세로형)",
|
||||
),
|
||||
m_id : Optional[int] = Field(None, description="마케팅 인텔리전스 ID 값")
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,15 @@ Lyric Background Tasks
|
|||
import traceback
|
||||
|
||||
from sqlalchemy import select
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
from sqlalchemy.exc import SQLAlchemyError
|
||||
|
||||
from app.database.session import BackgroundSessionLocal
|
||||
from app.home.models import Image, Project, MarketingIntel
|
||||
from app.lyric.models import Lyric
|
||||
from app.utils.chatgpt_prompt import ChatgptService, ChatGPTResponseError
|
||||
from app.utils.subtitles import SubtitleContentsGenerator
|
||||
from app.utils.creatomate import CreatomateService
|
||||
from app.utils.prompts.prompts import Prompt
|
||||
from app.utils.logger import get_logger
|
||||
|
||||
|
|
@ -158,3 +162,55 @@ async def generate_lyric_background(
|
|||
elapsed = (time.perf_counter() - task_start) * 1000
|
||||
logger.error(f"[generate_lyric_background] EXCEPTION - task_id: {task_id}, error: {e} ({elapsed:.1f}ms)", exc_info=True)
|
||||
await _update_lyric_status(task_id, "failed", f"Error: {str(e)}", lyric_id)
|
||||
|
||||
async def generate_subtitle_background(
|
||||
orientation: str,
|
||||
task_id: str
|
||||
) -> None:
|
||||
logger.info(f"[generate_subtitle_background] task_id: {task_id}, {orientation}")
|
||||
creatomate_service = CreatomateService(orientation=orientation)
|
||||
template = await creatomate_service.get_one_template_data_async(creatomate_service.template_id)
|
||||
pitchings = creatomate_service.extract_text_format_from_template(template)
|
||||
|
||||
subtitle_generator = SubtitleContentsGenerator()
|
||||
|
||||
async with BackgroundSessionLocal() as session:
|
||||
project_result = await session.execute(
|
||||
select(Project)
|
||||
.where(Project.task_id == task_id)
|
||||
.order_by(Project.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
project = project_result.scalar_one_or_none()
|
||||
marketing_result = await session.execute(
|
||||
select(MarketingIntel).where(MarketingIntel.id == project.marketing_intelligence)
|
||||
)
|
||||
marketing_intelligence = marketing_result.scalar_one_or_none()
|
||||
|
||||
store_address = project.detail_region_info
|
||||
customer_name = project.store_name
|
||||
logger.info(f"[generate_subtitle_background] customer_name: {customer_name}, {store_address}")
|
||||
|
||||
generated_subtitles = await subtitle_generator.generate_subtitle_contents(
|
||||
marketing_intelligence = marketing_intelligence.intel_result,
|
||||
pitching_label_list = pitchings,
|
||||
customer_name = customer_name,
|
||||
detail_region_info = store_address,
|
||||
)
|
||||
pitching_output_list = generated_subtitles.pitching_results
|
||||
|
||||
subtitle_modifications = {pitching_output.pitching_tag : pitching_output.pitching_data for pitching_output in pitching_output_list}
|
||||
logger.info(f"[generate_subtitle_background] subtitle_modifications: {subtitle_modifications}")
|
||||
|
||||
async with BackgroundSessionLocal() as session:
|
||||
marketing_result = await session.execute(
|
||||
select(MarketingIntel).where(MarketingIntel.id == project.marketing_intelligence)
|
||||
)
|
||||
marketing_intelligence = marketing_result.scalar_one_or_none()
|
||||
marketing_intelligence.subtitle = subtitle_modifications
|
||||
await session.commit()
|
||||
logger.info(f"[generate_subtitle_background] task_id: {task_id} DONE")
|
||||
|
||||
|
||||
|
||||
return
|
||||
|
|
|
|||
|
|
@ -239,6 +239,8 @@ def select_template(orientation:OrientationType):
|
|||
return DHST0001
|
||||
elif orientation == "vertical":
|
||||
return DVST0001
|
||||
else:
|
||||
raise
|
||||
|
||||
async def get_shared_client() -> httpx.AsyncClient:
|
||||
"""공유 HTTP 클라이언트를 반환합니다. 없으면 생성합니다."""
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ Video API Router
|
|||
"""
|
||||
|
||||
import json
|
||||
import asyncio
|
||||
|
||||
from typing import Literal
|
||||
|
||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query
|
||||
|
|
@ -145,6 +147,34 @@ async def generate_video(
|
|||
image_urls: list[str] = []
|
||||
|
||||
try:
|
||||
subtitle_done = False
|
||||
count = 0
|
||||
async with AsyncSessionLocal() as session:
|
||||
project_result = await session.execute(
|
||||
select(Project)
|
||||
.where(Project.task_id == task_id)
|
||||
.order_by(Project.created_at.desc())
|
||||
.limit(1)
|
||||
)
|
||||
project = project_result.scalar_one_or_none()
|
||||
|
||||
while not subtitle_done:
|
||||
async with AsyncSessionLocal() as session:
|
||||
logger.info(f"[generate_video] Checking subtitle- task_id: {task_id}, count : {count}")
|
||||
marketing_result = await session.execute(
|
||||
select(MarketingIntel).where(MarketingIntel.id == project.marketing_intelligence)
|
||||
)
|
||||
marketing_intelligence = marketing_result.scalar_one_or_none()
|
||||
subtitle_done = bool(marketing_intelligence.subtitle)
|
||||
if subtitle_done:
|
||||
logger.info(f"[generate_video] Check subtitle done task_id: {task_id}")
|
||||
break
|
||||
await asyncio.sleep(5)
|
||||
if count > 12 :
|
||||
raise Exception("subtitle 결과 생성 실패")
|
||||
count += 1
|
||||
|
||||
|
||||
# 세션을 명시적으로 열고 DB 작업 후 바로 닫음
|
||||
async with AsyncSessionLocal() as session:
|
||||
# ===== 순차 쿼리 실행: Project, Lyric, Song, Image =====
|
||||
|
|
@ -198,10 +228,8 @@ async def generate_video(
|
|||
detail=f"task_id '{task_id}'에 해당하는 Project를 찾을 수 없습니다.",
|
||||
)
|
||||
project_id = project.id
|
||||
marketing_intelligence = project.marketing_intelligence
|
||||
store_address = project.detail_region_info
|
||||
customer_name = project.store_name
|
||||
marketing_intelligence = project.marketing_intelligence
|
||||
# customer_name = project.store_name
|
||||
|
||||
marketing_result = await session.execute(
|
||||
select(MarketingIntel).where(MarketingIntel.id == project.marketing_intelligence)
|
||||
|
|
@ -296,8 +324,6 @@ async def generate_video(
|
|||
# 2단계: 외부 API 호출 (세션 사용 안함 - 커넥션 풀 점유 없음)
|
||||
# ==========================================================================
|
||||
stage2_start = time.perf_counter()
|
||||
|
||||
subtitle_generator = SubtitleContentsGenerator()
|
||||
|
||||
try:
|
||||
logger.info(
|
||||
|
|
@ -325,17 +351,7 @@ async def generate_video(
|
|||
)
|
||||
logger.debug(f"[generate_video] Modifications created - task_id: {task_id}")
|
||||
|
||||
pitchings = creatomate_service.extract_text_format_from_template(template)
|
||||
|
||||
generated_subtitles = await subtitle_generator.generate_subtitle_contents(
|
||||
marketing_intelligence = marketing_intelligence.intel_result,
|
||||
pitching_label_list = pitchings,
|
||||
customer_name = customer_name,
|
||||
detail_region_info = store_address,
|
||||
)
|
||||
pitching_output_list = generated_subtitles.pitching_results
|
||||
|
||||
subtitle_modifications = {pitching_output.pitching_tag : pitching_output.pitching_data for pitching_output in pitching_output_list}
|
||||
subtitle_modifications = marketing_intelligence.subtitle
|
||||
|
||||
modifications.update(subtitle_modifications)
|
||||
# 6-3. elements 수정
|
||||
|
|
|
|||
Loading…
Reference in New Issue