Merge branch 'jxxghp:v2' into v2

This commit is contained in:
winter0245
2026-01-13 09:30:16 +08:00
committed by GitHub
16 changed files with 408 additions and 227 deletions

View File

@@ -315,21 +315,6 @@ class MediaChain(ChainBase):
)
return None
@staticmethod
def is_bluray_folder(fileitem: schemas.FileItem) -> bool:
"""
判断是否为原盘目录
"""
if not fileitem or fileitem.type != "dir":
return False
# 蓝光原盘目录必备的文件或文件夹
required_files = ['BDMV', 'CERTIFICATE']
# 检查目录下是否存在所需文件或文件夹
for item in StorageChain().list_files(fileitem):
if item.name in required_files:
return True
return False
@eventmanager.register(EventType.MetadataScrape)
def scrape_metadata_event(self, event: Event):
"""
@@ -370,7 +355,7 @@ class MediaChain(ChainBase):
else:
if file_list:
# 如果是BDMV原盘目录只对根目录进行刮削不处理子目录
if self.is_bluray_folder(fileitem):
if storagechain.is_bluray_folder(fileitem):
logger.info(f"检测到BDMV原盘目录只对根目录进行刮削{fileitem.path}")
self.scrape_metadata(fileitem=fileitem,
mediainfo=mediainfo,
@@ -563,10 +548,23 @@ class MediaChain(ChainBase):
logger.info("电影NFO刮削已关闭跳过")
else:
# 电影目录
if recursive:
# 处理文件
if self.is_bluray_folder(fileitem):
# 原盘目录
files = __list_files(_fileitem=fileitem)
is_bluray_folder = storagechain.contains_bluray_subdirectories(files)
if recursive and not is_bluray_folder:
# 处理非原盘目录内的文件
for file in files:
if file.type == "dir":
# 电影不处理子目录
continue
self.scrape_metadata(fileitem=file,
mediainfo=mediainfo,
init_folder=False,
parent=fileitem,
overwrite=overwrite)
# 生成目录内图片文件
if init_folder:
if is_bluray_folder:
# 检查电影NFO开关
if scraping_switchs.get('movie_nfo', True):
nfo_path = filepath / (filepath.name + ".nfo")
if overwrite or not storagechain.get_file_item(storage=fileitem.storage, path=nfo_path):
@@ -581,20 +579,6 @@ class MediaChain(ChainBase):
logger.info(f"已存在nfo文件{nfo_path}")
else:
logger.info("电影NFO刮削已关闭跳过")
else:
# 处理目录内的文件
files = __list_files(_fileitem=fileitem)
for file in files:
if file.type == "dir":
# 电影不处理子目录
continue
self.scrape_metadata(fileitem=file,
mediainfo=mediainfo,
init_folder=False,
parent=fileitem,
overwrite=overwrite)
# 生成目录内图片文件
if init_folder:
# 图片
image_dict = self.metadata_img(mediainfo=mediainfo)
if image_dict:

View File

@@ -133,22 +133,29 @@ class StorageChain(ChainBase):
"""
return self.run_module("support_transtype", storage=storage)
def is_bluray_folder(self, fileitem: Optional[schemas.FileItem]) -> bool:
"""
检查是否蓝光目录
"""
if not fileitem or fileitem.type != "dir":
return False
return self.contains_bluray_subdirectories(self.list_files(fileitem))
@staticmethod
def contains_bluray_subdirectories(fileitems: Optional[List[schemas.FileItem]]) -> bool:
"""
判断是否包含蓝光必备的文件夹
"""
required_files = ("BDMV", "CERTIFICATE")
for item in fileitems or []:
if item.type == "dir" and item.name in required_files:
return True
return False
def delete_media_file(self, fileitem: schemas.FileItem, delete_self: bool = True) -> bool:
"""
删除媒体文件,以及不含媒体文件的目录
"""
def __is_bluray_dir(_fileitem: schemas.FileItem) -> bool:
"""
检查是否蓝光目录
"""
_dir_files = self.list_files(fileitem=_fileitem, recursion=False)
if _dir_files:
for _f in _dir_files:
if _f.type == "dir" and _f.name in ["BDMV", "CERTIFICATE"]:
return True
return False
media_exts = settings.RMT_MEDIAEXT + settings.DOWNLOAD_TMPEXT
fileitem_path = Path(fileitem.path) if fileitem.path else Path("")
if len(fileitem_path.parts) <= 2:
@@ -156,7 +163,7 @@ class StorageChain(ChainBase):
return False
if fileitem.type == "dir":
# 本身是目录
if __is_bluray_dir(fileitem):
if self.is_bluray_folder(fileitem):
logger.warn(f"正在删除蓝光原盘目录:【{fileitem.storage}{fileitem.path}")
if not self.delete_file(fileitem):
logger.warn(f"{fileitem.storage}{fileitem.path} 删除失败")

