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=消息组ID,TemplateID=模板ID,Sign=短信签名内容。 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 = "" 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 = 10 # 视频异步生成不再使用 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_recovery;dispatcher 只扫描 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 # 模块异步任务容灾配置。 # 覆盖 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()