├── network ├── __init__.py ├── client_data.py └── network.py ├── start.bat ├── requirement.txt ├── 打包.bat ├── .gitattributes ├── config ├── data.db ├── config.yaml └── config.default.yaml ├── docker ├── package_list.txt ├── MediaServerTools ├── Dockerfile ├── README.md └── start.sh ├── .gitignore ├── system ├── singleton.py ├── log.py └── config.py ├── README.md ├── .github └── workflows │ ├── docker-Hub-Description.yml │ ├── docker-build-nightly.yml │ └── docker-image.yml ├── main.py ├── test.py ├── api ├── server │ ├── serverbase.py │ ├── emby.py │ ├── jellyfin.py │ └── plex.py ├── sql.py ├── nastools.py ├── tmdb.py ├── douban.py └── mediasql.py └── media.py /network/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /start.bat: -------------------------------------------------------------------------------- 1 | python main.py -------------------------------------------------------------------------------- /requirement.txt: -------------------------------------------------------------------------------- 1 | zhconv 2 | requests 3 | pyjson 4 | pyyaml 5 | html2text 6 | plexapi -------------------------------------------------------------------------------- /打包.bat: -------------------------------------------------------------------------------- 1 | pyinstaller -D --version-file="ver.txt" main.py --name="media_server_tools" -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /config/data.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DDSRem-Dev/MediaServerTools-Docker/HEAD/config/data.db -------------------------------------------------------------------------------- /docker/package_list.txt: -------------------------------------------------------------------------------- 1 | python3-dev 2 | py3-pip 3 | bash 4 | su-exec 5 | git 6 | tzdata 7 | zip 8 | unzip -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # .gitignore 2 | 3 | __pycache__/ 4 | *.py[cod] 5 | build/ 6 | dist/ 7 | *.spec 8 | config.yaml 9 | *.txt 10 | *.db -------------------------------------------------------------------------------- /docker/MediaServerTools: -------------------------------------------------------------------------------- 1 | __ __ _ _ ____ _____ _ 2 | | \/ | ___ __| (_) __ _/ ___| ___ _ ____ _____ _ _|_ _|__ ___ | |___ 3 | | |\/| |/ _ \/ _` | |/ _` \___ \ / _ \ '__\ \ / / _ \ '__|| |/ _ \ / _ \| / __| 4 | | | | | __/ (_| | | (_| |___) | __/ | \ V / __/ | | | (_) | (_) | \__ \ 5 | |_| |_|\___|\__,_|_|\__,_|____/ \___|_| \_/ \___|_| |_|\___/ \___/|_|___/ 6 | -------------------------------------------------------------------------------- /system/singleton.py: -------------------------------------------------------------------------------- 1 | import threading 2 | 3 | lock = threading.RLock() 4 | _instance = {} 5 | 6 | 7 | # 单例模式 8 | def singleton(cls): 9 | # 创建字典用来保存类的实例对象 10 | global _instance 11 | 12 | def _singleton(*args, **kwargs): 13 | # 先判断这个类有没有对象 14 | if cls not in _instance: 15 | with lock: 16 | if cls not in _instance: 17 | _instance[cls] = cls(*args, **kwargs) 18 | pass 19 | # 将实例对象返回 20 | return _instance[cls] 21 | 22 | return _singleton -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MediaServerTools 2 | Emby/Jellyfin/Plex 媒体中文自动同步 3 | 1. 中文标题 4 | 2. 媒体概述 5 | 3. 中文人名(Plex暂时不支持) 6 | 4. 中文扮演(Plex暂时不支持) 7 | 5. 剧集概述评分图片同步 8 | 6. 剧集组自定义同步 9 | 7. 媒体搜刮检查是否正确(配合NasTools) 10 | 11 | 12 | * 注意使用本工具需要媒体服务器本身刮削了tmdb的完整数据,工具只是获取原有的数据进行替换 13 | 1. 配置文件config/config.yaml 14 | 2. win下使用安装Python3安装过程连续点击下一步 15 | 3. 安装依赖模块 16 | * python -m pip install -r requirement.txt 17 | 4. 启动cmd输入python main.py 18 | 19 | ![image](https://user-images.githubusercontent.com/23020770/188265314-73610b4e-264d-4b8c-9750-e707512f7fef.png) 20 | ![image](https://user-images.githubusercontent.com/23020770/188306989-c722673e-2dac-4c79-8cb1-1a4eb3a35aa2.png) 21 | ![image](https://user-images.githubusercontent.com/23020770/202453243-255b1c95-cbdf-4f24-a215-16399a442ff6.png) 22 | -------------------------------------------------------------------------------- /.github/workflows/docker-Hub-Description.yml: -------------------------------------------------------------------------------- 1 | name: Docker Hub Description 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | job: 8 | name: 更新DockerHub介绍 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: Login to DockerHub 15 | uses: docker/login-action@v2 16 | with: 17 | username: ${{ secrets.DOCKER_USERNAME }} 18 | password: ${{ secrets.DOCKER_PASSWORD }} 19 | 20 | - 21 | name: Docker Hub Description 22 | uses: peter-evans/dockerhub-description@v2 23 | with: 24 | username: ${{ secrets.DOCKER_USERNAME }} 25 | password: ${{ secrets.DOCKER_PASSWORD }} 26 | repository: ddsderek/mediaservertools 27 | short-description: MediaServerTools Image 28 | readme-filepath: ./docker/README.md 29 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | 4 | from media import media 5 | from system.config import config 6 | from system.log import log 7 | 8 | if __name__ == '__main__': 9 | try: 10 | if 'MEDIASERVERTOOLS_CONFIG' not in os.environ: 11 | if 'EMBYTOOLS_CONFIG' in os.environ: 12 | path = os.environ['EMBYTOOLS_CONFIG'] 13 | else: 14 | path = os.path.join(os.getcwd(), 'config') 15 | os.environ['MEDIASERVERTOOLS_CONFIG'] = path 16 | 17 | 18 | path = os.path.join(os.environ['MEDIASERVERTOOLS_CONFIG'], 'config.yaml') 19 | configinfo = config(path=path) 20 | mediaclient = media(configinfo=configinfo) 21 | while True: 22 | try: 23 | log().info('开始刷新媒体库元数据') 24 | mediaclient.start_scan_media() 25 | log().info('刷新媒体库元数据完成') 26 | time.sleep(configinfo.systemdata['updatetime'] * 3600) 27 | except Exception as result: 28 | log().info(result) 29 | except Exception as result: 30 | log().info("文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result)) -------------------------------------------------------------------------------- /.github/workflows/docker-build-nightly.yml: -------------------------------------------------------------------------------- 1 | name: Build MediaServerTools Nightly 2 | 3 | on: 4 | schedule: 5 | - cron: '0 1 * * *' 6 | 7 | jobs: 8 | push: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@master 14 | 15 | - name: Set Version 16 | id: set-version 17 | run: | 18 | echo "::set-output name=version::${GITHUB_REF:10}" 19 | echo "ReleaseTag=${GITHUB_REF:10}" 20 | 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v2 23 | 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v2 26 | 27 | - name: Login to DockerHub 28 | uses: docker/login-action@v2 29 | with: 30 | username: ${{ secrets.DOCKER_USERNAME }} 31 | password: ${{ secrets.DOCKER_PASSWORD }} 32 | 33 | - 34 | name: Build MediaServerTools 35 | uses: docker/build-push-action@v3 36 | with: 37 | context: ./docker/ 38 | file: ./docker/Dockerfile 39 | platforms: | 40 | linux/amd64 41 | linux/arm64 42 | linux/arm/v7 43 | linux/arm/v6 44 | push: true 45 | build-args: | 46 | VERSION=${{ steps.set-version.outputs.version }} 47 | tags: | 48 | ${{ secrets.DOCKER_USERNAME }}/mediaservertools:nightly 49 | -------------------------------------------------------------------------------- /network/client_data.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import threading 3 | 4 | 5 | class ClientData(object): 6 | client = None 7 | allotnum = None 8 | allotmaxnum = None 9 | lock = None 10 | 11 | def __init__(self, allotnum: int = 0, allotmaxnum: int = 3, proxy: str = None): 12 | self.lock = threading.Lock() 13 | self.allotnum = allotnum 14 | self.allotmaxnum = allotmaxnum 15 | self.client = requests.Session() 16 | 17 | # 设置代理 18 | if proxy: 19 | self.client.proxies = {"http": proxy, "https": proxy} 20 | 21 | def get(self, url, **kwargs): 22 | """ 23 | GET 24 | :param url 25 | :return p, err 26 | """ 27 | self.lock.acquire() 28 | p = None 29 | err = None 30 | try: 31 | p = self.client.get(url=url, **kwargs) 32 | except Exception as result: 33 | err = "异常错误:{}".format(result) 34 | self.lock.release() 35 | return p, err 36 | 37 | def post(self, url, **kwargs): 38 | """ 39 | GET 40 | :param url 41 | :return p, err 42 | """ 43 | self.lock.acquire() 44 | p = None 45 | err = None 46 | try: 47 | p = self.client.post(url=url, **kwargs) 48 | except Exception as result: 49 | err = "异常错误:{}".format(result) 50 | self.lock.release() 51 | return p, err 52 | -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:3.16 2 | 3 | ENV WORK_DIR=/MediaServerTools \ 4 | MEDIASERVERTOOLS_CONFIG=/config \ 5 | LANG="C.UTF-8" \ 6 | PS1="\[\e[32m\][\[\e[m\]\[\e[36m\]\u \[\e[m\]\[\e[37m\]@ \[\e[m\]\[\e[34m\]\h\[\e[m\]\[\e[32m\]]\[\e[m\] \[\e[37;35m\]in\[\e[m\] \[\e[33m\]\w\[\e[m\] \[\e[32m\][\[\e[m\]\[\e[37m\]\d\[\e[m\] \[\e[m\]\[\e[37m\]\t\[\e[m\]\[\e[32m\]]\[\e[m\] \n\[\e[1;31m\]$ \[\e[0m\]" \ 7 | TZ=Asia/Shanghai \ 8 | PUID=1000 \ 9 | PGID=1000 \ 10 | UMASK=022 \ 11 | REPO_URL=https://github.com/DDS-Derek/MediaServerTools.git \ 12 | REPO_RAW_URL=https://raw.githubusercontent.com/DDS-Derek/MediaServerTools \ 13 | MediaServerTools_AUTO_UPDATE=true \ 14 | MediaServerTools_CN_UPDATE=true 15 | 16 | WORKDIR ${WORK_DIR} 17 | 18 | RUN \ 19 | apk add --no-cache $(echo $(wget --no-check-certificate -qO- ${REPO_RAW_URL}/main/docker/package_list.txt)) \ 20 | && pip install --upgrade pip setuptools wheel \ 21 | && pip install -r ${REPO_RAW_URL}/main/requirement.txt \ 22 | && ln -sf /usr/share/zoneinfo/${TZ} /etc/localtime \ 23 | && echo ${TZ} > /etc/timezone \ 24 | && rm -rf \ 25 | /tmp/* \ 26 | /root/.cache \ 27 | /var/cache/apk/* 28 | 29 | RUN git config --global pull.ff only && \ 30 | git clone -b main ${REPO_URL} ${WORK_DIR} --recurse-submodule && \ 31 | git config --global --add safe.directory ${WORK_DIR} && \ 32 | chmod -R 755 ${WORK_DIR} 33 | 34 | ENTRYPOINT [ "/MediaServerTools/docker/start.sh" ] 35 | -------------------------------------------------------------------------------- /system/log.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from logging.handlers import RotatingFileHandler 3 | import os 4 | from system.singleton import singleton 5 | 6 | 7 | @singleton 8 | class log(object): 9 | logger = None 10 | 11 | def __init__(self) -> None: 12 | try: 13 | path = os.path.join(os.environ["MEDIASERVERTOOLS_CONFIG"], "log.txt") 14 | self.logger = logging.getLogger(__name__) 15 | self.logger.setLevel(logging.INFO) 16 | handler = RotatingFileHandler( 17 | filename=path, 18 | maxBytes=10 * 1024 * 1024, 19 | backupCount=3, 20 | encoding="utf-8", 21 | ) 22 | handler.setFormatter( 23 | logging.Formatter("%(asctime)s\t%(levelname)s: %(message)s") 24 | ) 25 | self.logger.addHandler(handler) 26 | streamhandler = logging.StreamHandler() 27 | streamhandler.setFormatter( 28 | logging.Formatter("%(asctime)s\t%(levelname)s: %(message)s") 29 | ) 30 | self.logger.addHandler(streamhandler) 31 | except Exception as result: 32 | print(result) 33 | 34 | def debug(self, message): 35 | self.logger.debug(message) 36 | 37 | def info(self, message): 38 | self.logger.info(message) 39 | 40 | def warn(self, message): 41 | self.logger.warn(message) 42 | 43 | def error(self, message): 44 | self.logger.error(message) 45 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from media import media 4 | from system.config import config 5 | from system.log import log 6 | 7 | if __name__ == '__main__': 8 | try: 9 | if 'MEDIASERVERTOOLS_CONFIG' not in os.environ: 10 | if 'EMBYTOOLS_CONFIG' in os.environ: 11 | path = os.environ['EMBYTOOLS_CONFIG'] 12 | else: 13 | path = os.path.join(os.getcwd(), 'config') 14 | os.environ['MEDIASERVERTOOLS_CONFIG'] = path 15 | 16 | 17 | path = os.path.join(os.environ['MEDIASERVERTOOLS_CONFIG'], 'config.yaml') 18 | configinfo = config(path=path) 19 | mediaclient = media(configinfo=configinfo) 20 | while True: 21 | try: 22 | #ret, mediainfo = mediaclient.nastoolsclient.name_test('[OPFans枫雪动漫][ONE PIECE 海贼王][第1032话][1080p][周日版][MKV][简繁内封]') 23 | #ret, mediainfo = mediaclient.meidiaserverclient.get_items() 24 | ret, mediainfo = mediaclient.tmdbclient.get_tv_season_info('64387', '2') 25 | #ret, mediainfo = mediaclient.tmdbclient.get_person_info('1796805') 26 | print(mediainfo) 27 | except Exception as result: 28 | log().info("文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result)) 29 | except Exception as result: 30 | log().info("文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result)) -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Build MediaServerTools 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | push: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - 12 | name: Checkout 13 | uses: actions/checkout@master 14 | 15 | - name: Set Version 16 | id: set-version 17 | run: | 18 | echo "::set-output name=version::${GITHUB_REF:10}" 19 | echo "ReleaseTag=${GITHUB_REF:10}" 20 | 21 | - name: Set up QEMU 22 | uses: docker/setup-qemu-action@v2 23 | 24 | - name: Set up Docker Buildx 25 | uses: docker/setup-buildx-action@v2 26 | 27 | - name: Login to DockerHub 28 | uses: docker/login-action@v2 29 | with: 30 | username: ${{ secrets.DOCKER_USERNAME }} 31 | password: ${{ secrets.DOCKER_PASSWORD }} 32 | 33 | - 34 | name: Build MediaServerTools 35 | uses: docker/build-push-action@v3 36 | with: 37 | context: ./docker/ 38 | file: ./docker/Dockerfile 39 | platforms: | 40 | linux/amd64 41 | linux/arm64 42 | linux/arm/v7 43 | linux/arm/v6 44 | push: true 45 | build-args: | 46 | VERSION=${{ steps.set-version.outputs.version }} 47 | tags: | 48 | ${{ secrets.DOCKER_USERNAME }}/mediaservertools:${{ steps.set-version.outputs.version }} 49 | ${{ secrets.DOCKER_USERNAME }}/mediaservertools:latest 50 | 51 | - 52 | name: Docker Hub Description 53 | uses: peter-evans/dockerhub-description@v2 54 | with: 55 | username: ${{ secrets.DOCKER_USERNAME }} 56 | password: ${{ secrets.DOCKER_PASSWORD }} 57 | repository: ddsderek/mediaservertools 58 | short-description: MediaServerTools Image 59 | readme-filepath: ./docker/README.md 60 | -------------------------------------------------------------------------------- /docker/README.md: -------------------------------------------------------------------------------- 1 | # MediaServerTools 2 | 3 | ## 简介 4 | 5 | Emby/Jellyfin/Plex 媒体中文自动同步 6 | 1. 中文标题 7 | 2. 媒体概述 8 | 3. 中文人名(Plex暂时不支持) 9 | 4. 中文扮演(Plex暂时不支持) 10 | 5. 剧集概述评分图片同步 11 | 6. 剧集组自定义同步 12 | 7. 媒体搜刮检查是否正确(配合NasTools) 13 | 14 | - Dockerhub: https://hub.docker.com/r/ddsderek/mediaservertools 15 | - Github: https://github.com/sleikang/MediaServerTools 16 | 17 | * 注意使用本工具需要媒体服务器本身刮削了tmdb的完整数据,工具只是获取原有的数据进行替换 18 | 19 | ## 部署 20 | 21 | 设置`MediaServerTools_AUTO_UPDATE`=true,重启容器即可自动更新MediaServerTools程序 22 | 23 | **docker-cli** 24 | 25 | ```bash 26 | docker run -itd \ 27 | --name MediaServerTools \ 28 | -v /root/config:/config \ 29 | -e TZ=Asia/Shanghai \ 30 | -e PUID=1000 \ 31 | -e PGID=1000 \ 32 | -e UMASK=022 \ 33 | -e MediaServerTools_AUTO_UPDATE=true \ 34 | -e MediaServerTools_CN_UPDATE=true \ 35 | --net=host \ 36 | --log-opt max-size=5m \ 37 | ddsderek/mediaservertools:latest 38 | ``` 39 | 40 | **docker-compose** 41 | 42 | ```yaml 43 | version: '3.3' 44 | services: 45 | MediaServerTools: 46 | container_name: MediaServerTools 47 | volumes: 48 | - './config:/config' 49 | environment: 50 | - TZ=Asia/Shanghai 51 | - PUID=1000 52 | - PGID=1000 53 | - UMASK=022 54 | - MediaServerTools_AUTO_UPDATE=true # 自动更新 55 | - MediaServerTools_CN_UPDATE=true # 使用国内源更新 56 | network_mode: host 57 | logging: 58 | driver: json-file 59 | options: 60 | max-size: 5m 61 | image: 'ddsderek/mediaservertools:latest' 62 | ``` 63 | 64 | 65 | ![image](https://user-images.githubusercontent.com/23020770/188265314-73610b4e-264d-4b8c-9750-e707512f7fef.png) 66 | ![image](https://user-images.githubusercontent.com/23020770/188306989-c722673e-2dac-4c79-8cb1-1a4eb3a35aa2.png) 67 | ![image](https://user-images.githubusercontent.com/23020770/202453243-255b1c95-cbdf-4f24-a215-16399a442ff6.png) 68 | -------------------------------------------------------------------------------- /config/config.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | nastools: 3 | #域名 包含http(s)和端口号后面不带/ 4 | #例如http://xxx.xxx.xxx:3000 5 | host: "http://127.0.0.1:3000" 6 | #用户名 7 | username: "" 8 | #密码 9 | passwd: "" 10 | plex: 11 | #域名 包含http(s)和端口号后面不带/ 12 | #例如https://xxx.xxx.xxx:32400 13 | host: "http://127.0.0.1:32400" 14 | #X-Plex-Token 15 | key: "" 16 | jellyfin: 17 | #域名 包含http(s)和端口号后面不带/ 18 | #例如https://xxx.xxx.xxx:8920 19 | host: "http://127.0.0.1:8096" 20 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 21 | userid: "" 22 | #APIKEY 23 | key: "" 24 | emby: 25 | #域名 包含http(s)和端口号后面不带/ 26 | #例如https://xxx.xxx.xxx:8920 27 | host: "http://127.0.0.1:8096" 28 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 29 | userid: "" 30 | #APIKEY 31 | key: "" 32 | tmdb: 33 | #APIKEY 34 | key: "" 35 | #代理 36 | #proxy: "" 37 | #API 38 | host: "https://api.themoviedb.org/3" 39 | #媒体数据缓存失效时间 [天] 40 | mediacachefailtime: 1 41 | #TMDB人物数据缓存失效时间 [天] 42 | peoplecachefailtime: 10 43 | douban: 44 | #APIKEY 45 | key: "0ac44ae016490db2204ce0a042db2916" 46 | #豆瓣PC cookie 47 | cookie: "" 48 | #媒体数据缓存失效时间 [天] 49 | mediacachefailtime: 30 50 | #TMDB人物数据缓存失效时间 [天] 51 | peoplecachefailtime: 120 52 | 53 | system: 54 | #使用媒体服务器 Emby Jellyfin Plex 55 | mediaserver: "Emby" 56 | #线程数量 豆瓣API有请求频率限制建议线程数量不要过多 57 | threadnum: 10 58 | #是否刷新人名 59 | updatepeople: False 60 | #是否更新概述 61 | updateoverview: False 62 | #是否更新剧集组, 在TMDB有些剧集会有剧集组, EMBY只能刮削初始播放日期的剧集, 例如纸房子是有5季, EMBY只能刮削初始播放日期3季的版本 63 | updateseasongroup: False 64 | #每次刷新全部媒体间隔时间 [小时] 65 | updatetime: 12 66 | #检查媒体搜刮是否正确 不正确自动更新 需要配合NasTools使用 67 | checkmediasearch: False 68 | #任务完成等待时间 [秒] 豆瓣API有请求频率限制建议时间可以设置长一点 69 | taskdonespace: 0 70 | #删除没有头像的演员 71 | delnotimagepeople: False 72 | #剧集组配置, 在启用更新剧集组后有效 73 | #例如 74 | #seasongroup: 75 | #- EMBY媒体名称|TMDB episode_group剧集组ID 76 | #- "纸房子|5eb730dfca7ec6001f7beb51" 77 | seasongroup: 78 | - "纸房子|5eb730dfca7ec6001f7beb51" 79 | -------------------------------------------------------------------------------- /config/config.default.yaml: -------------------------------------------------------------------------------- 1 | api: 2 | nastools: 3 | #域名 包含http(s)和端口号后面不带/ 4 | #例如http://xxx.xxx.xxx:3000 5 | host: "http://127.0.0.1:3000" 6 | #用户名 7 | username: "" 8 | #密码 9 | passwd: "" 10 | plex: 11 | #域名 包含http(s)和端口号后面不带/ 12 | #例如https://xxx.xxx.xxx:32400 13 | host: "http://127.0.0.1:32400" 14 | #X-Plex-Token 15 | key: "" 16 | jellyfin: 17 | #域名 包含http(s)和端口号后面不带/ 18 | #例如https://xxx.xxx.xxx:8920 19 | host: "http://127.0.0.1:8096" 20 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 21 | userid: "" 22 | #APIKEY 23 | key: "" 24 | emby: 25 | #域名 包含http(s)和端口号后面不带/ 26 | #例如https://xxx.xxx.xxx:8920 27 | host: "http://127.0.0.1:8096" 28 | #用户ID 进EMBY 用户点击管理员账号配置可以在网址上看到userId 29 | userid: "" 30 | #APIKEY 31 | key: "" 32 | tmdb: 33 | #APIKEY 34 | key: "" 35 | #代理 36 | #proxy: "" 37 | #API 38 | host: "https://api.themoviedb.org/3" 39 | #媒体数据缓存失效时间 [天] 40 | mediacachefailtime: 1 41 | #TMDB人物数据缓存失效时间 [天] 42 | peoplecachefailtime: 10 43 | douban: 44 | #APIKEY 45 | key: "0ac44ae016490db2204ce0a042db2916" 46 | #豆瓣PC cookie 47 | cookie: "" 48 | #媒体数据缓存失效时间 [天] 49 | mediacachefailtime: 30 50 | #TMDB人物数据缓存失效时间 [天] 51 | peoplecachefailtime: 120 52 | 53 | system: 54 | #使用媒体服务器 Emby Jellyfin Plex 55 | mediaserver: "Emby" 56 | #线程数量 豆瓣API有请求频率限制建议线程数量不要过多 57 | threadnum: 10 58 | #是否刷新人名 59 | updatepeople: False 60 | #是否更新概述 61 | updateoverview: False 62 | #是否更新剧集组, 在TMDB有些剧集会有剧集组, EMBY只能刮削初始播放日期的剧集, 例如纸房子是有5季, EMBY只能刮削初始播放日期3季的版本 63 | updateseasongroup: False 64 | #每次刷新全部媒体间隔时间 [小时] 65 | updatetime: 12 66 | #检查媒体搜刮是否正确 不正确自动更新 需要配合NasTools使用 67 | checkmediasearch: False 68 | #任务完成等待时间 [秒] 豆瓣API有请求频率限制建议时间可以设置长一点 69 | taskdonespace: 0 70 | #删除没有头像的演员 71 | delnotimagepeople: False 72 | #剧集组配置, 在启用更新剧集组后有效 73 | #例如 74 | #seasongroup: 75 | #- EMBY媒体名称|TMDB episode_group剧集组ID 76 | #- "纸房子|5eb730dfca7ec6001f7beb51" 77 | seasongroup: 78 | - "纸房子|5eb730dfca7ec6001f7beb51" 79 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /network/network.py: -------------------------------------------------------------------------------- 1 | from network.client_data import ClientData 2 | import threading 3 | 4 | 5 | class Network: 6 | maxnumconnect = None 7 | maxnumcache = None 8 | clientlist = None 9 | lock = None 10 | 11 | def __init__( 12 | self, maxnumconnect: int = 1, maxnumcache: int = 5, proxy: str = None 13 | ) -> None: 14 | """ 15 | 构造函数 16 | :param maxnumconnect 最大连接数 17 | :param maxnumcache 最大缓存数 18 | """ 19 | self.lock = threading.Lock() 20 | self.maxnumcache = maxnumcache 21 | self.maxnumconnect = maxnumconnect 22 | self.clientlist = [] 23 | if self.maxnumcache < 0: 24 | self.maxnumcache = 1 25 | if self.maxnumconnect < 0: 26 | self.maxnumconnect = 1 27 | for i in range(self.maxnumconnect): 28 | self.clientlist.append( 29 | ClientData(allotmaxnum=self.maxnumcache, proxy=proxy) 30 | ) 31 | 32 | def get(self, url, **kwargs): 33 | """ 34 | GET 35 | :param url 36 | :return p, err 37 | """ 38 | p = None 39 | ret, num, err = self.__getclient__() 40 | if ret: 41 | p, err = self.clientlist[num].get(url=url, **kwargs) 42 | self.__releasecache__(num=num) 43 | return p, err 44 | 45 | def post(self, url, **kwargs): 46 | """ 47 | POST 48 | :param url 49 | :param data 50 | :param headers 51 | :return p, err 52 | """ 53 | p = None 54 | ret, num, err = self.__getclient__() 55 | if ret: 56 | p, err = self.clientlist[num].post(url=url, **kwargs) 57 | self.__releasecache__(num=num) 58 | return p, err 59 | 60 | def __getclient__(self): 61 | """ 62 | 获取客户端 63 | :return True, num, err 成功返回True, 编号, None 失败返回False, None, 错误 64 | """ 65 | # 优先空闲连接 66 | for i in range(len(self.clientlist)): 67 | if ( 68 | self.clientlist[i].allotnum < self.clientlist[i].allotmaxnum 69 | and self.clientlist[i].allotnum == 0 70 | ): 71 | self.clientlist[i].allotnum += 1 72 | return True, i, None 73 | 74 | # 优先缓存数少的 75 | minallotnum = -1 76 | num = -1 77 | 78 | for i in range(len(self.clientlist)): 79 | if self.clientlist[i].allotnum < self.clientlist[i].allotmaxnum: 80 | if minallotnum == -1: 81 | minallotnum = self.clientlist[i].allotnum 82 | num = i 83 | elif minallotnum > self.clientlist[i].allotnum: 84 | minallotnum = self.clientlist[i].allotnum 85 | num = i 86 | 87 | if num > -1: 88 | self.clientlist[num].allotnum += 1 89 | return True, num, None 90 | 91 | return False, None, "连接缓存已满" 92 | 93 | def __releasecache__(self, num: int): 94 | """ 95 | 释放连接 96 | :param num 连接编号 97 | """ 98 | self.clientlist[num].allotnum -= 1 99 | -------------------------------------------------------------------------------- /api/sql.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | import threading 4 | 5 | class sql: 6 | sqlconnect = None 7 | lock = None 8 | err = None 9 | 10 | def __init__(self) -> None: 11 | try: 12 | self.lock = threading.Lock() 13 | path = os.path.join(os.environ['MEDIASERVERTOOLS_CONFIG'], 'data.db') 14 | self.sqlconnect = sqlite3.connect(database=path, check_same_thread=False) 15 | self.execution(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);') 16 | self.execution(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);') 17 | self.execution(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);') 18 | self.execution(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);') 19 | self.execution(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);') 20 | self.execution(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);') 21 | except Exception as reuslt: 22 | self.err = '数据库异常错误, {}'.format(reuslt) 23 | 24 | def query(self, sql : str): 25 | """ 26 | 查询数据库 27 | :param sql 语句 28 | :return True or False, data 29 | """ 30 | self.lock.acquire() 31 | done = False 32 | data = None 33 | try: 34 | cursor = self.sqlconnect.cursor() 35 | cursor.execute(sql) 36 | data = cursor.fetchall() 37 | done = True 38 | except Exception as reuslt: 39 | self.err = '数据库异常错误, {}'.format(reuslt) 40 | self.lock.release() 41 | return done, data 42 | 43 | def execution(self, sql : str, data = None): 44 | """ 45 | 执行数据库 46 | :param sql 语句 47 | :return True or False 48 | """ 49 | self.lock.acquire() 50 | done = False 51 | try: 52 | cursor = self.sqlconnect.cursor() 53 | if data: 54 | cursor.execute(sql, data) 55 | else: 56 | cursor.execute(sql) 57 | self.sqlconnect.commit() 58 | done = True 59 | except Exception as reuslt: 60 | self.err = '数据库异常错误, {}'.format(reuslt) 61 | self.lock.release() 62 | return done -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /docker/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | Green="\033[32m" 4 | Red="\033[31m" 5 | Yellow='\033[33m' 6 | Font="\033[0m" 7 | INFO="[${Green}INFO${Font}]" 8 | ERROR="[${Red}ERROR${Font}]" 9 | WARN="[${Yellow}WARN${Font}]" 10 | Time=$(date +"%Y-%m-%d %T") 11 | INFO(){ 12 | echo -e "${Time} ${INFO} ${1}" 13 | } 14 | ERROR(){ 15 | echo -e "${Time} ${ERROR} ${1}" 16 | } 17 | WARN(){ 18 | echo -e "${Time} ${WARN} ${1}" 19 | } 20 | 21 | # 初始设置 22 | function setting { 23 | touch /setting.lock 24 | 25 | # 兼容旧config文件路径 26 | if [ -d /opt/config ] && [ ! -d /config ]; then 27 | INFO "使用v1.x版本config路径配置" 28 | rm -rf /config 29 | ln -s /opt/config / 30 | else 31 | mkdir -p /config 32 | fi 33 | } 34 | 35 | # 自动更新 36 | function app_update { 37 | INFO "更新程序..." 38 | git remote set-url origin ${REPO_URL} &>/dev/null 39 | git clean -dffx 40 | git reset --hard HEAD 41 | git pull 42 | } 43 | 44 | function requirement_update { 45 | INFO "检测到requirement.txt有变化,重新安装依赖..." 46 | if [ "${MediaServerTools_CN_UPDATE}" = "true" ]; then 47 | pip install --upgrade pip setuptools wheel -i https://pypi.tuna.tsinghua.edu.cn/simple 48 | pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple 49 | else 50 | pip install --upgrade pip setuptools wheel 51 | pip install -r requirement.txt 52 | fi 53 | } 54 | 55 | function package_list_update { 56 | INFO "检测到package_list.txt有变化,更新软件包..." 57 | if [ "${NASTOOL_CN_UPDATE}" = "true" ]; then 58 | sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/g' /etc/apk/repositories 59 | apk update -f 60 | fi 61 | apk add --no-cache $(echo $(cat docker/package_list.txt)) 62 | } 63 | 64 | function backup_config { 65 | INFO "备份config文件中..." 66 | if [ -f /config/config_backup.zip ]; then 67 | rm -rf /config/config_backup.zip 68 | fi 69 | if [ -f /config/log.txt ]; then 70 | zip -r /config/config_backup.zip /config -x='/config/log.txt' 71 | else 72 | zip -r /config/config_backup.zip /config 73 | fi 74 | if [ -f /config/config_backup.zip ]; then 75 | if [[ "$(stat -c '%u' /config/config_backup.zip)" != "${PUID}" ]] || [[ "$(stat -c '%g' /config/config_backup.zip)" != "${PGID}" ]]; then 76 | chown ${PUID}:${PGID} /config/config_backup.zip 77 | fi 78 | if [ $? -ne 0 ]; then 79 | ERROR "备份失败,未成功设置权限" 80 | else 81 | INFO "备份成功" 82 | fi 83 | else 84 | ERROR "备份失败,未正常生成文件" 85 | fi 86 | } 87 | 88 | if [ ! -f /setting.lock ]; then 89 | setting 90 | fi 91 | 92 | if [ "${MediaServerTools_AUTO_UPDATE}" = "true" ]; then 93 | backup_config 94 | if [ ! -s /tmp/requirement.txt.sha256sum ]; then 95 | sha256sum requirement.txt > /tmp/requirement.txt.sha256sum 96 | fi 97 | if [ ! -s /tmp/package_list.txt.sha256sum ]; then 98 | sha256sum docker/package_list.txt > /tmp/package_list.txt.sha256sum 99 | fi 100 | app_update 101 | if [ $? -eq 0 ]; then 102 | INFO "更新成功" 103 | hash_old=$(cat /tmp/requirement.txt.sha256sum) 104 | hash_new=$(sha256sum requirement.txt) 105 | if [ "$hash_old" != "$hash_new" ]; then 106 | requirement_update 107 | if [ $? -ne 0 ]; then 108 | ERROR "无法安装依赖,请更新镜像" 109 | else 110 | INFO "依赖安装成功" 111 | sha256sum requirement.txt > /tmp/requirement.txt.sha256sum 112 | fi 113 | fi 114 | hash_old=$(cat /tmp/package_list.txt.sha256sum) 115 | hash_new=$(sha256sum docker/package_list.txt) 116 | if [ "$hash_old" != "$hash_new" ]; then 117 | package_list_update 118 | if [ $? -ne 0 ]; then 119 | ERROR "无法更新软件包,请更新镜像" 120 | else 121 | INFO "软件包安装成功" 122 | sha256sum docker/package_list.txt > /tmp/package_list.txt.sha256sum 123 | fi 124 | fi 125 | else 126 | WARN "更新失败,继续使用旧的程序来启动" 127 | fi 128 | else 129 | INFO "程序自动升级已关闭,如需自动升级请在创建容器时设置环境变量:MediaServerTools_AUTO_UPDATE=true" 130 | fi 131 | 132 | # 权限设置 133 | chown -R ${PUID}:${PGID} ${WORK_DIR} 134 | 135 | # 兼容旧环境变量 136 | if [[ -n "${MEDIASERVERTOOLS_CONFIG}" ]]; then 137 | if [[ "$(stat -c '%u' ${MEDIASERVERTOOLS_CONFIG})" != "${PUID}" ]] || [[ "$(stat -c '%g' ${MEDIASERVERTOOLS_CONFIG})" != "${PGID}" ]]; then 138 | chown ${PUID}:${PGID} ${MEDIASERVERTOOLS_CONFIG} 139 | fi 140 | fi 141 | if [[ -n "${EMBYTOOLS_CONFIG}" ]]; then 142 | WARN "使用旧Config路径环境变量" 143 | if [[ "$(stat -c '%u' ${EMBYTOOLS_CONFIG})" != "${PUID}" ]] || [[ "$(stat -c '%g' ${EMBYTOOLS_CONFIG})" != "${PGID}" ]]; then 144 | chown ${PUID}:${PGID} ${EMBYTOOLS_CONFIG} 145 | fi 146 | fi 147 | 148 | if [[ "$(stat -c '%A' ${WORK_DIR}/docker/start.sh)" != "-rwxr-xr-x" ]]; then 149 | chmod 755 ${WORK_DIR}/docker/start.sh 150 | fi 151 | 152 | umask ${UMASK} 153 | 154 | # 启动 155 | echo -e "————————————————————————————————————————————————————————————————————————————————————————" 156 | cat ${WORK_DIR}/docker/MediaServerTools 157 | echo -e " 158 | 159 | 以PUID=${PUID},PGID=${PGID},Umask=${UMASK}的身份启动程序 160 | ———————————————————————————————————————————————————————————————————————————————————————— 161 | 162 | " 163 | 164 | exec su-exec ${PUID}:${PGID} python3 main.py 165 | -------------------------------------------------------------------------------- /system/config.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | from system.log import log 3 | import os 4 | 5 | 6 | class config: 7 | path = None 8 | configdata = None 9 | apidata = None 10 | systemdata = None 11 | 12 | def __init__(self, path: str) -> None: 13 | try: 14 | # 打开文件 15 | self.path = path 16 | if not os.path.exists(path=self.path): 17 | log().info("配置文件[{}]不存在, 开始生成默认配置文件".format(self.path)) 18 | file = open(self.path, "w", encoding="utf-8") 19 | data = { 20 | "api": { 21 | "nastools": { 22 | "host": "http://127.0.0.1:3000", 23 | "authorization": "", 24 | "username": "", 25 | "passwd": "", 26 | }, 27 | "emby": { 28 | "host": "http://127.0.0.1:8096", 29 | "userid": "", 30 | "key": "", 31 | }, 32 | "jellyfin": { 33 | "host": "http://127.0.0.1:8096", 34 | "userid": "", 35 | "key": "", 36 | }, 37 | "tmdb": { 38 | "key": "", 39 | "mediacachefailtime": 1, 40 | "peoplecachefailtime": 10, 41 | }, 42 | "douban": { 43 | "key": "0ac44ae016490db2204ce0a042db2916", 44 | "cookie": "", 45 | "mediacachefailtime": 1, 46 | "peoplecachefailtime": 10, 47 | }, 48 | }, 49 | "system": { 50 | "threadnum": 1, 51 | "updatepeople": True, 52 | "updateoverview": True, 53 | "updateseasongroup": False, 54 | "updatetime": 1, 55 | "taskdonespace": 1, 56 | "delnotimagepeople": False, 57 | "checkmediasearch": False, 58 | "seasongroup": ["纸房子|62ed7ac87d5504007e4ab046"], 59 | }, 60 | } 61 | yaml.dump(data=data, stream=file) 62 | log().info("生成默认配置文件[{}]完成".format(self.path)) 63 | file.close() 64 | file = open(self.path, "r", encoding="utf-8") 65 | self.configdata = yaml.load(file, Loader=yaml.FullLoader) 66 | self.systemdata = self.configdata["system"] 67 | self.apidata = self.configdata["api"] 68 | 69 | self.__config_check__( 70 | self.apidata["nastools"], "host", "http://127.0.0.1:3000" 71 | ) 72 | self.__config_check__(self.apidata["nastools"], "authorization", "") 73 | self.__config_check__(self.apidata["nastools"], "username", "") 74 | self.__config_check__(self.apidata["nastools"], "passwd", "") 75 | self.__config_check__(self.apidata["emby"], "host", "http://127.0.0.1:8096") 76 | self.__config_check__(self.apidata["emby"], "userid", "") 77 | self.__config_check__(self.apidata["emby"], "key", "") 78 | self.__config_check__( 79 | self.apidata["jellyfin"], "host", "http://127.0.0.1:8096" 80 | ) 81 | self.__config_check__(self.apidata["jellyfin"], "userid", "") 82 | self.__config_check__(self.apidata["jellyfin"], "key", "") 83 | self.__config_check__(self.apidata["tmdb"], "key", "") 84 | self.__config_check__(self.apidata["tmdb"], "mediacachefailtime", 1) 85 | self.__config_check__(self.apidata["tmdb"], "peoplecachefailtime", 10) 86 | self.__config_check__( 87 | self.apidata["douban"], "key", "0ac44ae016490db2204ce0a042db2916" 88 | ) 89 | self.__config_check__(self.apidata["douban"], "cookie", "") 90 | self.__config_check__(self.apidata["douban"], "mediacachefailtime", 1) 91 | self.__config_check__(self.apidata["douban"], "peoplecachefailtime", 10) 92 | self.__config_check__(self.systemdata, "mediaserver", "Emby") 93 | self.__config_check__(self.systemdata, "threadnum", 1) 94 | self.__config_check__(self.systemdata, "updatepeople", True) 95 | self.__config_check__(self.systemdata, "updateoverview", True) 96 | self.__config_check__(self.systemdata, "updateseasongroup", False) 97 | self.__config_check__(self.systemdata, "updatetime", 1) 98 | self.__config_check__(self.systemdata, "taskdonespace", 1) 99 | self.__config_check__(self.systemdata, "delnotimagepeople", False) 100 | self.__config_check__(self.systemdata, "checkmediasearch", False) 101 | self.__config_check__( 102 | self.systemdata, "seasongroup", ["纸房子|5eb730dfca7ec6001f7beb51"] 103 | ) 104 | except Exception as result: 105 | log().info("配置异常错误, {}".format(result)) 106 | 107 | def __config_check__(self, config, key, defaultvalue): 108 | try: 109 | if key not in config: 110 | log().info("配置项[{}]不存在, 创建默认值[{}]".format(key, defaultvalue)) 111 | config[key] = defaultvalue 112 | with open(file=self.path, mode="w") as file: 113 | yaml.safe_dump(self.configdata, file, default_flow_style=False) 114 | except Exception as result: 115 | log().info("配置异常错误, {}".format(result)) 116 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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 | url = iteminfo["info_url"] 115 | p, err = self.client.get(url=url, headers=self.mobileheaders) 116 | if not self.__get_status__(p=p, err=err): 117 | return False, iteminfo 118 | text = html2text.html2text(html=p.text) 119 | text = text.replace("/\n", "/ ").replace("# 影片信息\n\n", "") 120 | infolist = re.findall(pattern="([\s\S]+?)\s*\|\s*([\s\S]+?)\n", string=text) 121 | iteminfo["info"] = {} 122 | for info in infolist: 123 | if info[0] == "---": 124 | continue 125 | valuelist = info[1].split(" / ") 126 | if len(valuelist) > 1: 127 | iteminfo["info"][info[0]] = [] 128 | for value in valuelist: 129 | iteminfo["info"][info[0]].append( 130 | re.sub(pattern="\s+", repl="", string=value) 131 | ) 132 | else: 133 | iteminfo["info"][info[0]] = re.sub( 134 | pattern="\s+", repl="", string=info[1] 135 | ) 136 | return True, iteminfo 137 | except Exception as result: 138 | self.err = "文件[{}]行[{}]异常错误:{}".format( 139 | result.__traceback__.tb_frame.f_globals["__file__"], 140 | result.__traceback__.tb_lineno, 141 | result, 142 | ) 143 | return False, iteminfo 144 | 145 | def get_tv_celebrities_info(self, tvid: str): 146 | """ 147 | 获取演员信息 148 | :param tvid 电影ID 149 | """ 150 | iteminfo = {} 151 | try: 152 | url = "{}/tv/{}/celebrities?apikey={}".format(self.host, tvid, self.key) 153 | p, err = self.client.get(url=url, headers=self.mobileheaders) 154 | if not self.__get_status__(p=p, err=err): 155 | return False, iteminfo 156 | iteminfo = json.loads(p.text) 157 | return True, iteminfo 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 get_celebrity_info(self, celebrityid: str): 167 | """ 168 | 获取人物信息 169 | :param celebrityid 人物ID 170 | :param True of False, celebrityinfo 171 | """ 172 | iteminfo = {} 173 | try: 174 | url = "{}/celebrity/{}?apikey={}".format(self.host, celebrityid, self.key) 175 | p, err = self.client.get(url=url, headers=self.mobileheaders) 176 | if not self.__get_status__(p=p, err=err): 177 | return False, iteminfo 178 | iteminfo = json.loads(p.text) 179 | return True, iteminfo 180 | except Exception as result: 181 | self.err = "文件[{}]行[{}]异常错误:{}".format( 182 | result.__traceback__.tb_frame.f_globals["__file__"], 183 | result.__traceback__.tb_lineno, 184 | result, 185 | ) 186 | return False, iteminfo 187 | 188 | def search_media_pc(self, title: str): 189 | """ 190 | 搜索电影信息 191 | :param title 标题 192 | :return True or False, iteminfo 193 | """ 194 | iteminfo = {} 195 | try: 196 | url = "https://movie.douban.com/j/subject_suggest?q={}".format(title) 197 | p, err = self.client.get(url=url, headers=self.pcheaders) 198 | if not self.__get_status__(p=p, err=err): 199 | return False, iteminfo 200 | medialist = json.loads(p.text) 201 | for media in medialist: 202 | if re.search(pattern="第[\s\S]+季", string=media["title"]): 203 | media["target_type"] = "tv" 204 | media["target_id"] = media["id"] 205 | iteminfo["items"] = medialist 206 | return True, iteminfo 207 | except Exception as result: 208 | self.err = "文件[{}]行[{}]异常错误:{}".format( 209 | result.__traceback__.tb_frame.f_globals["__file__"], 210 | result.__traceback__.tb_lineno, 211 | result, 212 | ) 213 | return False, iteminfo 214 | 215 | def search_media(self, title: str): 216 | """ 217 | 搜索电影信息 218 | :param title 标题 219 | :return True or False, iteminfo 220 | """ 221 | iteminfo = {} 222 | try: 223 | url = "{}/search/movie?q={}&start=0&count=2&apikey={}".format( 224 | self.host, title, self.key 225 | ) 226 | p, err = self.client.get(url=url, headers=self.mobileheaders) 227 | if not self.__get_status__(p=p, err=err): 228 | return False, iteminfo 229 | iteminfo = json.loads(p.text) 230 | return True, iteminfo 231 | except Exception as result: 232 | self.err = "文件[{}]行[{}]异常错误:{}".format( 233 | result.__traceback__.tb_frame.f_globals["__file__"], 234 | result.__traceback__.tb_lineno, 235 | result, 236 | ) 237 | return False, iteminfo 238 | 239 | def search_media_weixin(self, title: str): 240 | """ 241 | 搜索媒体信息 242 | :param title 标题 243 | :return True or False, iteminfo 244 | """ 245 | iteminfo = {} 246 | try: 247 | url = "{}/search/weixin?q={}&start=0&count=3&apikey={}".format( 248 | self.host, title, self.key 249 | ) 250 | p, err = self.client.get(url=url, headers=self.mobileheaders) 251 | if not self.__get_status__(p=p, err=err): 252 | return False, iteminfo 253 | iteminfo = json.loads(p.text) 254 | return True, iteminfo 255 | except Exception as result: 256 | self.err = "文件[{}]行[{}]异常错误:{}".format( 257 | result.__traceback__.tb_frame.f_globals["__file__"], 258 | result.__traceback__.tb_lineno, 259 | result, 260 | ) 261 | return False, iteminfo 262 | 263 | def __get_status__(self, p, err): 264 | try: 265 | if p == None: 266 | self.err = err 267 | return False 268 | if p.status_code != 200: 269 | rootobject = json.loads(p.text) 270 | if "localized_message" in rootobject: 271 | self.err = rootobject["localized_message"] 272 | else: 273 | self.err = p.text 274 | return False 275 | return True 276 | except Exception as result: 277 | self.err = "文件[{}]行[{}]异常错误:{}".format( 278 | result.__traceback__.tb_frame.f_globals["__file__"], 279 | result.__traceback__.tb_lineno, 280 | result, 281 | ) 282 | 283 | return False 284 | -------------------------------------------------------------------------------- /api/mediasql.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | 4 | from api.sql import sql 5 | from system.config import config 6 | 7 | 8 | class mediasql(sql): 9 | tmdbmediacachefailtime = None 10 | tmdbpeoplecachefailtime = None 11 | doubanmediacachefailtime = None 12 | doubanpeoplecachefailtime = None 13 | 14 | def __init__(self, configinfo : config) -> None: 15 | super().__init__() 16 | self.tmdbmediacachefailtime = configinfo.apidata['tmdb']['mediacachefailtime'] 17 | self.tmdbpeoplecachefailtime = configinfo.apidata['tmdb']['peoplecachefailtime'] 18 | self.doubanmediacachefailtime = configinfo.apidata['douban']['mediacachefailtime'] 19 | self.doubanpeoplecachefailtime = configinfo.apidata['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(sql="select * from douban_tv where media_name like '%{}%';".format(title)) 32 | else: 33 | ret, datalist = self.query(sql="select * from douban_movie where media_name like '%{}%';".format(title)) 34 | if not ret or not len(datalist): 35 | return False, None 36 | for data in datalist: 37 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 38 | nowtime = datetime.datetime.now() 39 | day = (nowtime - datatime).days 40 | if day > self.doubanmediacachefailtime: 41 | continue 42 | iteminfo['items'].append(json.loads(data[3])) 43 | return True, iteminfo 44 | return False, None 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 write_douban_media(self, mediatype : int, id : str, iteminfo): 50 | """ 51 | 写入豆瓣媒体 52 | :param mediatype 媒体类型 1TV 2电影 53 | :param id 媒体ID 54 | :param iteminfo 媒体信息 55 | :return True or False 56 | """ 57 | try: 58 | if mediatype == 1: 59 | ret, datalist = self.query(sql="select * from douban_tv where media_id = '{}';".format(id)) 60 | else: 61 | ret, datalist = self.query(sql="select * from douban_movie where media_id = '{}';".format(id)) 62 | if not ret: 63 | return False 64 | for data in datalist: 65 | if mediatype == 1: 66 | ret = self.execution(sql="update douban_tv set media_brief = ?, update_time = ? where media_id = ?;", data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 67 | else: 68 | ret = self.execution(sql="update douban_movie set media_brief = ?, update_time = ? where media_id = ?;", data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 69 | return ret 70 | 71 | if mediatype == 1: 72 | ret = self.execution(sql="insert into douban_tv(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, ?, '', '', ?);", data=(id, iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 73 | else: 74 | ret = self.execution(sql="insert into douban_movie(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, ?, '', '', ?);", data=(id, iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 75 | return ret 76 | except Exception as result: 77 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 78 | return False 79 | 80 | def get_douban_media_info(self, mediatype : int, id : str): 81 | """ 82 | 获取豆瓣媒体信息 83 | :param mediatype 媒体类型 1TV 2电影 84 | :param id 媒体ID 85 | :return True or False, iteminfo 86 | """ 87 | iteminfo = {} 88 | try: 89 | if mediatype == 1: 90 | ret, datalist = self.query(sql="select * from douban_tv where media_id = '{}';".format(id)) 91 | else: 92 | ret, datalist = self.query(sql="select * from douban_movie where media_id = '{}';".format(id)) 93 | if not ret or not len(datalist): 94 | return False, None 95 | for data in datalist: 96 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 97 | nowtime = datetime.datetime.now() 98 | day = (nowtime - datatime).days 99 | if day > self.doubanmediacachefailtime: 100 | continue 101 | iteminfo = json.loads(data[4]) 102 | return True, iteminfo 103 | return False, None 104 | except Exception as result: 105 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 106 | return False, iteminfo 107 | 108 | def write_douban_media_info(self, mediatype : int, id : str, iteminfo): 109 | """ 110 | 写入豆瓣媒体信息 111 | :param mediatype 媒体类型 1TV 2电影 112 | :param id 媒体ID 113 | :param iteminfo 媒体信息 114 | :return True or False 115 | """ 116 | try: 117 | if mediatype == 1: 118 | ret, datalist = self.query(sql="select * from douban_tv where media_id = '{}';".format(id)) 119 | else: 120 | ret, datalist = self.query(sql="select * from douban_movie where media_id = '{}';".format(id)) 121 | if not ret: 122 | return False 123 | for data in datalist: 124 | if mediatype == 1: 125 | ret = self.execution(sql="update douban_tv set media_data = ?, update_time = ? where media_id = ?;", data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 126 | else: 127 | ret = self.execution(sql="update douban_movie set media_data = ?, update_time = ? where media_id = ?;", data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 128 | return ret 129 | 130 | if mediatype == 1: 131 | ret = self.execution(sql="insert into douban_tv(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, '', ?, '', ?);", data=(id, iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 132 | else: 133 | ret = self.execution(sql="insert into douban_movie(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, ?, '', ?, '', ?);", data=(id, iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 134 | return ret 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 get_douban_celebrities_info(self, mediatype : int, id : str): 140 | """ 141 | 获取豆瓣媒体演员信息 142 | :param mediatype 媒体类型 1TV 2电影 143 | :param id 媒体ID 144 | :return True or False, iteminfo 145 | """ 146 | iteminfo = {} 147 | try: 148 | if mediatype == 1: 149 | ret, datalist = self.query(sql="select * from douban_tv where media_id = '{}';".format(id)) 150 | else: 151 | ret, datalist = self.query(sql="select * from douban_movie where media_id = '{}';".format(id)) 152 | if not ret or not len(datalist): 153 | return False, None 154 | for data in datalist: 155 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 156 | nowtime = datetime.datetime.now() 157 | day = (nowtime - datatime).days 158 | if day > self.doubanmediacachefailtime: 159 | continue 160 | iteminfo = json.loads(data[5]) 161 | return True, iteminfo 162 | return False, None 163 | except Exception as result: 164 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 165 | return False, iteminfo 166 | 167 | def write_douban_celebrities_info(self, mediatype : int, id : str, iteminfo): 168 | """ 169 | 写入豆瓣媒体演员信息 170 | :param mediatype 媒体类型 1TV 2电影 171 | :param id 媒体ID 172 | :param iteminfo 演员信息 173 | :return True or False 174 | """ 175 | try: 176 | if mediatype == 1: 177 | ret, datalist = self.query(sql="select * from douban_tv where media_id = '{}';".format(id)) 178 | else: 179 | ret, datalist = self.query(sql="select * from douban_movie where media_id = '{}';".format(id)) 180 | if not ret: 181 | return False 182 | for data in datalist: 183 | if mediatype == 1: 184 | ret = self.execution(sql="update douban_tv set media_celebrities = ?, update_time = ? where media_id = ?;", data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 185 | else: 186 | ret = self.execution(sql="update douban_movie set media_celebrities = ?, update_time = ? where media_id = ?;", data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 187 | return ret 188 | 189 | if mediatype == 1: 190 | ret = self.execution(sql="insert into douban_tv(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, '', '', '', ?, ?);", data=(id, json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 191 | else: 192 | ret = self.execution(sql="insert into douban_movie(id, media_id, media_name, media_brief, media_data, media_celebrities, update_time) values(null, ?, '', '', '', ?, ?);", data=(id, json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 193 | return ret 194 | except Exception as result: 195 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 196 | return False 197 | 198 | def get_douban_people_info(self, id : str): 199 | """ 200 | 获取豆瓣演员信息 201 | :param mediatype 媒体类型 1TV 2电影 202 | :param id 人物ID 203 | :return True or False, iteminfo 204 | """ 205 | iteminfo = {} 206 | try: 207 | ret, datalist = self.query(sql="select * from douban_people where people_id = '{}';".format(id)) 208 | if not ret or not len(datalist): 209 | return False, None 210 | for data in datalist: 211 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 212 | nowtime = datetime.datetime.now() 213 | day = (nowtime - datatime).days 214 | if day > self.doubanpeoplecachefailtime: 215 | continue 216 | iteminfo = json.loads(data[3]) 217 | return True, iteminfo 218 | return False, None 219 | except Exception as result: 220 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 221 | return False, iteminfo 222 | 223 | def write_douban_people_info(self, id : str, iteminfo): 224 | """ 225 | 写入豆瓣演员信息 226 | :param id 人物ID 227 | :param name 人物姓名 228 | :param iteminfo 人物信息 229 | :return True or False 230 | """ 231 | try: 232 | ret, datalist = self.query(sql="select * from douban_people where people_id = '{}';".format(id)) 233 | if not ret: 234 | return False 235 | for data in datalist: 236 | ret = self.execution(sql="update douban_people set people_name = ?, people_data = ?, update_time = ? where people_id = ?;", data=(iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[2])) 237 | return ret 238 | ret = self.execution(sql="insert into douban_people(id, people_id, people_name, people_data, update_time) values(null, ?, ?, ?, ?)", data=(id, iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 239 | return ret 240 | except Exception as result: 241 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 242 | return False 243 | 244 | 245 | def get_tmdb_media_info(self, mediatype : int, id : str, language): 246 | """ 247 | 读取TMDB媒体信息 248 | :param mediatype 媒体类型 1TV 2电影 249 | :param id 媒体ID 250 | :param language 语言 251 | :returen True or False, iteminfo 252 | """ 253 | try: 254 | if mediatype == 1: 255 | ret, datalist = self.query(sql="select * from tmdb_tv where media_id = '{}';".format(id)) 256 | else: 257 | ret, datalist = self.query(sql="select * from tmdb_movie where media_id = '{}';".format(id)) 258 | if not ret or not len(datalist): 259 | return False, None 260 | for data in datalist: 261 | item = None 262 | if language == 'zh-CN': 263 | item = data[3] 264 | elif language == 'zh-SG': 265 | item = data[4] 266 | elif language == 'zh-TW': 267 | item = data[5] 268 | elif language == 'zh-HK': 269 | item = data[6] 270 | if not item: 271 | return False, None 272 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 273 | nowtime = datetime.datetime.now() 274 | day = (nowtime - datatime).days 275 | if day > self.tmdbmediacachefailtime: 276 | continue 277 | return True, json.loads(item) 278 | return False, None 279 | except Exception as result: 280 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 281 | return False, None 282 | 283 | def write_tmdb_media_info(self, mediatype : int, id : str, language, iteminfo): 284 | """ 285 | 写入TMDB媒体信息 286 | :param mediatype 媒体类型 1TV 2电影 287 | :param id 媒体ID 288 | :param language 语言 289 | :param iteminfo 媒体信息 290 | :returen True or False 291 | """ 292 | try: 293 | if mediatype == 1: 294 | ret, datalist = self.query(sql="select * from tmdb_tv where media_id = '{}';".format(id)) 295 | else: 296 | ret, datalist = self.query(sql="select * from tmdb_movie where media_id = '{}';".format(id)) 297 | if not ret: 298 | return False 299 | key = None 300 | if language == 'zh-CN': 301 | key = 'media_data_zh_cn' 302 | elif language == 'zh-SG': 303 | key = 'media_data_zh_sg' 304 | elif language == 'zh-TW': 305 | key = 'media_data_zh_tw' 306 | elif language == 'zh-HK': 307 | key = 'media_data_zh_hk' 308 | if not key: 309 | self.err = '当前语言[{}]不支持'.format(language) 310 | return False 311 | for data in datalist: 312 | if mediatype == 1: 313 | ret = self.execution(sql="update tmdb_tv set {} = ?, media_name = ?, update_time = ? where media_id = ?;".format(key), data=(json.dumps(iteminfo), iteminfo['name'], datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 314 | else: 315 | ret = self.execution(sql="update tmdb_movie set {} = ?, media_name = ?, update_time = ? where media_id = ?;".format(key), data=(json.dumps(iteminfo), iteminfo['title'], datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 316 | return ret 317 | if mediatype == 1: 318 | ret = self.execution(sql="insert into tmdb_tv(id, media_id, media_name, {}, update_time) values(null, ?, ?, ?, ?);".format(key), data=(id, iteminfo['name'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 319 | else: 320 | ret = self.execution(sql="insert into tmdb_movie(id, media_id, media_name, {}, update_time) values(null, ?, ?, ?, ?);".format(key), data=(id, iteminfo['title'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 321 | return ret 322 | except Exception as result: 323 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 324 | return False 325 | 326 | def get_tmdb_season_info(self, id : str, seasonid, language): 327 | """ 328 | 读取TMDB季信息 329 | :param id 媒体ID 330 | :param language 语言 331 | :returen True or False, iteminfo 332 | """ 333 | try: 334 | ret, datalist = self.query(sql="select * from tmdb_tv where media_id = '{}';".format(id)) 335 | if not ret or not len(datalist): 336 | return False, None 337 | for data in datalist: 338 | item = None 339 | if language == 'zh-CN': 340 | item = data[7] 341 | elif language == 'zh-SG': 342 | item = data[8] 343 | elif language == 'zh-TW': 344 | item = data[9] 345 | elif language == 'zh-HK': 346 | item = data[10] 347 | if not item: 348 | return False, None 349 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 350 | nowtime = datetime.datetime.now() 351 | day = (nowtime - datatime).days 352 | if day > self.tmdbmediacachefailtime: 353 | continue 354 | root_object = json.loads(item) 355 | if type(root_object) == dict: 356 | if root_object['season_number'] == int(seasonid): 357 | return True, root_object 358 | else: 359 | for season in root_object: 360 | if season['season_number'] == int(seasonid): 361 | return True, season 362 | 363 | return False, None 364 | except Exception as result: 365 | self.err = "文件[{}][{}]异常错误:".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 366 | return False, None 367 | 368 | def write_tmdb_season_info(self, id : str, seasonid, language, iteminfo): 369 | """ 370 | 写入TMDB季信息 371 | :param id 媒体ID 372 | :param language 语言 373 | :param iteminfo 季信息 374 | :returen True or False 375 | """ 376 | try: 377 | ret, datalist = self.query(sql="select * from tmdb_tv where media_id = '{}';".format(id)) 378 | if not ret: 379 | return False 380 | key = None 381 | if language == 'zh-CN': 382 | key = 'season_data_zh_cn' 383 | elif language == 'zh-SG': 384 | key = 'season_data_zh_sg' 385 | elif language == 'zh-TW': 386 | key = 'season_data_zh_tw' 387 | elif language == 'zh-HK': 388 | key = 'season_data_zh_hk' 389 | if not key: 390 | self.err = '当前语言[{}]不支持'.format(language) 391 | return False 392 | data_array = [] 393 | data_array.append(iteminfo) 394 | for data in datalist: 395 | item = None 396 | if language == 'zh-CN': 397 | item = data[7] 398 | elif language == 'zh-SG': 399 | item = data[8] 400 | elif language == 'zh-TW': 401 | item = data[9] 402 | elif language == 'zh-HK': 403 | item = data[10] 404 | if not item: 405 | ret = self.execution(sql="update tmdb_tv set {} = ?, update_time = ? where media_id = ?;".format(key), data=(json.dumps(data_array), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 406 | else: 407 | root_object = json.loads(item) 408 | if type(root_object) == dict: 409 | if root_object['season_number'] == int(seasonid): 410 | continue 411 | data_array.append(root_object) 412 | else: 413 | for season in root_object: 414 | if season['season_number'] == int(seasonid): 415 | continue 416 | data_array.append(season) 417 | ret = self.execution(sql="update tmdb_tv set {} = ?, update_time = ? where media_id = ?;".format(key), data=(json.dumps(data_array), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 418 | return ret 419 | ret = self.execution(sql="insert into tmdb_tv(id, media_id, {}, update_time) values(null, ?, ?, ?);".format(key), data=(id, json.dumps(data_array), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 420 | return ret 421 | except Exception as result: 422 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 423 | return False 424 | 425 | def get_tmdb_people_info(self, id : str, language): 426 | """ 427 | 获取TMDB人物信息 428 | :param id 人物ID 429 | :param language 语言 430 | :returen True or False, iteminfo 431 | """ 432 | try: 433 | ret, datalist = self.query(sql="select * from tmdb_people where people_id = '{}';".format(id)) 434 | if not ret or not len(datalist): 435 | return False, None 436 | for data in datalist: 437 | item = None 438 | if language == 'zh-CN': 439 | item = data[3] 440 | elif language == 'zh-SG': 441 | item = data[4] 442 | elif language == 'zh-TW': 443 | item = data[5] 444 | elif language == 'zh-HK': 445 | item = data[6] 446 | if not item: 447 | return False, None 448 | datatime = datetime.datetime.strptime(data[-1], '%Y-%m-%d %H:%M:%S') 449 | nowtime = datetime.datetime.now() 450 | day = (nowtime - datatime).days 451 | if day > self.tmdbpeoplecachefailtime: 452 | continue 453 | return True, json.loads(item) 454 | return False, None 455 | except Exception as result: 456 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 457 | return False, None 458 | 459 | def write_tmdb_people_info(self, id : str, language, iteminfo): 460 | """ 461 | 写入TMDB人物信息 462 | :param id 人物ID 463 | :param language 语言 464 | :param iteminfo 人物信息 465 | :returen True or False 466 | """ 467 | try: 468 | ret, datalist = self.query(sql="select * from tmdb_people where people_id = '{}';".format(id)) 469 | if not ret: 470 | return False 471 | key = None 472 | if language == 'zh-CN': 473 | key = 'people_data_zh_cn' 474 | elif language == 'zh-SG': 475 | key = 'people_data_zh_sg' 476 | elif language == 'zh-TW': 477 | key = 'people_data_zh_tw' 478 | elif language == 'zh-HK': 479 | key = 'people_data_zh_hk' 480 | if not key: 481 | self.err = '当前语言[{}]不支持'.format(language) 482 | return False 483 | for data in datalist: 484 | ret = self.execution(sql="update tmdb_people set {} = ?, update_time = ? where people_id = ?;".format(key), data=(json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'), data[1])) 485 | return ret 486 | ret = self.execution(sql="insert into tmdb_people(id, people_id, people_name, {}, update_time) values(null, ?, ?, ?, ?);".format(key), data=(id, iteminfo['name'], json.dumps(iteminfo), datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'))) 487 | return ret 488 | except Exception as result: 489 | self.err = "文件[{}]行[{}]异常错误:{}".format(result.__traceback__.tb_frame.f_globals["__file__"], result.__traceback__.tb_lineno, result) 490 | return False -------------------------------------------------------------------------------- /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 | mediaservertype = None 21 | nastoolsclient = None 22 | meidiaserverclient = None 23 | tmdbclient = None 24 | doubanclient = None 25 | languagelist = None 26 | threadpool = None 27 | updatepeople = None 28 | updateoverview = None 29 | updateseasongroup = None 30 | taskdonespace = None 31 | delnotimagepeople = None 32 | checkmediasearch = None 33 | configload = None 34 | sqlclient = None 35 | configinfo = None 36 | threadnum = None 37 | semaphore = None 38 | 39 | def __init__(self, configinfo: config) -> None: 40 | """ 41 | :param configinfo 配置 42 | """ 43 | try: 44 | self.mediaservertype = configinfo.systemdata["mediaserver"] 45 | self.configload = False 46 | self.configinfo = configinfo 47 | self.sqlclient = mediasql(configinfo=configinfo) 48 | self.nastoolsclient = nastools( 49 | host=configinfo.apidata["nastools"]["host"], 50 | authorization=configinfo.apidata["nastools"]["authorization"], 51 | username=configinfo.apidata["nastools"]["username"], 52 | passwd=configinfo.apidata["nastools"]["passwd"], 53 | ) 54 | 55 | self.tmdbclient = tmdb( 56 | key=configinfo.apidata["tmdb"]["key"], 57 | host=configinfo.apidata["tmdb"].get("host", 'https://api.themoviedb.org/3'), 58 | proxy=configinfo.apidata["tmdb"].get("proxy", None), 59 | ) 60 | self.doubanclient = douban( 61 | key=configinfo.apidata["douban"]["key"], 62 | cookie=configinfo.apidata["douban"]["cookie"], 63 | ) 64 | self.languagelist = ["zh-CN", "zh-SG", "zh-TW", "zh-HK"] 65 | self.threadnum = configinfo.systemdata["threadnum"] 66 | self.semaphore = BoundedSemaphore(self.threadnum) 67 | self.threadpool = ThreadPoolExecutor(max_workers=self.threadnum) 68 | self.updatepeople = configinfo.systemdata["updatepeople"] 69 | self.updateoverview = configinfo.systemdata["updateoverview"] 70 | self.taskdonespace = configinfo.systemdata["taskdonespace"] 71 | self.delnotimagepeople = configinfo.systemdata["delnotimagepeople"] 72 | self.updateseasongroup = configinfo.systemdata["updateseasongroup"] 73 | self.checkmediasearch = configinfo.systemdata["checkmediasearch"] 74 | if "Emby" in self.mediaservertype: 75 | self.meidiaserverclient = emby( 76 | host=configinfo.apidata["emby"]["host"], 77 | userid=configinfo.apidata["emby"]["userid"], 78 | key=configinfo.apidata["emby"]["key"], 79 | ) 80 | self.configload = True 81 | elif "Jellyfin" in self.mediaservertype: 82 | self.meidiaserverclient = jellyfin( 83 | host=configinfo.apidata["jellyfin"]["host"], 84 | userid=configinfo.apidata["jellyfin"]["userid"], 85 | key=configinfo.apidata["jellyfin"]["key"], 86 | ) 87 | self.configload = True 88 | elif "Plex" in self.mediaservertype: 89 | self.meidiaserverclient = plex( 90 | host=configinfo.apidata["plex"]["host"], 91 | userid="", 92 | key=configinfo.apidata["plex"]["key"], 93 | ) 94 | self.configload = True 95 | else: 96 | log().info("当前设置媒体服务器[{}]不支持".format(self.mediaservertype)) 97 | self.configload = False 98 | 99 | except Exception as result: 100 | log().info("配置异常错误, {}".format(result)) 101 | 102 | def start_scan_media(self): 103 | """ 104 | 开始扫描媒体 105 | :return True or False 106 | """ 107 | if not self.configload: 108 | log().info("配置未正常加载") 109 | return False 110 | # 获取媒体库根文件夹 111 | ret, itmes = self.meidiaserverclient.get_items() 112 | if not ret: 113 | log().info( 114 | "获取{}媒体总列表失败, {}".format( 115 | self.mediaservertype, self.meidiaserverclient.err 116 | ) 117 | ) 118 | return False 119 | ret, iteminfo = self.meidiaserverclient.get_items_count() 120 | log().info("总媒体数量[{}]".format(iteminfo["MovieCount"] + iteminfo["SeriesCount"])) 121 | if not ret: 122 | log().info( 123 | "获取{}媒体数量失败, {}".format( 124 | self.mediaservertype, self.meidiaserverclient.err 125 | ) 126 | ) 127 | return False 128 | 129 | for item in self.__check_media_info__(itemlist=itmes): 130 | if not item: 131 | continue 132 | self.__submit_task__(item=item) 133 | while self.semaphore._value != self.threadnum: 134 | time.sleep(0.1) 135 | return ret 136 | 137 | def __submit_task__(self, item): 138 | """ 139 | 提交任务 140 | :param item 141 | """ 142 | self.semaphore.acquire() 143 | self.threadpool.submit(self.__start_task__, item) 144 | 145 | def __start_task__(self, item): 146 | """ 147 | 开始任务 148 | :param item 149 | """ 150 | ret, name = self.__to_deal_with_item__(item=item) 151 | if ret: 152 | log().info("媒体[{}]处理完成".format(name)) 153 | else: 154 | log().info("媒体[{}]处理失败".format(name)) 155 | self.semaphore.release() 156 | 157 | def __check_media_info__(self, itemlist): 158 | """ 159 | 检查媒体信息 160 | :param itemlist 项目列表 161 | :return True or False 162 | """ 163 | try: 164 | for item in itemlist["Items"]: 165 | if "Folder" in item["Type"] and ( 166 | "CollectionType" not in item 167 | or "boxsets" not in item["CollectionType"] 168 | ): 169 | ret, items = self.meidiaserverclient.get_items(parentid=item["Id"]) 170 | if not ret: 171 | log().info( 172 | "获取{}媒体[{}]ID[{}]列表失败, {}".format( 173 | self.mediaservertype, 174 | item["Name"], 175 | item["Id"], 176 | self.meidiaserverclient.err, 177 | ) 178 | ) 179 | continue 180 | for tmpitem in self.__check_media_info__(itemlist=items): 181 | yield tmpitem 182 | else: 183 | if "Series" in item["Type"] or "Movie" in item["Type"]: 184 | yield item 185 | 186 | except Exception as result: 187 | log().info( 188 | "文件[{}]行[{}]异常错误:{}".format( 189 | result.__traceback__.tb_frame.f_globals["__file__"], 190 | result.__traceback__.tb_lineno, 191 | result, 192 | ) 193 | ) 194 | 195 | yield {} 196 | 197 | def __check_media_info_search__(self, item): 198 | """ 199 | 检查媒体信息搜索是否正确 200 | :param item 媒体信息 201 | """ 202 | try: 203 | ret, iteminfo = self.meidiaserverclient.get_item_info(itemid=item["Id"]) 204 | if not ret: 205 | log().info( 206 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 207 | self.mediaservertype, 208 | item["Name"], 209 | item["Id"], 210 | self.meidiaserverclient.err, 211 | ) 212 | ) 213 | return False 214 | tmdbid = "" 215 | if "Tmdb" in iteminfo["ProviderIds"]: 216 | tmdbid = iteminfo["ProviderIds"]["Tmdb"] 217 | elif "tmdb" in iteminfo["ProviderIds"]: 218 | tmdbid = iteminfo["ProviderIds"]["tmdb"] 219 | name = re.sub( 220 | pattern="\s+-\s+\d{1,4}[k|K|p|P]|\s+\(\d{4}\)|\.\S{1,4}$", 221 | repl="", 222 | string=iteminfo["FileName"], 223 | ) 224 | year = None 225 | redata = re.search(pattern="\((\d{4})\)", string=iteminfo["FileName"]) 226 | if redata: 227 | year = redata.group(1) 228 | if ( 229 | name in item["Name"] 230 | and ( 231 | year 232 | and "ProductionYear" in iteminfo 233 | and year in str(iteminfo["ProductionYear"]) 234 | ) 235 | and tmdbid 236 | ): 237 | return True 238 | mediatype = "MOV" 239 | if "Movie" in item["Type"]: 240 | mediatype = "MOV" 241 | else: 242 | mediatype = "TV" 243 | ret, mediainfo = self.nastoolsclient.media_info( 244 | name=name, year=year, type=mediatype 245 | ) 246 | if not ret: 247 | log().info( 248 | "[{}]媒体名称[{}]与原始名称[{}]不一致可能识别错误, NasTools识别媒体[{}]失败, {}".format( 249 | self.mediaservertype, 250 | item["Name"], 251 | iteminfo["FileName"], 252 | name, 253 | self.nastoolsclient.err, 254 | ) 255 | ) 256 | return False 257 | testtmdbid = None 258 | if year and year != mediainfo["year"]: 259 | return False 260 | if mediainfo["tmdbid"] > 0: 261 | testtmdbid = str(mediainfo["tmdbid"]) 262 | if not testtmdbid or tmdbid == testtmdbid: 263 | return True 264 | if "Series" in item["Type"]: 265 | ret, tvinfo = self.__get_tmdb_media_info__( 266 | mediatype=1, name=item["Name"], id=testtmdbid 267 | ) 268 | if not ret: 269 | return False 270 | if len(tvinfo["seasons"]) < iteminfo["ChildCount"]: 271 | return False 272 | 273 | ret, searchinfo = self.meidiaserverclient.search_movie( 274 | itemid=item["Id"], tmdbid=testtmdbid, name=name, year=year 275 | ) 276 | if not ret: 277 | log().info( 278 | "{}搜索媒体[{}]ID[{}]TMDB[{}]信息失败, {}".format( 279 | self.mediaservertype, 280 | item["Name"], 281 | item["Id"], 282 | testtmdbid, 283 | self.meidiaserverclient.err, 284 | ) 285 | ) 286 | return False 287 | for info in searchinfo: 288 | if "Plex" not in self.mediaservertype: 289 | info["Type"] = iteminfo["Type"] 290 | info["IsFolder"] = iteminfo["IsFolder"] 291 | ret = self.meidiaserverclient.apply_search( 292 | itemid=item["Id"], iteminfo=info 293 | ) 294 | if not ret: 295 | log().info( 296 | "{}更新媒体[{}]ID[{}]TMDB[{}]信息失败, {}".format( 297 | self.mediaservertype, 298 | item["Name"], 299 | item["Id"], 300 | testtmdbid, 301 | self.meidiaserverclient.err, 302 | ) 303 | ) 304 | return False 305 | log().info( 306 | "{}更新媒体[{}]ID[{}]TMDB[{}]更新为媒体[{}]TMDB[{}]".format( 307 | self.mediaservertype, 308 | item["Name"], 309 | item["Id"], 310 | tmdbid, 311 | mediainfo["title"], 312 | testtmdbid, 313 | ) 314 | ) 315 | item["Name"] = mediainfo["title"] 316 | break 317 | return True 318 | except Exception as result: 319 | log().info( 320 | "文件[{}]行[{}]异常错误:{}".format( 321 | result.__traceback__.tb_frame.f_globals["__file__"], 322 | result.__traceback__.tb_lineno, 323 | result, 324 | ) 325 | ) 326 | return False 327 | 328 | def __to_deal_with_item__(self, item): 329 | """ 330 | 处理媒体 331 | :param item 332 | :return True or False 333 | """ 334 | try: 335 | if self.checkmediasearch: 336 | _ = self.__check_media_info_search__(item=item) 337 | 338 | updatename = False 339 | updatepeople = False 340 | updateoverview = False 341 | ret, iteminfo = self.meidiaserverclient.get_item_info(itemid=item["Id"]) 342 | if not ret: 343 | log().info( 344 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 345 | self.mediaservertype, 346 | item["Name"], 347 | item["Id"], 348 | self.meidiaserverclient.err, 349 | ) 350 | ) 351 | return False, item["Name"] 352 | if "LockedFields" not in iteminfo: 353 | iteminfo["LockedFields"] = [] 354 | tmdbid = None 355 | imdbid = None 356 | if "Tmdb" in iteminfo["ProviderIds"]: 357 | tmdbid = iteminfo["ProviderIds"]["Tmdb"] 358 | elif "tmdb" in iteminfo["ProviderIds"]: 359 | tmdbid = iteminfo["ProviderIds"]["tmdb"] 360 | 361 | if "Imdb" in iteminfo["ProviderIds"]: 362 | imdbid = iteminfo["ProviderIds"]["Imdb"] 363 | elif "imdb" in iteminfo["ProviderIds"]: 364 | imdbid = iteminfo["ProviderIds"]["imdb"] 365 | 366 | doubanmediainfo = None 367 | 368 | if not self.__is_chinese__(string=item["Name"]): 369 | if not tmdbid and not imdbid: 370 | log().info( 371 | "{}媒体[{}]ID[{}]Tmdb|Imdb不存在".format( 372 | self.mediaservertype, item["Name"], item["Id"] 373 | ) 374 | ) 375 | return False, item["Name"] 376 | name = None 377 | if tmdbid: 378 | if "Series" in item["Type"]: 379 | ret, name = self.__get_tmdb_media_name__( 380 | mediatype=1, datatype=1, name=item["Name"], id=tmdbid 381 | ) 382 | else: 383 | ret, name = self.__get_tmdb_media_name__( 384 | mediatype=2, datatype=1, name=item["Name"], id=tmdbid 385 | ) 386 | if imdbid and not name: 387 | if "Series" in item["Type"]: 388 | ret, doubanmediainfo = self.__get_douban_media_info__( 389 | mediatype=1, name=item["Name"], id=imdbid 390 | ) 391 | else: 392 | ret, doubanmediainfo = self.__get_douban_media_info__( 393 | mediatype=2, name=item["Name"], id=imdbid 394 | ) 395 | if ret: 396 | if self.__is_chinese__(string=doubanmediainfo["title"], mode=2): 397 | name = doubanmediainfo["title"] 398 | 399 | if name: 400 | originalname = iteminfo["Name"] 401 | iteminfo["Name"] = name 402 | if "Name" not in iteminfo["LockedFields"]: 403 | iteminfo["LockedFields"].append("Name") 404 | updatename = True 405 | 406 | if self.updatepeople and "People" in iteminfo: 407 | updatepeople = self.__update_people__( 408 | item=item, iteminfo=iteminfo, imdbid=imdbid 409 | ) 410 | 411 | if "Series" in item["Type"]: 412 | ret, seasons = self.meidiaserverclient.get_items( 413 | parentid=item["Id"], type="Season" 414 | ) 415 | if not ret: 416 | log().info( 417 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 418 | self.mediaservertype, 419 | item["Name"], 420 | item["Id"], 421 | self.meidiaserverclient.err, 422 | ) 423 | ) 424 | else: 425 | for season in seasons["Items"]: 426 | if "Jellyfin" in self.mediaservertype: 427 | ret, seasoninfo = self.meidiaserverclient.get_item_info( 428 | itemid=season["Id"] 429 | ) 430 | if not ret: 431 | log().info( 432 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 433 | self.mediaservertype, 434 | season["Name"], 435 | season["Id"], 436 | self.meidiaserverclient.err, 437 | ) 438 | ) 439 | continue 440 | ret = self.__update_people__( 441 | item=season, iteminfo=seasoninfo, imdbid=imdbid 442 | ) 443 | if not ret: 444 | continue 445 | if ret: 446 | _ = self.__refresh_people__( 447 | item=season, iteminfo=seasoninfo 448 | ) 449 | log().info( 450 | "原始媒体名称[{}] 第[{}]季更新人物".format( 451 | iteminfo["Name"], season["IndexNumber"] 452 | ) 453 | ) 454 | ret = self.meidiaserverclient.set_item_info( 455 | itemid=seasoninfo["Id"], iteminfo=seasoninfo 456 | ) 457 | ret, episodes = self.meidiaserverclient.get_items( 458 | parentid=season["Id"], type="Episode" 459 | ) 460 | if not ret: 461 | log().info( 462 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 463 | self.mediaservertype, 464 | season["Name"], 465 | season["Id"], 466 | self.meidiaserverclient.err, 467 | ) 468 | ) 469 | continue 470 | for episode in episodes["Items"]: 471 | ( 472 | ret, 473 | episodeinfo, 474 | ) = self.meidiaserverclient.get_item_info( 475 | itemid=episode["Id"] 476 | ) 477 | if not ret: 478 | log().info( 479 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 480 | self.mediaservertype, 481 | episode["Name"], 482 | episode["Id"], 483 | self.meidiaserverclient.err, 484 | ) 485 | ) 486 | else: 487 | ret = self.__update_people__( 488 | item=episode, 489 | iteminfo=episodeinfo, 490 | imdbid=imdbid, 491 | ) 492 | if not ret: 493 | continue 494 | ret = self.meidiaserverclient.set_item_info( 495 | itemid=episodeinfo["Id"], iteminfo=episodeinfo 496 | ) 497 | if ret: 498 | if "Jellyfin" in self.mediaservertype: 499 | _ = self.__refresh_people__( 500 | item=episode, iteminfo=episodeinfo 501 | ) 502 | log().info( 503 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新人物".format( 504 | iteminfo["Name"], 505 | season["IndexNumber"], 506 | episode["IndexNumber"], 507 | ) 508 | ) 509 | 510 | if self.updateoverview: 511 | if "Overview" not in iteminfo or not self.__is_chinese__( 512 | string=iteminfo["Overview"] 513 | ): 514 | if not tmdbid and not imdbid: 515 | log().info( 516 | "{}媒体[{}]ID[{}]Tmdb|Imdb不存在".format( 517 | self.mediaservertype, item["Name"], item["Id"] 518 | ) 519 | ) 520 | return False, item["Name"] 521 | ret = False 522 | if tmdbid: 523 | if "Series" in item["Type"]: 524 | ret, overview = self.__get_tmdb_media_name__( 525 | mediatype=1, datatype=2, name=item["Name"], id=tmdbid 526 | ) 527 | else: 528 | ret, overview = self.__get_tmdb_media_name__( 529 | mediatype=2, datatype=2, name=item["Name"], id=tmdbid 530 | ) 531 | if ret: 532 | iteminfo["Overview"] = overview 533 | if "Overview" not in iteminfo["LockedFields"]: 534 | iteminfo["LockedFields"].append("Overview") 535 | updateoverview = True 536 | elif imdbid: 537 | if not doubanmediainfo: 538 | if "Series" in item["Type"]: 539 | ret, doubanmediainfo = self.__get_douban_media_info__( 540 | mediatype=1, name=item["Name"], id=imdbid 541 | ) 542 | else: 543 | ret, doubanmediainfo = self.__get_douban_media_info__( 544 | mediatype=2, name=item["Name"], id=imdbid 545 | ) 546 | if doubanmediainfo: 547 | if doubanmediainfo["intro"]: 548 | iteminfo["Overview"] = doubanmediainfo["intro"] 549 | if "Overview" not in iteminfo["LockedFields"]: 550 | iteminfo["LockedFields"].append("Overview") 551 | updateoverview = True 552 | 553 | if "Series" in item["Type"]: 554 | ret, seasons = self.meidiaserverclient.get_items( 555 | parentid=item["Id"], type="Season" 556 | ) 557 | if not ret: 558 | log().info( 559 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 560 | self.mediaservertype, 561 | item["Name"], 562 | item["Id"], 563 | self.meidiaserverclient.err, 564 | ) 565 | ) 566 | else: 567 | seasongroupinfo = None 568 | groupid = None 569 | if self.updateseasongroup: 570 | for groupinfo in self.configinfo.systemdata["seasongroup"]: 571 | infolist = groupinfo.split("|") 572 | if len(infolist) < 2: 573 | continue 574 | if infolist[0] == item["Name"]: 575 | groupid = infolist[1] 576 | break 577 | for season in seasons["Items"]: 578 | ret, episodes = self.meidiaserverclient.get_items( 579 | parentid=season["Id"], type="Episode" 580 | ) 581 | if not ret: 582 | log().info( 583 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 584 | self.mediaservertype, 585 | season["Name"], 586 | season["Id"], 587 | self.meidiaserverclient.err, 588 | ) 589 | ) 590 | continue 591 | for episode in episodes["Items"]: 592 | ( 593 | ret, 594 | episodeinfo, 595 | ) = self.meidiaserverclient.get_item_info( 596 | itemid=episode["Id"] 597 | ) 598 | if not ret: 599 | log().info( 600 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 601 | self.mediaservertype, 602 | episode["Name"], 603 | episode["Id"], 604 | self.meidiaserverclient.err, 605 | ) 606 | ) 607 | else: 608 | imageurl = None 609 | name = None 610 | overview = None 611 | ommunityrating = None 612 | if not groupid: 613 | if ( 614 | "Overview" not in episodeinfo 615 | or not self.__is_chinese__( 616 | string=episodeinfo["Overview"] 617 | ) 618 | ): 619 | ( 620 | ret, 621 | name, 622 | overview, 623 | ommunityrating, 624 | imageurl, 625 | ) = self.__get_tmdb_tv_season_info__( 626 | name=item["Name"], 627 | tvid=tmdbid, 628 | seasonid=season["IndexNumber"], 629 | episodeid=episode["IndexNumber"], 630 | ) 631 | if not ret: 632 | continue 633 | else: 634 | if not seasongroupinfo: 635 | ( 636 | ret, 637 | seasongroupinfo, 638 | ) = self.__get_tmdb_tv_season_group_info__( 639 | name=item["Name"], groupid=groupid 640 | ) 641 | if not ret: 642 | continue 643 | tmdbepisodeinfo = None 644 | for seasondata in seasongroupinfo["groups"]: 645 | if ( 646 | seasondata["order"] 647 | != season["IndexNumber"] 648 | ): 649 | continue 650 | for episodedata in seasondata["episodes"]: 651 | if ( 652 | episodedata["order"] + 1 653 | != episode["IndexNumber"] 654 | ): 655 | continue 656 | tmdbepisodeinfo = episodedata 657 | break 658 | if tmdbepisodeinfo: 659 | break 660 | if not tmdbepisodeinfo: 661 | log().info( 662 | "原始媒体名称[{}] 第[{}]季 第[{}]集未匹配到TMDB剧集组数据".format( 663 | iteminfo["Name"], 664 | season["IndexNumber"], 665 | episode["IndexNumber"], 666 | ) 667 | ) 668 | continue 669 | if ( 670 | tmdbepisodeinfo["name"] 671 | != episodeinfo["Name"] 672 | ): 673 | name = tmdbepisodeinfo["name"] 674 | if ( 675 | "Overview" not in episodeinfo 676 | or tmdbepisodeinfo["overview"] 677 | != episodeinfo["Overview"] 678 | ): 679 | overview = tmdbepisodeinfo["overview"] 680 | if ( 681 | "still_path" in tmdbepisodeinfo 682 | and tmdbepisodeinfo["still_path"] 683 | ): 684 | imageurl = "https://www.themoviedb.org/t/p/original{}".format( 685 | tmdbepisodeinfo["still_path"] 686 | ) 687 | if ( 688 | "CommunityRating" not in episodeinfo 689 | or episodeinfo["CommunityRating"] 690 | != tmdbepisodeinfo["vote_average"] 691 | ): 692 | ommunityrating = tmdbepisodeinfo[ 693 | "vote_average" 694 | ] 695 | 696 | if not name and not overview and not ommunityrating: 697 | continue 698 | if "LockedFields" not in episodeinfo: 699 | episodeinfo["LockedFields"] = [] 700 | if name: 701 | episodeinfo["Name"] = name 702 | if overview: 703 | episodeinfo["Overview"] = overview 704 | if ommunityrating: 705 | episodeinfo["CommunityRating"] = ommunityrating 706 | if "Name" not in episodeinfo["LockedFields"]: 707 | episodeinfo["LockedFields"].append("Name") 708 | if "Overview" not in episodeinfo["LockedFields"]: 709 | episodeinfo["LockedFields"].append("Overview") 710 | ret = self.meidiaserverclient.set_item_info( 711 | itemid=episodeinfo["Id"], iteminfo=episodeinfo 712 | ) 713 | if ret: 714 | if overview: 715 | log().info( 716 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新概述".format( 717 | iteminfo["Name"], 718 | season["IndexNumber"], 719 | episode["IndexNumber"], 720 | ) 721 | ) 722 | if ommunityrating: 723 | log().info( 724 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新评分".format( 725 | iteminfo["Name"], 726 | season["IndexNumber"], 727 | episode["IndexNumber"], 728 | ) 729 | ) 730 | if imageurl: 731 | ret = self.meidiaserverclient.set_item_image( 732 | itemid=episodeinfo["Id"], imageurl=imageurl 733 | ) 734 | if ret: 735 | log().info( 736 | "原始媒体名称[{}] 第[{}]季 第[{}]集更新图片".format( 737 | iteminfo["Name"], 738 | season["IndexNumber"], 739 | episode["IndexNumber"], 740 | ) 741 | ) 742 | 743 | if not updatename and not updatepeople and not updateoverview: 744 | return True, item["Name"] 745 | ret = self.meidiaserverclient.set_item_info( 746 | itemid=iteminfo["Id"], iteminfo=iteminfo 747 | ) 748 | if ret: 749 | if updatename: 750 | log().info( 751 | "原始媒体名称[{}]更新为[{}]".format(originalname, iteminfo["Name"]) 752 | ) 753 | if updatepeople: 754 | if "Jellyfin" in self.mediaservertype: 755 | _ = self.__refresh_people__(item=item, iteminfo=iteminfo) 756 | log().info("原始媒体名称[{}]更新人物".format(iteminfo["Name"])) 757 | if updateoverview: 758 | log().info("原始媒体名称[{}]更新概述".format(iteminfo["Name"])) 759 | time.sleep(self.taskdonespace) 760 | return True, item["Name"] 761 | 762 | except Exception as result: 763 | log().info( 764 | "文件[{}]行[{}]异常错误:{}".format( 765 | result.__traceback__.tb_frame.f_globals["__file__"], 766 | result.__traceback__.tb_lineno, 767 | result, 768 | ) 769 | ) 770 | return False, item["Name"] 771 | 772 | def __refresh_people__(self, item, iteminfo): 773 | """ 774 | 刷新人物元数据 775 | :param iteminfo 项目信息 776 | :return True of False 777 | """ 778 | try: 779 | ret, newiteminfo = self.meidiaserverclient.get_item_info(itemid=item["Id"]) 780 | if not ret: 781 | log().info( 782 | "获取{}媒体[{}]ID[{}]信息失败, {}".format( 783 | self.mediaservertype, 784 | item["Name"], 785 | item["Id"], 786 | self.meidiaserverclient.err, 787 | ) 788 | ) 789 | return False 790 | for people, newpeople in zip(iteminfo["People"], newiteminfo["People"]): 791 | if ( 792 | people["Id"] in newpeople["Id"] 793 | or people["Name"] not in newpeople["Name"] 794 | ): 795 | continue 796 | ret, peopleinfo = self.meidiaserverclient.get_item_info( 797 | itemid=people["Id"] 798 | ) 799 | if not ret: 800 | log().info( 801 | "获取{}人物信息失败, {}".format( 802 | self.mediaservertype, self.meidiaserverclient.err 803 | ) 804 | ) 805 | continue 806 | ret, newpeopleinfo = self.meidiaserverclient.get_item_info( 807 | itemid=newpeople["Id"] 808 | ) 809 | if not ret: 810 | log().info( 811 | "获取{}人物信息失败, {}".format( 812 | self.mediaservertype, self.meidiaserverclient.err 813 | ) 814 | ) 815 | continue 816 | newpeopleinfo["ProviderIds"] = peopleinfo["ProviderIds"] 817 | ret = self.meidiaserverclient.set_item_info( 818 | itemid=newpeopleinfo["Id"], iteminfo=newpeopleinfo 819 | ) 820 | if not ret: 821 | log().info( 822 | "更新{}人物信息失败, {}".format( 823 | self.mediaservertype, self.meidiaserverclient.err 824 | ) 825 | ) 826 | continue 827 | ret, newpeopleinfo = self.meidiaserverclient.get_item_info( 828 | itemid=newpeople["Id"] 829 | ) 830 | if not ret: 831 | log().info( 832 | "获取{}人物信息失败, {}".format( 833 | self.mediaservertype, self.meidiaserverclient.err 834 | ) 835 | ) 836 | continue 837 | """ 838 | ret = self.meidiaserverclient.refresh(peopleinfo['Id']) 839 | if not ret: 840 | log().info('刷新{}人物信息失败, {}'.format(self.mediaservertype, self.meidiaserverclient.err)) 841 | """ 842 | time.sleep(self.taskdonespace) 843 | 844 | return True 845 | except Exception as result: 846 | log().info("异常错误: {}".format(result)) 847 | return False 848 | 849 | def __update_people__(self, item, iteminfo, imdbid): 850 | """ 851 | 更新人物 852 | :param item 项目 853 | :param iteminfo 项目信息 854 | :param imdbid IMDB ID 855 | :return True of False 856 | """ 857 | updatepeople = False 858 | try: 859 | doubanmediainfo = None 860 | doubancelebritiesinfo = None 861 | needdelpeople = [] 862 | for people in iteminfo["People"]: 863 | ret, peopleinfo = self.meidiaserverclient.get_item_info( 864 | itemid=people["Id"] 865 | ) 866 | if not ret: 867 | log().info( 868 | "获取{}人物信息失败, {}".format( 869 | self.mediaservertype, self.meidiaserverclient.err 870 | ) 871 | ) 872 | continue 873 | 874 | peopleimdbid = None 875 | peopletmdbid = None 876 | peoplename = None 877 | if "Tmdb" in peopleinfo["ProviderIds"]: 878 | peopletmdbid = peopleinfo["ProviderIds"]["Tmdb"] 879 | elif "tmdb" in peopleinfo["ProviderIds"]: 880 | peopletmdbid = peopleinfo["ProviderIds"]["tmdb"] 881 | 882 | if "Imdb" in peopleinfo["ProviderIds"]: 883 | peopleimdbid = peopleinfo["ProviderIds"]["Imdb"] 884 | elif "imdb" in peopleinfo["ProviderIds"]: 885 | peopleimdbid = peopleinfo["ProviderIds"]["imdb"] 886 | 887 | if not self.__is_chinese__(string=people["Name"], mode=1): 888 | if "LockedFields" not in peopleinfo: 889 | peopleinfo["LockedFields"] = [] 890 | 891 | if not peopletmdbid and not peopleimdbid: 892 | log().info( 893 | "{}人物[{}]ID[{}]Tmdb|Imdb不存在".format( 894 | self.mediaservertype, 895 | peopleinfo["Name"], 896 | peopleinfo["Id"], 897 | ) 898 | ) 899 | needdelpeople.append(peopleinfo["Id"]) 900 | continue 901 | 902 | if peopleimdbid and imdbid: 903 | if not doubanmediainfo: 904 | if "Series" in item["Type"]: 905 | ret, doubanmediainfo = self.__get_douban_media_info__( 906 | mediatype=1, name=item["Name"], id=imdbid 907 | ) 908 | else: 909 | ret, doubanmediainfo = self.__get_douban_media_info__( 910 | mediatype=2, name=item["Name"], id=imdbid 911 | ) 912 | 913 | if doubanmediainfo and not doubancelebritiesinfo: 914 | if "Series" in item["Type"]: 915 | ( 916 | ret, 917 | doubancelebritiesinfo, 918 | ) = self.__get_douban_media_celebrities_info__( 919 | mediatype=1, 920 | name=item["Name"], 921 | id=doubanmediainfo["id"], 922 | ) 923 | else: 924 | ( 925 | ret, 926 | doubancelebritiesinfo, 927 | ) = self.__get_douban_media_celebrities_info__( 928 | mediatype=2, 929 | name=item["Name"], 930 | id=doubanmediainfo["id"], 931 | ) 932 | 933 | if doubancelebritiesinfo: 934 | ret, celebrities = self.__get_people_info__( 935 | celebritiesinfo=doubancelebritiesinfo, 936 | people=people, 937 | imdbid=peopleimdbid, 938 | ) 939 | if ret and self.__is_chinese__(string=celebrities["name"]): 940 | peoplename = re.sub( 941 | pattern="\s+", repl="", string=celebrities["name"] 942 | ) 943 | if not peoplename: 944 | if self.__is_chinese__(string=peopleinfo["Name"], mode=2): 945 | peoplename = re.sub( 946 | pattern="\s+", repl="", string=peopleinfo["Name"] 947 | ) 948 | peoplename = zhconv.convert(peopleinfo["Name"], "zh-cn") 949 | elif peopletmdbid: 950 | ret, peoplename = self.__get_tmdb_person_name( 951 | name=peopleinfo["Name"], personid=peopletmdbid 952 | ) 953 | 954 | if peoplename: 955 | originalpeoplename = people["Name"] 956 | peopleinfo["Name"] = peoplename 957 | if "Name" not in peopleinfo["LockedFields"]: 958 | peopleinfo["LockedFields"].append("Name") 959 | people["Name"] = peoplename 960 | if "Emby" in self.mediaservertype: 961 | ret = self.meidiaserverclient.set_item_info( 962 | itemid=peopleinfo["Id"], iteminfo=peopleinfo 963 | ) 964 | else: 965 | ret = True 966 | if ret: 967 | log().info( 968 | "原始人物名称[{}]更新为[{}]".format( 969 | originalpeoplename, peoplename 970 | ) 971 | ) 972 | updatepeople = True 973 | elif "Role" not in people or not self.__is_chinese__( 974 | string=people["Role"], mode=2 975 | ): 976 | if imdbid: 977 | if not doubanmediainfo: 978 | if "Series" in item["Type"]: 979 | ret, doubanmediainfo = self.__get_douban_media_info__( 980 | mediatype=1, name=item["Name"], id=imdbid 981 | ) 982 | else: 983 | ret, doubanmediainfo = self.__get_douban_media_info__( 984 | mediatype=2, name=item["Name"], id=imdbid 985 | ) 986 | 987 | if doubanmediainfo and not doubancelebritiesinfo: 988 | if "Series" in item["Type"]: 989 | ( 990 | ret, 991 | doubancelebritiesinfo, 992 | ) = self.__get_douban_media_celebrities_info__( 993 | mediatype=1, 994 | name=item["Name"], 995 | id=doubanmediainfo["id"], 996 | ) 997 | else: 998 | ( 999 | ret, 1000 | doubancelebritiesinfo, 1001 | ) = self.__get_douban_media_celebrities_info__( 1002 | mediatype=2, 1003 | name=item["Name"], 1004 | id=doubanmediainfo["id"], 1005 | ) 1006 | 1007 | if peopleimdbid and doubanmediainfo and doubancelebritiesinfo: 1008 | ret, celebrities = self.__get_people_info__( 1009 | celebritiesinfo=doubancelebritiesinfo, 1010 | people=people, 1011 | imdbid=peopleimdbid, 1012 | ) 1013 | if ret: 1014 | if self.__is_chinese__( 1015 | string=re.sub( 1016 | pattern="饰\s+", 1017 | repl="", 1018 | string=celebrities["character"], 1019 | ) 1020 | ): 1021 | people["Role"] = re.sub( 1022 | pattern="饰\s+", 1023 | repl="", 1024 | string=celebrities["character"], 1025 | ) 1026 | updatepeople = True 1027 | doubanname = re.sub( 1028 | pattern="\s+", repl="", string=celebrities["name"] 1029 | ) 1030 | if people["Name"] != doubanname and self.__is_chinese__( 1031 | string=doubanname 1032 | ): 1033 | originalpeoplename = people["Name"] 1034 | peopleinfo["Name"] = doubanname 1035 | people["Name"] = doubanname 1036 | if "Emby" in self.mediaservertype: 1037 | ret = self.meidiaserverclient.set_item_info( 1038 | itemid=peopleinfo["Id"], iteminfo=peopleinfo 1039 | ) 1040 | else: 1041 | ret = True 1042 | if ret: 1043 | log().info( 1044 | "原始人物名称[{}]更新为[{}]".format( 1045 | originalpeoplename, people["Name"] 1046 | ) 1047 | ) 1048 | updatepeople = True 1049 | 1050 | if "Emby" in self.mediaservertype: 1051 | peoplelist = [] 1052 | peoples = [] 1053 | for people in iteminfo["People"]: 1054 | if self.delnotimagepeople: 1055 | if "PrimaryImageTag" not in people: 1056 | updatepeople = True 1057 | continue 1058 | if people["Name"] + people["Type"] not in peoplelist: 1059 | peoplelist.append(people["Name"] + people["Type"]) 1060 | peoples.append(people) 1061 | else: 1062 | updatepeople = True 1063 | iteminfo["People"] = peoples 1064 | 1065 | if needdelpeople: 1066 | peoples = [] 1067 | for people in iteminfo["People"]: 1068 | if people["Id"] in needdelpeople: 1069 | continue 1070 | peoples.append(people) 1071 | 1072 | iteminfo["People"] = peoples 1073 | updatepeople = True 1074 | 1075 | except Exception as result: 1076 | log().info("异常错误: {}".format(result)) 1077 | return updatepeople 1078 | 1079 | def __get_people_info__(self, celebritiesinfo, people, imdbid): 1080 | """ 1081 | 获取人物信息 1082 | :param celebritiesinfo 演员信息 1083 | :param 人物信息 1084 | :param imdbid 1085 | :return True of False, celebrities 1086 | """ 1087 | try: 1088 | if people["Type"] == "Director": 1089 | for celebrities in celebritiesinfo["directors"]: 1090 | if "info" not in celebrities: 1091 | continue 1092 | haspeople = False 1093 | for imdb in celebrities["info"]["extra"]["info"]: 1094 | if imdb[0] != "IMDb编号": 1095 | continue 1096 | if imdb[1] == imdbid: 1097 | haspeople = True 1098 | break 1099 | 1100 | if haspeople: 1101 | return True, celebrities 1102 | 1103 | for celebrities in celebritiesinfo["directors"]: 1104 | if ( 1105 | people["Name"] == celebrities["name"] 1106 | or people["Name"] == celebrities["latin_name"] 1107 | ): 1108 | return True, celebrities 1109 | 1110 | if people["Type"] == "Actor": 1111 | for celebrities in celebritiesinfo["actors"]: 1112 | if "info" not in celebrities: 1113 | continue 1114 | haspeople = False 1115 | for imdb in celebrities["info"]["extra"]["info"]: 1116 | if imdb[0] != "IMDb编号": 1117 | continue 1118 | if imdb[1] == imdbid: 1119 | haspeople = True 1120 | break 1121 | 1122 | if haspeople: 1123 | return True, celebrities 1124 | 1125 | for celebrities in celebritiesinfo["actors"]: 1126 | if ( 1127 | people["Name"] == celebrities["name"] 1128 | or people["Name"] == celebrities["latin_name"] 1129 | ): 1130 | return True, celebrities 1131 | 1132 | except Exception as result: 1133 | log().info("异常错误: {}".format(result)) 1134 | return False, None 1135 | 1136 | def __get_douban_media_celebrities_info__(self, mediatype: int, name: str, id: str): 1137 | """ 1138 | 获取豆瓣媒体演员信息 1139 | :param mediatype 媒体类型 1TV 2电影 1140 | :name name 媒体名称 1141 | :id id 媒体ID 1142 | :return True or False, celebritiesinfo 1143 | """ 1144 | try: 1145 | ret, celebritiesinfo = self.sqlclient.get_douban_celebrities_info( 1146 | mediatype=mediatype, id=id 1147 | ) 1148 | if not ret: 1149 | if mediatype == 1: 1150 | ret, celebritiesinfo = self.doubanclient.get_tv_celebrities_info( 1151 | tvid=id 1152 | ) 1153 | else: 1154 | ret, celebritiesinfo = self.doubanclient.get_movie_celebrities_info( 1155 | movieid=id 1156 | ) 1157 | if not ret: 1158 | log().info( 1159 | "获取豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1160 | name, id, self.doubanclient.err 1161 | ) 1162 | ) 1163 | return False, None 1164 | ret = self.sqlclient.write_douban_celebrities_info( 1165 | mediatype=mediatype, id=id, iteminfo=celebritiesinfo 1166 | ) 1167 | if not ret: 1168 | log().info( 1169 | "保存豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1170 | name, id, self.doubanclient.err 1171 | ) 1172 | ) 1173 | for celebrities in celebritiesinfo["directors"]: 1174 | ret, info = self.sqlclient.get_douban_people_info(id=celebrities["id"]) 1175 | if not ret: 1176 | ret, info = self.doubanclient.get_celebrity_info( 1177 | celebrityid=celebrities["id"] 1178 | ) 1179 | if not ret: 1180 | log().info( 1181 | "获取豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1182 | name, id, self.doubanclient.err 1183 | ) 1184 | ) 1185 | continue 1186 | ret = self.sqlclient.write_douban_people_info( 1187 | id=celebrities["id"], iteminfo=info 1188 | ) 1189 | if not ret: 1190 | log().info( 1191 | "获取豆瓣媒体[{}]ID[{}]演员信息失败, {}".format( 1192 | name, id, self.doubanclient.err 1193 | ) 1194 | ) 1195 | celebrities["info"] = info 1196 | for celebrities in celebritiesinfo["actors"]: 1197 | ret, info = self.sqlclient.get_douban_people_info(id=celebrities["id"]) 1198 | if not ret: 1199 | ret, info = self.doubanclient.get_celebrity_info( 1200 | celebrityid=celebrities["id"] 1201 | ) 1202 | if not ret: 1203 | log().info( 1204 | "获取豆瓣媒体[{}]ID[{}]演员[{}]ID[{}]信息失败, {}".format( 1205 | name, 1206 | id, 1207 | celebrities["name"], 1208 | celebrities["id"], 1209 | self.doubanclient.err, 1210 | ) 1211 | ) 1212 | continue 1213 | ret = self.sqlclient.write_douban_people_info( 1214 | id=celebrities["id"], iteminfo=info 1215 | ) 1216 | if not ret: 1217 | log().info( 1218 | "保存豆瓣媒体[{}]ID[{}]演员[{}]ID[{}]信息失败, {}".format( 1219 | name, 1220 | id, 1221 | celebrities["name"], 1222 | celebrities["id"], 1223 | self.doubanclient.err, 1224 | ) 1225 | ) 1226 | celebrities["info"] = info 1227 | return True, celebritiesinfo 1228 | except Exception as result: 1229 | log().info( 1230 | "文件[{}]行[{}]异常错误:{}".format( 1231 | result.__traceback__.tb_frame.f_globals["__file__"], 1232 | result.__traceback__.tb_lineno, 1233 | result, 1234 | ) 1235 | ) 1236 | return False, None 1237 | 1238 | def __get_douban_media_info__(self, mediatype: int, name: str, id: str): 1239 | """ 1240 | 获取豆瓣媒体信息 1241 | :param mediatype 媒体类型 1TV 2电影 1242 | :name name 媒体名称 1243 | :id imdb 1244 | :return True or False, mediainfo 1245 | """ 1246 | try: 1247 | ret, items = self.sqlclient.search_douban_media( 1248 | mediatype=mediatype, title=name 1249 | ) 1250 | if not ret: 1251 | ret, items = self.doubanclient.search_media_pc(name) 1252 | if not ret: 1253 | ret, items = self.doubanclient.search_media(name) 1254 | if not ret: 1255 | ret, items = self.doubanclient.search_media_weixin(name) 1256 | if not ret: 1257 | log().info( 1258 | "豆瓣搜索媒体[{}]失败, {}".format(name, str(self.doubanclient.err)) 1259 | ) 1260 | return False, None 1261 | 1262 | for item in items["items"]: 1263 | if mediatype == 1: 1264 | if "target_type" in item and item["target_type"] != "tv": 1265 | continue 1266 | elif mediatype == 2: 1267 | if "target_type" in item and item["target_type"] != "movie": 1268 | continue 1269 | ret, mediainfo = self.sqlclient.get_douban_media_info( 1270 | mediatype=mediatype, id=item["target_id"] 1271 | ) 1272 | if not ret: 1273 | if mediatype == 2: 1274 | ret, mediainfo = self.doubanclient.get_movie_info( 1275 | movieid=item["target_id"] 1276 | ) 1277 | else: 1278 | ret, mediainfo = self.doubanclient.get_tv_info( 1279 | tvid=item["target_id"] 1280 | ) 1281 | if not ret: 1282 | log().info( 1283 | "获取豆瓣媒体[{}]ID[{}]信息失败, {}".format( 1284 | item["title"], item["target_id"], self.doubanclient.err 1285 | ) 1286 | ) 1287 | return False, None 1288 | ret = self.sqlclient.write_douban_media_info( 1289 | mediatype=mediatype, id=item["target_id"], iteminfo=mediainfo 1290 | ) 1291 | if not ret: 1292 | log().info( 1293 | "保存豆瓣媒体[{}]ID[{}]信息失败, {}".format( 1294 | item["title"], item["target_id"], self.doubanclient.err 1295 | ) 1296 | ) 1297 | if "IMDb" not in mediainfo["info"]: 1298 | continue 1299 | if mediainfo["info"]["IMDb"] == id: 1300 | ret = self.sqlclient.write_douban_media( 1301 | mediatype=mediatype, id=item["target_id"], iteminfo=item 1302 | ) 1303 | if not ret: 1304 | log().info( 1305 | "保存豆瓣媒体[{}]ID[{}]信息失败, {}".format( 1306 | item["title"], item["target_id"], self.doubanclient.err 1307 | ) 1308 | ) 1309 | return True, mediainfo 1310 | 1311 | except Exception as result: 1312 | log().info( 1313 | "文件[{}]行[{}]异常错误:{}".format( 1314 | result.__traceback__.tb_frame.f_globals["__file__"], 1315 | result.__traceback__.tb_lineno, 1316 | result, 1317 | ) 1318 | ) 1319 | return False, None 1320 | 1321 | def __get_tmdb_media_info__( 1322 | self, mediatype: int, name: str, id: str, language: str = "zh-CN" 1323 | ): 1324 | """ 1325 | 获取tmdb媒体信息 1326 | :param mediatype 媒体类型 1TV 2电影 1327 | :name name 媒体名称 1328 | :param id tmdbid 1329 | :return True or False, iteminfo 1330 | """ 1331 | try: 1332 | iteminfo = None 1333 | if mediatype == 1: 1334 | ret, iteminfo = self.sqlclient.get_tmdb_media_info( 1335 | mediatype=mediatype, id=id, language=language 1336 | ) 1337 | if not ret: 1338 | ret, iteminfo = self.tmdbclient.get_tv_info( 1339 | tvid=id, language=language 1340 | ) 1341 | if not ret: 1342 | log().info( 1343 | "获取TMDB媒体[{}]ID[{}]信息失败, {}".format( 1344 | name, id, self.tmdbclient.err 1345 | ) 1346 | ) 1347 | return False, None 1348 | ret = self.sqlclient.write_tmdb_media_info( 1349 | mediatype=mediatype, id=id, language=language, iteminfo=iteminfo 1350 | ) 1351 | if not ret: 1352 | log().info( 1353 | "保存TMDB媒体[{}]ID[{}]信息失败, {}".format( 1354 | name, id, self.tmdbclient.err 1355 | ) 1356 | ) 1357 | return True, iteminfo 1358 | else: 1359 | ret, iteminfo = self.sqlclient.get_tmdb_media_info( 1360 | mediatype=mediatype, id=id, language=language 1361 | ) 1362 | if not ret: 1363 | ret, iteminfo = self.tmdbclient.get_movie_info( 1364 | movieid=id, language=language 1365 | ) 1366 | if not ret: 1367 | log().info( 1368 | "获取TMDB媒体[{}]ID[{}]信息失败, {}".format( 1369 | name, id, self.tmdbclient.err 1370 | ) 1371 | ) 1372 | return False, None 1373 | ret = self.sqlclient.write_tmdb_media_info( 1374 | mediatype=mediatype, id=id, language=language, iteminfo=iteminfo 1375 | ) 1376 | if not ret: 1377 | log().info( 1378 | "保存TMDB媒体[{}]ID[{}]信息失败, {}".format( 1379 | name, id, self.tmdbclient.err 1380 | ) 1381 | ) 1382 | return True, iteminfo 1383 | 1384 | except Exception as result: 1385 | log().info( 1386 | "文件[{}]行[{}]异常错误:{}".format( 1387 | result.__traceback__.tb_frame.f_globals["__file__"], 1388 | result.__traceback__.tb_lineno, 1389 | result, 1390 | ) 1391 | ) 1392 | return False, None 1393 | 1394 | def __get_tmdb_media_name__( 1395 | self, mediatype: int, datatype: int, name: str, id: str 1396 | ): 1397 | """ 1398 | 获取tmdb媒体中文名称 1399 | :param mediatype 媒体类型 1TV 2电影 1400 | :param datatype 数据类型 1名字 2概述 1401 | :name name 媒体名称 1402 | :param id tmdbid 1403 | :return True or False, name 1404 | """ 1405 | try: 1406 | for language in self.languagelist: 1407 | if mediatype == 1: 1408 | ret, tvinfo = self.__get_tmdb_media_info__( 1409 | mediatype=mediatype, name=name, id=id, language=language 1410 | ) 1411 | if not ret: 1412 | continue 1413 | if datatype == 1: 1414 | if self.__is_chinese__(string=tvinfo["name"]): 1415 | if self.__is_chinese__(string=tvinfo["name"], mode=3): 1416 | return True, zhconv.convert(tvinfo["name"], "zh-cn") 1417 | return True, tvinfo["name"] 1418 | else: 1419 | ret, name = self.__alternative_name__( 1420 | alternativetitles=tvinfo 1421 | ) 1422 | if not ret: 1423 | continue 1424 | return True, name 1425 | else: 1426 | if self.__is_chinese__(string=tvinfo["overview"]): 1427 | if self.__is_chinese__(string=tvinfo["overview"], mode=3): 1428 | return True, zhconv.convert(tvinfo["overview"], "zh-cn") 1429 | return True, tvinfo["overview"] 1430 | else: 1431 | ret, movieinfo = self.__get_tmdb_media_info__( 1432 | mediatype=mediatype, name=name, id=id, language=language 1433 | ) 1434 | if not ret: 1435 | continue 1436 | if datatype == 1: 1437 | if self.__is_chinese__(string=movieinfo["title"]): 1438 | if self.__is_chinese__(string=movieinfo["title"], mode=3): 1439 | return True, zhconv.convert(movieinfo["title"], "zh-cn") 1440 | return True, movieinfo["title"] 1441 | else: 1442 | ret, name = self.__alternative_name__( 1443 | alternativetitles=movieinfo 1444 | ) 1445 | if not ret: 1446 | continue 1447 | return True, name 1448 | else: 1449 | if self.__is_chinese__(string=movieinfo["overview"]): 1450 | if self.__is_chinese__( 1451 | string=movieinfo["overview"], mode=3 1452 | ): 1453 | return True, zhconv.convert( 1454 | movieinfo["overview"], "zh-cn" 1455 | ) 1456 | return True, movieinfo["overview"] 1457 | 1458 | except Exception as result: 1459 | log().info( 1460 | "文件[{}]行[{}]异常错误:{}".format( 1461 | result.__traceback__.tb_frame.f_globals["__file__"], 1462 | result.__traceback__.tb_lineno, 1463 | result, 1464 | ) 1465 | ) 1466 | return False, None 1467 | 1468 | def __get_tmdb_tv_season_group_info__(self, name, groupid): 1469 | """ 1470 | 获取TMDB电视剧季组信息 1471 | :param name 媒体名称 1472 | :param groupid 组ID 1473 | :return True or False, iteminfo 1474 | """ 1475 | try: 1476 | for language in self.languagelist: 1477 | ret, iteminfo = self.tmdbclient.get_tv_season_group( 1478 | groupid=groupid, language=language 1479 | ) 1480 | if not ret: 1481 | log().info( 1482 | "获取TMDB剧集[{}]组ID[{}]信息失败, {}".format( 1483 | name, groupid, self.tmdbclient.err 1484 | ) 1485 | ) 1486 | continue 1487 | return True, iteminfo 1488 | 1489 | except Exception as result: 1490 | log().info( 1491 | "文件[{}]行[{}]异常错误:{}".format( 1492 | result.__traceback__.tb_frame.f_globals["__file__"], 1493 | result.__traceback__.tb_lineno, 1494 | result, 1495 | ) 1496 | ) 1497 | return False, None 1498 | 1499 | def __get_tmdb_tv_season_info__( 1500 | self, name: str, tvid: str, seasonid: str, episodeid: int 1501 | ): 1502 | """ 1503 | 获取tmdb季中文 1504 | :param name 媒体名称 1505 | :param tvid tmdbid 1506 | :param seasonid 季ID 1507 | :param episodeid 集ID 1508 | :return True or False, name, overview, ommunityrating, imageurl 1509 | """ 1510 | try: 1511 | for language in self.languagelist: 1512 | ret, seasoninfo = self.sqlclient.get_tmdb_season_info( 1513 | id=tvid, seasonid=seasonid, language=language 1514 | ) 1515 | if not ret: 1516 | ret, seasoninfo = self.tmdbclient.get_tv_season_info( 1517 | tvid=tvid, seasonid=seasonid, language=language 1518 | ) 1519 | if not ret: 1520 | log().info( 1521 | "获取TMDB媒体[{}]ID[{}]季ID[{}]信息失败, {}".format( 1522 | name, tvid, seasonid, self.tmdbclient.err 1523 | ) 1524 | ) 1525 | continue 1526 | ret = self.sqlclient.write_tmdb_season_info( 1527 | id=tvid, 1528 | seasonid=seasonid, 1529 | language=language, 1530 | iteminfo=seasoninfo, 1531 | ) 1532 | if not ret: 1533 | log().info( 1534 | "保存TMDB媒体[{}]ID[{}]季ID[{}]信息失败, {}".format( 1535 | name, tvid, seasonid, self.tmdbclient.err 1536 | ) 1537 | ) 1538 | for episodes in seasoninfo["episodes"]: 1539 | if episodes["episode_number"] > episodeid: 1540 | break 1541 | if episodes["episode_number"] != episodeid: 1542 | continue 1543 | if self.__is_chinese__(string=episodes["overview"]): 1544 | name = None 1545 | overview = None 1546 | ommunityrating = None 1547 | imageurl = None 1548 | if self.__is_chinese__(string=episodes["name"], mode=3): 1549 | name = zhconv.convert(episodes["name"], "zh-cn") 1550 | else: 1551 | name = episodes["name"] 1552 | if self.__is_chinese__(string=episodes["overview"], mode=3): 1553 | overview = zhconv.convert(episodes["overview"], "zh-cn") 1554 | else: 1555 | overview = episodes["overview"] 1556 | if episodes["vote_average"] > 0: 1557 | ommunityrating = episodes["vote_average"] 1558 | if "still_path" in episodes and episodes["still_path"]: 1559 | imageurl = ( 1560 | "https://www.themoviedb.org/t/p/original{}".format( 1561 | episodes["still_path"] 1562 | ) 1563 | ) 1564 | return True, name, overview, ommunityrating, imageurl 1565 | 1566 | except Exception as result: 1567 | log().info( 1568 | "文件[{}]行[{}]异常错误:{}".format( 1569 | result.__traceback__.tb_frame.f_globals["__file__"], 1570 | result.__traceback__.tb_lineno, 1571 | result, 1572 | ) 1573 | ) 1574 | return False, None, None, None, None 1575 | 1576 | def __get_tmdb_person_name(self, name, personid): 1577 | """ 1578 | 获取tmdb人物中文 1579 | :param name 人物名称 1580 | :param personid 人物ID 1581 | :return True or False, name 1582 | """ 1583 | try: 1584 | for language in self.languagelist: 1585 | ret, personinfo = self.sqlclient.get_tmdb_people_info( 1586 | id=personid, language=language 1587 | ) 1588 | if not ret: 1589 | ret, personinfo = self.tmdbclient.get_person_info( 1590 | personid=personid, language=language 1591 | ) 1592 | if not ret: 1593 | log().info( 1594 | "获取TMDB人物[{}]ID[{}]信息失败, {}".format( 1595 | name, personid, self.tmdbclient.err 1596 | ) 1597 | ) 1598 | continue 1599 | ret = self.sqlclient.write_tmdb_people_info( 1600 | id=personid, language=language, iteminfo=personinfo 1601 | ) 1602 | if not ret: 1603 | log().info( 1604 | "保存TMDB人物[{}]ID[{}]信息失败, {}".format( 1605 | name, personid, self.tmdbclient.err 1606 | ) 1607 | ) 1608 | for name in personinfo["also_known_as"]: 1609 | if not self.__is_chinese__(string=name, mode=2): 1610 | continue 1611 | if self.__is_chinese__(string=name, mode=3): 1612 | name = zhconv.convert(name, "zh-cn") 1613 | return True, re.sub(pattern="\s+", repl="", string=name) 1614 | break 1615 | 1616 | except Exception as result: 1617 | log().info( 1618 | "文件[{}]行[{}]异常错误:{}".format( 1619 | result.__traceback__.tb_frame.f_globals["__file__"], 1620 | result.__traceback__.tb_lineno, 1621 | result, 1622 | ) 1623 | ) 1624 | return False, None 1625 | 1626 | def __alternative_name__(self, alternativetitles): 1627 | """ 1628 | 返回别名中文名称 1629 | :return True or False, name 1630 | """ 1631 | try: 1632 | if ( 1633 | "alternative_titles" not in alternativetitles 1634 | or "titles" not in alternativetitles["alternative_titles"] 1635 | ): 1636 | return False, None 1637 | for title in alternativetitles["alternative_titles"]["titles"]: 1638 | if "iso_3166_1" not in title: 1639 | continue 1640 | if title["iso_3166_1"] != "CN": 1641 | continue 1642 | if not self.__is_chinese__(string=title["title"]): 1643 | continue 1644 | if self.__is_chinese__(string=title["title"], mode=3): 1645 | return True, zhconv.convert(title["title"], "zh-cn") 1646 | return True, title["title"] 1647 | except Exception as result: 1648 | log().info( 1649 | "文件[{}]行[{}]异常错误:{}".format( 1650 | result.__traceback__.tb_frame.f_globals["__file__"], 1651 | result.__traceback__.tb_lineno, 1652 | result, 1653 | ) 1654 | ) 1655 | return False, None 1656 | 1657 | def __is_chinese__(self, string: str, mode: int = 1): 1658 | """ 1659 | 判断是否包含中文 1660 | :param string 需要判断的字符 1661 | :param mode 模式 1匹配简体和繁体 2只匹配简体 3只匹配繁体 1662 | :return True or False 1663 | """ 1664 | for ch in string: 1665 | if mode == 1: 1666 | if "\u4e00" <= ch <= "\u9FFF": 1667 | return True 1668 | elif mode == 2: 1669 | if "\u4e00" <= ch <= "\u9FFF": 1670 | if zhconv.convert(ch, "zh-cn") == ch: 1671 | return True 1672 | elif mode == 3: 1673 | if "\u4e00" <= ch <= "\u9FFF": 1674 | if zhconv.convert(ch, "zh-cn") != ch: 1675 | return True 1676 | if re.search(pattern="^[0-9]+$", string=string): 1677 | return True 1678 | return False 1679 | --------------------------------------------------------------------------------