From 53a514feb6697cc695ebfc7de75e32a60cafaf28 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Sun, 1 Oct 2023 14:16:36 +0800 Subject: [PATCH] =?UTF-8?q?fix=20personmeta=E6=94=AF=E6=8C=81=E8=B1=86?= =?UTF-8?q?=E7=93=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/chain/__init__.py | 11 ++ app/modules/douban/__init__.py | 306 +++++++++++++++++++++++++++-- app/plugins/personmeta/__init__.py | 123 ++++++++---- 3 files changed, 393 insertions(+), 47 deletions(-) diff --git a/app/chain/__init__.py b/app/chain/__init__.py index 7850dc0e..7c3c9f3f 100644 --- a/app/chain/__init__.py +++ b/app/chain/__init__.py @@ -115,6 +115,17 @@ class ChainBase(metaclass=ABCMeta): """ return self.run_module("recognize_media", meta=meta, mtype=mtype, tmdbid=tmdbid) + def match_doubaninfo(self, name: str, mtype: str = None, + year: str = None, season: int = None) -> Optional[dict]: + """ + 搜索和匹配豆瓣信息 + :param name: 标题 + :param mtype: 类型 + :param year: 年份 + :param season: 季 + """ + return self.run_module("match_doubaninfo", name=name, mtype=mtype, year=year, season=season) + def obtain_images(self, mediainfo: MediaInfo) -> Optional[MediaInfo]: """ 补充抓取媒体信息图片 diff --git a/app/modules/douban/__init__.py b/app/modules/douban/__init__.py index 314aa9c8..7b57c3e5 100644 --- a/app/modules/douban/__init__.py +++ b/app/modules/douban/__init__.py @@ -14,7 +14,6 @@ from app.utils.system import SystemUtils class DoubanModule(_ModuleBase): - doubanapi: DoubanApi = None scraper: DoubanScraper = None @@ -34,6 +33,271 @@ class DoubanModule(_ModuleBase): :param doubanid: 豆瓣ID :return: 豆瓣信息 """ + """ + { + "rating": { + "count": 287365, + "max": 10, + "star_count": 3.5, + "value": 6.6 + }, + "lineticket_url": "", + "controversy_reason": "", + "pubdate": [ + "2021-10-29(中国大陆)" + ], + "last_episode_number": null, + "interest_control_info": null, + "pic": { + "large": "https://img9.doubanio.com/view/photo/m_ratio_poster/public/p2707553644.webp", + "normal": "https://img9.doubanio.com/view/photo/s_ratio_poster/public/p2707553644.webp" + }, + "vendor_count": 6, + "body_bg_color": "f4f5f9", + "is_tv": false, + "head_info": null, + "album_no_interact": false, + "ticket_price_info": "", + "webisode_count": 0, + "year": "2021", + "card_subtitle": "2021 / 英国 美国 / 动作 惊悚 冒险 / 凯瑞·福永 / 丹尼尔·克雷格 蕾雅·赛杜", + "forum_info": null, + "webisode": null, + "id": "20276229", + "gallery_topic_count": 0, + "languages": [ + "英语", + "法语", + "意大利语", + "俄语", + "西班牙语" + ], + "genres": [ + "动作", + "惊悚", + "冒险" + ], + "review_count": 926, + "title": "007:无暇赴死", + "intro": "世界局势波诡云谲,再度出山的邦德(丹尼尔·克雷格 饰)面临有史以来空前的危机,传奇特工007的故事在本片中达到高潮。新老角色集结亮相,蕾雅·赛杜回归,二度饰演邦女郎玛德琳。系列最恐怖反派萨芬(拉米·马雷克 饰)重磅登场,毫不留情地展示了自己狠辣的一面,不仅揭开了玛德琳身上隐藏的秘密,还酝酿着危及数百万人性命的阴谋,幽灵党的身影也似乎再次浮出水面。半路杀出的新00号特工(拉什纳·林奇 饰)与神秘女子(安娜·德·阿玛斯 饰)看似与邦德同阵作战,但其真实目的依然成谜。关乎邦德生死的新仇旧怨接踵而至,暗潮汹涌之下他能否拯救世界?", + "interest_cmt_earlier_tip_title": "发布于上映前", + "has_linewatch": true, + "ugc_tabs": [ + { + "source": "reviews", + "type": "review", + "title": "影评" + }, + { + "source": "forum_topics", + "type": "forum", + "title": "讨论" + } + ], + "forum_topic_count": 857, + "ticket_promo_text": "", + "webview_info": {}, + "is_released": true, + "actors": [ + { + "name": "丹尼尔·克雷格", + "roles": [ + "演员", + "制片人", + "配音" + ], + "title": "丹尼尔·克雷格(同名)英国,英格兰,柴郡,切斯特影视演员", + "url": "https://movie.douban.com/celebrity/1025175/", + "user": null, + "character": "饰 詹姆斯·邦德 James Bond 007", + "uri": "douban://douban.com/celebrity/1025175?subject_id=27230907", + "avatar": { + "large": "https://qnmob3.doubanio.com/view/celebrity/raw/public/p42588.jpg?imageView2/2/q/80/w/600/h/3000/format/webp", + "normal": "https://qnmob3.doubanio.com/view/celebrity/raw/public/p42588.jpg?imageView2/2/q/80/w/200/h/300/format/webp" + }, + "sharing_url": "https://www.douban.com/doubanapp/dispatch?uri=/celebrity/1025175/", + "type": "celebrity", + "id": "1025175", + "latin_name": "Daniel Craig" + } + ], + "interest": null, + "vendor_icons": [ + "https://img9.doubanio.com/f/frodo/fbc90f355fc45d5d2056e0d88c697f9414b56b44/pics/vendors/tencent.png", + "https://img2.doubanio.com/f/frodo/8286b9b5240f35c7e59e1b1768cd2ccf0467cde5/pics/vendors/migu_video.png", + "https://img9.doubanio.com/f/frodo/88a62f5e0cf9981c910e60f4421c3e66aac2c9bc/pics/vendors/bilibili.png" + ], + "episodes_count": 0, + "color_scheme": { + "is_dark": true, + "primary_color_light": "868ca5", + "_base_color": [ + 0.6333333333333333, + 0.18867924528301885, + 0.20784313725490197 + ], + "secondary_color": "f4f5f9", + "_avg_color": [ + 0.059523809523809625, + 0.09790209790209795, + 0.5607843137254902 + ], + "primary_color_dark": "676c7f" + }, + "type": "movie", + "null_rating_reason": "", + "linewatches": [ + { + "url": "http://v.youku.com/v_show/id_XNTIwMzM2NDg5Mg==.html?tpa=dW5pb25faWQ9MzAwMDA4XzEwMDAwMl8wMl8wMQ&refer=esfhz_operation.xuka.xj_00003036_000000_FNZfau_19010900", + "source": { + "literal": "youku", + "pic": "https://img1.doubanio.com/img/files/file-1432869267.png", + "name": "优酷视频" + }, + "source_uri": "youku://play?vid=XNTIwMzM2NDg5Mg==&source=douban&refer=esfhz_operation.xuka.xj_00003036_000000_FNZfau_19010900", + "free": false + }, + ], + "info_url": "https://www.douban.com/doubanapp//h5/movie/20276229/desc", + "tags": [], + "durations": [ + "163分钟" + ], + "comment_count": 97204, + "cover": { + "description": "", + "author": { + "loc": { + "id": "108288", + "name": "北京", + "uid": "beijing" + }, + "kind": "user", + "name": "雨落下", + "reg_time": "2020-08-11 16:22:48", + "url": "https://www.douban.com/people/221011676/", + "uri": "douban://douban.com/user/221011676", + "id": "221011676", + "avatar_side_icon_type": 3, + "avatar_side_icon_id": "234", + "avatar": "https://img2.doubanio.com/icon/up221011676-2.jpg", + "is_club": false, + "type": "user", + "avatar_side_icon": "https://img2.doubanio.com/view/files/raw/file-1683625971.png", + "uid": "221011676" + }, + "url": "https://movie.douban.com/photos/photo/2707553644/", + "image": { + "large": { + "url": "https://img9.doubanio.com/view/photo/l/public/p2707553644.webp", + "width": 1082, + "height": 1600, + "size": 0 + }, + "raw": null, + "small": { + "url": "https://img9.doubanio.com/view/photo/s/public/p2707553644.webp", + "width": 405, + "height": 600, + "size": 0 + }, + "normal": { + "url": "https://img9.doubanio.com/view/photo/m/public/p2707553644.webp", + "width": 405, + "height": 600, + "size": 0 + }, + "is_animated": false + }, + "uri": "douban://douban.com/photo/2707553644", + "create_time": "2021-10-26 15:05:01", + "position": 0, + "owner_uri": "douban://douban.com/movie/20276229", + "type": "photo", + "id": "2707553644", + "sharing_url": "https://www.douban.com/doubanapp/dispatch?uri=/photo/2707553644/" + }, + "cover_url": "https://img9.doubanio.com/view/photo/m_ratio_poster/public/p2707553644.webp", + "restrictive_icon_url": "", + "header_bg_color": "676c7f", + "is_douban_intro": false, + "ticket_vendor_icons": [ + "https://img9.doubanio.com/view/dale-online/dale_ad/public/0589a62f2f2d7c2.jpg" + ], + "honor_infos": [], + "sharing_url": "https://movie.douban.com/subject/20276229/", + "subject_collections": [], + "wechat_timeline_share": "screenshot", + "countries": [ + "英国", + "美国" + ], + "url": "https://movie.douban.com/subject/20276229/", + "release_date": null, + "original_title": "No Time to Die", + "uri": "douban://douban.com/movie/20276229", + "pre_playable_date": null, + "episodes_info": "", + "subtype": "movie", + "directors": [ + { + "name": "凯瑞·福永", + "roles": [ + "导演", + "制片人", + "编剧", + "摄影", + "演员" + ], + "title": "凯瑞·福永(同名)美国,加利福尼亚州,奥克兰影视演员", + "url": "https://movie.douban.com/celebrity/1009531/", + "user": null, + "character": "导演", + "uri": "douban://douban.com/celebrity/1009531?subject_id=27215222", + "avatar": { + "large": "https://qnmob3.doubanio.com/view/celebrity/raw/public/p1392285899.57.jpg?imageView2/2/q/80/w/600/h/3000/format/webp", + "normal": "https://qnmob3.doubanio.com/view/celebrity/raw/public/p1392285899.57.jpg?imageView2/2/q/80/w/200/h/300/format/webp" + }, + "sharing_url": "https://www.douban.com/doubanapp/dispatch?uri=/celebrity/1009531/", + "type": "celebrity", + "id": "1009531", + "latin_name": "Cary Fukunaga" + } + ], + "is_show": false, + "in_blacklist": false, + "pre_release_desc": "", + "video": null, + "aka": [ + "007:生死有时(港)", + "007:生死交战(台)", + "007:间不容死", + "邦德25", + "007:没空去死(豆友译名)", + "James Bond 25", + "Never Dream of Dying", + "Shatterhand" + ], + "is_restrictive": false, + "trailer": { + "sharing_url": "https://www.douban.com/doubanapp/dispatch?uri=/movie/20276229/trailer%3Ftrailer_id%3D282585%26trailer_type%3DA", + "video_url": "https://vt1.doubanio.com/202310011325/3b1f5827e91dde7826dc20930380dfc2/view/movie/M/402820585.mp4", + "title": "中国预告片:终极决战版 (中文字幕)", + "uri": "douban://douban.com/movie/20276229/trailer?trailer_id=282585&trailer_type=A", + "cover_url": "https://img1.doubanio.com/img/trailer/medium/2712944408.jpg", + "term_num": 0, + "n_comments": 21, + "create_time": "2021-11-01", + "subject_title": "007:无暇赴死", + "file_size": 10520074, + "runtime": "00:42", + "type": "A", + "id": "282585", + "desc": "" + }, + "interest_cmt_earlier_tip_desc": "该短评的发布时间早于公开上映时间,作者可能通过其他渠道提前观看,请谨慎参考。其评分将不计入总评分。" + } + """ if not doubanid: return None logger.info(f"开始获取豆瓣信息:{doubanid} ...") @@ -129,22 +393,38 @@ class DoubanModule(_ModuleBase): return ret_medias - def __match(self, name: str, year: str, season: int = None) -> dict: + def match_doubaninfo(self, name: str, mtype: str = None, + year: str = None, season: int = None) -> dict: """ 搜索和匹配豆瓣信息 + :param name: 名称 + :param mtype: 类型 电影/电视剧 + :param year: 年份 + :param season: 季号 """ - result = self.doubanapi.search(f"{name} {year or ''}") + result = self.doubanapi.search(f"{name} {year or ''}".strip()) if not result: return {} for item_obj in result.get("items"): - if item_obj.get("type_name") not in (MediaType.TV.value, MediaType.MOVIE.value): + type_name = item_obj.get("type_name") + if type_name not in [MediaType.TV.value, MediaType.MOVIE.value]: continue - title = item_obj.get("title") + if mtype and mtype != type_name: + continue + if mtype == MediaType.TV and not season: + season = 1 + item = item_obj.get("target") + title = item.get("title") if not title: continue meta = MetaInfo(title) - if meta.name == name and (not season or meta.begin_season == season): - return item_obj + if type_name == MediaType.TV.value: + meta.type = MediaType.TV + meta.begin_season = meta.begin_season or 1 + if meta.name == name \ + and ((not season and not meta.begin_season) or meta.begin_season == season) \ + and (not year or item.get('year') == year): + return item return {} def movie_top250(self, page: int = 1, count: int = 30) -> List[dict]: @@ -173,7 +453,10 @@ class DoubanModule(_ModuleBase): if not meta.name: return # 根据名称查询豆瓣数据 - doubaninfo = self.__match(name=mediainfo.title, year=mediainfo.year, season=meta.begin_season) + doubaninfo = self.match_doubaninfo(name=mediainfo.title, + mtype=mediainfo.type.value, + year=mediainfo.year, + season=meta.begin_season) if not doubaninfo: logger.warn(f"未找到 {mediainfo.title} 的豆瓣信息") return @@ -192,9 +475,10 @@ class DoubanModule(_ModuleBase): if not meta.name: continue # 根据名称查询豆瓣数据 - doubaninfo = self.__match(name=mediainfo.title, - year=mediainfo.year, - season=meta.begin_season) + doubaninfo = self.match_doubaninfo(name=mediainfo.title, + mtype=mediainfo.type.value, + year=mediainfo.year, + season=meta.begin_season) if not doubaninfo: logger.warn(f"未找到 {mediainfo.title} 的豆瓣信息") break diff --git a/app/plugins/personmeta/__init__.py b/app/plugins/personmeta/__init__.py index d99105b2..31d55046 100644 --- a/app/plugins/personmeta/__init__.py +++ b/app/plugins/personmeta/__init__.py @@ -17,6 +17,7 @@ from app.chain.mediaserver import MediaServerChain from app.chain.tmdb import TmdbChain from app.core.config import settings from app.core.event import eventmanager, Event +from app.core.meta import MetaBase from app.log import logger from app.modules.emby import Emby from app.modules.jellyfin import Jellyfin @@ -230,7 +231,8 @@ class PersonMeta(_PluginBase): return # 事件数据 mediainfo: MediaInfo = event.event_data.get("mediainfo") - if not mediainfo: + meta: MetaBase = event.event_data.get("meta") + if not mediainfo or not meta: return # 延迟 if self._delay: @@ -246,7 +248,8 @@ class PersonMeta(_PluginBase): logger.warn(f"演职人员刮削 {mediainfo.title_year} 条目详情获取失败") return # 刮削演职人员信息 - self.__update_item(server=existsinfo.server, item=iteminfo, mediainfo=mediainfo) + self.__update_item(server=existsinfo.server, item=iteminfo, + mediainfo=mediainfo, season=meta.begin_season) def scrap_library(self): """ @@ -278,7 +281,8 @@ class PersonMeta(_PluginBase): logger.info(f"媒体库 {library.name} 的演员信息刮削完成") logger.info(f"服务器 {server} 的演员信息刮削完成") - def __update_item(self, server: str, item: MediaServerItem, mediainfo: MediaInfo = None): + def __update_item(self, server: str, item: MediaServerItem, + mediainfo: MediaInfo = None, season: int = None): """ 更新媒体服务器中的条目 """ @@ -293,11 +297,15 @@ class PersonMeta(_PluginBase): logger.warn(f"{item.title} 未识别到媒体信息") return + # 获取豆瓣演员信息 + douban_actors = self.__get_douban_actors(mediainfo=mediainfo, season=season) + # 获取媒体项 iteminfo = self.get_iteminfo(server=server, itemid=item.item_id) if not iteminfo: logger.warn(f"{item.title} 未找到媒体项") return + # 处理媒体项中的人物信息 if iteminfo.get("People"): """ @@ -318,7 +326,8 @@ class PersonMeta(_PluginBase): continue if StringUtils.is_chinese(people.get("Name")): continue - info = self.__update_people(server=server, people=people) + info = self.__update_people(server=server, people=people, + douban_actors=douban_actors) if info: peoples.append(info) else: @@ -327,6 +336,7 @@ class PersonMeta(_PluginBase): if peoples: iteminfo["People"] = peoples self.set_iteminfo(server=server, itemid=item.item_id, iteminfo=iteminfo) + # 处理季和集人物 if iteminfo.get("Type") and "Series" in iteminfo["Type"]: # 获取季媒体项 @@ -335,6 +345,8 @@ class PersonMeta(_PluginBase): logger.warn(f"{item.title} 未找到季媒体项") return for season in seasons["Items"]: + # 获取豆瓣演员信息 + season_actors = self.__get_douban_actors(mediainfo=mediainfo, season=season.get("IndexNumber")) # 如果是Jellyfin,更新季的人物,Emby/Plex季没有人物 if server == "jellyfin": seasoninfo = self.get_iteminfo(server=server, itemid=season.get("Id")) @@ -344,13 +356,15 @@ class PersonMeta(_PluginBase): # 更新季媒体项人物 peoples = [] if seasoninfo.get("People"): + for people in seasoninfo["People"]: if not people.get("Name"): continue if StringUtils.is_chinese(people.get("Name")): continue # 更新人物信息 - info = self.__update_people(server=server, people=people) + info = self.__update_people(server=server, people=people, + douban_actors=season_actors) if info: peoples.append(info) else: @@ -380,7 +394,8 @@ class PersonMeta(_PluginBase): if StringUtils.is_chinese(people.get("Name")): continue # 更新人物信息 - info = self.__update_people(server=server, people=people) + info = self.__update_people(server=server, people=people, + douban_actors=season_actors) if info: peoples.append(info) else: @@ -390,7 +405,7 @@ class PersonMeta(_PluginBase): episodeinfo["People"] = peoples self.set_iteminfo(server=server, itemid=episode.get("Id"), iteminfo=episodeinfo) - def __update_people(self, server: str, people: dict) -> Optional[dict]: + def __update_people(self, server: str, people: dict, douban_actors: list = None) -> Optional[dict]: """ 更新人物信息,返回替换后的人物信息 """ @@ -421,37 +436,56 @@ class PersonMeta(_PluginBase): if not personinfo: logger.debug(f"未找到人物 {people.get('Id')} 的信息") return None - # 获取人物的TMDBID - person_tmdbid, person_imdbid = __get_peopleid(personinfo) - if not person_tmdbid: - logger.warn(f"未找到人物 {people.get('Id')} 的tmdbid") - return None # 是否更新标志 - updated = False - # 查询人物TMDB详情 - person_tmdbinfo = self.tmdbchain.person_detail(int(person_tmdbid)) - if person_tmdbinfo: - cn_name = self.__get_chinese_name(person_tmdbinfo) - if cn_name: - updated = True - # 更新中文名 - personinfo["Name"] = cn_name - ret_people["Name"] = cn_name - if "Name" not in personinfo["LockedFields"]: - personinfo["LockedFields"].append("Name") - # 更新中文描述 - biography = person_tmdbinfo.get("biography") - if StringUtils.is_chinese(biography): - personinfo["Overview"] = biography - if "Overview" not in personinfo["LockedFields"]: - personinfo["LockedFields"].append("Overview") - # 更新人物图片 - profile_path = f"https://image.tmdb.org/t/p/original{person_tmdbinfo.get('profile_path')}" - if profile_path: - logger.info(f"更新人物 {people.get('Id')} 的图片:{profile_path}") - self.set_item_image(server=server, itemid=people.get("Id"), imageurl=profile_path) + updated_name = False + updated_overview = False + profile_path = None + # 从豆瓣演员中匹配中文名称 + if douban_actors: + for douban_actor in douban_actors: + if douban_actor.get("latin_name") == people.get("Name"): + # 名称 + personinfo["Name"] = douban_actor.get("name") + ret_people["Name"] = douban_actor.get("name") + updated_name = True + # 图片 + if douban_actor.get("avatar", {}).get("large"): + profile_path = douban_actor.get("avatar", {}).get("large") + break + if not updated_name or not updated_overview: + # 获取人物的TMDBID + person_tmdbid, person_imdbid = __get_peopleid(personinfo) + if person_tmdbid: + person_tmdbinfo = self.tmdbchain.person_detail(int(person_tmdbid)) + if person_tmdbinfo: + cn_name = self.__get_chinese_name(person_tmdbinfo) + if cn_name: + # 更新中文名 + personinfo["Name"] = cn_name + ret_people["Name"] = cn_name + updated_name = True + # 更新中文描述 + biography = person_tmdbinfo.get("biography") + if StringUtils.is_chinese(biography): + updated_overview = True + personinfo["Overview"] = biography + # 图片 + profile_path = f"https://image.tmdb.org/t/p/original{person_tmdbinfo.get('profile_path')}" + # 锁定人物信息 + if updated_name: + if "Name" not in personinfo["LockedFields"]: + personinfo["LockedFields"].append("Name") + if updated_overview: + if "Overview" not in personinfo["LockedFields"]: + personinfo["LockedFields"].append("Overview") + + # 更新人物图片 + if profile_path: + logger.info(f"更新人物 {people.get('Id')} 的图片:{profile_path}") + self.set_item_image(server=server, itemid=people.get("Id"), imageurl=profile_path) + # 更新人物信息 - if updated: + if updated_name or updated_overview: logger.info(f"更新人物 {people.get('Id')} 的信息:{personinfo}") ret = self.set_iteminfo(server=server, itemid=people.get("Id"), iteminfo=personinfo) if ret: @@ -460,6 +494,23 @@ class PersonMeta(_PluginBase): logger.error(f"更新人物信息失败:{err}") return None + def __get_douban_actors(self, mediainfo: MediaInfo, season: int = None) -> List[dict]: + """ + 获取豆瓣演员信息 + """ + # 随机休眠1-5秒 + time.sleep(1 + int(time.time()) % 5) + # 匹配豆瓣信息 + doubaninfo = self.chain.match_doubaninfo(name=mediainfo.title, + mtype=mediainfo.type.value, + year=mediainfo.year, + season=season) + # 豆瓣演员 + if doubaninfo: + doubanitem = self.chain.douban_info(doubaninfo.get("id")) or {} + return doubanitem.get("actors") or [] + return [] + @staticmethod def get_iteminfo(server: str, itemid: str) -> dict: """