├── .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"
--------------------------------------------------------------------------------