Files
video-gen/video-gen-api/app/config.py
T
2026-07-20 13:48:17 +08:00

308 lines
14 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.
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8")
APP_NAME: str = "VideoGen API"
APP_VERSION: str = "1.0.0"
DEBUG: bool = False
SECRET_KEY: str = "change-me"
DATABASE_URL: str = "sqlite+aiosqlite:///./videogen.db"
REDIS_URL: str = "" # Leave empty to disable Redis (rate limiting, captcha)
JWT_ALGORITHM: str = "HS256"
JWT_EXPIRE_MINUTES: int = 1440
JWT_EXPIRE_REMEMBER_MINUTES: int = 10080
SEEDANCE_API_KEY: str = ""
SEEDANCE_API_BASE: str = "https://ark.cn-beijing.volces.com/api/v3"
SEEDANCE_CALLBACK_URL: str = ""
LLM_API_BASE: str = "https://api.openai.com/v1"
LLM_API_KEY: str = ""
LLM_MODEL: str = "gpt-4o"
LLM_MOCK: bool = True
LLM_MEDIA_AS_BASE64: bool = True
ENCRYPTION_KEY: str = "changeme-32bytes-base64-key-here!!"
# SMS settings
# 兼容旧字段:SMS_API_URL/SMS_API_KEY/SMS_SIGN_NAME/SMS_TEMPLATE_CODE 保留,
# 新接入默认使用火山引擎短信 SDK。
SMS_API_URL: str = ""
SMS_API_KEY: str = ""
SMS_SIGN_NAME: str = "VideoGen"
SMS_TEMPLATE_CODE: str = "SMS_001"
SMS_MOCK: bool = True
# 火山引擎配置。
VOLC_ACCESS_KEY_ID: str = ""
VOLC_SECRET_ACCESS_KEY: str = ""
# 火山引擎短信配置。
# SmsAccount=消息组IDTemplateID=模板IDSign=短信签名内容。
VOLC_SMS_ACCESS_KEY_ID: str = ""
VOLC_SMS_SECRET_ACCESS_KEY: str = ""
VOLC_SMS_ACCOUNT: str = ""
VOLC_SMS_TEMPLATE_ID: str = ""
VOLC_SMS_SIGN: str = ""
SMS_CODE_LENGTH: int = 4
SMS_CODE_TTL_SECONDS: int = 300
SMS_SEND_INTERVAL_SECONDS: int = 60
SMS_DAILY_LIMIT: int = 20
# Payment settings
WECHAT_MCH_ID: str = ""
WECHAT_API_KEY: str = ""
WECHAT_CERT_PATH: str = ""
ALIPAY_APP_ID: str = ""
ALIPAY_PRIVATE_KEY: str = ""
ALIPAY_PUBLIC_KEY: str = ""
ALIPAY_NOTIFY_URL: str = ""
PAYMENT_MOCK: bool = False # Default off; use admin panel to enable for testing
STORAGE_TYPE: str = "local"
STORAGE_LOCAL_PATH: str = "./storage/generate/videos"
STORAGE_IMAGE_LOCAL_PATH: str = "./storage/generate/images"
STORAGE_VIDEO_COVER_LOCAL_PATH: str = "./storage/generate/covers"
UPLOAD_LOCAL_PATH: str = "./storage/uploads"
# 首页素材行业装修配置。
# 文件实际挂载仍走 /uploads,目录在 UPLOAD_LOCAL_PATH/home_materials 下。
HOME_MATERIAL_MAX_IMAGE_MB: int = 20
HOME_MATERIAL_MAX_VIDEO_MB: int = 300
HOME_MATERIAL_MAX_WATERMARK_MB: int = 10
HOME_MATERIAL_MAX_VIDEO_DURATION_SECONDS: int = 300
HOME_MATERIAL_IMAGE_WATERMARK_CONCURRENCY: int = 4
HOME_MATERIAL_VIDEO_WATERMARK_CONCURRENCY: int = 2
HOME_MATERIAL_FFMPEG_TIMEOUT_SECONDS: int = 600
HOME_MATERIAL_PROCESSING_STALE_MINUTES: int = 30
# 重复文字水印配置。
# 字体必须使用开源可商用字体文件,推荐 Noto Sans CJK SC / Source Han Sans SC,且前端预览层和后端实际生成均由后端使用该字体渲染。
# 不允许自动 fallback 到系统字体,避免预览不一致和字体授权风险。
HOME_MATERIAL_TEXT_WATERMARK_FONT: str = "./resource/font/NotoSansCJKsc-Regular.otf"
HOME_MATERIAL_TEXT_WATERMARK_FONT_LICENSE: str = "SIL Open Font License 1.1"
HOME_MATERIAL_TEXT_WATERMARK_MAX_TEXT_LENGTH: int = 64
HOME_MATERIAL_TEXT_WATERMARK_MAX_FONT_SIZE: int = 160
HOME_MATERIAL_TEXT_WATERMARK_MAX_GAP: int = 2000
HOME_MATERIAL_TEXT_WATERMARK_PREVIEW_MAX_WIDTH: int = 8192
HOME_MATERIAL_TEXT_WATERMARK_PREVIEW_MAX_HEIGHT: int = 8192
# 本地视频封面截帧配置。
# 说明:
# - FFMPEG_BIN 为空时自动从系统 PATH 查找 ffmpeg / ffmpeg.exe。
# - VIDEO_COVER_TIMEOUT_SECONDS 必须较短,避免 ffmpeg 异常卡住下载 worker。
FFMPEG_BIN: str = ""
# 视频超分配置。
# 本地处理器始终复用 FFMPEG_BIN,不允许由管理后台覆盖可执行文件路径。
VOLC_API_KEY: str = ""
VOLC_MEDIAKIT_API_BASE: str = "https://mediakit.cn-beijing.volces.com"
VIDEO_UPSCALE_LOCAL_QUEUE: str = "gen_video_upscale_local"
VIDEO_UPSCALE_REMOTE_QUEUE: str = "gen_video_upscale_remote"
VIDEO_UPSCALE_LOCAL_TIMEOUT_SECONDS: int = 3600
VIDEO_UPSCALE_MAX_ATTEMPTS: int = 3
VIDEO_UPSCALE_REMOTE_POLL_INTERVAL_SECONDS: int = 30
VIDEO_UPSCALE_REMOTE_POLL_TIMEOUT_SECONDS: int = 7200
VIDEO_UPSCALE_REMOTE_REQUEST_TIMEOUT_SECONDS: int = 30
VIDEO_UPSCALE_REMOTE_RESULT_DOWNLOAD_TIMEOUT_SECONDS: int = 600
VIDEO_UPSCALE_REMOTE_URL_PROBE_THRESHOLD_SECONDS: int = 600
VIDEO_UPSCALE_REMOTE_URL_PROBE_CONNECT_TIMEOUT_SECONDS: int = 3
VIDEO_UPSCALE_REMOTE_URL_PROBE_READ_TIMEOUT_SECONDS: int = 5
VIDEO_UPSCALE_LOCAL_SIGNED_URL_EXPIRE_SECONDS: int = 7200
VIDEO_UPSCALE_TASK_LEASE_SECONDS: int = 30 * 60
VIDEO_UPSCALE_RETRY_BACKOFF_SECONDS: int = 60
VIDEO_UPSCALE_RECOVERY_BATCH_SIZE: int = 50
VIDEO_UPSCALE_RECOVERY_LOCK_KEY: str = "vg:celery:video_upscale_recovery_lock"
VIDEO_UPSCALE_EXECUTION_LOCK_KEY_PREFIX: str = "vg:lock:upscale:execute"
VIDEO_UPSCALE_EXECUTION_LOCK_TTL_SECONDS: int = 30 * 60
VIDEO_UPSCALE_STAGE_HANDOFF_DELAY_SECONDS: int = 2
VIDEO_COVER_SEEK_TIME: str = "00:00:01"
VIDEO_COVER_FALLBACK_SEEK_TIME: str = "00:00:00"
VIDEO_COVER_WIDTH: int = 720
VIDEO_COVER_TIMEOUT_SECONDS: int = 15
VIDEO_COVER_FORMAT: str = "jpg"
CAPTCHA_ENABLED: bool = True
BASE_URL: str = "http://ceshi.apiforeign.minzhong.cn"
CORS_ORIGINS: list[str] = ["*"]
RATE_LIMIT_ENABLED: bool = True
# ChatAPI async generation pipeline settings
CELERY_BROKER_URL: str = ""
CELERY_RESULT_BACKEND: str = ""
# Celery result backend 仅保留近期排障状态;业务恢复以数据库状态和业务日志为准。
CELERY_RESULT_EXPIRES_SECONDS: int = 7200
# Celery async 兼容配置。
# single_loop:每个 Celery 子进程一个专用 event loop,推荐线上/本地统一使用。
# direct:旧版线程本地 loop 降级模式,建议配合 CELERY_DB_USE_NULLPOOL=true。
CELERY_ASYNC_RUNNER_MODE: str = "single_loop"
CELERY_DB_USE_NULLPOOL: bool = False
CELERY_STARTUP_RECOVERY_ENABLED: bool = True
CHATAPI_REQUEST_TIMEOUT_SECONDS: int = 180
CHATAPI_VIDEO_FPS: float = 0.5
CHATAPI_ASYNC_MAX_RETRIES: int = 3
CHATAPI_ASYNC_RETRY_BACKOFF_SECONDS: int = 30
CHATAPI_ASYNC_POLL_INTERVAL_SECONDS: int = 30
CHATAPI_ASYNC_IMAGE_DEADLINE_MINUTES: int = 30
# 视频异步生成不再使用 30 分钟最终超时;前 10 分钟高频轮询,之后降频,24 小时最后判定失败才退款。
CHATAPI_ASYNC_VIDEO_FINAL_DEADLINE_HOURS: int = 24
CHATAPI_ASYNC_VIDEO_HIGH_FREQ_MINUTES: int = 10
CHATAPI_ASYNC_VIDEO_HIGH_FREQ_POLL_SECONDS: int = 30
CHATAPI_ASYNC_VIDEO_BACKOFF_INITIAL_SECONDS: int = 60
CHATAPI_ASYNC_VIDEO_BACKOFF_MULTIPLIER: int = 2
CHATAPI_ASYNC_VIDEO_BACKOFF_MAX_SECONDS: int = 3600
CHATAPI_ASYNC_VIDEO_DIRECT_COUNTDOWN_MAX_SECONDS: int = 300
# Distributed provider concurrency limits. 0 means disabled/no-op.
ARK_CHAT_PROMPT_MAX_CONCURRENCY: int = 20
ARK_IMAGE_CREATE_MAX_CONCURRENCY: int = 10
ARK_VIDEO_CREATE_MAX_CONCURRENCY: int = 10
ARK_IMAGE_POLL_MAX_CONCURRENCY: int = 50
ARK_VIDEO_POLL_MAX_CONCURRENCY: int = 50
RESULT_DOWNLOAD_MAX_CONCURRENCY: int = 10
PROVIDER_LIMIT_WAIT_TIMEOUT_SECONDS: float = 30.0
PROVIDER_LIMIT_TOKEN_TTL_SECONDS: int = 600
CELERY_DB_POOL_SIZE: int = 1
CELERY_DB_MAX_OVERFLOW: int = 2
CELERY_DB_POOL_TIMEOUT: int = 60
CELERY_DB_POOL_RECYCLE: int = 1800
# Celery 图片/视频下载容灾配置。
# Redis broker 下 priority=0 最高,priority=9 最低。
DOWNLOAD_TASK_PRIORITY_NORMAL: int = 5
DOWNLOAD_TASK_PRIORITY_RECOVER: int = 0
DOWNLOAD_TASK_MAX_ATTEMPTS: int = 3
DOWNLOAD_TASK_RETRY_BACKOFF_SECONDS: int = 30
DOWNLOAD_TASK_LEASE_SECONDS: int = 10 * 60
DOWNLOAD_TASK_QUEUE_TIMEOUT_SECONDS: int = 5 * 60
DOWNLOAD_RECOVERY_BATCH_SIZE: int = 20
DOWNLOAD_RECOVERY_STARTUP_DELAY_SECONDS: int = 3
# 下载恢复自循环:不依赖 Celery beat,不新增 worker;由 gen_result_download 队列周期扫描 DB/Redis。
DOWNLOAD_RECOVERY_LOOP_ENABLED: bool = False
DOWNLOAD_RECOVERY_INTERVAL_SECONDS: int = 60
DOWNLOAD_RECOVERY_LOOP_LOCK_KEY: str = "vg:celery:download_recovery_loop_lock"
DOWNLOAD_RECOVERY_LOOP_LOCK_TTL_SECONDS: int = 55
DOWNLOAD_RETRY_COUNTDOWN_EXTRA_SECONDS: int = 1
DOWNLOAD_NON_RETRYABLE_LOCAL_ERRORS: bool = True
DOWNLOAD_EVENT_VERBOSE_ENABLED: bool = True
MEDIA_TOKEN_SNAPSHOT_ENABLED: bool = True
DOWNLOAD_ACTIVE_REDIS_HASH_KEY: str = "vg:celery:download:active"
DOWNLOAD_ACTIVE_REDIS_ZSET_KEY: str = "vg:celery:download:active_index"
# Celery 生成链路 / provider poll 容灾配置。
# 说明:
# - 启动容灾保留,但恢复扫描独立投递到 CELERY_RECOVERY_QUEUE。
# - worker_ready 每个 worker 都会尝试抢启动恢复锁,只有抢到锁的 worker 投递恢复协调任务。
# - poll active 使用独立 Redis key,避免影响稳定的下载 active 注册表。
GENERATION_RECOVERY_BATCH_SIZE: int = 20
GENERATION_RECOVERY_MAX_ROUNDS: int = 1
POLL_RECOVERY_BATCH_SIZE: int = 20
POLL_TASK_LEASE_SECONDS: int = 5 * 60
POLL_TASK_QUEUE_TIMEOUT_SECONDS: int = 2 * 60
POLL_ACTIVE_REDIS_HASH_KEY: str = "vg:celery:poll:active"
POLL_ACTIVE_REDIS_ZSET_KEY: str = "vg:celery:poll:active_index"
CELERY_STARTUP_RECOVERY_LOCK_KEY: str = "vg:celery:startup_recovery_lock"
CELERY_STARTUP_RECOVERY_LOCK_TTL_SECONDS: int = 120
CELERY_RECOVERY_QUEUE: str = "gen_recovery"
CELERY_RECOVERY_TASK_LOCK_TTL_SECONDS: int = 10 * 60
CELERY_RECOVERY_SOFT_TIME_LIMIT_SECONDS: int = 300
CELERY_RECOVERY_TIME_LIMIT_SECONDS: int = 420
CELERY_RECOVERY_STARTUP_TASK_LOCK_KEY: str = "vg:celery:startup_recovery_task_lock"
GENERATION_RECOVERY_LOCK_KEY: str = "vg:celery:generation_recovery_lock"
DOWNLOAD_RECOVERY_LOCK_KEY: str = "vg:celery:download_recovery_lock"
MODULE_ASYNC_RECOVERY_LOCK_KEY: str = "vg:celery:module_async_recovery_lock"
SHOT_SPLIT_RECOVERY_LOCK_KEY: str = "vg:celery:shot_split_recovery_lock"
# 视频到期轮询调度。
# Celery Beat 每分钟投递轻量 dispatcher 到 gen_recoverydispatcher 只扫描 next_poll_at 到期的视频任务。
POLL_DUE_DISPATCH_ENABLED: bool = True
POLL_DUE_DISPATCH_INTERVAL_SECONDS: int = 60
POLL_DUE_DISPATCH_BATCH_SIZE: int = 100
POLL_DUE_DISPATCH_LOCK_KEY: str = "vg:celery:poll_due_dispatch_lock"
POLL_DUE_DISPATCH_LOCK_TTL_SECONDS: int = 55
# Redis execution locks. These locks are fail-closed: when Redis is
# unavailable, the current Celery task retries and does not fall back to an
# unlocked database-only execution path.
GENERATION_CREATE_LOCK_KEY_PREFIX: str = "vg:lock:generation:create"
GENERATION_POLL_LOCK_KEY_PREFIX: str = "vg:lock:generation:poll"
GENERATION_DOWNLOAD_LOCK_KEY_PREFIX: str = "vg:lock:generation:download"
GENERATION_CREATE_LOCK_TTL_SECONDS: int = 10 * 60
GENERATION_POLL_LOCK_TTL_SECONDS: int = 5 * 60
GENERATION_DOWNLOAD_LOCK_TTL_SECONDS: int = 10 * 60
REDIS_EXECUTION_LOCK_RENEW_INTERVAL_SECONDS: int = 30
# PostgreSQL short row-lock wait and whole ordinary download timeout.
# The lock timeout is transaction-local (SET LOCAL), not a global DB setting.
GENERATION_DB_LOCK_TIMEOUT_SECONDS: int = 5
GENERATION_DOWNLOAD_TOTAL_TIMEOUT_SECONDS: int = 8 * 60
# 模块异步任务容灾配置。
# 覆盖 ModuleGenerationStep 提词任务、shot 原视频/片段分析、shot ffmpeg 切割 active 注册。
# 恢复扫描走 CELERY_RECOVERY_QUEUE,真实业务任务回到原始队列。
MODULE_ASYNC_RECOVERY_BATCH_SIZE: int = 20
MODULE_ASYNC_QUEUE_TIMEOUT_SECONDS: int = 5 * 60
MODULE_ASYNC_LEASE_SECONDS: int = 10 * 60
MODULE_ASYNC_LOCK_TTL_SECONDS: int = 10 * 60
MODULE_ASYNC_REQUEUE_DELAY_SECONDS: int = 10
MODULE_ASYNC_ACTIVE_REDIS_HASH_KEY: str = "vg:celery:module_async:active"
MODULE_ASYNC_ACTIVE_REDIS_ZSET_KEY: str = "vg:celery:module_async:active_index"
MODULE_ASYNC_LOCK_KEY_PREFIX: str = "vg:celery:module_async:lock"
RESOURCE_SIGN_SECRET: str = "resource-signature-secret-key-for-API-authentication"
RESOURCE_SIGN_EXPIRE_SECONDS: int = 60
RESOURCE_SIGN_ARG_EXPIRE: str = "exp"
RESOURCE_SIGN_ARG_SIGNATURE: str = "sign"
# 爆款开头复刻默认配置。素材校验由前端完成,后端只接收已有上传接口返回的链接。
HOT_OPENING_DEFAULT_VIDEO_DURATION: int = 4
HOT_OPENING_DEFAULT_VIDEO_RATIO: str = "9:16"
HOT_OPENING_DEFAULT_VIDEO_RESOLUTION: str = "480p"
HOT_OPENING_DEFAULT_TARGET_PLATFORM: str = "抖音"
# 拆镜复刻配置。
# 原始上传视频和拆镜片段都属于 uploads 素材域;只有 generate 生成结果走 token 验签。
SHOT_ANALYSIS_TIMEOUT_SECONDS: int = 3600
SHOT_ANALYSIS_TEMPERATURE: float = 0.1
SHOT_ANALYSIS_MAX_TOKENS: int = 5000
SHOT_ANALYSIS_VIDEO_FPS: float = 1.0
SHOT_ANALYSIS_MAX_LOCAL_VIDEO_MB: int = 45
SHOT_SEGMENT_LOCAL_PATH: str = "./storage/uploads/shot_segments"
SHOT_SEGMENT_URL_PREFIX: str = "/uploads/shot_segments"
SHOT_SPLIT_MIN_SECONDS: float = 2
SHOT_SPLIT_MAX_SECONDS: float = 15
SHOT_SPLIT_END_TOLERANCE_SECONDS: float = 0.5
SHOT_DURATION_TOLERANCE_SECONDS: float = 1.0
SHOT_FFMPEG_TIMEOUT_SECONDS: int = 120
SHOT_FFPROBE_TIMEOUT_SECONDS: int = 20
FFPROBE_BIN: str = ""
# 继续复用 gen_result_download 队列,但限制 ffmpeg 并发,避免拖慢 Chat 下载。
SHOT_SPLIT_MAX_CONCURRENT: int = 1
SHOT_SPLIT_MAX_RETRY_COUNT: int = 3
SHOT_SPLIT_RETRY_BACKOFF_SECONDS: int = 30
SHOT_SPLIT_LEASE_SECONDS: int = 10 * 60
SHOT_SPLIT_PENDING_TIMEOUT_SECONDS: int = 5 * 60
SHOT_SPLIT_RECOVERY_BATCH_SIZE: int = 20
SHOT_SPLIT_LOCK_KEY_PREFIX: str = "vg:shot_replicate:split:lock"
SHOT_SPLIT_SEMAPHORE_KEY_PREFIX: str = "vg:shot_replicate:split:semaphore"
SHOT_REPLICATE_DEFAULT_VIDEO_DURATION: int = 4
SHOT_REPLICATE_DEFAULT_VIDEO_RATIO: str = "9:16"
SHOT_REPLICATE_DEFAULT_VIDEO_RESOLUTION: str = "480p"
SHOT_REPLICATE_DEFAULT_TARGET_PLATFORM: str = "抖音"
settings = Settings()