o2o-castad-backend/app/ssulbox/constants.py

120 lines
4.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""썰박스 상수.
설정(SsulboxSettings)이 아니라 코드와 함께 고정되는 값들. 시나리오 ↔ 엔진 폴더 매핑,
생성 잡 stdout 마커 파싱 규칙, 태스크 상태 enum이 여기 있다.
"""
import re
from enum import Enum
from typing import Final
from config import apikey_settings
# =============================================================================
# 시나리오 ↔ 생성 엔진 폴더
# =============================================================================
# 프론트가 보내는 시나리오 코드를 generator/ 하위 엔진 디렉터리명으로 옮긴다.
# 엔진을 추가하면 여기와 프론트 ssulData.ts 양쪽을 함께 고쳐야 한다.
SCENARIO_ENGINE: Final[dict[str, str]] = {
"joseon": "animation",
"samgukji": "animation_samgukji",
"greek": "animation_greekroman",
"odyssey": "animation_odyssey",
}
ENGINE_SCENARIO: Final[dict[str, str]] = {v: k for k, v in SCENARIO_ENGINE.items()}
#: 시나리오 한글 표시명. SNS 업로드 SEO 프롬프트 입력({scenario_name})에 쓴다.
#: 프론트 i18n(ssulbox.scenario.*.name)과 값을 맞춘다.
SCENARIO_NAMES: Final[dict[str, str]] = {
"joseon": "조선왕",
"samgukji": "삼국지",
"greek": "그리스·로마 신화",
"odyssey": "오디세이",
}
SCENARIOS: Final[tuple[str, ...]] = tuple(SCENARIO_ENGINE)
# =============================================================================
# 생성 잡 상태
# =============================================================================
class SsulTaskStatus(str, Enum):
"""생성 잡의 권위 상태. 인메모리 진행률(step)과 달리 DB가 진실 원천이다."""
QUEUED = "queued"
RUNNING = "running"
DONE = "done"
ERROR = "error"
#: 프로세스 기동 시 고아로 판정해 환불·정리할 상태들
ORPHAN_STATUSES: Final[tuple[str, ...]] = (
SsulTaskStatus.QUEUED.value,
SsulTaskStatus.RUNNING.value,
)
# =============================================================================
# 생성 엔진 stdout 파싱
# =============================================================================
#: 엔진이 뱉는 진행 마커. 예) "[2/4] 스토리보드 생성"
STEP_RE: Final[re.Pattern[str]] = re.compile(r"\[(\d)\s*/\s*4\]")
#: 완료 마커. 예) "완성: output/animation/xxx/final.mp4"
DONE_RE: Final[re.Pattern[str]] = re.compile(r"완성[:]\s*(.+\.mp4)")
#: 업장명 마커. 예) "■ 가게: 골목냉면 / 소재 5줄 → 키워드로 사용"
#: place URL 을 직접 붙여넣어 검색을 거치지 않은 경우, 업장명을 얻을 수 있는
#: 유일한 경로다(엔진이 네이버 브리핑에서 뽑아 찍는다).
#: 주소는 찍지 않으므로 이 경로에서는 region 을 채울 수 없다.
STORE_RE: Final[re.Pattern[str]] = re.compile(r"가게\s*[:]\s*([^/\n]+?)\s*(?:/|$)")
#: 작업 폴더 마커. 예) "■ 작업 폴더 : C:\...\output\animation\골목냉면20260729"
#: 실패 시 정리할 대상을 알아내는 유일한 수단이다 — 실패하면 완료 마커가
#: 없어 mp4 경로로 폴더를 역산할 수 없다.
JOB_DIR_RE: Final[re.Pattern[str]] = re.compile(r"작업\s*폴더\s*[:]\s*(.+)")
#: step 1~4에 대응하는 사람이 읽는 단계명 (프론트 진행 표시용)
STEP_NAMES: Final[tuple[str, ...]] = ("대본 생성", "스토리보드", "이미지·음성", "영상 합성")
TOTAL_STEPS: Final[int] = len(STEP_NAMES)
# =============================================================================
# 크레딧 원장 멱등 키
# =============================================================================
#: credit_transaction.job_type 값. (job_type, job_ref, type) 유니크로 중복 차감을 막는다.
JOB_TYPE_SSUL: Final[str] = "ssul"
JOB_TYPE_VIDEO: Final[str] = "video"
# =============================================================================
# Gemini API 키 판정
# =============================================================================
#: castad config 의 GEMINI_API_KEY 기본값이 플레이스홀더 문자열이라
#: `if not key` 형태의 가드가 항상 통과해버린다. 반드시 gemini_key() 로 판정할 것.
_PLACEHOLDER_KEYS: Final[frozenset[str]] = frozenset(
{
"",
"your-gemeni-api-key", # config.py 기본값 (오타 그대로)
"your-gemini-api-key",
"none",
}
)
def gemini_key() -> str | None:
"""실제로 사용 가능한 Gemini API 키를 반환한다. 미설정이면 None.
Returns:
설정된 키. 플레이스홀더이거나 비어 있으면 None.
"""
key = (apikey_settings.GEMINI_API_KEY or "").strip()
return None if key.lower() in _PLACEHOLDER_KEYS else key
def is_generation_available() -> bool:
"""생성 파이프라인을 돌릴 수 있는 상태인지 여부."""
return gemini_key() is not None