MoviePilot/app/core/config.py
2024-06-24 12:41:59 +08:00

461 lines
15 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.

import secrets
import sys
import threading
from pathlib import Path
from typing import Optional, List
from pydantic import BaseSettings, validator
from app.utils.system import SystemUtils
class Settings(BaseSettings):
"""
系统配置类
"""
# 项目名称
PROJECT_NAME = "MoviePilot"
# 域名 格式https://movie-pilot.org
APP_DOMAIN: str = ""
# API路径
API_V1_STR: str = "/api/v1"
# 前端资源路径
FRONTEND_PATH: str = "/public"
# 密钥
SECRET_KEY: str = secrets.token_urlsafe(32)
# 允许的域名
ALLOWED_HOSTS: list = ["*"]
# TOKEN过期时间
ACCESS_TOKEN_EXPIRE_MINUTES: int = 60 * 24 * 8
# 时区
TZ: str = "Asia/Shanghai"
# API监听地址
HOST: str = "0.0.0.0"
# API监听端口
PORT: int = 3001
# 前端监听端口
NGINX_PORT: int = 3000
# 是否调试模式
DEBUG: bool = False
# 是否开发模式
DEV: bool = False
# 是否开启插件热加载
PLUGIN_AUTO_RELOAD: bool = False
# 配置文件目录
CONFIG_DIR: Optional[str] = None
# 超级管理员
SUPERUSER: str = "admin"
# API密钥需要更换
API_TOKEN: str = "moviepilot"
# 登录页面电影海报,tmdb/bing
WALLPAPER: str = "tmdb"
# 网络代理 IP:PORT
PROXY_HOST: Optional[str] = None
# 媒体搜索来源 themoviedb/douban/bangumi多个用,分隔
SEARCH_SOURCE: str = "themoviedb,douban,bangumi"
# 媒体识别来源 themoviedb/douban
RECOGNIZE_SOURCE: str = "themoviedb"
# 刮削来源 themoviedb/douban
SCRAP_SOURCE: str = "themoviedb"
# 新增已入库媒体是否跟随TMDB信息变化
SCRAP_FOLLOW_TMDB: bool = True
# TMDB图片地址
TMDB_IMAGE_DOMAIN: str = "image.tmdb.org"
# TMDB API地址
TMDB_API_DOMAIN: str = "api.themoviedb.org"
# TMDB API Key
TMDB_API_KEY: str = "db55323b8d3e4154498498a75642b381"
# TVDB API Key
TVDB_API_KEY: str = "6b481081-10aa-440c-99f2-21d17717ee02"
# Fanart开关
FANART_ENABLE: bool = True
# Fanart API Key
FANART_API_KEY: str = "d2d31f9ecabea050fc7d68aa3146015f"
# 支持的后缀格式
RMT_MEDIAEXT: list = ['.mp4', '.mkv', '.ts', '.iso',
'.rmvb', '.avi', '.mov', '.mpeg',
'.mpg', '.wmv', '.3gp', '.asf',
'.m4v', '.flv', '.m2ts', '.strm',
'.tp', '.f4v']
# 支持的字幕文件后缀格式
RMT_SUBEXT: list = ['.srt', '.ass', '.ssa', '.sup']
# 下载器临时文件后缀
DOWNLOAD_TMPEXT: list = ['.!qB', '.part']
# 支持的音轨文件后缀格式
RMT_AUDIO_TRACK_EXT: list = ['.mka']
# 索引器
INDEXER: str = "builtin"
# 订阅模式
SUBSCRIBE_MODE: str = "spider"
# RSS订阅模式刷新时间间隔分钟
SUBSCRIBE_RSS_INTERVAL: int = 30
# 订阅搜索开关
SUBSCRIBE_SEARCH: bool = False
# 用户认证站点
AUTH_SITE: str = ""
# 交互搜索自动下载用户ID使用,分割
AUTO_DOWNLOAD_USER: Optional[str] = None
# 消息通知渠道 telegram/wechat/slack/synologychat/vocechat/webpush多个通知渠道用,分隔
MESSAGER: str = "webpush"
# WeChat企业ID
WECHAT_CORPID: Optional[str] = None
# WeChat应用Secret
WECHAT_APP_SECRET: Optional[str] = None
# WeChat应用ID
WECHAT_APP_ID: Optional[str] = None
# WeChat代理服务器
WECHAT_PROXY: str = "https://qyapi.weixin.qq.com"
# WeChat Token
WECHAT_TOKEN: Optional[str] = None
# WeChat EncodingAESKey
WECHAT_ENCODING_AESKEY: Optional[str] = None
# WeChat 管理员
WECHAT_ADMINS: Optional[str] = None
# Telegram Bot Token
TELEGRAM_TOKEN: Optional[str] = None
# Telegram Chat ID
TELEGRAM_CHAT_ID: Optional[str] = None
# Telegram 用户ID使用,分隔
TELEGRAM_USERS: str = ""
# Telegram 管理员ID使用,分隔
TELEGRAM_ADMINS: str = ""
# Slack Bot User OAuth Token
SLACK_OAUTH_TOKEN: str = ""
# Slack App-Level Token
SLACK_APP_TOKEN: str = ""
# Slack 频道名称
SLACK_CHANNEL: str = ""
# SynologyChat Webhook
SYNOLOGYCHAT_WEBHOOK: str = ""
# SynologyChat Token
SYNOLOGYCHAT_TOKEN: str = ""
# VoceChat地址
VOCECHAT_HOST: str = ""
# VoceChat ApiKey
VOCECHAT_API_KEY: str = ""
# VoceChat 频道ID
VOCECHAT_CHANNEL_ID: str = ""
# 下载器 qbittorrent/transmission启用多个下载器时使用,分隔,只有第一个会被默认使用
DOWNLOADER: str = "qbittorrent"
# 下载器监控开关
DOWNLOADER_MONITOR: bool = True
# Qbittorrent地址IP:PORT
QB_HOST: Optional[str] = None
# Qbittorrent用户名
QB_USER: Optional[str] = None
# Qbittorrent密码
QB_PASSWORD: Optional[str] = None
# Qbittorrent分类自动管理
QB_CATEGORY: bool = False
# Qbittorrent按顺序下载
QB_SEQUENTIAL: bool = True
# Qbittorrent忽略队列限制强制继续
QB_FORCE_RESUME: bool = False
# Transmission地址IP:PORT
TR_HOST: Optional[str] = None
# Transmission用户名
TR_USER: Optional[str] = None
# Transmission密码
TR_PASSWORD: Optional[str] = None
# 种子标签
TORRENT_TAG: str = "MOVIEPILOT"
# 下载站点字幕
DOWNLOAD_SUBTITLE: bool = True
# 媒体服务器 emby/jellyfin/plex多个媒体服务器,分割
MEDIASERVER: str = "emby"
# 媒体服务器同步间隔(小时)
MEDIASERVER_SYNC_INTERVAL: Optional[int] = 6
# 媒体服务器同步黑名单,多个媒体库名称,分割
MEDIASERVER_SYNC_BLACKLIST: Optional[str] = None
# EMBY服务器地址IP:PORT
EMBY_HOST: Optional[str] = None
# EMBY外网地址http(s)://DOMAIN:PORT未设置时使用EMBY_HOST
EMBY_PLAY_HOST: Optional[str] = None
# EMBY Api Key
EMBY_API_KEY: Optional[str] = None
# Jellyfin服务器地址IP:PORT
JELLYFIN_HOST: Optional[str] = None
# Jellyfin外网地址http(s)://DOMAIN:PORT未设置时使用JELLYFIN_HOST
JELLYFIN_PLAY_HOST: Optional[str] = None
# Jellyfin Api Key
JELLYFIN_API_KEY: Optional[str] = None
# Plex服务器地址IP:PORT
PLEX_HOST: Optional[str] = None
# Plex外网地址http(s)://DOMAIN:PORT未设置时使用PLEX_HOST
PLEX_PLAY_HOST: Optional[str] = None
# Plex Token
PLEX_TOKEN: Optional[str] = None
# 转移方式 link/copy/move/softlink
TRANSFER_TYPE: str = "copy"
# 是否同盘优先
TRANSFER_SAME_DISK: bool = True
# CookieCloud是否启动本地服务
COOKIECLOUD_ENABLE_LOCAL: Optional[bool] = False
# CookieCloud服务器地址
COOKIECLOUD_HOST: str = "https://movie-pilot.org/cookiecloud"
# CookieCloud用户KEY
COOKIECLOUD_KEY: Optional[str] = None
# CookieCloud端对端加密密码
COOKIECLOUD_PASSWORD: Optional[str] = None
# CookieCloud同步间隔分钟
COOKIECLOUD_INTERVAL: Optional[int] = 60 * 24
# CookieCloud同步黑名单多个域名,分割
COOKIECLOUD_BLACKLIST: Optional[str] = None
# OCR服务器地址
OCR_HOST: str = "https://movie-pilot.org"
# CookieCloud对应的浏览器UA
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36 Edg/113.0.1774.57"
# 电视剧动漫的分类genre_ids
ANIME_GENREIDS = [16]
# 电影重命名格式
MOVIE_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \
"/{{title}}{% if year %} ({{year}}){% endif %}{% if part %}-{{part}}{% endif %}{% if videoFormat %} - {{videoFormat}}{% endif %}" \
"{{fileExt}}"
# 电视剧重命名格式
TV_RENAME_FORMAT: str = "{{title}}{% if year %} ({{year}}){% endif %}" \
"/Season {{season}}" \
"/{{title}} - {{season_episode}}{% if part %}-{{part}}{% endif %}{% if episode %} - 第 {{episode}} 集{% endif %}" \
"{{fileExt}}"
# 转移时覆盖模式
OVERWRITE_MODE: str = "size"
# 大内存模式
BIG_MEMORY_MODE: bool = False
# 插件市场仓库地址,多个地址使用,分隔,地址以/结尾
PLUGIN_MARKET: str = "https://github.com/jxxghp/MoviePilot-Plugins,https://github.com/thsrite/MoviePilot-Plugins,https://github.com/honue/MoviePilot-Plugins,https://github.com/InfinityPacer/MoviePilot-Plugins"
# Github token提高请求api限流阈值 ghp_****
GITHUB_TOKEN: Optional[str] = None
# Github代理服务器格式https://mirror.ghproxy.com/
GITHUB_PROXY: Optional[str] = ''
# 自动检查和更新站点资源包(站点索引、认证等)
AUTO_UPDATE_RESOURCE: bool = True
# 元数据识别缓存过期时间(小时)
META_CACHE_EXPIRE: int = 0
# 是否启用DOH解析域名
DOH_ENABLE: bool = True
# 搜索多个名称
SEARCH_MULTIPLE_NAME: bool = False
# 订阅数据共享
SUBSCRIBE_STATISTIC_SHARE: bool = True
# 插件安装数据共享
PLUGIN_STATISTIC_SHARE: bool = True
# 服务器地址,对应 https://github.com/jxxghp/MoviePilot-Server 项目
MP_SERVER_HOST: str = "https://movie-pilot.org"
# 【已弃用】刮削入库的媒体文件
SCRAP_METADATA: bool = True
# 【已弃用】下载保存目录,容器内映射路径需要一致
DOWNLOAD_PATH: Optional[str] = None
# 【已弃用】电影下载保存目录,容器内映射路径需要一致
DOWNLOAD_MOVIE_PATH: Optional[str] = None
# 【已弃用】电视剧下载保存目录,容器内映射路径需要一致
DOWNLOAD_TV_PATH: Optional[str] = None
# 【已弃用】动漫下载保存目录,容器内映射路径需要一致
DOWNLOAD_ANIME_PATH: Optional[str] = None
# 【已弃用】下载目录二级分类
DOWNLOAD_CATEGORY: bool = False
# 【已弃用】媒体库目录,多个目录使用,分隔
LIBRARY_PATH: Optional[str] = None
# 【已弃用】电影媒体库目录名
LIBRARY_MOVIE_NAME: str = "电影"
# 【已弃用】电视剧媒体库目录名
LIBRARY_TV_NAME: str = "电视剧"
# 【已弃用】动漫媒体库目录名,不设置时使用电视剧目录
LIBRARY_ANIME_NAME: Optional[str] = None
# 【已弃用】二级分类
LIBRARY_CATEGORY: bool = True
@validator("SUBSCRIBE_RSS_INTERVAL",
"COOKIECLOUD_INTERVAL",
"MEDIASERVER_SYNC_INTERVAL",
"META_CACHE_EXPIRE",
pre=True, always=True)
def convert_int(cls, value):
if not value:
return 0
try:
return int(value)
except (ValueError, TypeError):
raise ValueError(f"{value} 格式错误,不是有效数字!")
@property
def INNER_CONFIG_PATH(self):
return self.ROOT_PATH / "config"
@property
def CONFIG_PATH(self):
if self.CONFIG_DIR:
return Path(self.CONFIG_DIR)
elif SystemUtils.is_docker():
return Path("/config")
elif SystemUtils.is_frozen():
return Path(sys.executable).parent / "config"
return self.ROOT_PATH / "config"
@property
def TEMP_PATH(self):
return self.CONFIG_PATH / "temp"
@property
def ROOT_PATH(self):
return Path(__file__).parents[2]
@property
def PLUGIN_DATA_PATH(self):
return self.CONFIG_PATH / "plugins"
@property
def LOG_PATH(self):
return self.CONFIG_PATH / "logs"
@property
def COOKIE_PATH(self):
return self.CONFIG_PATH / "cookies"
@property
def CACHE_CONF(self):
if self.BIG_MEMORY_MODE:
return {
"tmdb": 1024,
"refresh": 50,
"torrents": 100,
"douban": 512,
"fanart": 512,
"meta": (self.META_CACHE_EXPIRE or 168) * 3600
}
return {
"tmdb": 256,
"refresh": 30,
"torrents": 50,
"douban": 256,
"fanart": 128,
"meta": (self.META_CACHE_EXPIRE or 72) * 3600
}
@property
def PROXY(self):
if self.PROXY_HOST:
return {
"http": self.PROXY_HOST,
"https": self.PROXY_HOST,
}
return None
@property
def PROXY_SERVER(self):
if self.PROXY_HOST:
return {
"server": self.PROXY_HOST
}
@property
def GITHUB_HEADERS(self):
"""
Github请求头
"""
if self.GITHUB_TOKEN:
return {
"Authorization": f"Bearer {self.GITHUB_TOKEN}"
}
return {}
@property
def DEFAULT_DOWNLOADER(self):
"""
默认下载器
"""
if not self.DOWNLOADER:
return None
return next((d for d in settings.DOWNLOADER.split(",") if d), None)
@property
def DOWNLOADERS(self):
"""
下载器列表
"""
if not self.DOWNLOADER:
return []
return [d for d in settings.DOWNLOADER.split(",") if d]
@property
def VAPID(self):
return {
"subject": f"mailto:{self.SUPERUSER}@movie-pilot.org",
"publicKey": "BH3w49sZA6jXUnE-yt4jO6VKh73lsdsvwoJ6Hx7fmPIDKoqGiUl2GEoZzy-iJfn4SfQQcx7yQdHf9RknwrL_lSM",
"privateKey": "JTixnYY0vEw97t9uukfO3UWKfHKJdT5kCQDiv3gu894"
}
def MP_DOMAIN(self, url: str = None):
if not self.APP_DOMAIN:
return None
domain = self.APP_DOMAIN.rstrip("/")
if not domain.startswith("http"):
domain = "http://" + domain
if not url:
return domain
return domain + "/" + url.lstrip("/")
def __init__(self, **kwargs):
super().__init__(**kwargs)
with self.CONFIG_PATH as p:
if not p.exists():
p.mkdir(parents=True, exist_ok=True)
if SystemUtils.is_frozen():
if not (p / "app.env").exists():
SystemUtils.copy(self.INNER_CONFIG_PATH / "app.env", p / "app.env")
with self.TEMP_PATH as p:
if not p.exists():
p.mkdir(parents=True, exist_ok=True)
with self.LOG_PATH as p:
if not p.exists():
p.mkdir(parents=True, exist_ok=True)
with self.COOKIE_PATH as p:
if not p.exists():
p.mkdir(parents=True, exist_ok=True)
class Config:
case_sensitive = True
class GlobalVar(object):
"""
全局标识
"""
# 系统停止事件
STOP_EVENT: threading.Event = threading.Event()
# webpush订阅
SUBSCRIPTIONS: List[dict] = []
def stop_system(self):
"""
停止系统
"""
self.STOP_EVENT.set()
def is_system_stopped(self):
"""
是否停止
"""
return self.STOP_EVENT.is_set()
def get_subscriptions(self):
"""
获取webpush订阅
"""
return self.SUBSCRIPTIONS
def push_subscription(self, subscription: dict):
"""
添加webpush订阅
"""
self.SUBSCRIPTIONS.append(subscription)
# 实例化配置
settings = Settings(
_env_file=Settings().CONFIG_PATH / "app.env",
_env_file_encoding="utf-8"
)
# 全局标识
global_vars = GlobalVar()