├── .gitignore ├── .gitlab-ci.yml ├── Dockerfile ├── Jenkinsfile ├── README.md ├── api ├── douban.py ├── media.py ├── media_config.py ├── mediasql.py ├── nastools.py ├── server │ ├── emby.py │ ├── jellyfin.py │ ├── plex.py │ └── serverbase.py ├── sql.py └── tmdb.py ├── config.default.yaml ├── config ├── config.default.yaml └── config.yaml ├── main.py ├── requirements.txt ├── start.bat ├── ver.txt └── 打包.bat /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | 3 | __pycache__/ 4 | *.py[cod] 5 | build/ 6 | dist/ 7 | *.spec 8 | config.yaml 9 | *.txt* 10 | !requirements.txt 11 | !ver*.txt 12 | config/* -------------------------------------------------------------------------------- /.gitlab-ci.yml: -------------------------------------------------------------------------------- 1 | services: 2 | - docker:dind 3 | 4 | stages: 5 | - build-and_push 6 | 7 | variables: 8 | DOCKER_DRIVER: overlay2 9 | DOCKER_HOST: tcp://docker:2375/ # 设置DOCKER_HOST指向docker服务 10 | DOCKER_TLS_CERTDIR: "" # 禁用TLS验证(仅在安全环境下使用) 11 | 12 | before_script: 13 | - docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD 14 | 15 | build-and_push: 16 | stage: build-and_push 17 | script: 18 | - docker build -t media_server_tools . 19 | - docker tag media_server_tools $DOCKER_HUB_USERNAME/media_server_tools:latest 20 | - docker push $DOCKER_HUB_USERNAME/media_server_tools:latest 21 | only: 22 | - main -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.8 2 | 3 | WORKDIR /app 4 | 5 | COPY . /app 6 | 7 | RUN pip install -r requirements.txt 8 | RUN pip install --index-url https://nexus3.smwap.top:88/repository/python/simple network 9 | RUN pip install --index-url https://nexus3.smwap.top:88/repository/python/simple system 10 | 11 | CMD ["python", "main.py"] 12 | -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | stages { 5 | stage('构建') { 6 | steps { 7 | script { 8 | sh 'docker build -t media_server_tools .' 9 | } 10 | } 11 | } 12 | 13 | stage('推送到Docker Hub') { 14 | steps { 15 | script { 16 | withCredentials([usernamePassword(credentialsId: 'docker-hub-credentials', usernameVariable: 'DOCKER_HUB_USERNAME', passwordVariable: 'DOCKER_HUB_PASSWORD')]) { 17 | sh "docker login -u $DOCKER_HUB_USERNAME -p $DOCKER_HUB_PASSWORD" 18 | sh 'docker tag media_server_tools sleikang/media_server_tools:latest' 19 | sh 'docker push sleikang/media_server_tools:latest' 20 | } 21 | } 22 | } 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # media_server_tools 2 | 3 |

Emby/Jellyfin/Plex 媒体中文自动同步

4 | 5 | 1. 中文标题 6 | 2. 媒体概述 7 | 3. 中文人名(Plex 暂时不支持) 8 | 4. 中文扮演(Plex 暂时不支持) 9 | 5. 剧集概述评分图片同步 10 | 6. 剧集组自定义同步 11 | 7. 媒体搜刮检查是否正确(配合 NasTools) 12 | 8. 替换豆瓣评分 13 |

注意使用本工具需要媒体服务器本身刮削了 tmdb 的完整数据,工具只是获取原有的数据进行替换

14 |

运行方式

15 | 16 | - 源码运行 17 | 18 | 1. 配置文件 config/config.yaml 19 | 2. win 下使用安装 Python3 安装过程连续点击下一步 20 | 3. 安装依赖模块 21 | 22 | - python -m pip install -r requirement.txt 23 | 24 | 4. 启动 cmd 输入 python main.py 25 | 26 | - exe 运行 27 | 28 | 1. 下载发布版本 29 | 2. 配置文件 config/config.yaml 30 | 3. 启动 media_server_tools.exe 31 | 32 | - docker compose 运行 33 | 34 | ``` 35 | version: '3' 36 | 37 | services: 38 | jd_server: 39 | image: sleikang/media_server_tools:latest 40 | container_name: media_server_tools 41 | environment: 42 | - TZ=Asia/Shanghai 43 | volumes: 44 | - /your_path/media_server_tools/log:/app/log #日志文件目录 45 | - /your_path/media_server_tools/config:/app/config #配置文件目录 46 | restart: always 47 | 48 | ``` 49 | 50 | - docker run 51 | 52 | ``` 53 | docker run -d \ 54 | --name media_server_tools \ 55 | -e TZ=Asia/Shanghai \ 56 | -v /your_path/media_server_tools/log:/app/log \ 57 | -v /your_path/media_server_tools/config:/app/config \ 58 | --restart always \ 59 | sleikang/media_server_tools:latest 60 | 61 | ``` 62 |

展示

63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /api/douban.py: -------------------------------------------------------------------------------- 1 | import json 2 | import re 3 | 4 | import html2text 5 | 6 | from network.network import Network 7 | 8 | 9 | class douban: 10 | host = None 11 | key = None 12 | mobileheaders = None 13 | pcheaders = None 14 | cookie = None 15 | client = None 16 | err = None 17 | 18 | def __init__(self, key: str, cookie: str) -> None: 19 | self.host = "https://frodo.douban.com/api/v2" 20 | self.key = key 21 | self.cookie = cookie 22 | self.mobileheaders = { 23 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.27(0x18001b33) NetType/WIFI Language/zh_CN", 24 | "Referer": "https://servicewechat.com/wx2f9b06c1de1ccfca/85/page-frame.html", 25 | "content-type": "application/json", 26 | "Connection": "keep-alive", 27 | } 28 | self.pcheaders = { 29 | "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36 Edg/105.0.1343.27", 30 | "Referer": "https://movie.douban.com/", 31 | "Cookie": self.cookie, 32 | "Connection": "keep-alive", 33 | } 34 | self.client = Network(maxnumconnect=10, maxnumcache=20) 35 | 36 | def get_movie_info(self, movieid: str): 37 | """ 38 | 获取电影信息 39 | :param movieid 电影ID 40 | """ 41 | iteminfo = {} 42 | try: 43 | url = "{}/movie/{}?apikey={}".format(self.host, movieid, self.key) 44 | p, err = self.client.get(url=url, headers=self.mobileheaders) 45 | if not self.__get_status__(p=p, err=err): 46 | return False, iteminfo 47 | iteminfo = json.loads(p.text) 48 | url = iteminfo["info_url"] 49 | p, err = self.client.get(url=url, headers=self.mobileheaders) 50 | if not self.__get_status__(p=p, err=err): 51 | return False, iteminfo 52 | text = html2text.html2text(html=p.text) 53 | text = text.replace("/\n", "/ ").replace("# 影片信息\n\n", "") 54 | infolist = re.findall(pattern="([\s\S]+?)\s*\|\s*([\s\S]+?)\n", string=text) 55 | iteminfo["info"] = {} 56 | for info in infolist: 57 | if info[0] == "---": 58 | continue 59 | valuelist = info[1].split(" / ") 60 | if len(valuelist) > 1: 61 | iteminfo["info"][info[0]] = [] 62 | for value in valuelist: 63 | iteminfo["info"][info[0]].append( 64 | re.sub(pattern="\s+", repl="", string=value) 65 | ) 66 | else: 67 | iteminfo["info"][info[0]] = re.sub( 68 | pattern="\s+", repl="", string=info[1] 69 | ) 70 | return True, iteminfo 71 | except Exception as result: 72 | self.err = "文件[{}]行[{}]异常错误:{}".format( 73 | result.__traceback__.tb_frame.f_globals["__file__"], 74 | result.__traceback__.tb_lineno, 75 | result, 76 | ) 77 | return False, iteminfo 78 | 79 | def get_movie_celebrities_info(self, movieid: str): 80 | """ 81 | 获取演员信息 82 | :param movieid 电影ID 83 | """ 84 | iteminfo = {} 85 | try: 86 | url = "{}/movie/{}/celebrities?apikey={}".format( 87 | self.host, movieid, self.key 88 | ) 89 | p, err = self.client.get(url=url, headers=self.mobileheaders) 90 | if not self.__get_status__(p=p, err=err): 91 | return False, iteminfo 92 | iteminfo = json.loads(p.text) 93 | return True, iteminfo 94 | except Exception as result: 95 | self.err = "文件[{}]行[{}]异常错误:{}".format( 96 | result.__traceback__.tb_frame.f_globals["__file__"], 97 | result.__traceback__.tb_lineno, 98 | result, 99 | ) 100 | return False, iteminfo 101 | 102 | def get_tv_info(self, tvid: str): 103 | """ 104 | 获取电视剧信息 105 | :param tvid 电影ID 106 | """ 107 | iteminfo = {} 108 | try: 109 | url = "{}/tv/{}?apikey={}".format(self.host, tvid, self.key) 110 | p, err = self.client.get(url=url, headers=self.mobileheaders) 111 | if not self.__get_status__(p=p, err=err): 112 | return False, iteminfo 113 | iteminfo = json.loads(p.text) 114 | if "info_url" not in iteminfo: 115 | self.err = "获取电视剧信息失败" 116 | return False, iteminfo 117 | url = iteminfo["info_url"] 118 | p, err = self.client.get(url=url, headers=self.mobileheaders) 119 | if not self.__get_status__(p=p, err=err): 120 | return False, iteminfo 121 | text = html2text.html2text(html=p.text) 122 | text = text.replace("/\n", "/ ").replace("# 影片信息\n\n", "") 123 | infolist = re.findall(pattern="([\s\S]+?)\s*\|\s*([\s\S]+?)\n", string=text) 124 | iteminfo["info"] = {} 125 | for info in infolist: 126 | if info[0] == "---": 127 | continue 128 | valuelist = info[1].split(" / ") 129 | if len(valuelist) > 1: 130 | iteminfo["info"][info[0]] = [] 131 | for value in valuelist: 132 | iteminfo["info"][info[0]].append( 133 | re.sub(pattern="\s+", repl="", string=value) 134 | ) 135 | else: 136 | iteminfo["info"][info[0]] = re.sub( 137 | pattern="\s+", repl="", string=info[1] 138 | ) 139 | return True, iteminfo 140 | except Exception as result: 141 | self.err = "文件[{}]行[{}]异常错误:{}".format( 142 | result.__traceback__.tb_frame.f_globals["__file__"], 143 | result.__traceback__.tb_lineno, 144 | result, 145 | ) 146 | return False, iteminfo 147 | 148 | def get_tv_celebrities_info(self, tvid: str): 149 | """ 150 | 获取演员信息 151 | :param tvid 电影ID 152 | """ 153 | iteminfo = {} 154 | try: 155 | url = "{}/tv/{}/celebrities?apikey={}".format(self.host, tvid, self.key) 156 | p, err = self.client.get(url=url, headers=self.mobileheaders) 157 | if not self.__get_status__(p=p, err=err): 158 | return False, iteminfo 159 | iteminfo = json.loads(p.text) 160 | return True, iteminfo 161 | except Exception as result: 162 | self.err = "文件[{}]行[{}]异常错误:{}".format( 163 | result.__traceback__.tb_frame.f_globals["__file__"], 164 | result.__traceback__.tb_lineno, 165 | result, 166 | ) 167 | return False, iteminfo 168 | 169 | def get_celebrity_info(self, celebrityid: str): 170 | """ 171 | 获取人物信息 172 | :param celebrityid 人物ID 173 | :param True of False, celebrityinfo 174 | """ 175 | iteminfo = {} 176 | try: 177 | url = "{}/celebrity/{}?apikey={}".format(self.host, celebrityid, self.key) 178 | p, err = self.client.get(url=url, headers=self.mobileheaders) 179 | if not self.__get_status__(p=p, err=err): 180 | return False, iteminfo 181 | iteminfo = json.loads(p.text) 182 | return True, iteminfo 183 | except Exception as result: 184 | self.err = "文件[{}]行[{}]异常错误:{}".format( 185 | result.__traceback__.tb_frame.f_globals["__file__"], 186 | result.__traceback__.tb_lineno, 187 | result, 188 | ) 189 | return False, iteminfo 190 | 191 | def search_media_pc(self, title: str): 192 | """ 193 | 搜索电影信息 194 | :param title 标题 195 | :return True or False, iteminfo 196 | """ 197 | iteminfo = {} 198 | try: 199 | url = "https://movie.douban.com/j/subject_suggest?q={}".format(title) 200 | p, err = self.client.get(url=url, headers=self.pcheaders) 201 | if not self.__get_status__(p=p, err=err): 202 | return False, iteminfo 203 | medialist = json.loads(p.text) 204 | for media in medialist: 205 | if ( 206 | re.search(pattern="第[\s\S]+季", string=media["title"]) 207 | or len(media["episode"]) > 1 208 | ): 209 | media["target_type"] = "tv" 210 | else: 211 | media["target_type"] = "movie" 212 | media["target_id"] = media["id"] 213 | iteminfo["items"] = medialist 214 | return True, iteminfo 215 | except Exception as result: 216 | self.err = "文件[{}]行[{}]异常错误:{}".format( 217 | result.__traceback__.tb_frame.f_globals["__file__"], 218 | result.__traceback__.tb_lineno, 219 | result, 220 | ) 221 | return False, iteminfo 222 | 223 | def search_media(self, title: str): 224 | """ 225 | 搜索电影信息 226 | :param title 标题 227 | :return True or False, iteminfo 228 | """ 229 | iteminfo = {} 230 | try: 231 | url = "{}/search/movie?q={}&start=0&count=2&apikey={}".format( 232 | self.host, title, self.key 233 | ) 234 | p, err = self.client.get(url=url, headers=self.mobileheaders) 235 | if not self.__get_status__(p=p, err=err): 236 | return False, iteminfo 237 | iteminfo = json.loads(p.text) 238 | return True, iteminfo 239 | except Exception as result: 240 | self.err = "文件[{}]行[{}]异常错误:{}".format( 241 | result.__traceback__.tb_frame.f_globals["__file__"], 242 | result.__traceback__.tb_lineno, 243 | result, 244 | ) 245 | return False, iteminfo 246 | 247 | def search_media_weixin(self, title: str): 248 | """ 249 | 搜索媒体信息 250 | :param title 标题 251 | :return True or False, iteminfo 252 | """ 253 | iteminfo = {} 254 | try: 255 | url = "{}/search/weixin?q={}&start=0&count=3&apikey={}".format( 256 | self.host, title, self.key 257 | ) 258 | p, err = self.client.get(url=url, headers=self.mobileheaders) 259 | if not self.__get_status__(p=p, err=err): 260 | return False, iteminfo 261 | iteminfo = json.loads(p.text) 262 | return True, iteminfo 263 | except Exception as result: 264 | self.err = "文件[{}]行[{}]异常错误:{}".format( 265 | result.__traceback__.tb_frame.f_globals["__file__"], 266 | result.__traceback__.tb_lineno, 267 | result, 268 | ) 269 | return False, iteminfo 270 | 271 | def __get_status__(self, p, err): 272 | try: 273 | if p == None: 274 | self.err = err 275 | return False 276 | if p.status_code != 200: 277 | rootobject = json.loads(p.text) 278 | if "localized_message" in rootobject: 279 | self.err = rootobject["localized_message"] 280 | else: 281 | self.err = p.text 282 | return False 283 | return True 284 | except Exception as result: 285 | self.err = "文件[{}]行[{}]异常错误:{}".format( 286 | result.__traceback__.tb_frame.f_globals["__file__"], 287 | result.__traceback__.tb_lineno, 288 | result, 289 | ) 290 | 291 | return False 292 | -------------------------------------------------------------------------------- /api/media.py: -------------------------------------------------------------------------------- 1 | import re 2 | import time 3 | from concurrent.futures import ThreadPoolExecutor 4 | from threading import BoundedSemaphore 5 | 6 | import zhconv 7 | 8 | from api.douban import douban 9 | from api.mediasql import mediasql 10 | from api.nastools import nastools 11 | from api.server.emby import emby 12 | from api.server.jellyfin import jellyfin 13 | from api.server.plex import plex 14 | from api.tmdb import tmdb 15 | from system.config import Config 16 | from system.log import log 17 | 18 | 19 | class media: 20 | media_server_type = None 21 | nas_tools_client = None 22 | meidia_server_client = None 23 | tmdb_client = None 24 | douban_client = None 25 | language_list = None 26 | thread_pool = None 27 | update_people = None 28 | update_overview = None 29 | update_season_group = None 30 | douban_api_space = None 31 | del_not_image_people = None 32 | check_media_search = None 33 | config_load = None 34 | sql_client = None 35 | config_info = None 36 | thread_num = None 37 | semaphore = None 38 | exclude_path = None 39 | exclude_media = None 40 | update_title = None 41 | update_score = None 42 | 43 | def __init__(self) -> None: 44 | """ 45 | :param configinfo 配置 46 | """ 47 | try: 48 | self.config_info = Config().get_config() 49 | self.media_server_type = self.config_info["system"]["mediaserver"] 50 | self.config_load = False 51 | self.sql_client = mediasql() 52 | self.nas_tools_client = nastools( 53 | host=self.config_info["api"]["nastools"]["host"], 54 | authorization=self.config_info["api"]["nastools"]["authorization"], 55 | username=self.config_info["api"]["nastools"]["username"], 56 | passwd=self.config_info["api"]["nastools"]["passwd"], 57 | ) 58 | 59 | self.tmdb_client = tmdb( 60 | key=self.config_info["api"]["tmdb"]["key"], 61 | host=self.config_info["api"]["tmdb"].get( 62 | "host", "https://api.themoviedb.org/3" 63 | ), 64 | proxy=self.config_info["api"]["tmdb"].get("proxy", None), 65 | ) 66 | self.douban_client = douban( 67 | key=self.config_info["api"]["douban"]["key"], 68 | cookie=self.config_info["api"]["douban"]["cookie"], 69 | ) 70 | self.language_list = ["zh-CN", "zh-SG", "zh-TW", "zh-HK"] 71 | self.thread_num = self.config_info["system"]["threadnum"] 72 | self.semaphore = BoundedSemaphore(self.thread_num) 73 | self.thread_pool = ThreadPoolExecutor(max_workers=self.thread_num) 74 | self.update_people = self.config_info["system"]["updatepeople"] 75 | self.update_overview = self.config_info["system"]["updateoverview"] 76 | self.douban_api_space = self.config_info["system"]["doubanapispace"] 77 | self.del_not_image_people = self.config_info["system"]["delnotimagepeople"] 78 | self.update_season_group = self.config_info["system"]["updateseasongroup"] 79 | self.check_media_search = self.config_info["system"]["checkmediasearch"] 80 | self.exclude_path = self.config_info["system"]["excludepath"] 81 | self.exclude_media = self.config_info["system"]["excludemedia"] 82 | self.update_title = self.config_info["system"]["updatetitle"] 83 | self.update_score = self.config_info["system"]["updatescore"] 84 | if "Emby" in self.media_server_type: 85 | self.meidia_server_client = emby( 86 | host=self.config_info["api"]["emby"]["host"], 87 | userid=self.config_info["api"]["emby"]["userid"], 88 | key=self.config_info["api"]["emby"]["key"], 89 | ) 90 | self.config_load = True 91 | elif "Jellyfin" in self.media_server_type: 92 | self.meidia_server_client = jellyfin( 93 | host=self.config_info["api"]["jellyfin"]["host"], 94 | userid=self.config_info["api"]["jellyfin"]["userid"], 95 | key=self.config_info["api"]["jellyfin"]["key"], 96 | ) 97 | self.config_load = True 98 | elif "Plex" in self.media_server_type: 99 | self.meidia_server_client = plex( 100 | host=self.config_info["api"]["plex"]["host"], 101 | userid="", 102 | key=self.config_info["api"]["plex"]["key"], 103 | ) 104 | self.config_load = True 105 | else: 106 | log().logger.info( 107 | "当前设置媒体服务器[{}]不支持".format(self.media_server_type) 108 | ) 109 | self.config_load = False 110 | 111 | except Exception as result: 112 | log().logger.info("配置异常错误, {}".format(result)) 113 | 114 | def start_scan_media(self): 115 | """ 116 | 开始扫描媒体 117 | :return True or False 118 | """ 119 | if not self.config_load: 120 | log().logger.info("配置未正常加载") 121 | return False 122 | # 获取媒体库根文件夹 123 | ret, itmes = self.meidia_server_client.get_items() 124 | if not ret: 125 | log().logger.info( 126 | "获取[{}]媒体总列表失败, {}".format( 127 | self.media_server_type, self.meidia_server_client.err 128 | ) 129 | ) 130 | return False 131 | ret, iteminfo = self.meidia_server_client.get_items_count() 132 | log().logger.info( 133 | "总媒体数量[{}]".format(iteminfo["MovieCount"] + iteminfo["SeriesCount"]) 134 | ) 135 | if not ret: 136 | log().logger.info( 137 | "获取[{}]媒体数量失败, {}".format( 138 | self.media_server_type, self.meidia_server_client.err 139 | ) 140 | ) 141 | return False 142 | 143 | ret = True 144 | 145 | for item in self.__check_media_info__(itemlist=itmes): 146 | if not item: 147 | continue 148 | self.__submit_task__(item=item) 149 | while self.semaphore._value != self.thread_num: 150 | time.sleep(0.1) 151 | return ret 152 | 153 | def __submit_task__(self, item): 154 | """ 155 | 提交任务 156 | :param item 157 | """ 158 | self.semaphore.acquire() 159 | self.thread_pool.submit(self.__start_task__, item) 160 | 161 | def __start_task__(self, item): 162 | """ 163 | 开始任务 164 | :param item 165 | """ 166 | ret, name = self.__to_deal_with_item__(item=item) 167 | if ret: 168 | log().logger.info("媒体[{}]处理完成".format(name)) 169 | else: 170 | log().logger.info("媒体[{}]处理失败".format(name)) 171 | self.semaphore.release() 172 | 173 | def __check_media_info__(self, itemlist): 174 | """ 175 | 检查媒体信息 176 | :param itemlist 项目列表 177 | :return True or False 178 | """ 179 | try: 180 | for item in itemlist["Items"]: 181 | if "Folder" in item["Type"] and ( 182 | "CollectionType" not in item 183 | or "boxsets" not in item["CollectionType"] 184 | ): 185 | # 排除文件夹 186 | is_exclude_media = False 187 | for exclude in self.exclude_path: 188 | match = re.match(exclude, item["Name"]) 189 | if match: 190 | is_exclude_media = True 191 | break 192 | if is_exclude_media: 193 | log().logger.info( 194 | "排除[{}]媒体文件夹[{}]".format( 195 | self.media_server_type, 196 | item["Name"], 197 | ) 198 | ) 199 | continue 200 | ret, items = self.meidia_server_client.get_items( 201 | parentid=item["Id"] 202 | ) 203 | if not ret: 204 | log().logger.info( 205 | "获取[{}]媒体[{}]ID[{}]列表失败, {}".format( 206 | self.media_server_type, 207 | item["Name"], 208 | item["Id"], 209 | self.meidia_server_client.err, 210 | ) 211 | ) 212 | continue 213 | for tmpitem in self.__check_media_info__(itemlist=items): 214 | yield tmpitem 215 | else: 216 | if "Series" in item["Type"] or "Movie" in item["Type"]: 217 | # 排除文件夹 218 | is_exclude_media = False 219 | for exclude in self.exclude_media: 220 | match = re.match(exclude, item["Name"]) 221 | if match: 222 | is_exclude_media = True 223 | break 224 | if is_exclude_media: 225 | log().logger.info( 226 | "排除[{}]媒体[{}]".format( 227 | self.media_server_type, 228 | item["Name"], 229 | ) 230 | ) 231 | continue 232 | yield item 233 | 234 | except Exception as result: 235 | log().logger.info( 236 | "异常错误:{}".format( 237 | result, 238 | ) 239 | ) 240 | 241 | yield {} 242 | 243 | def __check_media_info_search__(self, item): 244 | """ 245 | 检查媒体信息搜索是否正确 246 | :param item 媒体信息 247 | """ 248 | try: 249 | ret, iteminfo = self.meidia_server_client.get_item_info(itemid=item["Id"]) 250 | if not ret: 251 | log().logger.info( 252 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 253 | self.media_server_type, 254 | item["Name"], 255 | item["Id"], 256 | self.meidia_server_client.err, 257 | ) 258 | ) 259 | return False 260 | tmdbid = "" 261 | if "Tmdb" in iteminfo["ProviderIds"]: 262 | tmdbid = iteminfo["ProviderIds"]["Tmdb"] 263 | elif "tmdb" in iteminfo["ProviderIds"]: 264 | tmdbid = iteminfo["ProviderIds"]["tmdb"] 265 | name = re.sub( 266 | pattern="\\s+-\\s+\\d{1,4}[k|K|p|P]|\\s+\\(\\d{4}\\)|\\.\\S{1,4}$", 267 | repl="", 268 | string=iteminfo["FileName"], 269 | ) 270 | year = None 271 | redata = re.search(pattern="\\((\\d{4})\\)", string=iteminfo["FileName"]) 272 | if redata: 273 | year = redata.group(1) 274 | if ( 275 | name in item["Name"] 276 | and ( 277 | year 278 | and "ProductionYear" in iteminfo 279 | and year in str(iteminfo["ProductionYear"]) 280 | ) 281 | and tmdbid 282 | ): 283 | return True 284 | mediatype = "MOV" 285 | if "Movie" in item["Type"]: 286 | mediatype = "MOV" 287 | else: 288 | mediatype = "TV" 289 | ret, mediainfo = self.nas_tools_client.media_info( 290 | name=name, year=year, type=mediatype 291 | ) 292 | if not ret: 293 | log().logger.info( 294 | "[{}]媒体名称[{}]与原始名称[{}]不一致可能识别错误, NasTools识别媒体[{}]失败, {}".format( 295 | self.media_server_type, 296 | item["Name"], 297 | iteminfo["FileName"], 298 | name, 299 | self.nas_tools_client.err, 300 | ) 301 | ) 302 | return False 303 | testtmdbid = None 304 | if year and year != mediainfo["year"]: 305 | return False 306 | if mediainfo["tmdbid"] > 0: 307 | testtmdbid = str(mediainfo["tmdbid"]) 308 | if not testtmdbid or tmdbid == testtmdbid: 309 | return True 310 | if "Series" in item["Type"]: 311 | ret, tvinfo = self.__get_tmdb_media_info__( 312 | mediatype=1, name=item["Name"], id=testtmdbid 313 | ) 314 | if not ret: 315 | return False 316 | if len(tvinfo["seasons"]) < iteminfo["ChildCount"]: 317 | return False 318 | 319 | ret, searchinfo = self.meidia_server_client.search_movie( 320 | itemid=item["Id"], tmdbid=testtmdbid, name=name, year=year 321 | ) 322 | if not ret: 323 | log().logger.info( 324 | "[{}]搜索媒体[{}]ID[{}]TMDB[{}]信息失败, {}".format( 325 | self.media_server_type, 326 | item["Name"], 327 | item["Id"], 328 | testtmdbid, 329 | self.meidia_server_client.err, 330 | ) 331 | ) 332 | return False 333 | for info in searchinfo: 334 | if "Plex" not in self.media_server_type: 335 | info["Type"] = iteminfo["Type"] 336 | info["IsFolder"] = iteminfo["IsFolder"] 337 | ret = self.meidia_server_client.apply_search( 338 | itemid=item["Id"], iteminfo=info 339 | ) 340 | if not ret: 341 | log().logger.info( 342 | "[{}]更新媒体[{}]ID[{}]TMDB[{}]信息失败, {}".format( 343 | self.media_server_type, 344 | item["Name"], 345 | item["Id"], 346 | testtmdbid, 347 | self.meidia_server_client.err, 348 | ) 349 | ) 350 | return False 351 | log().logger.info( 352 | "[{}]更新媒体[{}]ID[{}]TMDB[{}]更新为媒体[{}]TMDB[{}]".format( 353 | self.media_server_type, 354 | item["Name"], 355 | item["Id"], 356 | tmdbid, 357 | mediainfo["title"], 358 | testtmdbid, 359 | ) 360 | ) 361 | item["Name"] = mediainfo["title"] 362 | break 363 | return True 364 | except Exception as result: 365 | log().logger.info( 366 | "异常错误:{}".format( 367 | result, 368 | ) 369 | ) 370 | return False 371 | 372 | def __to_deal_with_item__(self, item): 373 | """ 374 | 处理媒体 375 | :param item 376 | :return True or False 377 | """ 378 | try: 379 | if self.check_media_search: 380 | _ = self.__check_media_info_search__(item=item) 381 | 382 | update_name = False 383 | update_people = False 384 | update_overview = False 385 | update_score = False 386 | 387 | ret, item_info = self.meidia_server_client.get_item_info(itemid=item["Id"]) 388 | if not ret: 389 | log().logger.info( 390 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 391 | self.media_server_type, 392 | item["Name"], 393 | item["Id"], 394 | self.meidia_server_client.err, 395 | ) 396 | ) 397 | return False, item["Name"] 398 | if "LockedFields" not in item_info: 399 | item_info["LockedFields"] = [] 400 | tmdbid = None 401 | imdbid = None 402 | if "Tmdb" in item_info["ProviderIds"]: 403 | tmdbid = item_info["ProviderIds"]["Tmdb"] 404 | elif "tmdb" in item_info["ProviderIds"]: 405 | tmdbid = item_info["ProviderIds"]["tmdb"] 406 | 407 | if "Imdb" in item_info["ProviderIds"]: 408 | imdbid = item_info["ProviderIds"]["Imdb"] 409 | elif "imdb" in item_info["ProviderIds"]: 410 | imdbid = item_info["ProviderIds"]["imdb"] 411 | 412 | douban_media_info = None 413 | original_name = item_info["Name"] 414 | if "CommunityRating" not in item_info: 415 | item_info["CommunityRating"] = "0" 416 | original_score = item_info["CommunityRating"] 417 | if self.update_title: 418 | if not self.__is_chinese__(string=item["Name"]): 419 | if not tmdbid and not imdbid: 420 | log().logger.info( 421 | "[{}]媒体[{}]ID[{}]Tmdb|Imdb不存在".format( 422 | self.media_server_type, item["Name"], item["Id"] 423 | ) 424 | ) 425 | return False, item["Name"] 426 | name = None 427 | if tmdbid: 428 | if "Series" in item["Type"]: 429 | ret, name = self.__get_tmdb_media_name__( 430 | mediatype=1, datatype=1, name=item["Name"], id=tmdbid 431 | ) 432 | else: 433 | ret, name = self.__get_tmdb_media_name__( 434 | mediatype=2, datatype=1, name=item["Name"], id=tmdbid 435 | ) 436 | if imdbid and not name: 437 | if "Series" in item["Type"]: 438 | ret, douban_media_info = self.__get_douban_media_info__( 439 | mediatype=1, name=item["Name"], id=imdbid 440 | ) 441 | else: 442 | ret, douban_media_info = self.__get_douban_media_info__( 443 | mediatype=2, name=item["Name"], id=imdbid 444 | ) 445 | if ret: 446 | if self.__is_chinese__( 447 | string=douban_media_info["title"], mode=2 448 | ): 449 | name = douban_media_info["title"] 450 | 451 | if name: 452 | 453 | item_info["Name"] = name 454 | if "Name" not in item_info["LockedFields"]: 455 | item_info["LockedFields"].append("Name") 456 | update_name = True 457 | 458 | if self.update_score: 459 | if imdbid: 460 | if not douban_media_info: 461 | if "Series" in item["Type"]: 462 | ret, douban_media_info = self.__get_douban_media_info__( 463 | mediatype=1, name=item["Name"], id=imdbid 464 | ) 465 | else: 466 | ret, douban_media_info = self.__get_douban_media_info__( 467 | mediatype=2, name=item["Name"], id=imdbid 468 | ) 469 | if douban_media_info: 470 | score = douban_media_info["rating"]["value"] 471 | if "CommunityRating" not in item_info["LockedFields"]: 472 | item_info["LockedFields"].append("CommunityRating") 473 | if score > 0 and score != original_score: 474 | update_score = True 475 | item_info["CommunityRating"] = score 476 | 477 | if self.update_people and "People" in item_info: 478 | update_people = self.__update_people__( 479 | item=item, iteminfo=item_info, imdbid=imdbid 480 | ) 481 | 482 | if "Series" in item["Type"]: 483 | ret, seasons = self.meidia_server_client.get_items( 484 | parentid=item["Id"], type="Season" 485 | ) 486 | if not ret: 487 | log().logger.info( 488 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 489 | self.media_server_type, 490 | item["Name"], 491 | item["Id"], 492 | self.meidia_server_client.err, 493 | ) 494 | ) 495 | else: 496 | for season in seasons["Items"]: 497 | if "Jellyfin" in self.media_server_type: 498 | ret, seasoninfo = ( 499 | self.meidia_server_client.get_item_info( 500 | itemid=season["Id"] 501 | ) 502 | ) 503 | if not ret: 504 | log().logger.info( 505 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 506 | self.media_server_type, 507 | season["Name"], 508 | season["Id"], 509 | self.meidia_server_client.err, 510 | ) 511 | ) 512 | continue 513 | ret = self.__update_people__( 514 | item=season, iteminfo=seasoninfo, imdbid=imdbid 515 | ) 516 | if not ret: 517 | continue 518 | if ret: 519 | _ = self.__refresh_people__( 520 | item=season, iteminfo=seasoninfo 521 | ) 522 | log().logger.info( 523 | "原始媒体名称[{}] 第[{}]季更新人物".format( 524 | item_info["Name"], season["IndexNumber"] 525 | ) 526 | ) 527 | ret = self.meidia_server_client.set_item_info( 528 | itemid=seasoninfo["Id"], iteminfo=seasoninfo 529 | ) 530 | ret, episodes = self.meidia_server_client.get_items( 531 | parentid=season["Id"], type="Episode" 532 | ) 533 | if not ret: 534 | log().logger.info( 535 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 536 | self.media_server_type, 537 | season["Name"], 538 | season["Id"], 539 | self.meidia_server_client.err, 540 | ) 541 | ) 542 | continue 543 | for episode in episodes["Items"]: 544 | ( 545 | ret, 546 | episodeinfo, 547 | ) = self.meidia_server_client.get_item_info( 548 | itemid=episode["Id"] 549 | ) 550 | if not ret: 551 | log().logger.info( 552 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 553 | self.media_server_type, 554 | episode["Name"], 555 | episode["Id"], 556 | self.meidia_server_client.err, 557 | ) 558 | ) 559 | else: 560 | ret = self.__update_people__( 561 | item=episode, 562 | iteminfo=episodeinfo, 563 | imdbid=imdbid, 564 | ) 565 | if not ret: 566 | continue 567 | ret = self.meidia_server_client.set_item_info( 568 | itemid=episodeinfo["Id"], iteminfo=episodeinfo 569 | ) 570 | if ret: 571 | if "Jellyfin" in self.media_server_type: 572 | _ = self.__refresh_people__( 573 | item=episode, iteminfo=episodeinfo 574 | ) 575 | log().logger.info( 576 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新人物".format( 577 | item_info["Name"], 578 | season["IndexNumber"], 579 | episode["IndexNumber"], 580 | ) 581 | ) 582 | 583 | if self.update_overview: 584 | if "Overview" not in item_info or not self.__is_chinese__( 585 | string=item_info["Overview"] 586 | ): 587 | if not tmdbid and not imdbid: 588 | log().logger.info( 589 | "[{}]媒体[{}]ID[{}]Tmdb|Imdb不存在".format( 590 | self.media_server_type, item["Name"], item["Id"] 591 | ) 592 | ) 593 | return False, item["Name"] 594 | ret = False 595 | if tmdbid: 596 | if "Series" in item["Type"]: 597 | ret, overview = self.__get_tmdb_media_name__( 598 | mediatype=1, datatype=2, name=item["Name"], id=tmdbid 599 | ) 600 | else: 601 | ret, overview = self.__get_tmdb_media_name__( 602 | mediatype=2, datatype=2, name=item["Name"], id=tmdbid 603 | ) 604 | if ret: 605 | item_info["Overview"] = overview 606 | if "Overview" not in item_info["LockedFields"]: 607 | item_info["LockedFields"].append("Overview") 608 | update_overview = True 609 | elif imdbid: 610 | if not douban_media_info: 611 | if "Series" in item["Type"]: 612 | ret, douban_media_info = self.__get_douban_media_info__( 613 | mediatype=1, name=item["Name"], id=imdbid 614 | ) 615 | else: 616 | ret, douban_media_info = self.__get_douban_media_info__( 617 | mediatype=2, name=item["Name"], id=imdbid 618 | ) 619 | if douban_media_info: 620 | if douban_media_info["intro"]: 621 | item_info["Overview"] = douban_media_info["intro"] 622 | if "Overview" not in item_info["LockedFields"]: 623 | item_info["LockedFields"].append("Overview") 624 | update_overview = True 625 | 626 | if "Series" in item["Type"]: 627 | ret, seasons = self.meidia_server_client.get_items( 628 | parentid=item["Id"], type="Season" 629 | ) 630 | if not ret: 631 | log().logger.info( 632 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 633 | self.media_server_type, 634 | item["Name"], 635 | item["Id"], 636 | self.meidia_server_client.err, 637 | ) 638 | ) 639 | else: 640 | seasongroupinfo = None 641 | groupid = None 642 | if self.update_season_group: 643 | for groupinfo in self.config_info["system"]["seasongroup"]: 644 | infolist = groupinfo.split("|") 645 | if len(infolist) < 2: 646 | continue 647 | if infolist[0] == item["Name"]: 648 | groupid = infolist[1] 649 | break 650 | for season in seasons["Items"]: 651 | ret, episodes = self.meidia_server_client.get_items( 652 | parentid=season["Id"], type="Episode" 653 | ) 654 | if not ret: 655 | log().logger.info( 656 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 657 | self.media_server_type, 658 | season["Name"], 659 | season["Id"], 660 | self.meidia_server_client.err, 661 | ) 662 | ) 663 | continue 664 | for episode in episodes["Items"]: 665 | ( 666 | ret, 667 | episodeinfo, 668 | ) = self.meidia_server_client.get_item_info( 669 | itemid=episode["Id"] 670 | ) 671 | if not ret: 672 | log().logger.info( 673 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 674 | self.media_server_type, 675 | episode["Name"], 676 | episode["Id"], 677 | self.meidia_server_client.err, 678 | ) 679 | ) 680 | else: 681 | imageurl = None 682 | name = None 683 | overview = None 684 | ommunityrating = None 685 | if not groupid: 686 | if ( 687 | "Overview" not in episodeinfo 688 | or not self.__is_chinese__( 689 | string=episodeinfo["Overview"] 690 | ) 691 | ): 692 | ( 693 | ret, 694 | name, 695 | overview, 696 | ommunityrating, 697 | imageurl, 698 | ) = self.__get_tmdb_tv_season_info__( 699 | name=item["Name"], 700 | tvid=tmdbid, 701 | seasonid=season["IndexNumber"], 702 | episodeid=episode["IndexNumber"], 703 | ) 704 | if not ret: 705 | continue 706 | else: 707 | if not seasongroupinfo: 708 | ( 709 | ret, 710 | seasongroupinfo, 711 | ) = self.__get_tmdb_tv_season_group_info__( 712 | name=item["Name"], groupid=groupid 713 | ) 714 | if not ret: 715 | continue 716 | tmdbepisodeinfo = None 717 | for seasondata in seasongroupinfo["groups"]: 718 | if ( 719 | seasondata["order"] 720 | != season["IndexNumber"] 721 | ): 722 | continue 723 | for episodedata in seasondata["episodes"]: 724 | if ( 725 | episodedata["order"] + 1 726 | != episode["IndexNumber"] 727 | ): 728 | continue 729 | tmdbepisodeinfo = episodedata 730 | break 731 | if tmdbepisodeinfo: 732 | break 733 | if not tmdbepisodeinfo: 734 | log().logger.info( 735 | "原始媒体名称[{}] 第[{}]季 第[{}]集未匹配到TMDB剧集组数据".format( 736 | item_info["Name"], 737 | season["IndexNumber"], 738 | episode["IndexNumber"], 739 | ) 740 | ) 741 | continue 742 | if ( 743 | tmdbepisodeinfo["name"] 744 | != episodeinfo["Name"] 745 | ): 746 | name = tmdbepisodeinfo["name"] 747 | if ( 748 | "Overview" not in episodeinfo 749 | or tmdbepisodeinfo["overview"] 750 | != episodeinfo["Overview"] 751 | ): 752 | overview = tmdbepisodeinfo["overview"] 753 | if ( 754 | "still_path" in tmdbepisodeinfo 755 | and tmdbepisodeinfo["still_path"] 756 | ): 757 | imageurl = "https://www.themoviedb.org/t/p/original{}".format( 758 | tmdbepisodeinfo["still_path"] 759 | ) 760 | if ( 761 | "CommunityRating" not in episodeinfo 762 | or episodeinfo["CommunityRating"] 763 | != tmdbepisodeinfo["vote_average"] 764 | ): 765 | ommunityrating = tmdbepisodeinfo[ 766 | "vote_average" 767 | ] 768 | 769 | if not name and not overview and not ommunityrating: 770 | continue 771 | if "LockedFields" not in episodeinfo: 772 | episodeinfo["LockedFields"] = [] 773 | if name: 774 | episodeinfo["Name"] = name 775 | if overview: 776 | episodeinfo["Overview"] = overview 777 | if ommunityrating: 778 | episodeinfo["CommunityRating"] = ommunityrating 779 | if "Name" not in episodeinfo["LockedFields"]: 780 | episodeinfo["LockedFields"].append("Name") 781 | if "Overview" not in episodeinfo["LockedFields"]: 782 | episodeinfo["LockedFields"].append("Overview") 783 | ret = self.meidia_server_client.set_item_info( 784 | itemid=episodeinfo["Id"], iteminfo=episodeinfo 785 | ) 786 | if ret: 787 | if overview: 788 | log().logger.info( 789 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新概述".format( 790 | item_info["Name"], 791 | season["IndexNumber"], 792 | episode["IndexNumber"], 793 | ) 794 | ) 795 | if ommunityrating: 796 | log().logger.info( 797 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新评分".format( 798 | item_info["Name"], 799 | season["IndexNumber"], 800 | episode["IndexNumber"], 801 | ) 802 | ) 803 | if imageurl: 804 | ret = self.meidia_server_client.set_item_image( 805 | itemid=episodeinfo["Id"], imageurl=imageurl 806 | ) 807 | if ret: 808 | log().logger.info( 809 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新图片".format( 810 | item_info["Name"], 811 | season["IndexNumber"], 812 | episode["IndexNumber"], 813 | ) 814 | ) 815 | 816 | if ( 817 | not update_name 818 | and not update_people 819 | and not update_overview 820 | and not update_score 821 | ): 822 | return True, item["Name"] 823 | ret = self.meidia_server_client.set_item_info( 824 | itemid=item_info["Id"], iteminfo=item_info 825 | ) 826 | if ret: 827 | if update_name: 828 | log().logger.info( 829 | "原始媒体名称[{}]更新为[{}]".format( 830 | original_name, item_info["Name"] 831 | ) 832 | ) 833 | if update_score: 834 | log().logger.info( 835 | "原始媒体名称[{}]更新豆瓣评分[{}]".format( 836 | item_info["Name"], item_info["CommunityRating"] 837 | ) 838 | ) 839 | if update_people: 840 | if "Jellyfin" in self.media_server_type: 841 | _ = self.__refresh_people__(item=item, iteminfo=item_info) 842 | log().logger.info( 843 | "原始媒体名称[{}]更新人物".format(item_info["Name"]) 844 | ) 845 | if update_overview: 846 | log().logger.info( 847 | "原始媒体名称[{}]更新概述".format(item_info["Name"]) 848 | ) 849 | 850 | return True, item["Name"] 851 | 852 | except Exception as result: 853 | log().logger.info( 854 | "异常错误:{}".format( 855 | result, 856 | ) 857 | ) 858 | return False, item["Name"] 859 | 860 | def __refresh_people__(self, item, iteminfo): 861 | """ 862 | 刷新人物元数据 863 | :param iteminfo 项目信息 864 | :return True of False 865 | """ 866 | try: 867 | ret, newiteminfo = self.meidia_server_client.get_item_info( 868 | itemid=item["Id"] 869 | ) 870 | if not ret: 871 | log().logger.info( 872 | "获取[{}]媒体[{}]ID[{}]信息失败, {}".format( 873 | self.media_server_type, 874 | item["Name"], 875 | item["Id"], 876 | self.meidia_server_client.err, 877 | ) 878 | ) 879 | return False 880 | for people, newpeople in zip(iteminfo["People"], newiteminfo["People"]): 881 | if ( 882 | people["Id"] in newpeople["Id"] 883 | or people["Name"] not in newpeople["Name"] 884 | ): 885 | continue 886 | ret, peopleinfo = self.meidia_server_client.get_item_info( 887 | itemid=people["Id"] 888 | ) 889 | if not ret: 890 | log().logger.info( 891 | "获取[{}]人物信息失败, {}".format( 892 | self.media_server_type, self.meidia_server_client.err 893 | ) 894 | ) 895 | continue 896 | ret, newpeopleinfo = self.meidia_server_client.get_item_info( 897 | itemid=newpeople["Id"] 898 | ) 899 | if not ret: 900 | log().logger.info( 901 | "获取[{}]人物信息失败, {}".format( 902 | self.media_server_type, self.meidia_server_client.err 903 | ) 904 | ) 905 | continue 906 | newpeopleinfo["ProviderIds"] = peopleinfo["ProviderIds"] 907 | ret = self.meidia_server_client.set_item_info( 908 | itemid=newpeopleinfo["Id"], iteminfo=newpeopleinfo 909 | ) 910 | if not ret: 911 | log().logger.info( 912 | "更新[{}]人物信息失败, {}".format( 913 | self.media_server_type, self.meidia_server_client.err 914 | ) 915 | ) 916 | continue 917 | ret, newpeopleinfo = self.meidia_server_client.get_item_info( 918 | itemid=newpeople["Id"] 919 | ) 920 | if not ret: 921 | log().logger.info( 922 | "获取[{}]人物信息失败, {}".format( 923 | self.media_server_type, self.meidia_server_client.err 924 | ) 925 | ) 926 | continue 927 | """ 928 | ret = self.meidiaserverclient.refresh(peopleinfo['Id']) 929 | if not ret: 930 | log().logger.info('刷新[{}]人物信息失败, {}'.format(self.mediaservertype, self.meidiaserverclient.err)) 931 | """ 932 | 933 | return True 934 | except Exception as result: 935 | log().logger.info("异常错误: {}".format(result)) 936 | return False 937 | 938 | def __update_people__(self, item, iteminfo, imdbid): 939 | """ 940 | 更新人物 941 | :param item 项目 942 | :param iteminfo 项目信息 943 | :param imdbid IMDB ID 944 | :return True of False 945 | """ 946 | updatepeople = False 947 | try: 948 | doubanmediainfo = None 949 | doubancelebritiesinfo = None 950 | needdelpeople = [] 951 | for people in iteminfo["People"]: 952 | ret, peopleinfo = self.meidia_server_client.get_item_info( 953 | itemid=people["Id"] 954 | ) 955 | if not ret: 956 | log().logger.info( 957 | "获取[{}]人物信息失败, {}".format( 958 | self.media_server_type, self.meidia_server_client.err 959 | ) 960 | ) 961 | continue 962 | 963 | peopleimdbid = None 964 | peopletmdbid = None 965 | peoplename = None 966 | if "Tmdb" in peopleinfo["ProviderIds"]: 967 | peopletmdbid = peopleinfo["ProviderIds"]["Tmdb"] 968 | elif "tmdb" in peopleinfo["ProviderIds"]: 969 | peopletmdbid = peopleinfo["ProviderIds"]["tmdb"] 970 | 971 | if "Imdb" in peopleinfo["ProviderIds"]: 972 | peopleimdbid = peopleinfo["ProviderIds"]["Imdb"] 973 | elif "imdb" in peopleinfo["ProviderIds"]: 974 | peopleimdbid = peopleinfo["ProviderIds"]["imdb"] 975 | 976 | if not self.__is_chinese__(string=people["Name"], mode=1): 977 | if "LockedFields" not in peopleinfo: 978 | peopleinfo["LockedFields"] = [] 979 | 980 | if not peopletmdbid and not peopleimdbid: 981 | log().logger.info( 982 | "[{}]人物[{}]ID[{}]Tmdb|Imdb不存在".format( 983 | self.media_server_type, 984 | peopleinfo["Name"], 985 | peopleinfo["Id"], 986 | ) 987 | ) 988 | needdelpeople.append(peopleinfo["Id"]) 989 | continue 990 | 991 | if peopleimdbid and imdbid: 992 | if not doubanmediainfo: 993 | if "Series" in item["Type"]: 994 | ret, doubanmediainfo = self.__get_douban_media_info__( 995 | mediatype=1, name=item["Name"], id=imdbid 996 | ) 997 | else: 998 | ret, doubanmediainfo = self.__get_douban_media_info__( 999 | mediatype=2, name=item["Name"], id=imdbid 1000 | ) 1001 | 1002 | if doubanmediainfo and not doubancelebritiesinfo: 1003 | if "Series" in item["Type"]: 1004 | ( 1005 | ret, 1006 | doubancelebritiesinfo, 1007 | ) = self.__get_douban_media_celebrities_info__( 1008 | mediatype=1, 1009 | name=item["Name"], 1010 | id=doubanmediainfo["id"], 1011 | ) 1012 | else: 1013 | ( 1014 | ret, 1015 | doubancelebritiesinfo, 1016 | ) = self.__get_douban_media_celebrities_info__( 1017 | mediatype=2, 1018 | name=item["Name"], 1019 | id=doubanmediainfo["id"], 1020 | ) 1021 | 1022 | if doubancelebritiesinfo: 1023 | ret, celebrities = self.__get_people_info__( 1024 | celebritiesinfo=doubancelebritiesinfo, 1025 | people=people, 1026 | imdbid=peopleimdbid, 1027 | ) 1028 | if ret and self.__is_chinese__(string=celebrities["name"]): 1029 | peoplename = re.sub( 1030 | pattern="\\s+", repl="", string=celebrities["name"] 1031 | ) 1032 | if not peoplename: 1033 | if self.__is_chinese__(string=peopleinfo["Name"], mode=2): 1034 | peoplename = re.sub( 1035 | pattern="\\s+", repl="", string=peopleinfo["Name"] 1036 | ) 1037 | peoplename = zhconv.convert(peopleinfo["Name"], "zh-cn") 1038 | elif peopletmdbid: 1039 | ret, peoplename = self.__get_tmdb_person_name( 1040 | name=peopleinfo["Name"], personid=peopletmdbid 1041 | ) 1042 | 1043 | if peoplename: 1044 | originalpeoplename = people["Name"] 1045 | peopleinfo["Name"] = peoplename 1046 | if "Name" not in peopleinfo["LockedFields"]: 1047 | peopleinfo["LockedFields"].append("Name") 1048 | people["Name"] = peoplename 1049 | if "Emby" in self.media_server_type: 1050 | ret = self.meidia_server_client.set_item_info( 1051 | itemid=peopleinfo["Id"], iteminfo=peopleinfo 1052 | ) 1053 | else: 1054 | ret = True 1055 | if ret: 1056 | log().logger.info( 1057 | "原始人物名称[{}]更新为[{}]".format( 1058 | originalpeoplename, peoplename 1059 | ) 1060 | ) 1061 | updatepeople = True 1062 | elif "Role" not in people or not self.__is_chinese__( 1063 | string=people["Role"], mode=2 1064 | ): 1065 | if imdbid: 1066 | if not doubanmediainfo: 1067 | if "Series" in item["Type"]: 1068 | ret, doubanmediainfo = self.__get_douban_media_info__( 1069 | mediatype=1, name=item["Name"], id=imdbid 1070 | ) 1071 | else: 1072 | ret, doubanmediainfo = self.__get_douban_media_info__( 1073 | mediatype=2, name=item["Name"], id=imdbid 1074 | ) 1075 | 1076 | if doubanmediainfo and not doubancelebritiesinfo: 1077 | if "Series" in item["Type"]: 1078 | ( 1079 | ret, 1080 | doubancelebritiesinfo, 1081 | ) = self.__get_douban_media_celebrities_info__( 1082 | mediatype=1, 1083 | name=item["Name"], 1084 | id=doubanmediainfo["id"], 1085 | ) 1086 | else: 1087 | ( 1088 | ret, 1089 | doubancelebritiesinfo, 1090 | ) = self.__get_douban_media_celebrities_info__( 1091 | mediatype=2, 1092 | name=item["Name"], 1093 | id=doubanmediainfo["id"], 1094 | ) 1095 | 1096 | if peopleimdbid and doubanmediainfo and doubancelebritiesinfo: 1097 | ret, celebrities = self.__get_people_info__( 1098 | celebritiesinfo=doubancelebritiesinfo, 1099 | people=people, 1100 | imdbid=peopleimdbid, 1101 | ) 1102 | if ret: 1103 | if self.__is_chinese__( 1104 | string=re.sub( 1105 | pattern="饰\\s+", 1106 | repl="", 1107 | string=celebrities["character"], 1108 | ) 1109 | ): 1110 | people["Role"] = re.sub( 1111 | pattern="饰\\s+", 1112 | repl="", 1113 | string=celebrities["character"], 1114 | ) 1115 | updatepeople = True 1116 | doubanname = re.sub( 1117 | pattern="\\s+", repl="", string=celebrities["name"] 1118 | ) 1119 | if doubanname.find("演员") != -1: 1120 | continue 1121 | if people["Name"] != doubanname and self.__is_chinese__( 1122 | string=doubanname 1123 | ): 1124 | originalpeoplename = people["Name"] 1125 | peopleinfo["Name"] = doubanname 1126 | people["Name"] = doubanname 1127 | if "Emby" in self.media_server_type: 1128 | ret = self.meidia_server_client.set_item_info( 1129 | itemid=peopleinfo["Id"], iteminfo=peopleinfo 1130 | ) 1131 | else: 1132 | ret = True 1133 | if ret: 1134 | log().logger.info( 1135 | "原始人物名称[{}]更新为[{}]".format( 1136 | originalpeoplename, people["Name"] 1137 | ) 1138 | ) 1139 | updatepeople = True 1140 | 1141 | if "Emby" in self.media_server_type: 1142 | peoplelist = [] 1143 | peoples = [] 1144 | for people in iteminfo["People"]: 1145 | if self.del_not_image_people: 1146 | if "PrimaryImageTag" not in people: 1147 | updatepeople = True 1148 | continue 1149 | if people["Name"] + people["Type"] not in peoplelist: 1150 | peoplelist.append(people["Name"] + people["Type"]) 1151 | peoples.append(people) 1152 | else: 1153 | updatepeople = True 1154 | iteminfo["People"] = peoples 1155 | 1156 | if needdelpeople: 1157 | peoples = [] 1158 | for people in iteminfo["People"]: 1159 | if people["Id"] in needdelpeople: 1160 | continue 1161 | peoples.append(people) 1162 | 1163 | iteminfo["People"] = peoples 1164 | updatepeople = True 1165 | 1166 | except Exception as result: 1167 | log().logger.info("异常错误: {}".format(result)) 1168 | return updatepeople 1169 | 1170 | def __get_people_info__(self, celebritiesinfo, people, imdbid): 1171 | """ 1172 | 获取人物信息 1173 | :param celebritiesinfo 演员信息 1174 | :param 人物信息 1175 | :param imdbid 1176 | :return True of False, celebrities 1177 | """ 1178 | try: 1179 | if people["Type"] == "Director": 1180 | for celebrities in celebritiesinfo["directors"]: 1181 | if "info" not in celebrities: 1182 | continue 1183 | haspeople = False 1184 | for imdb in celebrities["info"]["extra"]["info"]: 1185 | if imdb[0] != "IMDb编号": 1186 | continue 1187 | if imdb[1] == imdbid: 1188 | haspeople = True 1189 | break 1190 | 1191 | if haspeople: 1192 | return True, celebrities 1193 | 1194 | for celebrities in celebritiesinfo["directors"]: 1195 | if ( 1196 | people["Name"] == celebrities["name"] 1197 | or people["Name"] == celebrities["latin_name"] 1198 | ): 1199 | return True, celebrities 1200 | 1201 | if people["Type"] == "Actor": 1202 | for celebrities in celebritiesinfo["actors"]: 1203 | if "info" not in celebrities: 1204 | continue 1205 | haspeople = False 1206 | for imdb in celebrities["info"]["extra"]["info"]: 1207 | if imdb[0] != "IMDb编号": 1208 | continue 1209 | if imdb[1] == imdbid: 1210 | haspeople = True 1211 | break 1212 | 1213 | if haspeople: 1214 | return True, celebrities 1215 | 1216 | for celebrities in celebritiesinfo["actors"]: 1217 | if ( 1218 | people["Name"] == celebrities["name"] 1219 | or people["Name"] == celebrities["latin_name"] 1220 | ): 1221 | return True, celebrities 1222 | 1223 | except Exception as result: 1224 | log().logger.info("异常错误: {}".format(result)) 1225 | return False, None 1226 | 1227 | def __get_douban_media_celebrities_info__(self, mediatype: int, name: str, id: str): 1228 | """ 1229 | 获取豆瓣媒体演员信息 1230 | :param mediatype 媒体类型 1TV 2电影 1231 | :name name 媒体名称 1232 | :id id 媒体ID 1233 | :return True or False, celebritiesinfo 1234 | """ 1235 | try: 1236 | ret, celebritiesinfo = self.sql_client.get_douban_celebrities_info( 1237 | mediatype=mediatype, id=id 1238 | ) 1239 | if not ret: 1240 | if mediatype == 1: 1241 | ret, celebritiesinfo = self.douban_client.get_tv_celebrities_info( 1242 | tvid=id 1243 | ) 1244 | else: 1245 | ret, celebritiesinfo = ( 1246 | self.douban_client.get_movie_celebrities_info(movieid=id) 1247 | ) 1248 | if not ret: 1249 | log().logger.info( 1250 | "获取豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1251 | name, id, self.douban_client.err 1252 | ) 1253 | ) 1254 | return False, None 1255 | ret = self.sql_client.write_douban_celebrities_info( 1256 | mediatype=mediatype, id=id, iteminfo=celebritiesinfo 1257 | ) 1258 | if not ret: 1259 | log().logger.info( 1260 | "保存豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1261 | name, id, self.douban_client.err 1262 | ) 1263 | ) 1264 | for celebrities in celebritiesinfo["directors"]: 1265 | ret, info = self.sql_client.get_douban_people_info(id=celebrities["id"]) 1266 | if not ret: 1267 | ret, info = self.douban_client.get_celebrity_info( 1268 | celebrityid=celebrities["id"] 1269 | ) 1270 | if not ret: 1271 | log().logger.info( 1272 | "获取豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1273 | name, id, self.douban_client.err 1274 | ) 1275 | ) 1276 | continue 1277 | ret = self.sql_client.write_douban_people_info( 1278 | id=celebrities["id"], iteminfo=info 1279 | ) 1280 | if not ret: 1281 | log().logger.info( 1282 | "获取豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1283 | name, id, self.douban_client.err 1284 | ) 1285 | ) 1286 | celebrities["info"] = info 1287 | for celebrities in celebritiesinfo["actors"]: 1288 | ret, info = self.sql_client.get_douban_people_info(id=celebrities["id"]) 1289 | if not ret: 1290 | ret, info = self.douban_client.get_celebrity_info( 1291 | celebrityid=celebrities["id"] 1292 | ) 1293 | if not ret: 1294 | log().logger.info( 1295 | "获取豆瓣媒体[{}]ID[{}]演员[{}]ID[{}]信息失败, {}".format( 1296 | name, 1297 | id, 1298 | celebrities["name"], 1299 | celebrities["id"], 1300 | self.douban_client.err, 1301 | ) 1302 | ) 1303 | continue 1304 | ret = self.sql_client.write_douban_people_info( 1305 | id=celebrities["id"], iteminfo=info 1306 | ) 1307 | if not ret: 1308 | log().logger.info( 1309 | "保存豆瓣媒体[{}]ID[{}]演员[{}]ID[{}]信息失败, {}".format( 1310 | name, 1311 | id, 1312 | celebrities["name"], 1313 | celebrities["id"], 1314 | self.douban_client.err, 1315 | ) 1316 | ) 1317 | celebrities["info"] = info 1318 | return True, celebritiesinfo 1319 | except Exception as result: 1320 | log().logger.info( 1321 | "异常错误:{}".format( 1322 | result, 1323 | ) 1324 | ) 1325 | return False, None 1326 | 1327 | def __get_douban_media_info__(self, mediatype: int, name: str, id: str): 1328 | """ 1329 | 获取豆瓣媒体信息 1330 | :param mediatype 媒体类型 1TV 2电影 1331 | :name name 媒体名称 1332 | :id imdb 1333 | :return True or False, mediainfo 1334 | """ 1335 | try: 1336 | ret, items = self.sql_client.search_douban_media( 1337 | mediatype=mediatype, title=name 1338 | ) 1339 | if not ret: 1340 | time.sleep(self.douban_api_space) 1341 | ret, items = self.douban_client.search_media_pc(name) 1342 | if not ret or not items["items"]: 1343 | time.sleep(self.douban_api_space) 1344 | ret, items = self.douban_client.search_media(name) 1345 | if not ret or not items["items"]: 1346 | time.sleep(self.douban_api_space) 1347 | ret, items = self.douban_client.search_media_weixin(name) 1348 | if not ret or not items["items"]: 1349 | log().logger.info( 1350 | "豆瓣搜索媒体[{}]失败, {}".format( 1351 | name, str(self.douban_client.err) 1352 | ) 1353 | ) 1354 | return False, None 1355 | 1356 | for item in items["items"]: 1357 | if mediatype == 1: 1358 | if "target_type" in item and item["target_type"] != "tv": 1359 | continue 1360 | elif mediatype == 2: 1361 | if "target_type" in item and item["target_type"] != "movie": 1362 | continue 1363 | ret, mediainfo = self.sql_client.get_douban_media_info( 1364 | mediatype=mediatype, id=item["target_id"] 1365 | ) 1366 | if not ret: 1367 | if mediatype == 2: 1368 | ret, mediainfo = self.douban_client.get_movie_info( 1369 | movieid=item["target_id"] 1370 | ) 1371 | else: 1372 | ret, mediainfo = self.douban_client.get_tv_info( 1373 | tvid=item["target_id"] 1374 | ) 1375 | if not ret: 1376 | log().logger.info( 1377 | "获取豆瓣媒体[{}]ID[{}]信息失败, {}".format( 1378 | item["title"], item["target_id"], self.douban_client.err 1379 | ) 1380 | ) 1381 | return False, None 1382 | ret = self.sql_client.write_douban_media_info( 1383 | mediatype=mediatype, id=item["target_id"], iteminfo=mediainfo 1384 | ) 1385 | if not ret: 1386 | log().logger.info( 1387 | "保存豆瓣媒体[{}]ID[{}]信息失败, {}".format( 1388 | item["title"], item["target_id"], self.douban_client.err 1389 | ) 1390 | ) 1391 | if "IMDb" not in mediainfo["info"]: 1392 | continue 1393 | if mediainfo["info"]["IMDb"] == id: 1394 | ret = self.sql_client.write_douban_media( 1395 | mediatype=mediatype, id=item["target_id"], iteminfo=item 1396 | ) 1397 | if not ret: 1398 | log().logger.info( 1399 | "保存豆瓣媒体[{}]ID[{}]信息失败, {}".format( 1400 | item["title"], item["target_id"], self.douban_client.err 1401 | ) 1402 | ) 1403 | return True, mediainfo 1404 | 1405 | except Exception as result: 1406 | log().logger.info( 1407 | "异常错误:{}".format( 1408 | result, 1409 | ) 1410 | ) 1411 | return False, None 1412 | 1413 | def __get_tmdb_media_info__( 1414 | self, mediatype: int, name: str, id: str, language: str = "zh-CN" 1415 | ): 1416 | """ 1417 | 获取tmdb媒体信息 1418 | :param mediatype 媒体类型 1TV 2电影 1419 | :name name 媒体名称 1420 | :param id tmdbid 1421 | :return True or False, iteminfo 1422 | """ 1423 | try: 1424 | iteminfo = None 1425 | if mediatype == 1: 1426 | ret, iteminfo = self.sql_client.get_tmdb_media_info( 1427 | mediatype=mediatype, id=id, language=language 1428 | ) 1429 | if not ret: 1430 | ret, iteminfo = self.tmdb_client.get_tv_info( 1431 | tvid=id, language=language 1432 | ) 1433 | if not ret: 1434 | log().logger.info( 1435 | "获取TMDB媒体[{}]ID[{}]信息失败, {}".format( 1436 | name, id, self.tmdb_client.err 1437 | ) 1438 | ) 1439 | return False, None 1440 | ret = self.sql_client.write_tmdb_media_info( 1441 | mediatype=mediatype, id=id, language=language, iteminfo=iteminfo 1442 | ) 1443 | if not ret: 1444 | log().logger.info( 1445 | "保存TMDB媒体[{}]ID[{}]信息失败, {}".format( 1446 | name, id, self.tmdb_client.err 1447 | ) 1448 | ) 1449 | return True, iteminfo 1450 | else: 1451 | ret, iteminfo = self.sql_client.get_tmdb_media_info( 1452 | mediatype=mediatype, id=id, language=language 1453 | ) 1454 | if not ret: 1455 | ret, iteminfo = self.tmdb_client.get_movie_info( 1456 | movieid=id, language=language 1457 | ) 1458 | if not ret: 1459 | log().logger.info( 1460 | "获取TMDB媒体[{}]ID[{}]信息失败, {}".format( 1461 | name, id, self.tmdb_client.err 1462 | ) 1463 | ) 1464 | return False, None 1465 | ret = self.sql_client.write_tmdb_media_info( 1466 | mediatype=mediatype, id=id, language=language, iteminfo=iteminfo 1467 | ) 1468 | if not ret: 1469 | log().logger.info( 1470 | "保存TMDB媒体[{}]ID[{}]信息失败, {}".format( 1471 | name, id, self.tmdb_client.err 1472 | ) 1473 | ) 1474 | return True, iteminfo 1475 | 1476 | except Exception as result: 1477 | log().logger.info( 1478 | "异常错误:{}".format( 1479 | result, 1480 | ) 1481 | ) 1482 | return False, None 1483 | 1484 | def __get_tmdb_media_name__( 1485 | self, mediatype: int, datatype: int, name: str, id: str 1486 | ): 1487 | """ 1488 | 获取tmdb媒体中文名称 1489 | :param mediatype 媒体类型 1TV 2电影 1490 | :param datatype 数据类型 1名字 2概述 1491 | :name name 媒体名称 1492 | :param id tmdbid 1493 | :return True or False, name 1494 | """ 1495 | try: 1496 | for language in self.language_list: 1497 | if mediatype == 1: 1498 | ret, tvinfo = self.__get_tmdb_media_info__( 1499 | mediatype=mediatype, name=name, id=id, language=language 1500 | ) 1501 | if not ret: 1502 | continue 1503 | if datatype == 1: 1504 | if self.__is_chinese__(string=tvinfo["name"]): 1505 | if self.__is_chinese__(string=tvinfo["name"], mode=3): 1506 | return True, zhconv.convert(tvinfo["name"], "zh-cn") 1507 | return True, tvinfo["name"] 1508 | else: 1509 | ret, name = self.__alternative_name__( 1510 | alternativetitles=tvinfo 1511 | ) 1512 | if not ret: 1513 | continue 1514 | return True, name 1515 | else: 1516 | if self.__is_chinese__(string=tvinfo["overview"]): 1517 | if self.__is_chinese__(string=tvinfo["overview"], mode=3): 1518 | return True, zhconv.convert(tvinfo["overview"], "zh-cn") 1519 | return True, tvinfo["overview"] 1520 | else: 1521 | ret, movieinfo = self.__get_tmdb_media_info__( 1522 | mediatype=mediatype, name=name, id=id, language=language 1523 | ) 1524 | if not ret: 1525 | continue 1526 | if datatype == 1: 1527 | if self.__is_chinese__(string=movieinfo["title"]): 1528 | if self.__is_chinese__(string=movieinfo["title"], mode=3): 1529 | return True, zhconv.convert(movieinfo["title"], "zh-cn") 1530 | return True, movieinfo["title"] 1531 | else: 1532 | ret, name = self.__alternative_name__( 1533 | alternativetitles=movieinfo 1534 | ) 1535 | if not ret: 1536 | continue 1537 | return True, name 1538 | else: 1539 | if self.__is_chinese__(string=movieinfo["overview"]): 1540 | if self.__is_chinese__( 1541 | string=movieinfo["overview"], mode=3 1542 | ): 1543 | return True, zhconv.convert( 1544 | movieinfo["overview"], "zh-cn" 1545 | ) 1546 | return True, movieinfo["overview"] 1547 | 1548 | except Exception as result: 1549 | log().logger.info( 1550 | "异常错误:{}".format( 1551 | result, 1552 | ) 1553 | ) 1554 | return False, None 1555 | 1556 | def __get_tmdb_tv_season_group_info__(self, name, groupid): 1557 | """ 1558 | 获取TMDB电视剧季组信息 1559 | :param name 媒体名称 1560 | :param groupid 组ID 1561 | :return True or False, iteminfo 1562 | """ 1563 | try: 1564 | for language in self.language_list: 1565 | ret, iteminfo = self.tmdb_client.get_tv_season_group( 1566 | groupid=groupid, language=language 1567 | ) 1568 | if not ret: 1569 | log().logger.info( 1570 | "获取TMDB剧集[{}]组ID[{}]信息失败, {}".format( 1571 | name, groupid, self.tmdb_client.err 1572 | ) 1573 | ) 1574 | continue 1575 | return True, iteminfo 1576 | 1577 | except Exception as result: 1578 | log().logger.info( 1579 | "异常错误:{}".format( 1580 | result, 1581 | ) 1582 | ) 1583 | return False, None 1584 | 1585 | def __get_tmdb_tv_season_info__( 1586 | self, name: str, tvid: str, seasonid: str, episodeid: int 1587 | ): 1588 | """ 1589 | 获取tmdb季中文 1590 | :param name 媒体名称 1591 | :param tvid tmdbid 1592 | :param seasonid 季ID 1593 | :param episodeid 集ID 1594 | :return True or False, name, overview, ommunityrating, imageurl 1595 | """ 1596 | try: 1597 | for language in self.language_list: 1598 | ret, seasoninfo = self.sql_client.get_tmdb_season_info( 1599 | id=tvid, seasonid=seasonid, language=language 1600 | ) 1601 | if not ret: 1602 | ret, seasoninfo = self.tmdb_client.get_tv_season_info( 1603 | tvid=tvid, seasonid=seasonid, language=language 1604 | ) 1605 | if not ret: 1606 | log().logger.info( 1607 | "获取TMDB媒体[{}]ID[{}]季ID[{}]信息失败, {}".format( 1608 | name, tvid, seasonid, self.tmdb_client.err 1609 | ) 1610 | ) 1611 | continue 1612 | ret = self.sql_client.write_tmdb_season_info( 1613 | id=tvid, 1614 | seasonid=seasonid, 1615 | language=language, 1616 | iteminfo=seasoninfo, 1617 | ) 1618 | if not ret: 1619 | log().logger.info( 1620 | "保存TMDB媒体[{}]ID[{}]季ID[{}]信息失败, {}".format( 1621 | name, tvid, seasonid, self.tmdb_client.err 1622 | ) 1623 | ) 1624 | for episodes in seasoninfo["episodes"]: 1625 | if episodes["episode_number"] > episodeid: 1626 | break 1627 | if episodes["episode_number"] != episodeid: 1628 | continue 1629 | if self.__is_chinese__(string=episodes["overview"]): 1630 | name = None 1631 | overview = None 1632 | ommunityrating = None 1633 | imageurl = None 1634 | if self.__is_chinese__(string=episodes["name"], mode=3): 1635 | name = zhconv.convert(episodes["name"], "zh-cn") 1636 | else: 1637 | name = episodes["name"] 1638 | if self.__is_chinese__(string=episodes["overview"], mode=3): 1639 | overview = zhconv.convert(episodes["overview"], "zh-cn") 1640 | else: 1641 | overview = episodes["overview"] 1642 | if episodes["vote_average"] > 0: 1643 | ommunityrating = episodes["vote_average"] 1644 | if "still_path" in episodes and episodes["still_path"]: 1645 | imageurl = ( 1646 | "https://www.themoviedb.org/t/p/original{}".format( 1647 | episodes["still_path"] 1648 | ) 1649 | ) 1650 | return True, name, overview, ommunityrating, imageurl 1651 | 1652 | except Exception as result: 1653 | log().logger.info( 1654 | "异常错误:{}".format( 1655 | result, 1656 | ) 1657 | ) 1658 | return False, None, None, None, None 1659 | 1660 | def __get_tmdb_person_name(self, name, personid): 1661 | """ 1662 | 获取tmdb人物中文 1663 | :param name 人物名称 1664 | :param personid 人物ID 1665 | :return True or False, name 1666 | """ 1667 | try: 1668 | for language in self.language_list: 1669 | ret, personinfo = self.sql_client.get_tmdb_people_info( 1670 | id=personid, language=language 1671 | ) 1672 | if not ret: 1673 | ret, personinfo = self.tmdb_client.get_person_info( 1674 | personid=personid, language=language 1675 | ) 1676 | if not ret: 1677 | log().logger.info( 1678 | "获取TMDB人物[{}]ID[{}]信息失败, {}".format( 1679 | name, personid, self.tmdb_client.err 1680 | ) 1681 | ) 1682 | continue 1683 | ret = self.sql_client.write_tmdb_people_info( 1684 | id=personid, language=language, iteminfo=personinfo 1685 | ) 1686 | if not ret: 1687 | log().logger.info( 1688 | "保存TMDB人物[{}]ID[{}]信息失败, {}".format( 1689 | name, personid, self.tmdb_client.err 1690 | ) 1691 | ) 1692 | for name in personinfo["also_known_as"]: 1693 | if not self.__is_chinese__(string=name, mode=2): 1694 | continue 1695 | if self.__is_chinese__(string=name, mode=3): 1696 | name = zhconv.convert(name, "zh-cn") 1697 | return True, re.sub(pattern="\\s+", repl="", string=name) 1698 | break 1699 | 1700 | except Exception as result: 1701 | log().logger.info( 1702 | "异常错误:{}".format( 1703 | result, 1704 | ) 1705 | ) 1706 | return False, None 1707 | 1708 | def __alternative_name__(self, alternativetitles): 1709 | """ 1710 | 返回别名中文名称 1711 | :return True or False, name 1712 | """ 1713 | try: 1714 | if ( 1715 | "alternative_titles" not in alternativetitles 1716 | or "titles" not in alternativetitles["alternative_titles"] 1717 | ): 1718 | return False, None 1719 | for title in alternativetitles["alternative_titles"]["titles"]: 1720 | if "iso_3166_1" not in title: 1721 | continue 1722 | if title["iso_3166_1"] != "CN": 1723 | continue 1724 | if not self.__is_chinese__(string=title["title"]): 1725 | continue 1726 | if self.__is_chinese__(string=title["title"], mode=3): 1727 | return True, zhconv.convert(title["title"], "zh-cn") 1728 | return True, title["title"] 1729 | except Exception as result: 1730 | log().logger.info( 1731 | "异常错误:{}".format( 1732 | result, 1733 | ) 1734 | ) 1735 | return False, None 1736 | 1737 | def __is_chinese__(self, string: str, mode: int = 1): 1738 | """ 1739 | 判断是否包含中文 1740 | :param string 需要判断的字符 1741 | :param mode 模式 1匹配简体和繁体 2只匹配简体 3只匹配繁体 1742 | :return True or False 1743 | """ 1744 | for ch in string: 1745 | if mode == 1: 1746 | if "\u4e00" <= ch <= "\u9FFF": 1747 | return True 1748 | elif mode == 2: 1749 | if "\u4e00" <= ch <= "\u9FFF": 1750 | if zhconv.convert(ch, "zh-cn") == ch: 1751 | return True 1752 | elif mode == 3: 1753 | if "\u4e00" <= ch <= "\u9FFF": 1754 | if zhconv.convert(ch, "zh-cn") != ch: 1755 | return True 1756 | if re.search(pattern="^[0-9]+$", string=string): 1757 | return True 1758 | return False 1759 | -------------------------------------------------------------------------------- /api/media_config.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from system.log import log 3 | import os 4 | 5 | 6 | class MediaConfig: 7 | def __init__(self) -> None: 8 | try: 9 | # 打开文件 10 | self.defaultconfig = yaml.load( 11 | open("config.default.yaml", "r", encoding="utf-8"), 12 | Loader=yaml.FullLoader, 13 | ) 14 | self.configdata = yaml.load( 15 | open( 16 | os.path.join(os.getcwd(), "config", "config.yaml"), 17 | "r", 18 | encoding="utf-8", 19 | ), 20 | Loader=yaml.FullLoader, 21 | ) 22 | 23 | # 递归检查配置文件是否包含默认配置文件的所有配置项 24 | for key in self.defaultconfig: 25 | self.__config_check__(self.configdata, key, self.defaultconfig[key]) 26 | 27 | except Exception as result: 28 | log().logger.info("配置异常错误, {}".format(result)) 29 | 30 | def __config_check__(self, config, key, defaultvalue): 31 | try: 32 | if key not in config: 33 | log().logger.info( 34 | "配置项[{}]不存在, 创建默认值[{}]".format(key, defaultvalue) 35 | ) 36 | config[key] = defaultvalue 37 | with open(file=self.path, mode="w", encoding="utf-8") as file: 38 | yaml.safe_dump( 39 | self.configdata, 40 | file, 41 | default_flow_style=False, 42 | allow_unicode=True, 43 | ) 44 | if isinstance(defaultvalue, dict): 45 | for subkey in defaultvalue: 46 | self.__config_check__(config[key], subkey, defaultvalue[subkey]) 47 | 48 | except Exception as result: 49 | log().logger.info("配置异常错误, {}".format(result)) 50 | -------------------------------------------------------------------------------- /api/mediasql.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | from api.sql import sql 4 | from system.config import Config 5 | 6 | 7 | class mediasql(sql): 8 | tmdbmediacachefailtime = None 9 | tmdbpeoplecachefailtime = None 10 | doubanmediacachefailtime = None 11 | doubanpeoplecachefailtime = None 12 | 13 | def __init__(self) -> None: 14 | super().__init__() 15 | config = Config().get_config() 16 | self.tmdbmediacachefailtime = config["api"]["tmdb"]["mediacachefailtime"] 17 | self.tmdbpeoplecachefailtime = config["api"]["tmdb"]["peoplecachefailtime"] 18 | self.doubanmediacachefailtime = config["api"]["douban"]["mediacachefailtime"] 19 | self.doubanpeoplecachefailtime = config["api"]["douban"]["peoplecachefailtime"] 20 | 21 | def search_douban_media(self, mediatype: int, title: str): 22 | """ 23 | 搜索豆瓣媒体 24 | :param mediatype 媒体类型 1TV 2电影 25 | :param titel 媒体标题 26 | :return True or False, iteminfo 27 | """ 28 | iteminfo = {"items": []} 29 | try: 30 | if mediatype == 1: 31 | ret, datalist = self.query( 32 | sql="select * from douban_tv where media_name like '%{}%';".format( 33 | title 34 | ) 35 | ) 36 | else: 37 | ret, datalist = self.query( 38 | sql="select * from douban_movie where media_name like '%{}%';".format( 39 | title 40 | ) 41 | ) 42 | if not ret or not len(datalist): 43 | return False, None 44 | for data in datalist: 45 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 46 | nowtime = datetime.datetime.now() 47 | day = (nowtime - datatime).days 48 | if day > self.doubanmediacachefailtime: 49 | continue 50 | iteminfo["items"].append(json.loads(data[3])) 51 | return True, iteminfo 52 | return False, None 53 | except Exception as result: 54 | self.err = "文件[{}]行[{}]异常错误:{}".format( 55 | result.__traceback__.tb_frame.f_globals["__file__"], 56 | result.__traceback__.tb_lineno, 57 | result, 58 | ) 59 | return False, iteminfo 60 | 61 | def write_douban_media(self, mediatype: int, id: str, iteminfo): 62 | """ 63 | 写入豆瓣媒体 64 | :param mediatype 媒体类型 1TV 2电影 65 | :param id 媒体ID 66 | :param iteminfo 媒体信息 67 | :return True or False 68 | """ 69 | try: 70 | if mediatype == 1: 71 | ret, datalist = self.query( 72 | sql="select * from douban_tv where media_id = '{}';".format(id) 73 | ) 74 | else: 75 | ret, datalist = self.query( 76 | sql="select * from douban_movie where media_id = '{}';".format(id) 77 | ) 78 | if not ret: 79 | return False 80 | for data in datalist: 81 | if mediatype == 1: 82 | ret = self.execution( 83 | sql="update douban_tv set media_brief = ?, update_time = ? where media_id = ?;", 84 | data=( 85 | json.dumps(iteminfo), 86 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 87 | data[1], 88 | ), 89 | ) 90 | else: 91 | ret = self.execution( 92 | sql="update douban_movie set media_brief = ?, update_time = ? where media_id = ?;", 93 | data=( 94 | json.dumps(iteminfo), 95 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 96 | data[1], 97 | ), 98 | ) 99 | return ret 100 | 101 | if mediatype == 1: 102 | ret = self.execution( 103 | sql="insert into douban_tv(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, ?, '', '', ?);", 104 | data=( 105 | id, 106 | iteminfo["title"], 107 | json.dumps(iteminfo), 108 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 109 | ), 110 | ) 111 | else: 112 | ret = self.execution( 113 | sql="insert into douban_movie(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, ?, '', '', ?);", 114 | data=( 115 | id, 116 | iteminfo["title"], 117 | json.dumps(iteminfo), 118 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 119 | ), 120 | ) 121 | return ret 122 | except Exception as result: 123 | self.err = "文件[{}]行[{}]异常错误:{}".format( 124 | result.__traceback__.tb_frame.f_globals["__file__"], 125 | result.__traceback__.tb_lineno, 126 | result, 127 | ) 128 | return False 129 | 130 | def get_douban_media_info(self, mediatype: int, id: str): 131 | """ 132 | 获取豆瓣媒体信息 133 | :param mediatype 媒体类型 1TV 2电影 134 | :param id 媒体ID 135 | :return True or False, iteminfo 136 | """ 137 | iteminfo = {} 138 | try: 139 | if mediatype == 1: 140 | ret, datalist = self.query( 141 | sql="select * from douban_tv where media_id = '{}';".format(id) 142 | ) 143 | else: 144 | ret, datalist = self.query( 145 | sql="select * from douban_movie where media_id = '{}';".format(id) 146 | ) 147 | if not ret or not len(datalist): 148 | return False, None 149 | for data in datalist: 150 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 151 | nowtime = datetime.datetime.now() 152 | day = (nowtime - datatime).days 153 | if day > self.doubanmediacachefailtime: 154 | continue 155 | iteminfo = json.loads(data[4]) 156 | return True, iteminfo 157 | return False, None 158 | except Exception as result: 159 | self.err = "文件[{}]行[{}]异常错误:{}".format( 160 | result.__traceback__.tb_frame.f_globals["__file__"], 161 | result.__traceback__.tb_lineno, 162 | result, 163 | ) 164 | return False, iteminfo 165 | 166 | def write_douban_media_info(self, mediatype: int, id: str, iteminfo): 167 | """ 168 | 写入豆瓣媒体信息 169 | :param mediatype 媒体类型 1TV 2电影 170 | :param id 媒体ID 171 | :param iteminfo 媒体信息 172 | :return True or False 173 | """ 174 | try: 175 | if mediatype == 1: 176 | ret, datalist = self.query( 177 | sql="select * from douban_tv where media_id = '{}';".format(id) 178 | ) 179 | else: 180 | ret, datalist = self.query( 181 | sql="select * from douban_movie where media_id = '{}';".format(id) 182 | ) 183 | if not ret: 184 | return False 185 | for data in datalist: 186 | if mediatype == 1: 187 | ret = self.execution( 188 | sql="update douban_tv set media_data = ?, update_time = ? where media_id = ?;", 189 | data=( 190 | json.dumps(iteminfo), 191 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 192 | data[1], 193 | ), 194 | ) 195 | else: 196 | ret = self.execution( 197 | sql="update douban_movie set media_data = ?, update_time = ? where media_id = ?;", 198 | data=( 199 | json.dumps(iteminfo), 200 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 201 | data[1], 202 | ), 203 | ) 204 | return ret 205 | 206 | if mediatype == 1: 207 | ret = self.execution( 208 | sql="insert into douban_tv(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, '', ?, '', ?);", 209 | data=( 210 | id, 211 | iteminfo["title"], 212 | json.dumps(iteminfo), 213 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 214 | ), 215 | ) 216 | else: 217 | ret = self.execution( 218 | sql="insert into douban_movie(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, '', ?, '', ?);", 219 | data=( 220 | id, 221 | iteminfo["title"], 222 | json.dumps(iteminfo), 223 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 224 | ), 225 | ) 226 | return ret 227 | except Exception as result: 228 | self.err = "文件[{}]行[{}]异常错误:{}".format( 229 | result.__traceback__.tb_frame.f_globals["__file__"], 230 | result.__traceback__.tb_lineno, 231 | result, 232 | ) 233 | return False 234 | 235 | def get_douban_celebrities_info(self, mediatype: int, id: str): 236 | """ 237 | 获取豆瓣媒体演员信息 238 | :param mediatype 媒体类型 1TV 2电影 239 | :param id 媒体ID 240 | :return True or False, iteminfo 241 | """ 242 | iteminfo = {} 243 | try: 244 | if mediatype == 1: 245 | ret, datalist = self.query( 246 | sql="select * from douban_tv where media_id = '{}';".format(id) 247 | ) 248 | else: 249 | ret, datalist = self.query( 250 | sql="select * from douban_movie where media_id = '{}';".format(id) 251 | ) 252 | if not ret or not len(datalist): 253 | return False, None 254 | for data in datalist: 255 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 256 | nowtime = datetime.datetime.now() 257 | day = (nowtime - datatime).days 258 | if day > self.doubanmediacachefailtime: 259 | continue 260 | iteminfo = json.loads(data[5]) 261 | return True, iteminfo 262 | return False, None 263 | except Exception as result: 264 | self.err = "文件[{}]行[{}]异常错误:{}".format( 265 | result.__traceback__.tb_frame.f_globals["__file__"], 266 | result.__traceback__.tb_lineno, 267 | result, 268 | ) 269 | return False, iteminfo 270 | 271 | def write_douban_celebrities_info(self, mediatype: int, id: str, iteminfo): 272 | """ 273 | 写入豆瓣媒体演员信息 274 | :param mediatype 媒体类型 1TV 2电影 275 | :param id 媒体ID 276 | :param iteminfo 演员信息 277 | :return True or False 278 | """ 279 | try: 280 | if mediatype == 1: 281 | ret, datalist = self.query( 282 | sql="select * from douban_tv where media_id = '{}';".format(id) 283 | ) 284 | else: 285 | ret, datalist = self.query( 286 | sql="select * from douban_movie where media_id = '{}';".format(id) 287 | ) 288 | if not ret: 289 | return False 290 | for data in datalist: 291 | if mediatype == 1: 292 | ret = self.execution( 293 | sql="update douban_tv set media_celebrities = ?, update_time = ? where media_id = ?;", 294 | data=( 295 | json.dumps(iteminfo), 296 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 297 | data[1], 298 | ), 299 | ) 300 | else: 301 | ret = self.execution( 302 | sql="update douban_movie set media_celebrities = ?, update_time = ? where media_id = ?;", 303 | data=( 304 | json.dumps(iteminfo), 305 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 306 | data[1], 307 | ), 308 | ) 309 | return ret 310 | 311 | if mediatype == 1: 312 | ret = self.execution( 313 | sql="insert into douban_tv(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, '', '', '', ?, ?);", 314 | data=( 315 | id, 316 | json.dumps(iteminfo), 317 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 318 | ), 319 | ) 320 | else: 321 | ret = self.execution( 322 | sql="insert into douban_movie(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, '', '', '', ?, ?);", 323 | data=( 324 | id, 325 | json.dumps(iteminfo), 326 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 327 | ), 328 | ) 329 | return ret 330 | except Exception as result: 331 | self.err = "文件[{}]行[{}]异常错误:{}".format( 332 | result.__traceback__.tb_frame.f_globals["__file__"], 333 | result.__traceback__.tb_lineno, 334 | result, 335 | ) 336 | return False 337 | 338 | def get_douban_people_info(self, id: str): 339 | """ 340 | 获取豆瓣演员信息 341 | :param mediatype 媒体类型 1TV 2电影 342 | :param id 人物ID 343 | :return True or False, iteminfo 344 | """ 345 | iteminfo = {} 346 | try: 347 | ret, datalist = self.query( 348 | sql="select * from douban_people where people_id = '{}';".format(id) 349 | ) 350 | if not ret or not len(datalist): 351 | return False, None 352 | for data in datalist: 353 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 354 | nowtime = datetime.datetime.now() 355 | day = (nowtime - datatime).days 356 | if day > self.doubanpeoplecachefailtime: 357 | continue 358 | iteminfo = json.loads(data[3]) 359 | return True, iteminfo 360 | return False, None 361 | except Exception as result: 362 | self.err = "文件[{}]行[{}]异常错误:{}".format( 363 | result.__traceback__.tb_frame.f_globals["__file__"], 364 | result.__traceback__.tb_lineno, 365 | result, 366 | ) 367 | return False, iteminfo 368 | 369 | def write_douban_people_info(self, id: str, iteminfo): 370 | """ 371 | 写入豆瓣演员信息 372 | :param id 人物ID 373 | :param name 人物姓名 374 | :param iteminfo 人物信息 375 | :return True or False 376 | """ 377 | try: 378 | ret, datalist = self.query( 379 | sql="select * from douban_people where people_id = '{}';".format(id) 380 | ) 381 | if not ret: 382 | return False 383 | for data in datalist: 384 | ret = self.execution( 385 | sql="update douban_people set people_name = ?, people_data = ?, update_time = ? where people_id = ?;", 386 | data=( 387 | iteminfo["title"], 388 | json.dumps(iteminfo), 389 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 390 | data[2], 391 | ), 392 | ) 393 | return ret 394 | ret = self.execution( 395 | sql="insert into douban_people(id, people_id, people_name, people_data, update_time) values(null, ?, ?, ?, ?)", 396 | data=( 397 | id, 398 | iteminfo["title"], 399 | json.dumps(iteminfo), 400 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 401 | ), 402 | ) 403 | return ret 404 | except Exception as result: 405 | self.err = "文件[{}]行[{}]异常错误:{}".format( 406 | result.__traceback__.tb_frame.f_globals["__file__"], 407 | result.__traceback__.tb_lineno, 408 | result, 409 | ) 410 | return False 411 | 412 | def get_tmdb_media_info(self, mediatype: int, id: str, language): 413 | """ 414 | 读取TMDB媒体信息 415 | :param mediatype 媒体类型 1TV 2电影 416 | :param id 媒体ID 417 | :param language 语言 418 | :returen True or False, iteminfo 419 | """ 420 | try: 421 | if mediatype == 1: 422 | ret, datalist = self.query( 423 | sql="select * from tmdb_tv where media_id = '{}';".format(id) 424 | ) 425 | else: 426 | ret, datalist = self.query( 427 | sql="select * from tmdb_movie where media_id = '{}';".format(id) 428 | ) 429 | if not ret or not len(datalist): 430 | return False, None 431 | for data in datalist: 432 | item = None 433 | if language == "zh-CN": 434 | item = data[3] 435 | elif language == "zh-SG": 436 | item = data[4] 437 | elif language == "zh-TW": 438 | item = data[5] 439 | elif language == "zh-HK": 440 | item = data[6] 441 | if not item: 442 | return False, None 443 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 444 | nowtime = datetime.datetime.now() 445 | day = (nowtime - datatime).days 446 | if day > self.tmdbmediacachefailtime: 447 | continue 448 | return True, json.loads(item) 449 | return False, None 450 | except Exception as result: 451 | self.err = "文件[{}]行[{}]异常错误:{}".format( 452 | result.__traceback__.tb_frame.f_globals["__file__"], 453 | result.__traceback__.tb_lineno, 454 | result, 455 | ) 456 | return False, None 457 | 458 | def write_tmdb_media_info(self, mediatype: int, id: str, language, iteminfo): 459 | """ 460 | 写入TMDB媒体信息 461 | :param mediatype 媒体类型 1TV 2电影 462 | :param id 媒体ID 463 | :param language 语言 464 | :param iteminfo 媒体信息 465 | :returen True or False 466 | """ 467 | try: 468 | if mediatype == 1: 469 | ret, datalist = self.query( 470 | sql="select * from tmdb_tv where media_id = '{}';".format(id) 471 | ) 472 | else: 473 | ret, datalist = self.query( 474 | sql="select * from tmdb_movie where media_id = '{}';".format(id) 475 | ) 476 | if not ret: 477 | return False 478 | key = None 479 | if language == "zh-CN": 480 | key = "media_data_zh_cn" 481 | elif language == "zh-SG": 482 | key = "media_data_zh_sg" 483 | elif language == "zh-TW": 484 | key = "media_data_zh_tw" 485 | elif language == "zh-HK": 486 | key = "media_data_zh_hk" 487 | if not key: 488 | self.err = "当前语言[{}]不支持".format(language) 489 | return False 490 | for data in datalist: 491 | if mediatype == 1: 492 | ret = self.execution( 493 | sql="update tmdb_tv set {} = ?, media_name = ?, update_time = ? where media_id = ?;".format( 494 | key 495 | ), 496 | data=( 497 | json.dumps(iteminfo), 498 | iteminfo["name"], 499 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 500 | data[1], 501 | ), 502 | ) 503 | else: 504 | ret = self.execution( 505 | sql="update tmdb_movie set {} = ?, media_name = ?, update_time = ? where media_id = ?;".format( 506 | key 507 | ), 508 | data=( 509 | json.dumps(iteminfo), 510 | iteminfo["title"], 511 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 512 | data[1], 513 | ), 514 | ) 515 | return ret 516 | if mediatype == 1: 517 | ret = self.execution( 518 | sql="insert into tmdb_tv(id, media_id, media_name, {}, update_time) values(null, ?, ?, ?, ?);".format( 519 | key 520 | ), 521 | data=( 522 | id, 523 | iteminfo["name"], 524 | json.dumps(iteminfo), 525 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 526 | ), 527 | ) 528 | else: 529 | ret = self.execution( 530 | sql="insert into tmdb_movie(id, media_id, media_name, {}, update_time) values(null, ?, ?, ?, ?);".format( 531 | key 532 | ), 533 | data=( 534 | id, 535 | iteminfo["title"], 536 | json.dumps(iteminfo), 537 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 538 | ), 539 | ) 540 | return ret 541 | except Exception as result: 542 | self.err = "文件[{}]行[{}]异常错误:{}".format( 543 | result.__traceback__.tb_frame.f_globals["__file__"], 544 | result.__traceback__.tb_lineno, 545 | result, 546 | ) 547 | return False 548 | 549 | def get_tmdb_season_info(self, id: str, seasonid, language): 550 | """ 551 | 读取TMDB季信息 552 | :param id 媒体ID 553 | :param language 语言 554 | :returen True or False, iteminfo 555 | """ 556 | try: 557 | ret, datalist = self.query( 558 | sql="select * from tmdb_tv where media_id = '{}';".format(id) 559 | ) 560 | if not ret or not len(datalist): 561 | return False, None 562 | for data in datalist: 563 | item = None 564 | if language == "zh-CN": 565 | item = data[7] 566 | elif language == "zh-SG": 567 | item = data[8] 568 | elif language == "zh-TW": 569 | item = data[9] 570 | elif language == "zh-HK": 571 | item = data[10] 572 | if not item: 573 | return False, None 574 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 575 | nowtime = datetime.datetime.now() 576 | day = (nowtime - datatime).days 577 | if day > self.tmdbmediacachefailtime: 578 | continue 579 | root_object = json.loads(item) 580 | if type(root_object) == dict: 581 | if root_object["season_number"] == int(seasonid): 582 | return True, root_object 583 | else: 584 | for season in root_object: 585 | if season["season_number"] == int(seasonid): 586 | return True, season 587 | 588 | return False, None 589 | except Exception as result: 590 | self.err = "文件[{}][{}]异常错误:".format( 591 | result.__traceback__.tb_frame.f_globals["__file__"], 592 | result.__traceback__.tb_lineno, 593 | result, 594 | ) 595 | return False, None 596 | 597 | def write_tmdb_season_info(self, id: str, seasonid, language, iteminfo): 598 | """ 599 | 写入TMDB季信息 600 | :param id 媒体ID 601 | :param language 语言 602 | :param iteminfo 季信息 603 | :returen True or False 604 | """ 605 | try: 606 | ret, datalist = self.query( 607 | sql="select * from tmdb_tv where media_id = '{}';".format(id) 608 | ) 609 | if not ret: 610 | return False 611 | key = None 612 | if language == "zh-CN": 613 | key = "season_data_zh_cn" 614 | elif language == "zh-SG": 615 | key = "season_data_zh_sg" 616 | elif language == "zh-TW": 617 | key = "season_data_zh_tw" 618 | elif language == "zh-HK": 619 | key = "season_data_zh_hk" 620 | if not key: 621 | self.err = "当前语言[{}]不支持".format(language) 622 | return False 623 | data_array = [] 624 | data_array.append(iteminfo) 625 | for data in datalist: 626 | item = None 627 | if language == "zh-CN": 628 | item = data[7] 629 | elif language == "zh-SG": 630 | item = data[8] 631 | elif language == "zh-TW": 632 | item = data[9] 633 | elif language == "zh-HK": 634 | item = data[10] 635 | if not item: 636 | ret = self.execution( 637 | sql="update tmdb_tv set {} = ?, update_time = ? where media_id = ?;".format( 638 | key 639 | ), 640 | data=( 641 | json.dumps(data_array), 642 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 643 | data[1], 644 | ), 645 | ) 646 | else: 647 | root_object = json.loads(item) 648 | if type(root_object) == dict: 649 | if root_object["season_number"] == int(seasonid): 650 | continue 651 | data_array.append(root_object) 652 | else: 653 | for season in root_object: 654 | if season["season_number"] == int(seasonid): 655 | continue 656 | data_array.append(season) 657 | ret = self.execution( 658 | sql="update tmdb_tv set {} = ?, update_time = ? where media_id = ?;".format( 659 | key 660 | ), 661 | data=( 662 | json.dumps(data_array), 663 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 664 | data[1], 665 | ), 666 | ) 667 | return ret 668 | ret = self.execution( 669 | sql="insert into tmdb_tv(id, media_id, {}, update_time) values(null, ?, ?, ?);".format( 670 | key 671 | ), 672 | data=( 673 | id, 674 | json.dumps(data_array), 675 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 676 | ), 677 | ) 678 | return ret 679 | except Exception as result: 680 | self.err = "文件[{}]行[{}]异常错误:{}".format( 681 | result.__traceback__.tb_frame.f_globals["__file__"], 682 | result.__traceback__.tb_lineno, 683 | result, 684 | ) 685 | return False 686 | 687 | def get_tmdb_people_info(self, id: str, language): 688 | """ 689 | 获取TMDB人物信息 690 | :param id 人物ID 691 | :param language 语言 692 | :returen True or False, iteminfo 693 | """ 694 | try: 695 | ret, datalist = self.query( 696 | sql="select * from tmdb_people where people_id = '{}';".format(id) 697 | ) 698 | if not ret or not len(datalist): 699 | return False, None 700 | for data in datalist: 701 | item = None 702 | if language == "zh-CN": 703 | item = data[3] 704 | elif language == "zh-SG": 705 | item = data[4] 706 | elif language == "zh-TW": 707 | item = data[5] 708 | elif language == "zh-HK": 709 | item = data[6] 710 | if not item: 711 | return False, None 712 | datatime = datetime.datetime.strptime(data[-1], "%Y-%m-%d %H:%M:%S") 713 | nowtime = datetime.datetime.now() 714 | day = (nowtime - datatime).days 715 | if day > self.tmdbpeoplecachefailtime: 716 | continue 717 | return True, json.loads(item) 718 | return False, None 719 | except Exception as result: 720 | self.err = "文件[{}]行[{}]异常错误:{}".format( 721 | result.__traceback__.tb_frame.f_globals["__file__"], 722 | result.__traceback__.tb_lineno, 723 | result, 724 | ) 725 | return False, None 726 | 727 | def write_tmdb_people_info(self, id: str, language, iteminfo): 728 | """ 729 | 写入TMDB人物信息 730 | :param id 人物ID 731 | :param language 语言 732 | :param iteminfo 人物信息 733 | :returen True or False 734 | """ 735 | try: 736 | ret, datalist = self.query( 737 | sql="select * from tmdb_people where people_id = '{}';".format(id) 738 | ) 739 | if not ret: 740 | return False 741 | key = None 742 | if language == "zh-CN": 743 | key = "people_data_zh_cn" 744 | elif language == "zh-SG": 745 | key = "people_data_zh_sg" 746 | elif language == "zh-TW": 747 | key = "people_data_zh_tw" 748 | elif language == "zh-HK": 749 | key = "people_data_zh_hk" 750 | if not key: 751 | self.err = "当前语言[{}]不支持".format(language) 752 | return False 753 | for data in datalist: 754 | ret = self.execution( 755 | sql="update tmdb_people set {} = ?, update_time = ? where people_id = ?;".format( 756 | key 757 | ), 758 | data=( 759 | json.dumps(iteminfo), 760 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 761 | data[1], 762 | ), 763 | ) 764 | return ret 765 | ret = self.execution( 766 | sql="insert into tmdb_people(id, people_id, people_name, {}, update_time) values(null, ?, ?, ?, ?);".format( 767 | key 768 | ), 769 | data=( 770 | id, 771 | iteminfo["name"], 772 | json.dumps(iteminfo), 773 | datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S"), 774 | ), 775 | ) 776 | return ret 777 | except Exception as result: 778 | self.err = "文件[{}]行[{}]异常错误:{}".format( 779 | result.__traceback__.tb_frame.f_globals["__file__"], 780 | result.__traceback__.tb_lineno, 781 | result, 782 | ) 783 | return False 784 | -------------------------------------------------------------------------------- /api/nastools.py: -------------------------------------------------------------------------------- 1 | import json 2 | from urllib import parse 3 | 4 | from network.network import Network 5 | 6 | 7 | class nastools: 8 | client = None 9 | host = None 10 | username = None 11 | passwd = None 12 | headers = None 13 | authorization = None 14 | token = None 15 | err = None 16 | 17 | def __init__( 18 | self, host: str, authorization: str, username: str, passwd: str 19 | ) -> None: 20 | self.client = Network(maxnumconnect=10, maxnumcache=20) 21 | self.authorization = authorization 22 | self.host = host 23 | self.username = username 24 | self.passwd = passwd 25 | self.headers = { 26 | "accept": "application/json", 27 | "Content-Type": "application/x-www-form-urlencoded", 28 | } 29 | 30 | def name_test(self, name): 31 | """ 32 | 名称识别测试 33 | :param name 媒体名称 34 | :return True or False, iteminfo 35 | """ 36 | iteminfo = {} 37 | try: 38 | if not self.__login__(): 39 | return False 40 | url = "{}/api/v1/service/name/test".format(self.host) 41 | data = "name={}".format(parse.quote(name)) 42 | p, err = self.client.post(url=url, data=data, headers=self.headers) 43 | if not self.__get_status__(p=p, err=err): 44 | return False, iteminfo 45 | iteminfo = json.loads(p.text)["data"]["data"] 46 | return True, iteminfo 47 | except Exception as result: 48 | self.err = "文件[{}]行[{}]异常错误:{}".format( 49 | result.__traceback__.tb_frame.f_globals["__file__"], 50 | result.__traceback__.tb_lineno, 51 | result, 52 | ) 53 | return False, iteminfo 54 | 55 | def media_info(self, name, year=None, type="MOV"): 56 | """ 57 | 媒体识别 58 | :param name 媒体名称 59 | :param year 年代 60 | :param type 媒体类型 电影MOV 剧集TV 61 | :return True or False, iteminfo 62 | """ 63 | iteminfo = {} 64 | try: 65 | if not self.__login__(): 66 | return False, iteminfo 67 | url = "{}/api/v1/media/info".format(self.host) 68 | if year: 69 | data = "type={}&title={}&year=".format(type, parse.quote(name), year) 70 | else: 71 | data = "type={}&title={}".format(type, parse.quote(name)) 72 | p, err = self.client.post(url=url, data=data, headers=self.headers) 73 | if not self.__get_status__(p=p, err=err): 74 | return False, iteminfo 75 | iteminfo = json.loads(p.text)["data"] 76 | return True, iteminfo 77 | except Exception as result: 78 | self.err = "文件[{}]行[{}]异常错误:{}".format( 79 | result.__traceback__.tb_frame.f_globals["__file__"], 80 | result.__traceback__.tb_lineno, 81 | result, 82 | ) 83 | return False, iteminfo 84 | 85 | def __login__(self): 86 | try: 87 | if self.token: 88 | url = "{}/api/v1/user/info".format(self.host) 89 | data = "username={}".format(self.username, self.passwd) 90 | self.headers["Authorization"] = self.authorization 91 | p, err = self.client.post(url=url, data=data, headers=self.headers) 92 | if self.__get_status__(p=p, err=err): 93 | return True 94 | url = "{}/api/v1/user/login".format(self.host) 95 | data = "username={}&password={}".format(self.username, self.passwd) 96 | p, err = self.client.post(url=url, data=data, headers=self.headers) 97 | if not self.__get_status__(p=p, err=err): 98 | return False 99 | rootobject = json.loads(p.text) 100 | self.token = rootobject["data"]["token"] 101 | self.headers["Authorization"] = self.token 102 | return True 103 | except Exception as result: 104 | self.err = "文件[{}]行[{}]异常错误:{}".format( 105 | result.__traceback__.tb_frame.f_globals["__file__"], 106 | result.__traceback__.tb_lineno, 107 | result, 108 | ) 109 | return False 110 | 111 | def __get_status__(self, p, err): 112 | try: 113 | if p == None: 114 | self.err = err 115 | return False 116 | if p.status_code != 200: 117 | self.err = p.text 118 | return False 119 | rootobject = json.loads(p.text) 120 | if rootobject["code"] != 0: 121 | self.err = rootobject["message"] 122 | return False 123 | return True 124 | except Exception as result: 125 | self.err = "文件[{}]行[{}]异常错误:{}".format( 126 | result.__traceback__.tb_frame.f_globals["__file__"], 127 | result.__traceback__.tb_lineno, 128 | result, 129 | ) 130 | 131 | return False 132 | -------------------------------------------------------------------------------- /api/server/emby.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from api.server.serverbase import serverbase 4 | 5 | 6 | class emby(serverbase): 7 | 8 | def get_items(self, parentid : str = '', type = None): 9 | """ 10 | 获取项目列表 11 | :param parentid 父文件夹ID 12 | :param type 类型 13 | :return True or False, items 14 | """ 15 | items = {} 16 | try: 17 | if len(parentid): 18 | url = '{}/emby/Users/{}/Items?ParentId={}&api_key={}'.format(self.host, self.userid, parentid, self.key) 19 | else: 20 | url = '{}/emby/Users/{}/Items?api_key={}'.format(self.host, self.userid, self.key) 21 | p, err = self.client.get(url=url) 22 | if not self.__get_status__(p=p, err=err): 23 | return False, items 24 | items = json.loads(p.text) 25 | return True, items 26 | except Exception as result: 27 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 28 | return False, items 29 | 30 | def get_items_count(self): 31 | """ 32 | 获取项目数量 33 | :return True or False, iteminfo 34 | """ 35 | iteminfo = {} 36 | try: 37 | url = '{}/emby/Items/Counts?api_key={}'.format(self.host, self.key) 38 | p, err = self.client.get(url=url) 39 | if not self.__get_status__(p=p, err=err): 40 | return False, iteminfo 41 | iteminfo = json.loads(p.text) 42 | return True, iteminfo 43 | except Exception as result: 44 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 45 | return False, iteminfo 46 | 47 | def get_item_info(self, itemid : str): 48 | """ 49 | 获取项目 50 | :param itemid 项目ID 51 | :return True or False, iteminfo 52 | """ 53 | iteminfo = {} 54 | try: 55 | url = '{}/emby/Users/{}/Items/{}?Fields=ChannelMappingInfo&api_key={}'.format(self.host, self.userid, itemid, self.key) 56 | p, err = self.client.get(url=url) 57 | if not self.__get_status__(p=p, err=err): 58 | return False, iteminfo 59 | iteminfo = json.loads(p.text) 60 | return True, iteminfo 61 | except Exception as result: 62 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 63 | return False, iteminfo 64 | 65 | def set_item_info(self, itemid : str, iteminfo): 66 | """ 67 | 更新项目 68 | :param iteminfo 项目信息 69 | :return True or False, iteminfo 70 | """ 71 | try: 72 | url = '{}/emby/Items/{}?api_key={}'.format(self.host, itemid, self.key) 73 | data = json.dumps(iteminfo) 74 | p, err = self.client.post(url=url, headers=self.headers, data=data) 75 | if not self.__get_status__(p=p, err=err): 76 | return False 77 | return True 78 | except Exception as result: 79 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 80 | return False 81 | 82 | def set_item_image(self, itemid : str, imageurl : str): 83 | """ 84 | 更新项目图片 85 | :param imageurl 图片URL 86 | :return True or False 87 | """ 88 | try: 89 | url = '{}/emby/Items/{}/Images/Primary/0/Url?api_key={}'.format(self.host, itemid, self.key) 90 | data = json.dumps({'Url': imageurl}) 91 | p, err = self.client.post(url=url, headers=self.headers, data=data) 92 | if not self.__get_status__(p=p, err=err): 93 | return False 94 | return True 95 | except Exception as result: 96 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 97 | return False 98 | 99 | def search_movie(self, itemid, tmdbid, name = None, year = None): 100 | """ 101 | 搜索视频 102 | :param itemid 项目ID 103 | :param tmdbid TMDB ID 104 | :param name 标题 105 | :param year 年代 106 | :return True or False, iteminfo 107 | """ 108 | iteminfo = {} 109 | try: 110 | url = '{}/emby/Items/RemoteSearch/Movie?api_key={}'.format(self.host, self.key) 111 | data = json.dumps({'SearchInfo':{'ProviderIds':{'Tmdb':tmdbid}},'ItemId':itemid}) 112 | p, err = self.client.post(url=url, headers=self.headers, data=data) 113 | if not self.__get_status__(p=p, err=err): 114 | return False, iteminfo 115 | iteminfo = json.loads(p.text) 116 | return True, iteminfo 117 | except Exception as result: 118 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 119 | return False, iteminfo 120 | 121 | def apply_search(self, itemid, iteminfo): 122 | """ 123 | 应用搜索 124 | :param itemid 项目ID 125 | :param iteminfo 项目信息 126 | :return True or False 127 | """ 128 | try: 129 | url = '{}/emby/Items/RemoteSearch/Apply/{}?ReplaceAllImages=true&api_key={}'.format(self.host, itemid, self.key) 130 | data = json.dumps(iteminfo) 131 | p, err = self.client.post(url=url, headers=self.headers, data=data) 132 | if not self.__get_status__(p=p, err=err): 133 | return False 134 | return True 135 | except Exception as result: 136 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 137 | return False 138 | 139 | def refresh(self, itemid): 140 | """ 141 | 刷新元数据 142 | :param itemid 项目ID 143 | :return True or False 144 | """ 145 | try: 146 | url = '{}/emby/Items/{}/Refresh?Recursive=true&ImageRefreshMode=FullRefresh&MetadataRefreshMode=FullRefresh&ReplaceAllImages=true&ReplaceAllMetadata=true&api_key={}'.format(self.host, itemid, self.key) 147 | p, err = self.client.post(url=url, headers=self.headers) 148 | if not self.__get_status__(p=p, err=err): 149 | return False 150 | return True 151 | except Exception as result: 152 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 153 | return False 154 | 155 | def __get_status__(self, p, err): 156 | try: 157 | if p == None: 158 | self.err = err 159 | return False 160 | if p.status_code != 200 and p.status_code != 204: 161 | self.err = p.text 162 | return False 163 | return True 164 | except Exception as result: 165 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 166 | 167 | return False -------------------------------------------------------------------------------- /api/server/jellyfin.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | from urllib import parse 4 | 5 | from api.server.serverbase import serverbase 6 | 7 | 8 | class jellyfin(serverbase): 9 | 10 | def get_items(self, parentid : str = '', type = None): 11 | """ 12 | 获取项目列表 13 | :param parentid 父文件夹ID 14 | :param type 类型 15 | :return True or False, items 16 | """ 17 | items = {} 18 | try: 19 | if len(parentid): 20 | url = '{}/Users/{}/Items?ParentId={}&api_key={}'.format(self.host, self.userid, parentid, self.key) 21 | else: 22 | url = '{}/Users/{}/Items?api_key={}'.format(self.host, self.userid, self.key) 23 | p, err = self.client.get(url=url) 24 | if not self.__get_status__(p=p, err=err): 25 | return False, items 26 | items = json.loads(p.text) 27 | return True, items 28 | except Exception as result: 29 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 30 | return False, items 31 | 32 | def get_items_count(self): 33 | """ 34 | 获取项目数量 35 | :return True or False, iteminfo 36 | """ 37 | iteminfo = {} 38 | try: 39 | url = '{}/Items/Counts?api_key={}'.format(self.host, self.key) 40 | p, err = self.client.get(url=url) 41 | if not self.__get_status__(p=p, err=err): 42 | return False, iteminfo 43 | iteminfo = json.loads(p.text) 44 | return True, iteminfo 45 | except Exception as result: 46 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 47 | return False, iteminfo 48 | 49 | def get_item_info(self, itemid : str): 50 | """ 51 | 获取项目 52 | :param itemid 项目ID 53 | :return True or False, iteminfo 54 | """ 55 | iteminfo = {} 56 | try: 57 | url = '{}/Users/{}/Items/{}?Fields=ChannelMappingInfo&api_key={}'.format(self.host, self.userid, itemid, self.key) 58 | p, err = self.client.get(url=url) 59 | if not self.__get_status__(p=p, err=err): 60 | return False, iteminfo 61 | iteminfo = json.loads(p.text) 62 | iteminfo['FileName'] = os.path.basename(iteminfo['Path']) 63 | return True, iteminfo 64 | except Exception as result: 65 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 66 | return False, iteminfo 67 | 68 | def set_item_info(self, itemid : str, iteminfo): 69 | """ 70 | 更新项目 71 | :param iteminfo 项目信息 72 | :return True or False, iteminfo 73 | """ 74 | try: 75 | url = '{}/Items/{}?api_key={}'.format(self.host, itemid, self.key) 76 | data = json.dumps(iteminfo) 77 | p, err = self.client.post(url=url, headers=self.headers, data=data) 78 | if not self.__get_status__(p=p, err=err): 79 | return False 80 | return True 81 | except Exception as result: 82 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 83 | return False 84 | 85 | def set_item_image(self, itemid : str, imageurl : str): 86 | """ 87 | 更新项目图片 88 | :param imageurl 图片URL 89 | :return True or False 90 | """ 91 | try: 92 | url = '{}/Items/{}/RemoteImages/Download?Type=Primary&ImageUrl={}&ProviderName=TheMovieDb&api_key={}'.format(self.host, itemid, parse.quote(imageurl), self.key) 93 | p, err = self.client.post(url=url, headers=self.headers) 94 | if not self.__get_status__(p=p, err=err): 95 | return False 96 | return True 97 | except Exception as result: 98 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 99 | return False 100 | 101 | def search_movie(self, itemid, tmdbid, name = None, year = None): 102 | """ 103 | 搜索视频 104 | :param itemid 项目ID 105 | :param tmdbid TMDB ID 106 | :param name 标题 107 | :param year 年代 108 | :return True or False, iteminfo 109 | """ 110 | iteminfo = {} 111 | try: 112 | url = '{}/Items/RemoteSearch/Movie?api_key={}'.format(self.host, self.key) 113 | data = json.dumps({'SearchInfo':{'ProviderIds':{'Tmdb':tmdbid}},'ItemId':itemid}) 114 | p, err = self.client.post(url=url, headers=self.headers, data=data) 115 | if not self.__get_status__(p=p, err=err): 116 | return False, iteminfo 117 | iteminfo = json.loads(p.text) 118 | return True, iteminfo 119 | except Exception as result: 120 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 121 | return False, iteminfo 122 | 123 | def apply_search(self, itemid, iteminfo): 124 | """ 125 | 应用搜索 126 | :param itemid 项目ID 127 | :param iteminfo 项目信息 128 | :return True or False 129 | """ 130 | try: 131 | url = '{}/Items/RemoteSearch/Apply/{}?ReplaceAllImages=true&api_key={}'.format(self.host, itemid, self.key) 132 | data = json.dumps(iteminfo) 133 | p, err = self.client.post(url=url, headers=self.headers, data=data) 134 | if not self.__get_status__(p=p, err=err): 135 | return False 136 | return True 137 | except Exception as result: 138 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 139 | return False 140 | 141 | def refresh(self, itemid): 142 | """ 143 | 刷新元数据 144 | :param itemid 项目ID 145 | :return True or False 146 | """ 147 | try: 148 | url = '{}/Items/{}/Refresh?Recursive=true&ImageRefreshMode=FullRefresh&MetadataRefreshMode=FullRefresh&ReplaceAllImages=true&ReplaceAllMetadata=true&api_key={}'.format(self.host, itemid, self.key) 149 | p, err = self.client.post(url=url, headers=self.headers) 150 | if not self.__get_status__(p=p, err=err): 151 | return False 152 | return True 153 | except Exception as result: 154 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 155 | return False 156 | 157 | def __get_status__(self, p, err): 158 | try: 159 | if p == None: 160 | self.err = err 161 | return False 162 | if p.status_code != 200 and p.status_code != 204: 163 | self.err = p.text 164 | return False 165 | return True 166 | except Exception as result: 167 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 168 | 169 | return False -------------------------------------------------------------------------------- /api/server/plex.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from plexapi.server import PlexServer 4 | 5 | from api.server.serverbase import serverbase 6 | 7 | 8 | class plex(serverbase): 9 | def __init__(self, host : str, userid : str, key : str) -> None: 10 | """ 11 | :param host 12 | :param userid 13 | :param key 14 | """ 15 | try: 16 | super().__init__(host=host, userid=userid, key=key) 17 | self.client = PlexServer(baseurl=host, token=key) 18 | except Exception as result: 19 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 20 | 21 | 22 | def get_items(self, parentid : str = '', type = None): 23 | """ 24 | 获取项目列表 25 | :param parentid 父文件夹ID 26 | :param type 类型 27 | :return True or False, items 28 | """ 29 | items = {} 30 | try: 31 | items['Items'] = [] 32 | if parentid: 33 | if type and 'Season' in type: 34 | plexitem = self.client.library.fetchItem(ekey=parentid) 35 | items['Items'] = [] 36 | for season in plexitem.seasons(): 37 | item = {} 38 | item['Name'] = season.title 39 | item['Id'] = season.key 40 | item['IndexNumber'] = season.seasonNumber 41 | item['Overview'] = season.summary 42 | items['Items'].append(item) 43 | elif type and 'Episode' in type: 44 | plexitem = self.client.library.fetchItem(ekey=parentid) 45 | items['Items'] = [] 46 | for episode in plexitem.episodes(): 47 | item = {} 48 | item['Name'] = episode.title 49 | item['Id'] = episode.key 50 | item['IndexNumber'] = episode.episodeNumber 51 | item['Overview'] = episode.summary 52 | item['CommunityRating'] = episode.audienceRating 53 | items['Items'].append(item) 54 | else: 55 | plexitems = self.client.library.sectionByID(sectionID=parentid) 56 | for plexitem in plexitems.all(): 57 | item = {} 58 | if 'movie' in plexitem.METADATA_TYPE: 59 | item['Type'] = 'Movie' 60 | item['IsFolder'] = False 61 | elif 'episode' in plexitem.METADATA_TYPE: 62 | item['Type'] = 'Series' 63 | item['IsFolder'] = False 64 | item['Name'] = plexitem.title 65 | item['Id'] = plexitem.key 66 | items['Items'].append(item) 67 | else: 68 | plexitems = self.client.library.sections() 69 | for plexitem in plexitems: 70 | item = {} 71 | if 'Directory' in plexitem.TAG: 72 | item['Type'] = 'Folder' 73 | item['IsFolder'] = True 74 | elif 'movie' in plexitem.METADATA_TYPE: 75 | item['Type'] = 'Movie' 76 | item['IsFolder'] = False 77 | elif 'episode' in plexitem.METADATA_TYPE: 78 | item['Type'] = 'Series' 79 | item['IsFolder'] = False 80 | item['Name'] = plexitem.title 81 | item['Id'] = plexitem.key 82 | items['Items'].append(item) 83 | 84 | return True, items 85 | except Exception as result: 86 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 87 | return False, items 88 | 89 | def get_items_count(self): 90 | """ 91 | 获取项目数量 92 | :return True or False, iteminfo 93 | """ 94 | iteminfo = {} 95 | try: 96 | iteminfo['MovieCount'] = 0 97 | iteminfo['SeriesCount'] = 0 98 | items = self.client.library.sections() 99 | for item in items: 100 | if 'movie' in item.METADATA_TYPE: 101 | iteminfo['MovieCount'] += item.totalSize 102 | elif 'episode' in item.METADATA_TYPE: 103 | iteminfo['SeriesCount'] += item.totalSize 104 | return True, iteminfo 105 | except Exception as result: 106 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 107 | return False, iteminfo 108 | 109 | def get_item_info(self, itemid : str): 110 | """ 111 | 获取项目 112 | :param itemid 项目ID 113 | :return True or False, iteminfo 114 | """ 115 | iteminfo = {} 116 | try: 117 | plexitem = self.client.library.fetchItem(ekey=itemid) 118 | if 'movie' in plexitem.METADATA_TYPE: 119 | iteminfo['Type'] = 'Movie' 120 | iteminfo['IsFolder'] = False 121 | elif 'episode' in plexitem.METADATA_TYPE: 122 | iteminfo['Type'] = 'Series' 123 | iteminfo['IsFolder'] = False 124 | if 'show' in plexitem.TYPE: 125 | iteminfo['ChildCount'] = plexitem.childCount 126 | iteminfo['Name'] = plexitem.title 127 | iteminfo['Id'] = plexitem.key 128 | iteminfo['ProductionYear'] = plexitem.year 129 | iteminfo['ProviderIds'] = {} 130 | for guid in plexitem.guids: 131 | idlist = str(guid.id).split(sep='://') 132 | if len(idlist) < 2: 133 | continue 134 | iteminfo['ProviderIds'][idlist[0]] = idlist[1] 135 | for location in plexitem.locations: 136 | iteminfo['Path'] = location 137 | iteminfo['FileName'] = os.path.basename(location) 138 | iteminfo['Overview'] = plexitem.summary 139 | iteminfo['CommunityRating'] = plexitem.audienceRating 140 | 141 | """ 142 | iteminfo['People'] = [] 143 | for actor in plexitem.actors: 144 | people = {} 145 | people['Id'] = actor.key 146 | people['Name'] = actor.tag 147 | people['Role'] = actor.role 148 | iteminfo['People'].append(people) 149 | """ 150 | return True, iteminfo 151 | except Exception as result: 152 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 153 | return False, iteminfo 154 | 155 | def set_item_info(self, itemid : str, iteminfo): 156 | """ 157 | 更新项目 158 | :param iteminfo 项目信息 159 | :return True or False, iteminfo 160 | """ 161 | try: 162 | plexitem = self.client.library.fetchItem(ekey=itemid) 163 | if 'CommunityRating' in iteminfo: 164 | edits = { 165 | 'audienceRating.value': iteminfo['CommunityRating'], 166 | 'audienceRating.locked': 1 167 | } 168 | plexitem.edit(**edits) 169 | plexitem.editTitle(iteminfo['Name']).editSummary(iteminfo['Overview']).reload() 170 | return True 171 | except Exception as result: 172 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 173 | return False 174 | 175 | def set_item_image(self, itemid : str, imageurl : str): 176 | """ 177 | 更新项目图片 178 | :param imageurl 图片URL 179 | :return True or False 180 | """ 181 | try: 182 | plexitem = self.client.library.fetchItem(ekey=itemid) 183 | plexitem.uploadPoster(url=imageurl) 184 | return True 185 | except Exception as result: 186 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 187 | return False 188 | 189 | def search_movie(self, itemid, tmdbid, name = None, year = None): 190 | """ 191 | 搜索视频 192 | :param itemid 项目ID 193 | :param tmdbid TMDB ID 194 | :param name 标题 195 | :param year 年代 196 | :return True or False, iteminfo 197 | """ 198 | iteminfo = [] 199 | try: 200 | plexitem = self.client.library.fetchItem(ekey=itemid) 201 | searchinfo = plexitem.matches(title=name, year=year) 202 | for info in searchinfo: 203 | if tmdbid: 204 | iteminfo.append(info) 205 | return True, iteminfo 206 | for info in searchinfo: 207 | if name in info.name and year in info.year: 208 | iteminfo.append(info) 209 | return True, iteminfo 210 | self.err = '未匹配到结果' 211 | except Exception as result: 212 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 213 | return False, iteminfo 214 | 215 | def apply_search(self, itemid, iteminfo): 216 | """ 217 | 应用搜索 218 | :param itemid 项目ID 219 | :param iteminfo 项目信息 220 | :return True or False 221 | """ 222 | try: 223 | plexitem = self.client.library.fetchItem(ekey=itemid) 224 | plexitem.fixMatch(searchResult=iteminfo) 225 | return True 226 | except Exception as result: 227 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 228 | return False 229 | -------------------------------------------------------------------------------- /api/server/serverbase.py: -------------------------------------------------------------------------------- 1 | from network.network import Network 2 | from abc import ABCMeta, abstractmethod 3 | 4 | 5 | class serverbase(metaclass=ABCMeta): 6 | host = None 7 | userid = None 8 | key = None 9 | headers = None 10 | err = None 11 | client = None 12 | 13 | def __init__(self, host: str, userid: str, key: str) -> None: 14 | """ 15 | :param host 16 | :param userid 17 | :param key 18 | """ 19 | self.host = host 20 | self.userid = userid 21 | self.key = key 22 | self.headers = {"Content-Type": "application/json"} 23 | self.client = Network(maxnumconnect=10, maxnumcache=20) 24 | 25 | @abstractmethod 26 | def get_items(self, parentid: str = "", type=None): 27 | """ 28 | 获取项目列表 29 | :param parentid 父文件夹ID 30 | :param type 类型 31 | :return True or False, items 32 | """ 33 | pass 34 | 35 | @abstractmethod 36 | def get_items_count(self): 37 | """ 38 | 获取项目数量 39 | :return True or False, iteminfo 40 | """ 41 | pass 42 | 43 | @abstractmethod 44 | def get_item_info(self, itemid: str): 45 | """ 46 | 获取项目 47 | :param itemid 项目ID 48 | :return True or False, iteminfo 49 | """ 50 | pass 51 | 52 | @abstractmethod 53 | def set_item_info(self, itemid: str, iteminfo): 54 | """ 55 | 更新项目 56 | :param iteminfo 项目信息 57 | :return True or False, iteminfo 58 | """ 59 | pass 60 | 61 | @abstractmethod 62 | def set_item_image(self, itemid: str, imageurl: str): 63 | """ 64 | 更新项目图片 65 | :param imageurl 图片URL 66 | :return True or False 67 | """ 68 | pass 69 | 70 | @abstractmethod 71 | def search_movie(self, itemid, tmdbid, name=None, year=None): 72 | """ 73 | 搜索视频 74 | :param itemid 项目ID 75 | :param tmdbid TMDB ID 76 | :param name 标题 77 | :param year 年代 78 | :return True or False, iteminfo 79 | """ 80 | pass 81 | 82 | @abstractmethod 83 | def apply_search(self, itemid, iteminfo): 84 | """ 85 | 应用搜索 86 | :param itemid 项目ID 87 | :param iteminfo 项目信息 88 | :return True or False 89 | """ 90 | pass 91 | 92 | def refresh(self, itemid): 93 | """ 94 | 刷新元数据 95 | :param itemid 项目ID 96 | :return True or False 97 | """ 98 | 99 | def __get_status__(self, p, err): 100 | pass 101 | -------------------------------------------------------------------------------- /api/sql.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | import threading 4 | 5 | 6 | class sql: 7 | sqlconnect = None 8 | lock = None 9 | err = None 10 | 11 | def __init__(self) -> None: 12 | try: 13 | self.lock = threading.Lock() 14 | path = os.path.join(os.getcwd(), "config", "data.db") 15 | self.sqlconnect = sqlite3.connect(database=path, check_same_thread=False) 16 | self.execution( 17 | sql='CREATE TABLE IF NOT EXISTS "douban_movie" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"media_id" text,"media_name" TEXT,"media_brief" TEXT,"media_data" TEXT,"media_celebrities" TEXT,"update_time" TEXT);' 18 | ) 19 | self.execution( 20 | sql='CREATE TABLE IF NOT EXISTS "douban_people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"people_id" text,"people_name" TEXT,"people_data" TEXT,"update_time" TEXT);' 21 | ) 22 | self.execution( 23 | sql='CREATE TABLE IF NOT EXISTS "douban_tv" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"media_id" text,"media_name" TEXT,"media_brief" TEXT,"media_data" TEXT,"media_celebrities" TEXT,"update_time" TEXT);' 24 | ) 25 | self.execution( 26 | sql='CREATE TABLE IF NOT EXISTS "tmdb_movie" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"media_id" text,"media_name" TEXT,"media_data_zh_cn" TEXT,"media_data_zh_sg" TEXT,"media_data_zh_tw" TEXT,"media_data_zh_hk" TEXT,"update_time" TEXT);' 27 | ) 28 | self.execution( 29 | sql='CREATE TABLE IF NOT EXISTS "tmdb_people" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"people_id" text,"people_name" TEXT,"people_data_zh_cn" TEXT,"people_data_zh_sg" TEXT,"people_data_zh_tw" TEXT,"people_data_zh_hk" TEXT,"update_time" TEXT);' 30 | ) 31 | self.execution( 32 | sql='CREATE TABLE IF NOT EXISTS "tmdb_tv" ("id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,"media_id" text,"media_name" TEXT,"media_data_zh_cn" TEXT,"media_data_zh_sg" TEXT,"media_data_zh_tw" TEXT,"media_data_zh_hk" TEXT,"season_data_zh_cn" TEXT,"season_data_zh_sg" TEXT,"season_data_zh_tw" TEXT,"season_data_zh_hk" TEXT,"update_time" TEXT);' 33 | ) 34 | except Exception as reuslt: 35 | self.err = "数据库异常错误, {}".format(reuslt) 36 | 37 | def query(self, sql: str): 38 | """ 39 | 查询数据库 40 | :param sql 语句 41 | :return True or False, data 42 | """ 43 | self.lock.acquire() 44 | done = False 45 | data = None 46 | try: 47 | cursor = self.sqlconnect.cursor() 48 | cursor.execute(sql) 49 | data = cursor.fetchall() 50 | done = True 51 | except Exception as reuslt: 52 | self.err = "数据库异常错误, {}".format(reuslt) 53 | self.lock.release() 54 | return done, data 55 | 56 | def execution(self, sql: str, data=None): 57 | """ 58 | 执行数据库 59 | :param sql 语句 60 | :return True or False 61 | """ 62 | self.lock.acquire() 63 | done = False 64 | try: 65 | cursor = self.sqlconnect.cursor() 66 | if data: 67 | cursor.execute(sql, data) 68 | else: 69 | cursor.execute(sql) 70 | self.sqlconnect.commit() 71 | done = True 72 | except Exception as reuslt: 73 | self.err = "数据库异常错误, {}".format(reuslt) 74 | self.lock.release() 75 | return done 76 | -------------------------------------------------------------------------------- /api/tmdb.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | from network.network import Network 4 | 5 | 6 | class tmdb: 7 | host = None 8 | key = None 9 | err = None 10 | client = None 11 | 12 | def __init__( 13 | self, key: str, host: str = "https://api.themoviedb.org/3", proxy: str = None 14 | ) -> None: 15 | """ 16 | :param key apikey 17 | """ 18 | self.host = host 19 | self.key = key 20 | self.client = Network(maxnumconnect=10, maxnumcache=20, proxy=proxy) 21 | 22 | def get_movie_info(self, movieid: str, language: str = "zh-CN"): 23 | """ 24 | 获取电影信息 25 | :param movieid 26 | :return True or False, iteminfo 27 | """ 28 | iteminfo = {} 29 | try: 30 | url = "{}/movie/{}?api_key={}&language={}&append_to_response=alternative_titles".format( 31 | self.host, movieid, self.key, language 32 | ) 33 | p, err = self.client.get(url=url) 34 | if not self.__get_status__(p=p, err=err): 35 | return False, iteminfo 36 | iteminfo = json.loads(p.text) 37 | return True, iteminfo 38 | except Exception as result: 39 | self.err = "文件[{}]行[{}]异常错误:{}".format( 40 | result.__traceback__.tb_frame.f_globals["__file__"], 41 | result.__traceback__.tb_lineno, 42 | result, 43 | ) 44 | return False, iteminfo 45 | 46 | def get_tv_info(self, tvid: str, language: str = "zh-CN"): 47 | """ 48 | 获取tv信息 49 | :param tvid 50 | :return True or False, iteminfo 51 | """ 52 | iteminfo = {} 53 | try: 54 | url = "{}/tv/{}?api_key={}&language={}&append_to_response=alternative_titles,episode_groups".format( 55 | self.host, tvid, self.key, language 56 | ) 57 | p, err = self.client.get(url=url) 58 | if not self.__get_status__(p=p, err=err): 59 | return False, iteminfo 60 | iteminfo = json.loads(p.text) 61 | return True, iteminfo 62 | except Exception as result: 63 | self.err = "文件[{}]行[{}]异常错误:{}".format( 64 | result.__traceback__.tb_frame.f_globals["__file__"], 65 | result.__traceback__.tb_lineno, 66 | result, 67 | ) 68 | return False, iteminfo 69 | 70 | def get_tv_season_group(self, groupid: str, language: str = "zh-CN"): 71 | """ 72 | 获取tv季组 73 | :param groupid 组id 74 | :return True or False, iteminfo 75 | """ 76 | iteminfo = {} 77 | try: 78 | url = "{}/tv/episode_group/{}?api_key={}&language={}&append_to_response=alternative_titles".format( 79 | self.host, groupid, self.key, language 80 | ) 81 | p, err = self.client.get(url=url) 82 | if not self.__get_status__(p=p, err=err): 83 | return False, iteminfo 84 | iteminfo = json.loads(p.text) 85 | return True, iteminfo 86 | except Exception as result: 87 | self.err = "文件[{}]行[{}]异常错误:{}".format( 88 | result.__traceback__.tb_frame.f_globals["__file__"], 89 | result.__traceback__.tb_lineno, 90 | result, 91 | ) 92 | return False, iteminfo 93 | 94 | def get_tv_season_info(self, tvid: str, seasonid: str, language: str = "zh-CN"): 95 | """ 96 | 获取tv季信息 97 | :param tvid 98 | :return True or False, iteminfo 99 | """ 100 | iteminfo = {} 101 | try: 102 | url = "{}/tv/{}/season/{}?api_key={}&language={}&append_to_response=alternative_titles".format( 103 | self.host, tvid, seasonid, self.key, language 104 | ) 105 | p, err = self.client.get(url=url) 106 | if not self.__get_status__(p=p, err=err): 107 | return False, iteminfo 108 | iteminfo = json.loads(p.text) 109 | return True, iteminfo 110 | except Exception as result: 111 | self.err = "文件[{}]行[{}]异常错误:{}".format( 112 | result.__traceback__.tb_frame.f_globals["__file__"], 113 | result.__traceback__.tb_lineno, 114 | result, 115 | ) 116 | return False, iteminfo 117 | 118 | def get_person_info(self, personid: str, language: str = "zh-CN"): 119 | """ 120 | 获取人物信息 121 | :param personid 人物ID 122 | :return True or False, personinfo 123 | """ 124 | personinfo = {} 125 | try: 126 | url = "{}/person/{}?api_key={}&language={}".format( 127 | self.host, personid, self.key, language 128 | ) 129 | p, err = self.client.get(url=url) 130 | if not self.__get_status__(p=p, err=err): 131 | return False, personinfo 132 | personinfo = json.loads(p.text) 133 | return True, personinfo 134 | except Exception as result: 135 | self.err = "文件[{}]行[{}]异常错误:{}".format( 136 | result.__traceback__.tb_frame.f_globals["__file__"], 137 | result.__traceback__.tb_lineno, 138 | result, 139 | ) 140 | return False, personinfo 141 | 142 | def __get_status__(self, p, err): 143 | try: 144 | if p == None: 145 | self.err = err 146 | return False 147 | if p.status_code != 200: 148 | rootobject = json.loads(p.text) 149 | if "status_message" in rootobject: 150 | self.err = rootobject["status_message"] 151 | else: 152 | self.err = p.text 153 | return False 154 | return True 155 | except Exception as result: 156 | self.err = "文件[{}]行[{}]异常错误:{}".format( 157 | result.__traceback__.tb_frame.f_globals["__file__"], 158 | result.__traceback__.tb_lineno, 159 | result, 160 | ) 161 | 162 | return False 163 | -------------------------------------------------------------------------------- /config.default.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | nastools: 3 | #授权码 4 | authorization: "" 5 | #域名 包含http(s)和端口号后面不带/ 6 | #例如http://xxx.xxx.xxx:3000 7 | host: "http://127.0.0.1:3000" 8 | #用户名 9 | username: "" 10 | #密码 11 | passwd: "" 12 | plex: 13 | #域名 包含http(s)和端口号后面不带/ 14 | #例如https://xxx.xxx.xxx:32400 15 | host: "http://127.0.0.1:32400" 16 | #X-Plex-Token 17 | key: "" 18 | jellyfin: 19 | #域名 包含http(s)和端口号后面不带/ 20 | #例如https://xxx.xxx.xxx:8920 21 | host: "http://127.0.0.1:8096" 22 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 23 | userid: "" 24 | #APIKEY 25 | key: "" 26 | emby: 27 | #域名 包含http(s)和端口号后面不带/ 28 | #例如https://xxx.xxx.xxx:8920 29 | host: "http://127.0.0.1:8096" 30 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 31 | userid: "" 32 | #APIKEY 33 | key: "" 34 | tmdb: 35 | #APIKEY 36 | key: "" 37 | #代理 38 | #proxy: "" 39 | #API 40 | host: "https://api.themoviedb.org/3" 41 | #媒体数据缓存失效时间 [天] 42 | mediacachefailtime: 1 43 | #TMDB人物数据缓存失效时间 [天] 44 | peoplecachefailtime: 10 45 | douban: 46 | #APIKEY 47 | key: "0ac44ae016490db2204ce0a042db2916" 48 | #豆瓣PC cookie 49 | cookie: "" 50 | #媒体数据缓存失效时间 [天] 51 | mediacachefailtime: 30 52 | #TMDB人物数据缓存失效时间 [天] 53 | peoplecachefailtime: 120 54 | 55 | system: 56 | #排除媒体文件夹 57 | #例如 58 | #excludepath: 59 | # - "AV" 60 | # - "xxx" 61 | excludepath: 62 | - "合集" 63 | #排除媒体 64 | #例如 65 | #excludemedia: 66 | # - "xxx" 67 | excludemedia: 68 | - "进击的巨人" 69 | #使用媒体服务器 Emby Jellyfin Plex 70 | mediaserver: "Emby" 71 | #线程数量 豆瓣API有请求频率限制建议线程数量不要过多 72 | threadnum: 10 73 | #是否更新豆瓣评分 74 | updatescore: False 75 | #是否更新标题 76 | updatetitle: True 77 | #是否刷新人名 78 | updatepeople: False 79 | #是否更新概述 80 | updateoverview: False 81 | #是否更新剧集组, 在TMDB有些剧集会有剧集组, EMBY只能刮削初始播放日期的剧集, 例如纸房子是有5季, EMBY只能刮削初始播放日期3季的版本 82 | updateseasongroup: False 83 | #每次刷新全部媒体间隔时间 [小时] 84 | updatetime: 12 85 | #检查媒体搜刮是否正确 不正确自动更新 需要配合NasTools使用 86 | checkmediasearch: False 87 | #豆瓣API延迟[秒] 豆瓣API有请求频率限制建议时间可以设置长一点 88 | doubanapispace: 0 89 | #删除没有头像的演员 90 | delnotimagepeople: False 91 | #剧集组配置, 在启用更新剧集组后有效 92 | #例如 93 | #seasongroup: 94 | #- EMBY媒体名称|TMDB episode_group剧集组ID 95 | #- "纸房子|5eb730dfca7ec6001f7beb51" 96 | seasongroup: 97 | - "纸房子|5eb730dfca7ec6001f7beb51" 98 | -------------------------------------------------------------------------------- /config/config.default.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | nastools: 3 | #授权码 4 | authorization: "" 5 | #域名 包含http(s)和端口号后面不带/ 6 | #例如http://xxx.xxx.xxx:3000 7 | host: "http://127.0.0.1:3000" 8 | #用户名 9 | username: "" 10 | #密码 11 | passwd: "" 12 | plex: 13 | #域名 包含http(s)和端口号后面不带/ 14 | #例如https://xxx.xxx.xxx:32400 15 | host: "http://127.0.0.1:32400" 16 | #X-Plex-Token 17 | key: "" 18 | jellyfin: 19 | #域名 包含http(s)和端口号后面不带/ 20 | #例如https://xxx.xxx.xxx:8920 21 | host: "http://127.0.0.1:8096" 22 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 23 | userid: "" 24 | #APIKEY 25 | key: "" 26 | emby: 27 | #域名 包含http(s)和端口号后面不带/ 28 | #例如https://xxx.xxx.xxx:8920 29 | host: "http://127.0.0.1:8096" 30 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 31 | userid: "" 32 | #APIKEY 33 | key: "" 34 | tmdb: 35 | #APIKEY 36 | key: "" 37 | #代理 38 | #proxy: "" 39 | #API 40 | host: "https://api.themoviedb.org/3" 41 | #媒体数据缓存失效时间 [天] 42 | mediacachefailtime: 1 43 | #TMDB人物数据缓存失效时间 [天] 44 | peoplecachefailtime: 10 45 | douban: 46 | #APIKEY 47 | key: "0ac44ae016490db2204ce0a042db2916" 48 | #豆瓣PC cookie 49 | cookie: "" 50 | #媒体数据缓存失效时间 [天] 51 | mediacachefailtime: 30 52 | #TMDB人物数据缓存失效时间 [天] 53 | peoplecachefailtime: 120 54 | 55 | system: 56 | #排除媒体文件夹 57 | #例如 58 | #excludepath: 59 | # - "AV" 60 | # - "xxx" 61 | excludepath: 62 | - "合集" 63 | #排除媒体 64 | #例如 65 | #excludemedia: 66 | # - "xxx" 67 | excludemedia: 68 | - "进击的巨人" 69 | #使用媒体服务器 Emby Jellyfin Plex 70 | mediaserver: "Emby" 71 | #线程数量 豆瓣API有请求频率限制建议线程数量不要过多 72 | threadnum: 10 73 | #是否更新豆瓣评分 74 | updatescore: False 75 | #是否更新标题 76 | updatetitle: True 77 | #是否刷新人名 78 | updatepeople: False 79 | #是否更新概述 80 | updateoverview: False 81 | #是否更新剧集组, 在TMDB有些剧集会有剧集组, EMBY只能刮削初始播放日期的剧集, 例如纸房子是有5季, EMBY只能刮削初始播放日期3季的版本 82 | updateseasongroup: False 83 | #每次刷新全部媒体间隔时间 [小时] 84 | updatetime: 12 85 | #检查媒体搜刮是否正确 不正确自动更新 需要配合NasTools使用 86 | checkmediasearch: False 87 | #豆瓣API延迟[秒] 豆瓣API有请求频率限制建议时间可以设置长一点 88 | doubanapispace: 0 89 | #删除没有头像的演员 90 | delnotimagepeople: False 91 | #剧集组配置, 在启用更新剧集组后有效 92 | #例如 93 | #seasongroup: 94 | #- EMBY媒体名称|TMDB episode_group剧集组ID 95 | #- "纸房子|5eb730dfca7ec6001f7beb51" 96 | seasongroup: 97 | - "纸房子|5eb730dfca7ec6001f7beb51" 98 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | nastools: 3 | #授权码 4 | authorization: "" 5 | #域名 包含http(s)和端口号后面不带/ 6 | #例如http://xxx.xxx.xxx:3000 7 | host: "http://127.0.0.1:3000" 8 | #用户名 9 | username: "" 10 | #密码 11 | passwd: "" 12 | plex: 13 | #域名 包含http(s)和端口号后面不带/ 14 | #例如https://xxx.xxx.xxx:32400 15 | host: "http://127.0.0.1:32400" 16 | #X-Plex-Token 17 | key: "" 18 | jellyfin: 19 | #域名 包含http(s)和端口号后面不带/ 20 | #例如https://xxx.xxx.xxx:8920 21 | host: "http://127.0.0.1:8096" 22 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 23 | userid: "" 24 | #APIKEY 25 | key: "" 26 | emby: 27 | #域名 包含http(s)和端口号后面不带/ 28 | #例如https://xxx.xxx.xxx:8920 29 | host: "http://127.0.0.1:8096" 30 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 31 | userid: "" 32 | #APIKEY 33 | key: "" 34 | tmdb: 35 | #APIKEY 36 | key: "" 37 | #代理 38 | #proxy: "" 39 | #API 40 | host: "https://api.themoviedb.org/3" 41 | #媒体数据缓存失效时间 [天] 42 | mediacachefailtime: 1 43 | #TMDB人物数据缓存失效时间 [天] 44 | peoplecachefailtime: 10 45 | douban: 46 | #APIKEY 47 | key: "0ac44ae016490db2204ce0a042db2916" 48 | #豆瓣PC cookie 49 | cookie: "" 50 | #媒体数据缓存失效时间 [天] 51 | mediacachefailtime: 30 52 | #TMDB人物数据缓存失效时间 [天] 53 | peoplecachefailtime: 120 54 | 55 | system: 56 | #排除媒体文件夹 57 | #例如 58 | #excludepath: 59 | # - "AV" 60 | # - "xxx" 61 | excludepath: 62 | - "合集" 63 | #排除媒体 64 | #例如 65 | #excludemedia: 66 | # - "xxx" 67 | excludemedia: 68 | - "进击的巨人" 69 | #使用媒体服务器 Emby Jellyfin Plex 70 | mediaserver: "Emby" 71 | #线程数量 豆瓣API有请求频率限制建议线程数量不要过多 72 | threadnum: 10 73 | #是否更新豆瓣评分 74 | updatescore: False 75 | #是否更新标题 76 | updatetitle: True 77 | #是否刷新人名 78 | updatepeople: False 79 | #是否更新概述 80 | updateoverview: False 81 | #是否更新剧集组, 在TMDB有些剧集会有剧集组, EMBY只能刮削初始播放日期的剧集, 例如纸房子是有5季, EMBY只能刮削初始播放日期3季的版本 82 | updateseasongroup: False 83 | #每次刷新全部媒体间隔时间 [小时] 84 | updatetime: 12 85 | #检查媒体搜刮是否正确 不正确自动更新 需要配合NasTools使用 86 | checkmediasearch: False 87 | #豆瓣API延迟[秒] 豆瓣API有请求频率限制建议时间可以设置长一点 88 | doubanapispace: 0 89 | #删除没有头像的演员 90 | delnotimagepeople: False 91 | #剧集组配置, 在启用更新剧集组后有效 92 | #例如 93 | #seasongroup: 94 | #- EMBY媒体名称|TMDB episode_group剧集组ID 95 | #- "纸房子|5eb730dfca7ec6001f7beb51" 96 | seasongroup: 97 | - "纸房子|5eb730dfca7ec6001f7beb51" 98 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import shutil 4 | from api.media import media 5 | from system.log import log 6 | from api.media_config import MediaConfig 7 | from system.config import Config 8 | 9 | if __name__ == "__main__": 10 | try: 11 | if not os.path.exists(os.path.join(os.getcwd(), "config", "config.yaml")): 12 | log().logger.info( 13 | "配置文件不存在, 拷贝默认配置文件[config.default.yaml]->[/config/config.yaml]" 14 | ) 15 | shutil.copy("config.default.yaml", "config/config.yaml") 16 | 17 | if not os.path.exists( 18 | os.path.join(os.getcwd(), "config", "config.default.yaml") 19 | ): 20 | log().logger.info( 21 | "默认配置文件不存在, 拷贝默认配置文件[config.default.yaml]->[/config/config.default.yaml]" 22 | ) 23 | shutil.copy("config.default.yaml", "config/config.default.yaml") 24 | _ = MediaConfig() 25 | config = Config().get_config() 26 | mediaclient = media() 27 | while True: 28 | try: 29 | log().logger.info("开始刷新媒体库元数据") 30 | mediaclient.start_scan_media() 31 | log().logger.info("刷新媒体库元数据完成") 32 | time.sleep(config["system"]["updatetime"] * 3600) 33 | except Exception as result: 34 | log().logger.info(result) 35 | except Exception as result: 36 | log().logger.info( 37 | "文件[{}]行[{}]异常错误:{}".format( 38 | result.__traceback__.tb_frame.f_globals["__file__"], 39 | result.__traceback__.tb_lineno, 40 | result, 41 | ) 42 | ) 43 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | zhconv 2 | requests 3 | pyjson 4 | pyyaml 5 | html2text 6 | plexapi -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | python main.py -------------------------------------------------------------------------------- /ver.txt: -------------------------------------------------------------------------------- 1 | VSVersionInfo( 2 | ffi=FixedFileInfo( 3 | filevers=(1, 0, 0, 0), 4 | prodvers=(1, 0, 0, 0), 5 | mask=0x3f, 6 | flags=0x0, 7 | OS=0x40004, 8 | fileType=0x1, 9 | subtype=0x0, 10 | date=(0, 0) 11 | ), 12 | kids=[ 13 | StringFileInfo( 14 | [ 15 | StringTable( 16 | u'080404b0', 17 | [StringStruct(u'CompanyName', u'神秘开发'), 18 | StringStruct(u'FileDescription', u'媒体服务工具'), 19 | StringStruct(u'FileVersion', u'1.0.0.0'), 20 | StringStruct(u'InternalName', u'media_server_tools.exe'), 21 | StringStruct(u'LegalCopyright', u'Copyright (C) 2022'), 22 | StringStruct(u'OriginalFilename', u'media_server_tools.exe'), 23 | StringStruct(u'ProductName', u'media_server_tools'), 24 | StringStruct(u'ProductVersion', u'1.0.0.0')]) 25 | ]), 26 | VarFileInfo([VarStruct(u'Translation', [2052, 1200])]) 27 | ] 28 | ) -------------------------------------------------------------------------------- /打包.bat: -------------------------------------------------------------------------------- 1 | pyinstaller -D --version-file="ver.txt" main.py --name="media_server_tools" --------------------------------------------------------------------------------