"""Centralized runtime configuration — env-driven, no hardcoded constants. Every value has a sensible default so local/dev runs work with zero config; override any of them via environment variables (see server/.env.example). Import as `from app import config as cfg` and read `cfg.`. """ from __future__ import annotations import os from pathlib import Path ENGINE = Path(__file__).resolve().parents[2] # engine/higgsfield_shorts def _csv(name: str, default: str) -> list[str]: raw = os.getenv(name, default) return [s.strip() for s in raw.split(",") if s.strip()] def _path(name: str, default: Path) -> Path: val = os.getenv(name) return Path(val) if val else default # ---- HTTP / CORS ---- PORT = int(os.getenv("PORT", "10001")) CORS_ORIGINS = _csv("CORS_ORIGINS", "*") # 콤마구분. 운영 시 도메인으로 제한. # ---- 비동기 작업 큐 (인메모리 스레드풀) ---- # 동시에 실제로 생성을 돌릴 작업 수. 초과분은 큐에서 대기(status=queued). # Higgsfield/Remotion이 무거우므로(CPU·메모리·외부 단가) 보수적으로 둔다. MAX_CONCURRENT_JOBS = int(os.getenv("MAX_CONCURRENT_JOBS", "2")) # 완료/실패한 작업 레코드를 메모리에 보관할 시간(초). 지나면 폴링 GET 시 청소. JOB_RETENTION_SECONDS = int(os.getenv("JOB_RETENTION_SECONDS", "3600")) # ---- LLM (OpenAI) ---- # 인증: SDK가 OPENAI_API_KEY 를 환경에서 읽음. OPENAI_MODEL = os.getenv("OPENAI_MODEL", "gpt-4o") # ---- 비디오 생성 백엔드 선택 ---- # "higgsfield" — Higgsfield(dop/seedance v1/kling). 입력 이미지 1장만 사용. # "fal" — fal.ai Seedance 2.0 reference-to-video. 입력 이미지 최대 9장(멀티) + 9:16 세로. VIDEO_BACKEND = os.getenv("VIDEO_BACKEND", "higgsfield").lower() # ---- Higgsfield (공통) ---- # 백엔드 선택: "cli"(기존 검증 경로, ~/.higgsfield 디바이스 로그인) | "api"(공식 SDK, 컨테이너 친화) HIGGSFIELD_BACKEND = os.getenv("HIGGSFIELD_BACKEND", "cli").lower() HIGGSFIELD_ASPECT_RATIO = os.getenv("HIGGSFIELD_ASPECT_RATIO", "9:16") HIGGSFIELD_DURATION = int(os.getenv("HIGGSFIELD_DURATION", "8")) HIGGSFIELD_WAIT_TIMEOUT = os.getenv("HIGGSFIELD_WAIT_TIMEOUT", "15m") # ---- Higgsfield: CLI 백엔드 (subprocess) ---- HIGGSFIELD_MODEL = os.getenv("HIGGSFIELD_MODEL", "marketing_studio_video") HIGGSFIELD_MODE = os.getenv("HIGGSFIELD_MODE", "tv_spot") HIGGSFIELD_GENERATE_AUDIO = os.getenv("HIGGSFIELD_GENERATE_AUDIO", "true") # ---- Higgsfield: API 백엔드 (공식 higgsfield-client SDK) ---- # 인증: SDK가 HF_KEY="key:secret" 또는 HF_API_KEY/HF_API_SECRET 를 환경에서 읽음. # 편의상 HIGGSFIELD_API_KEY/SECRET 로 줘도 아래에서 HF_* 로 매핑. # 주의: 공개 문서가 다루는 비디오 모델은 DoP image2video. marketing_studio_video 가 # API로 노출되는지는 미검증 → app/variant 를 env로 빼둠(대시보드 확인 후 교체). HIGGSFIELD_API_APP = os.getenv("HIGGSFIELD_API_APP", "/v1/image2video/dop") HIGGSFIELD_API_VARIANT = os.getenv("HIGGSFIELD_API_VARIANT", "dop-turbo") # DoP image2video 는 입력 이미지 정확히 1장만 허용(실측). 모델 교체 시 늘릴 수 있음. HIGGSFIELD_API_MAX_IMAGES = int(os.getenv("HIGGSFIELD_API_MAX_IMAGES", "1")) _hf_key = os.getenv("HIGGSFIELD_API_KEY") _hf_secret = os.getenv("HIGGSFIELD_API_SECRET") if _hf_key and not os.getenv("HF_API_KEY"): os.environ["HF_API_KEY"] = _hf_key if _hf_secret and not os.getenv("HF_API_SECRET"): os.environ["HF_API_SECRET"] = _hf_secret # ---- fal.ai (Seedance 2.0 reference-to-video, VIDEO_BACKEND=fal) ---- # 인증: SDK가 FAL_KEY("keyid:secret")를 환경에서 읽음. 편의상 FAL_API_KEY 로 줘도 매핑. FAL_MODEL_ID = os.getenv("FAL_MODEL_ID", "bytedance/seedance-2.0/reference-to-video") FAL_MAX_IMAGES = int(os.getenv("FAL_MAX_IMAGES", "9")) # Seedance 2.0 멀티이미지 최대 9장 FAL_ASPECT_RATIO = os.getenv("FAL_ASPECT_RATIO", "9:16") # auto/16:9/9:16/4:3/3:4/1:1/21:9 FAL_RESOLUTION = os.getenv("FAL_RESOLUTION", "720p") # 480p/720p/1080p FAL_DURATION = os.getenv("FAL_DURATION", "8") # "auto" 또는 4~15(초) _fal_key = os.getenv("FAL_API_KEY") if _fal_key and not os.getenv("FAL_KEY"): os.environ["FAL_KEY"] = _fal_key # ---- Remotion (Node render) ---- REMOTION_FPS = int(os.getenv("REMOTION_FPS", "30")) REMOTION_COMPOSITION = os.getenv("REMOTION_COMPOSITION", "MumumShort") # ---- Paths (Docker는 환경변수로 오버라이드) ---- WEBAPP_DIR = _path("WEBAPP_DIR", ENGINE / "webapp") OUTPUTS_DIR = _path("OUTPUTS_DIR", ENGINE / "server" / "outputs") UPLOADS_DIR = _path("UPLOADS_DIR", ENGINE / "server" / ".uploads")