diff --git a/app/helper/nfo.py b/app/helper/nfo.py index 886c8ec4..ce483ef9 100644 --- a/app/helper/nfo.py +++ b/app/helper/nfo.py @@ -1,5 +1,6 @@ import xml.etree.ElementTree as ET from pathlib import Path +from typing import List, Optional class NfoReader: @@ -8,6 +9,9 @@ class NfoReader: self.tree = ET.parse(xml_file_path) self.root = self.tree.getroot() - def get_element_value(self, element_path): + def get_element_value(self, element_path) -> Optional[str]: element = self.root.find(element_path) return element.text if element is not None else None + + def get_elements(self, element_path) -> List[ET.Element]: + return self.root.findall(element_path) diff --git a/app/modules/themoviedb/scraper.py b/app/modules/themoviedb/scraper.py index f5aad695..12133092 100644 --- a/app/modules/themoviedb/scraper.py +++ b/app/modules/themoviedb/scraper.py @@ -122,29 +122,28 @@ class TmdbScraper: except Exception as e: logger.error(f"{file_path} 刮削失败:{e}") + def __get_chinese_name(self, person: dict): + """ + 获取TMDB别名中的中文名 + """ + if not person.get("id"): + return "" + try: + personinfo = self.tmdb.get_person_detail(person.get("id")) + if personinfo: + also_known_as = personinfo.get("also_known_as") or [] + if also_known_as: + for name in also_known_as: + if name and StringUtils.is_chinese(name): + return name + except Exception as err: + logger.error(f"获取人物中文名失败:{err}") + return person.get("name") or "" + def __gen_common_nfo(self, mediainfo: MediaInfo, doc, root): """ 生成公共NFO """ - - def __get_chinese_name(person: dict): - """ - 获取TMDB别名中的中文名 - """ - if not person.get("id"): - return "" - try: - personinfo = self.tmdb.get_person_detail(person.get("id")) - if personinfo: - also_known_as = personinfo.get("also_known_as") or [] - if also_known_as: - for name in also_known_as: - if name and StringUtils.is_chinese(name): - return name - except Exception as err: - logger.error(f"获取人物中文名失败:{err}") - return person.get("name") or "" - # 添加时间 DomUtils.add_node(doc, root, "dateadded", time.strftime('%Y-%m-%d %H:%M:%S', @@ -175,21 +174,18 @@ class TmdbScraper: # 导演 for director in mediainfo.directors: # 获取中文名 - cn_name = __get_chinese_name(director) + cn_name = self.__get_chinese_name(director) xdirector = DomUtils.add_node(doc, root, "director", cn_name) xdirector.setAttribute("tmdbid", str(director.get("id") or "")) # 演员 for actor in mediainfo.actors: # 获取中文名 - cn_name = __get_chinese_name(actor) + cn_name = self.__get_chinese_name(actor) xactor = DomUtils.add_node(doc, root, "actor") DomUtils.add_node(doc, xactor, "name", cn_name) DomUtils.add_node(doc, xactor, "type", "Actor") DomUtils.add_node(doc, xactor, "role", actor.get("character") or actor.get("role") or "") - DomUtils.add_node(doc, xactor, "order", actor.get("order") if actor.get("order") is not None else "") DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "") - DomUtils.add_node(doc, xactor, "thumb", actor.get('image')) - DomUtils.add_node(doc, xactor, "profile", actor.get('profile')) # 风格 genres = mediainfo.genres or [] for genre in genres: @@ -330,14 +326,18 @@ class TmdbScraper: directors = episodeinfo.get("crew") or [] for director in directors: if director.get("known_for_department") == "Directing": - xdirector = DomUtils.add_node(doc, root, "director", director.get("name") or "") + # 获取中文名 + cn_name = self.__get_chinese_name(director) + xdirector = DomUtils.add_node(doc, root, "director", cn_name) xdirector.setAttribute("tmdbid", str(director.get("id") or "")) # 演员 actors = episodeinfo.get("guest_stars") or [] for actor in actors: if actor.get("known_for_department") == "Acting": + # 获取中文名 + cn_name = self.__get_chinese_name(actor) xactor = DomUtils.add_node(doc, root, "actor") - DomUtils.add_node(doc, xactor, "name", actor.get("name") or "") + DomUtils.add_node(doc, xactor, "name", cn_name) DomUtils.add_node(doc, xactor, "type", "Actor") DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "") # 保存文件 diff --git a/app/plugins/personmeta/__init__.py b/app/plugins/personmeta/__init__.py index 32e1893d..d83a7ef8 100644 --- a/app/plugins/personmeta/__init__.py +++ b/app/plugins/personmeta/__init__.py @@ -1,8 +1,17 @@ +from pathlib import Path from typing import Any, List, Dict, Tuple +from requests import RequestException + +from app.chain.tmdb import TmdbChain from app.core.event import eventmanager, Event +from app.helper.nfo import NfoReader +from app.log import logger from app.plugins import _PluginBase -from app.schemas.types import EventType +from app.schemas import TransferInfo, MediaInfo +from app.schemas.types import EventType, MediaType +from app.utils.common import retry +from app.utils.http import RequestUtils class PersonMeta(_PluginBase): @@ -28,11 +37,15 @@ class PersonMeta(_PluginBase): auth_level = 1 # 私有属性 + tmdbchain = None _enabled = False + _metadir = "" def init_plugin(self, config: dict = None): + self.tmdbchain = TmdbChain(self.db) if config: self._enabled = config.get("enabled") + self._metadir = config.get("metadir") def get_state(self) -> bool: return self._enabled @@ -72,11 +85,33 @@ class PersonMeta(_PluginBase): ] } ] + }, + { + 'component': 'VRow', + 'content': [ + { + 'component': 'VCol', + 'props': { + 'cols': 12, + }, + 'content': [ + { + 'component': 'VTextField', + 'props': { + 'model': 'metadir', + 'label': '人物元数据目录', + 'placeholder': '/metadata/people' + } + } + ] + } + ] } ] } ], { - "enabled": False + "enabled": False, + "metadir": "" } def get_page(self) -> List[dict]: @@ -87,7 +122,78 @@ class PersonMeta(_PluginBase): """ 根据事件实时刮削演员信息 """ - pass + if not self._enabled: + return + # 下载人物头像 + if not self._metadir: + logger.warning("人物元数据目录未配置,无法下载人物头像") + return + # 事件数据 + mediainfo: MediaInfo = event.event_data.get("mediainfo") + transferinfo: TransferInfo = event.event_data.get("transferinfo") + if not mediainfo or not transferinfo: + return + # 文件路径 + filepath = transferinfo.target_path + if not filepath: + return + # 电影 + if mediainfo.type == MediaType.MOVIE: + # nfo文件 + nfofile = filepath.with_name("movie.nfo") + if not nfofile.exists(): + nfofile = filepath.parent / f"{filepath.stem}.nfo" + if not nfofile.exists(): + logger.warning(f"电影nfo文件不存在:{nfofile}") + return + else: + # nfo文件 + nfofile = filepath.parent.with_name("tvshow.nfo") + if not nfofile.exists(): + logger.warning(f"剧集nfo文件不存在:{nfofile}") + return + # 读取nfo文件 + nfo = NfoReader(nfofile) + # 读取演员信息 + actors = nfo.get_elements("actor") or [] + for actor in actors: + # 演员ID + actor_id = actor.find("id").text + if not actor_id: + continue + # 演员名称 + actor_name = actor.find("name").text + # 查询演员详情 + actor_info = self.tmdbchain.person_detail(actor_id) + if not actor_info: + continue + # 演员头像 + actor_image = actor_info.get("profile_path") + if not actor_image: + continue + # 计算保存路径 + image_path = Path(self._metadir) / f"{actor_name}-tmdb-{actor_id}{Path(actor_image).suffix}" + if image_path.exists(): + continue + # 下载图片 + self.download_image(f"https://image.tmdb.org/t/p/original{actor_image}", image_path) + + @staticmethod + @retry(RequestException, logger=logger) + def download_image(image_url: str, path: Path): + """ + 下载图片,保存到指定路径 + """ + try: + logger.info(f"正在下载演职人员图片:{image_url} ...") + r = RequestUtils().get_res(url=image_url, raise_exception=True) + if r: + path.write_bytes(r.content) + logger.info(f"图片已保存:{path}") + else: + logger.info(f"图片下载失败,请检查网络连通性:{image_url}") + except Exception as err: + logger.error(f"图片下载失败:{err}") def stop_service(self): """