Files
MoviePilot/app/modules/douban/scraper.py
Qinghe Li 85b367527e fix(scraper): align built-in NFO results with text contract (#7049)
* fix(scraper): return NFO text from built-in providers

* test: cover type migration boundaries

* fix: silence dynamic import lint diagnostics

* test: keep coverage policy stable for nfo repair

* fix: preserve merged scraper documentation during conflict repair

---------

Co-authored-by: Derrors <derrors@local>
Co-authored-by: jxxghp <jxxghp@gmail.com>
2026-10-17 19:53:06 +08:00

141 lines
5.7 KiB
Python

"""
依据豆瓣媒体信息生成 NFO XML 和主海报、背景图片地址清单。
"""
from pathlib import Path
from typing import Optional
from xml.dom import minidom
from app.domain.context import MediaInfo
from app.foundation.dom import DomUtils
from app.schemas.types import MediaType
class DoubanScraper:
"""
根据豆瓣媒体信息生成 NFO 与图片清单。
"""
_force_nfo = False
_force_img = False
def get_metadata_nfo(self, mediainfo: MediaInfo, season: Optional[int] = None) -> Optional[str]:
"""
生成电影、整剧或季级别的 NFO XML。
:param mediainfo: 包含豆瓣简介、演职员和评分的媒体对象
:param season: 季号;非电影且提供季号时生成季文档
:return: 包含 UTF-8 编码声明的 XML 文本;无法生成时返回 None
"""
if mediainfo.type == MediaType.MOVIE:
doc = self.__gen_movie_nfo_file(mediainfo=mediainfo)
else:
if season is not None:
doc = self.__gen_tv_season_nfo_file(mediainfo=mediainfo, season=season)
else:
doc = self.__gen_tv_nfo_file(mediainfo=mediainfo)
if doc:
return doc.toprettyxml(indent=" ", encoding="utf-8").decode("utf-8")
return None
@staticmethod
def get_metadata_img(mediainfo: MediaInfo, season: Optional[int] = None, episode: Optional[int] = None) -> dict[str, str]:
"""
生成主媒体图片文件名到原始地址的映射。
:param mediainfo: 提供海报与背景图片路径的媒体对象
:param season: 豆瓣不提供季图片;非 None 时返回空字典
:param episode: 豆瓣不提供单集图片;非零时返回空字典
:return: 已有主海报及背景图的文件名与地址;没有图片时为空字典
"""
ret_dict = {}
if season is not None:
return {}
if episode:
return {}
if mediainfo.poster_path:
ret_dict[f"poster{Path(mediainfo.poster_path).suffix}"] = mediainfo.poster_path
if mediainfo.backdrop_path:
ret_dict[f"backdrop{Path(mediainfo.backdrop_path).suffix}"] = mediainfo.backdrop_path
return ret_dict
@staticmethod
def __gen_common_nfo(mediainfo: MediaInfo, doc: minidom.Document, root: minidom.Element) -> minidom.Document:
"""
向已有文档追加豆瓣简介、演职员和评分信息。
"""
xplot = DomUtils.add_node(doc, root, "plot")
xplot.appendChild(doc.createCDATASection(mediainfo.overview or ""))
xoutline = DomUtils.add_node(doc, root, "outline")
xoutline.appendChild(doc.createCDATASection(mediainfo.overview or ""))
for director in mediainfo.directors:
DomUtils.add_node(doc, root, "director", director.get("name") or "")
for actor in mediainfo.actors:
xactor = DomUtils.add_node(doc, root, "actor")
DomUtils.add_node(doc, xactor, "name", actor.get("name") or "")
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, "thumb", actor.get('avatar', {}).get('normal'))
DomUtils.add_node(doc, xactor, "profile", actor.get('url'))
DomUtils.add_node(doc, root, "rating", mediainfo.vote_average or "0")
return doc
def __gen_movie_nfo_file(self, mediainfo: MediaInfo) -> minidom.Document:
"""
生成电影的NFO描述文件。
:param mediainfo: 豆瓣信息
"""
doc = minidom.Document()
root = DomUtils.add_node(doc, doc, "movie")
doc = self.__gen_common_nfo(mediainfo=mediainfo,
doc=doc,
root=root)
DomUtils.add_node(doc, root, "title", mediainfo.title or "")
DomUtils.add_node(doc, root, "year", mediainfo.year or "")
return doc
def __gen_tv_nfo_file(self, mediainfo: MediaInfo) -> minidom.Document:
"""
生成电视剧的NFO描述文件。
:param mediainfo: 媒体信息
"""
doc = minidom.Document()
root = DomUtils.add_node(doc, doc, "tvshow")
doc = self.__gen_common_nfo(mediainfo=mediainfo,
doc=doc,
root=root)
DomUtils.add_node(doc, root, "title", mediainfo.title or "")
DomUtils.add_node(doc, root, "year", mediainfo.year or "")
DomUtils.add_node(doc, root, "season", "-1")
DomUtils.add_node(doc, root, "episode", "-1")
return doc
@staticmethod
def __gen_tv_season_nfo_file(mediainfo: MediaInfo,
season: int) -> minidom.Document:
"""
生成电视剧季的NFO描述文件。
:param mediainfo: 媒体信息
:param season: 季号
"""
doc = minidom.Document()
root = DomUtils.add_node(doc, doc, "season")
xplot = DomUtils.add_node(doc, root, "plot")
xplot.appendChild(doc.createCDATASection(mediainfo.overview or ""))
xoutline = DomUtils.add_node(doc, root, "outline")
xoutline.appendChild(doc.createCDATASection(mediainfo.overview or ""))
DomUtils.add_node(doc, root, "title", "季 %s" % season)
DomUtils.add_node(doc, root, "premiered", mediainfo.release_date or "")
DomUtils.add_node(doc, root, "releasedate", mediainfo.release_date or "")
DomUtils.add_node(doc, root, "year", mediainfo.release_date[:4] if mediainfo.release_date else "")
DomUtils.add_node(doc, root, "seasonnumber", str(season))
return doc