View File

@@ -1635,7 +1635,7 @@ class SubscribeChain(ChainBase):
info = schemas.SubscribeEpisodeInfo()
info.title = episode.name
info.description = episode.overview
info.backdrop = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/w500${episode.still_path}"
info.backdrop = settings.TMDB_IMAGE_URL(episode.still_path, "w500")
episodes[episode.episode_number] = info
elif subscribe.type == MediaType.TV.value:
# 根据开始结束集计算集信息

View File

@@ -376,7 +376,7 @@ class TransferChain(ChainBase, metaclass=Singleton):
self._transfer_interval = 15
# 事件管理器
self.jobview = JobManager()
# 移成功的文件清单
# 移成功的文件清单
self._success_target_files: Dict[str, List[str]] = {}
# 启动整理任务
self.__init()
@@ -873,7 +873,7 @@ class TransferChain(ChainBase, metaclass=Singleton):
state, errmsg = self.do_transfer(
fileitem=FileItem(
storage="local",
path=file_path.as_posix(),
path=file_path.as_posix() + ("/" if file_path.is_dir() else ""),
type="dir" if not file_path.is_file() else "file",
name=file_path.name,
size=file_path.stat().st_size,
@@ -908,16 +908,6 @@ class TransferChain(ChainBase, metaclass=Singleton):
"""
storagechain = StorageChain()
def __contains_bluray_sub(_fileitems: List[FileItem]) -> bool:
"""
判断是否包含蓝光子目录
"""
if _fileitems:
for sub in _fileitems:
if sub.type == "dir" and sub.name in ["BDMV", "CERTIFICATE"]:
return True
return False
def __is_bluray_sub(_path: str) -> bool:
"""
判断是否蓝光原盘目录内的子目录或文件
@@ -949,7 +939,7 @@ class TransferChain(ChainBase, metaclass=Singleton):
# 蓝光原盘根目录
sub_items = storagechain.list_files(fileitem) or []
if __contains_bluray_sub(sub_items):
if storagechain.contains_bluray_subdirectories(sub_items):
return [(fileitem, True)]
# 需要整理的文件项列表

View File

@@ -843,6 +843,18 @@ class Settings(BaseSettings, ConfigModel, LogConfigModel):
rename_format = re.sub(r'/+', '/', rename_format)
return rename_format.strip("/")
def TMDB_IMAGE_URL(self, file_path: str, file_size: str = "original") -> str:
"""
获取TMDB图片网址
:param file_path: TMDB API返回的xxx_path
:param file_size: 图片大小,例如:'original', 'w500'
:return: 图片的完整URL
"""
return (
f"https://{self.TMDB_IMAGE_DOMAIN}/t/p/{file_size}/{file_path.removeprefix('/')}"
)
# 实例化配置
settings = Settings()

View File

@@ -479,11 +479,11 @@ class MediaInfo:
self.episode_groups = info.pop("episode_groups").get("results") or []
# 海报
if info.get('poster_path'):
self.poster_path = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{info.get('poster_path')}"
if path := info.get('poster_path'):
self.poster_path = settings.TMDB_IMAGE_URL(path)
# 背景
if info.get('backdrop_path'):
self.backdrop_path = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{info.get('backdrop_path')}"
if path := info.get('backdrop_path'):
self.backdrop_path = settings.TMDB_IMAGE_URL(path)
# 导演和演员
self.directors, self.actors = __directors_actors(info)
# 别名和译名

View File

@@ -132,6 +132,15 @@ class TransHandler:
return self.result.model_copy()
else:
new_path = target_path / fileitem.name
# 在整理目录前先尝试获取原盘大小避免整理记录出现0字节的情况
# TODO 当前只计算STREAM目录内的文件大小如果需要精确则递归完整目录
if stream_fileitem := source_oper.get_item(
Path(fileitem.path) / "BDMV" / "STREAM"
):
fileitem.size = 0
files = source_oper.list(stream_fileitem) or []
for file in files:
fileitem.size += file.size
# 整理目录
new_diritem, errmsg = self.__transfer_dir(fileitem=fileitem,
mediainfo=mediainfo,

View File

@@ -867,19 +867,19 @@ class TheMovieDbModule(_ModuleBase):
backdrops = images.get("backdrops")
if backdrops:
backdrops = sorted(backdrops, key=lambda x: x.get("vote_average"), reverse=True)
mediainfo.backdrop_path = backdrops[0].get("file_path")
mediainfo.backdrop_path = settings.TMDB_IMAGE_URL(backdrops[0].get("file_path"))
# 标志
if not mediainfo.logo_path:
logos = images.get("logos")
if logos:
logos = sorted(logos, key=lambda x: x.get("vote_average"), reverse=True)
mediainfo.logo_path = logos[0].get("file_path")
mediainfo.logo_path = settings.TMDB_IMAGE_URL(logos[0].get("file_path"))
# 海报
if not mediainfo.poster_path:
posters = images.get("posters")
if posters:
posters = sorted(posters, key=lambda x: x.get("vote_average"), reverse=True)
mediainfo.poster_path = posters[0].get("file_path")
mediainfo.poster_path = settings.TMDB_IMAGE_URL(posters[0].get("file_path"))
return mediainfo
def obtain_images(self, mediainfo: MediaInfo) -> Optional[MediaInfo]:
@@ -957,7 +957,7 @@ class TheMovieDbModule(_ModuleBase):
image_path = seasoninfo.get(image_type.value)
if image_path:
return f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/{image_prefix}{image_path}"
return settings.TMDB_IMAGE_URL(image_path, image_prefix)
return None
def tmdb_movie_similar(self, tmdbid: int) -> List[MediaInfo]:

View File

@@ -85,10 +85,10 @@ class TmdbScraper:
seasoninfo = self.original_tmdb(mediainfo).get_tv_season_detail(mediainfo.tmdb_id, season)
if seasoninfo:
episodeinfo = self.__get_episode_detail(seasoninfo, episode)
if episodeinfo and episodeinfo.get("still_path"):
if still_path := episodeinfo.get("still_path"):
# TMDB集still图片
still_name = f"{episode}"
still_url = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{episodeinfo.get('still_path')}"
still_url = settings.TMDB_IMAGE_URL(still_path)
images[still_name] = still_url
else:
# 季的图片
@@ -115,7 +115,7 @@ class TmdbScraper:
if _mediainfo:
for attr_name, attr_value in _mediainfo.items():
if attr_name.endswith("_path") and attr_value is not None:
image_url = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{attr_value}"
image_url = settings.TMDB_IMAGE_URL(attr_value)
image_name = attr_name.replace("_path", "") + Path(image_url).suffix
images[image_name] = image_url
return images
@@ -127,11 +127,11 @@ class TmdbScraper:
"""
# TMDB季poster图片
sea_seq = str(season).rjust(2, '0')
if seasoninfo.get("poster_path"):
if poster_path := seasoninfo.get("poster_path"):
# 后缀
ext = Path(seasoninfo.get('poster_path')).suffix
ext = Path(poster_path).suffix
# URL
url = f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{seasoninfo.get('poster_path')}"
url = settings.TMDB_IMAGE_URL(poster_path)
# S0海报格式不同
if season == 0:
image_name = f"season-specials-poster{ext}"
@@ -190,8 +190,8 @@ class TmdbScraper:
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, "tmdbid", actor.get("id") or "")
DomUtils.add_node(doc, xactor, "thumb",
f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{actor.get('profile_path')}")
if profile_path := actor.get('profile_path'):
DomUtils.add_node(doc, xactor, "thumb", settings.TMDB_IMAGE_URL(profile_path))
DomUtils.add_node(doc, xactor, "profile",
f"https://www.themoviedb.org/person/{actor.get('id')}")
# 风格
@@ -330,8 +330,8 @@ class TmdbScraper:
DomUtils.add_node(doc, xactor, "name", actor.get("name") or "")
DomUtils.add_node(doc, xactor, "type", "Actor")
DomUtils.add_node(doc, xactor, "tmdbid", actor.get("id") or "")
DomUtils.add_node(doc, xactor, "thumb",
f"https://{settings.TMDB_IMAGE_DOMAIN}/t/p/original{actor.get('profile_path')}")
if profile_path := actor.get('profile_path'):
DomUtils.add_node(doc, xactor, "thumb", settings.TMDB_IMAGE_URL(profile_path))
DomUtils.add_node(doc, xactor, "profile",
f"https://www.themoviedb.org/person/{actor.get('id')}")
return doc

View File

@@ -50,7 +50,7 @@ class TmdbCache(metaclass=WeakSingleton):
"""
获取缓存KEY
"""
return f"[{meta.type.value if meta.type else '未知'}]{meta.tmdbid or meta.name}-{meta.year}-{meta.begin_season}"
return f"[{meta.type.value if meta.type else '未知'}][{settings.TMDB_LOCALE}]{meta.tmdbid or meta.name}-{meta.year}-{meta.begin_season}"
def get(self, meta: MetaBase):
"""

View File

@@ -826,7 +826,7 @@ class TmdbApi:
# 转换多语种标题
self.__update_tmdbinfo_extra_title(tmdb_info)
# 转换中文标题
if settings.TMDB_LOCALE == "zh":
if self.tmdb.language in ("zh", "zh-CN"):
self.__update_tmdbinfo_cn_title(tmdb_info)
return tmdb_info
@@ -2134,7 +2134,7 @@ class TmdbApi:
# 转换多语种标题
self.__update_tmdbinfo_extra_title(tmdb_info)
# 转换中文标题
if settings.TMDB_LOCALE == "zh":
if self.tmdb.language in ("zh", "zh-CN"):
self.__update_tmdbinfo_cn_title(tmdb_info)
return tmdb_info

View File

@@ -695,11 +695,13 @@ class Monitor(ConfigReloadMixin, metaclass=SingletonClass):
# 全程加锁
with lock:
is_bluray_folder = False
# 蓝光原盘文件处理
if __is_bluray_sub(event_path):
event_path = __get_bluray_dir(event_path)
if not event_path:
return
is_bluray_folder = True
# TTL缓存控重
if self._cache.get(str(event_path)):
@@ -708,13 +710,20 @@ class Monitor(ConfigReloadMixin, metaclass=SingletonClass):
self._cache[str(event_path)] = True
try:
logger.info(f"开始整理文件: {event_path}")
if is_bluray_folder:
logger.info(f"开始整理蓝光原盘: {event_path}")
else:
logger.info(f"开始整理文件: {event_path}")
# 开始整理
TransferChain().do_transfer(
fileitem=FileItem(
storage=storage,
path=event_path.as_posix(),
type="file",
path=(
event_path.as_posix()
if not is_bluray_folder
else event_path.as_posix() + "/"
),
type="file" if not is_bluray_folder else "dir",
name=event_path.name,
basename=event_path.stem,
extension=event_path.suffix[1:],

View File

@@ -479,6 +479,8 @@ class SystemUtils:
def is_bluray_dir(dir_path: Path) -> bool:
"""
判断是否为蓝光原盘目录
(该方法已弃用,改用`StorageChain().is_bluray_folder)`
"""
if not dir_path.is_dir():
return False

View File

@@ -1,161 +1,99 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
# 文件列表结构 list[tuple(名称, 子文件列表 或 文件大小)]
bluray_files = [
{
"name": "FOLDER",
"children": [
{
"name": "Digimon",
"children": [
{
"name": "Digimon (2055)",
"children": [
{
"name": "BDMV",
"children": [
{
"name": "STREAM",
"children": [
{
"name": "00000.m2ts",
"size": 104857600,
},
{
"name": "00001.m2ts",
"size": 104857600,
},
(
"FOLDER",
[
(
"Digimon",
[
(
"Digimon BluRay (2055)",
[
(
"BDMV",
[
(
"STREAM",
[
("00000.m2ts", 104857600),
("00001.m2ts", 104857600),
],
},
),
],
},
{
"name": "CERTIFICATE",
"children": [],
},
),
("CERTIFICATE", None),
],
},
{
"name": "Digimon (2099)",
"children": [
{
"name": "BDMV",
"children": [
{
"name": "STREAM",
"children": [
{
"name": "00000.m2ts",
"size": 104857600,
},
{
"name": "00001.m2ts",
"size": 104857600,
},
{
"name": "00002.m2ts.!qB",
"size": 104857600,
},
),
(
"Digimon BluRay (2099)",
[
(
"BDMV",
[
(
"STREAM",
[
("00000.m2ts", 104857600),
("00001.m2ts", 104857600),
("00002.m2ts.!qB", 104857600),
],
},
),
],
},
{
"name": "CERTIFICATE",
"children": [],
},
),
("CERTIFICATE", None),
],
},
{
"name": "Digimon (2199)",
"children": [
{
"name": "Digimon.2199.mp4",
"size": 104857600,
},
],
},
),
("Digimon (2199)", [("Digimon.2199.mp4", 104857600)]),
],
},
{
"name": "Pokemon (2016)",
"children": [
{
"name": "BDMV",
"children": [
{
"name": "STREAM",
"children": [
{
"name": "00000.m2ts",
"size": 104857600,
},
{
"name": "00001.m2ts",
"size": 104857600,
},
),
(
"Pokemon BluRay (2016)",
[
(
"BDMV",
[
(
"STREAM",
[
("00000.m2ts", 104857600),
("00001.m2ts", 104857600),
],
},
)
],
},
{
"name": "CERTIFICATE",
"children": [],
},
),
("CERTIFICATE", None),
],
},
{
"name": "Pokemon (2021)",
"children": [
{
"name": "BDMV",
"children": [
{
"name": "STREAM",
"children": [
{
"name": "00000.m2ts",
"size": 104857600,
},
{
"name": "00001.m2ts",
"size": 104857600,
},
),
(
"Pokemon BluRay (2021)",
[
(
"BDMV",
[
(
"STREAM",
[
("00000.m2ts", 104857600),
("00001.m2ts", 104857600),
],
},
)
],
},
{
"name": "CERTIFICATE",
"children": [],
},
),
("CERTIFICATE", None),
],
},
{
"name": "Pokemon (2028)",
"children": [
{
"name": "Pokemon.2028.mkv",
"size": 104857600,
},
{
"name": "Pokemon.2028.hdr.mkv.!qB",
"size": 104857600,
},
),
(
"Pokemon (2028)",
[
("Pokemon.2028.mkv", 104857600),
("Pokemon.2028.hdr.mkv.!qB", 104857600),
],
},
{
"name": "Pokemon.2029.mp4",
"size": 104857600,
},
{
"name": "Pokemon (2030)",
"children": [
{
"name": "S",
"size": 104857600,
},
],
},
),
("Pokemon.2029.mp4", 104857600),
("Pokemon.2039.mp4", 104857600),
("Pokemon (2030)", [("S", 104857600)]),
],
},
)
]

View File

@@ -1,5 +1,6 @@
import unittest
from tests.test_bluray import BluRayTest
from tests.test_metainfo import MetaInfoTest
from tests.test_object import ObjectUtilsTest
@@ -12,6 +13,9 @@ if __name__ == '__main__':
suite.addTest(MetaInfoTest('test_emby_format_ids'))
suite.addTest(ObjectUtilsTest('test_check_method'))
# 测试蓝光目录识别
suite.addTest(BluRayTest())
# 运行测试
runner = unittest.TextTestRunner()
runner.run(suite)

226
tests/test_bluray.py Normal file
View File

@@ -0,0 +1,226 @@
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from pathlib import Path
from typing import Optional
from unittest import TestCase
from unittest.mock import patch
from app import schemas
from app.chain.media import MediaChain
from app.chain.storage import StorageChain
from app.chain.transfer import TransferChain
from app.core.context import MediaInfo
from app.core.event import Event
from app.core.metainfo import MetaInfoPath
from app.db.models.transferhistory import TransferHistory
from app.log import logger
from app.schemas.types import EventType
from tests.cases.files import bluray_files
class BluRayTest(TestCase):
def __init__(self, methodName="test"):
super().__init__(methodName)
self.__history = []
self.__root = schemas.FileItem(
path="/", name="", type="dir", extension="", size=0
)
self.__all = {self.__root.path: self.__root}
def __build_child(parent: schemas.FileItem, files: list[tuple[str, list | int]]):
parent.children = []
for name, children in files:
sep = "" if parent.path.endswith("/") else "/"
file_item = schemas.FileItem(
path=f"{parent.path}{sep}{name}",
name=name,
extension=Path(name).suffix[1:],
basename=Path(name).stem,
type="file" if isinstance(children, int) else "dir",
size=children if isinstance(children, int) else 0,
)
parent.children.append(file_item)
self.__all[file_item.path] = file_item
if isinstance(children, list):
__build_child(file_item, children)
__build_child(self.__root, bluray_files)
def _test_do_transfer(self):
def __test_do_transfer(path: str):
self.__history.clear()
TransferChain().do_transfer(
force=False,
background=False,
fileitem=StorageChain().get_file_item(None, Path(path)),
)
return self.__history
self.assertEqual(
[
"/FOLDER/Digimon/Digimon BluRay (2055)",
"/FOLDER/Digimon/Digimon BluRay (2099)",
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
],
__test_do_transfer("/FOLDER/Digimon"),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon BluRay (2055)",
],
__test_do_transfer("/FOLDER/Digimon/Digimon BluRay (2055)"),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon BluRay (2055)",
],
__test_do_transfer("/FOLDER/Digimon/Digimon BluRay (2055)/BDMV"),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon BluRay (2055)",
],
__test_do_transfer("/FOLDER/Digimon/Digimon BluRay (2055)/BDMV/STREAM"),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon BluRay (2055)",
],
__test_do_transfer(
"/FOLDER/Digimon/Digimon BluRay (2055)/BDMV/STREAM/00001.m2ts"
),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
],
__test_do_transfer("/FOLDER/Digimon/Digimon (2199)"),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
],
__test_do_transfer("/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4"),
)
self.assertEqual(
[
"/FOLDER/Pokemon.2029.mp4",
],
__test_do_transfer("/FOLDER/Pokemon.2029.mp4"),
)
self.assertEqual(
[
"/FOLDER/Digimon/Digimon BluRay (2055)",
"/FOLDER/Digimon/Digimon BluRay (2099)",
"/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4",
"/FOLDER/Pokemon BluRay (2016)",
"/FOLDER/Pokemon BluRay (2021)",
"/FOLDER/Pokemon (2028)/Pokemon.2028.mkv",
"/FOLDER/Pokemon.2029.mp4",
"/FOLDER/Pokemon.2039.mp4",
],
__test_do_transfer("/FOLDER"),
)
def _test_scrape_metadata(self, mock_metadata_nfo):
def __test_scrape_metadata(path: str, excepted_nfo_count: int = 1):
"""
分别测试手动和自动刮削
"""
fileitem = StorageChain().get_file_item(None, Path(path))
meta = MetaInfoPath(Path(fileitem.path))
mediainfo = MediaInfo(tmdb_info={"id": 1, "title": "Test"})
# 测试手动刮削
logger.debug(f"测试手动刮削 {path}")
mock_metadata_nfo.call_count = 0
MediaChain().scrape_metadata(
fileitem=fileitem, meta=meta, mediainfo=mediainfo, overwrite=True
)
# 确保调用了指定次数的metadata_nfo
self.assertEqual(mock_metadata_nfo.call_count, excepted_nfo_count)
# 测试自动刮削
logger.debug(f"测试自动刮削 {path}")
mock_metadata_nfo.call_count = 0
MediaChain().scrape_metadata_event(
Event(
event_type=EventType.MetadataScrape,
event_data={
"meta": meta,
"mediainfo": mediainfo,
"fileitem": fileitem,
"file_list": [fileitem.path],
"overwrite": False,
},
)
)
# 调用了指定次数的metadata_nfo
self.assertEqual(mock_metadata_nfo.call_count, excepted_nfo_count)
# 刮削原盘目录
__test_scrape_metadata("/FOLDER/Digimon/Digimon BluRay (2099)")
# 刮削电影文件
__test_scrape_metadata("/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4")
# 刮削电影目录
__test_scrape_metadata("/FOLDER", excepted_nfo_count=2)
@patch("app.chain.ChainBase.metadata_img", return_value=None) # 避免获取图片
@patch("app.chain.ChainBase.__init__", return_value=None) # 避免不必要的模块初始化
@patch("app.db.transferhistory_oper.TransferHistoryOper.get_by_src")
@patch("app.chain.storage.StorageChain.list_files")
@patch("app.chain.storage.StorageChain.get_parent_item")
@patch("app.chain.storage.StorageChain.get_file_item")
def test(
self,
mock_get_file_item,
mock_get_parent_item,
mock_list_files,
mock_get_by_src,
*_,
):
def get_file_item(storage: str, path: Path):
path_posix = path.as_posix()
return self.__all.get(path_posix)
def get_parent_item(fileitem: schemas.FileItem):
return get_file_item(None, Path(fileitem.path).parent)
def list_files(fileitem: schemas.FileItem, recursion: bool = False):
if fileitem.type != "dir":
return None
if recursion:
result = []
file_path = f"{fileitem.path}/"
for path, item in self.__all.items():
if path.startswith(file_path):
result.append(item)
return result
else:
return fileitem.children
def get_by_src(src: str, storage: Optional[str] = None):
self.__history.append(src)
result = TransferHistory()
result.status = True
return result
mock_get_file_item.side_effect = get_file_item
mock_get_parent_item.side_effect = get_parent_item
mock_list_files.side_effect = list_files
mock_get_by_src.side_effect = get_by_src
self._test_do_transfer()
with patch(
"app.chain.media.MediaChain.metadata_nfo", return_value=None
) as mock:
self._test_scrape_metadata(mock_metadata_nfo=mock)