fix: improve media image scraping

This commit is contained in:
jxxghp
2026-05-19 10:48:36 +08:00
parent 9e9c398177
commit 29dab5a312
6 changed files with 375 additions and 45 deletions
+79 -15
View File
@@ -363,6 +363,15 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
if season_image_name := season_image_name_map.get(metadata_type):
hint_ext = Path(filename_hint).suffix if filename_hint else ".jpg"
final_filename = f"{season_image_name}{hint_ext}"
elif item_type == ScrapingTarget.MOVIE and current_fileitem.type == "file":
# 电影文件的图片应与视频文件同级保存,避免把图片路径拼到文件名下面。
target_dir_item = parent_fileitem or self.storagechain.get_parent_item(
current_fileitem
)
if not target_dir_item:
logger.error(f"无法获取文件 {current_fileitem.path} 的父目录项。")
return current_fileitem, None
target_dir_path = Path(target_dir_item.path)
# 如果是 EPISODE 类型的图片(如thumb),通常也是放在文件同级目录,文件名与视频文件一致
elif (
metadata_type in [ScrapingMetadata.THUMB]
@@ -390,6 +399,52 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
target_full_path = target_dir_path / final_filename
return target_dir_item, target_full_path
def _get_target_fileitems_and_paths(
self,
current_fileitem: schemas.FileItem,
item_type: ScrapingTarget,
metadata_type: ScrapingMetadata,
filename_hint: Optional[str] = None,
parent_fileitem: Optional[schemas.FileItem] = None,
) -> List[Tuple[schemas.FileItem, Path]]:
"""
根据刮削上下文生成一个或多个保存目标。
季图片需要同时兼容根目录 seasonxx-poster 和季目录 poster 两种命名。
"""
target_item, target_path = self._get_target_fileitem_and_path(
current_fileitem=current_fileitem,
item_type=item_type,
metadata_type=metadata_type,
filename_hint=filename_hint,
parent_fileitem=parent_fileitem,
)
targets = [(target_item, target_path)] if target_path else []
if (
item_type != ScrapingTarget.SEASON
or not filename_hint
or not filename_hint.lower().startswith("season")
or metadata_type not in {
ScrapingMetadata.POSTER,
ScrapingMetadata.BANNER,
ScrapingMetadata.THUMB,
}
):
return targets
season_parent_item = parent_fileitem or self.storagechain.get_parent_item(
current_fileitem
)
if not season_parent_item:
logger.warn(f"无法获取季目录 {current_fileitem.path} 的父目录项,跳过根目录季图片")
return targets
season_root_path = Path(current_fileitem.path).with_name(filename_hint)
root_target = (season_parent_item, season_root_path)
if root_target not in targets:
targets.insert(0, root_target)
return targets
def metadata_nfo(
self,
meta: MetaBase,
@@ -773,7 +828,7 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
self.scrape_metadata(
fileitem=fileitem,
mediainfo=mediainfo,
init_folder=False,
init_folder=True,
parent=self.storagechain.get_parent_item(fileitem),
overwrite=overwrite,
)
@@ -985,8 +1040,8 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
)
continue
# 获取目标 FileItem (`base_item`) 和 Path (`image_path`)
base_item, image_path = self._get_target_fileitem_and_path(
# 获取目标 FileItem 和 Path,季图片会同时写根目录和季目录。
image_targets = self._get_target_fileitems_and_paths(
current_fileitem=current_fileitem,
item_type=item_type,
metadata_type=metadata_type,
@@ -994,19 +1049,20 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
parent_fileitem=parent_fileitem,
)
if not image_path:
continue
for base_item, image_path in image_targets:
if not image_path:
continue
# 文件存在检查
file_exists = self.storagechain.get_file_item(
storage=base_item.storage, path=image_path
)
# 刮削决策
if self._should_scrape(option, bool(file_exists), overwrite):
self._download_and_save_image(
fileitem=base_item, path=image_path, url=image_url
# 文件存在检查
file_exists = self.storagechain.get_file_item(
storage=base_item.storage, path=image_path
)
# 刮削决策
if self._should_scrape(option, bool(file_exists), overwrite):
self._download_and_save_image(
fileitem=base_item, path=image_path, url=image_url
)
else:
logger.debug(
f"未找到图片类型 {image_name} 对应的 ScrapingMetadata,跳过。"
@@ -1092,7 +1148,7 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
处理电影刮削
"""
if fileitem.type == "file":
# 电影文件:仅处理 NFO
# 电影文件始终处理 NFO,直接初始化文件时再补同级目录图片。
self._scrape_nfo_generic(
current_fileitem=fileitem,
meta=meta,
@@ -1101,6 +1157,14 @@ class MediaChain(ChainBase, ConfigReloadMixin, metaclass=Singleton):
parent_fileitem=parent,
overwrite=overwrite,
)
if init_folder:
self._scrape_images_generic(
current_fileitem=fileitem,
mediainfo=mediainfo,
item_type=ScrapingTarget.MOVIE,
parent_fileitem=parent,
overwrite=overwrite,
)
else:
# 电影目录:递归处理文件并初始化目录
self._handle_movie_directory(
+40 -17
View File
@@ -969,7 +969,24 @@ class TheMovieDbModule(_ModuleBase):
return None
@staticmethod
def _process_tmdb_images(mediainfo: MediaInfo, images: dict) -> MediaInfo:
def _pick_best_tmdb_image(images: list) -> Optional[str]:
"""
从 TMDB 图片候选中选出评分最高的文件路径。
"""
if not images:
return None
images = sorted(
images,
key=lambda x: (
x.get("vote_average") or 0,
x.get("vote_count") or 0,
),
reverse=True,
)
return images[0].get("file_path")
@classmethod
def _process_tmdb_images(cls, mediainfo: MediaInfo, images: dict) -> MediaInfo:
"""
处理 TMDB 图片数据
:param mediainfo: 媒体信息
@@ -980,22 +997,16 @@ class TheMovieDbModule(_ModuleBase):
images = images[0]
# 背景图
if not mediainfo.backdrop_path:
backdrops = images.get("backdrops")
if backdrops:
backdrops = sorted(backdrops, key=lambda x: x.get("vote_average"), reverse=True)
mediainfo.backdrop_path = settings.TMDB_IMAGE_URL(backdrops[0].get("file_path"))
if image_path := cls._pick_best_tmdb_image(images.get("backdrops")):
mediainfo.backdrop_path = settings.TMDB_IMAGE_URL(image_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 = settings.TMDB_IMAGE_URL(logos[0].get("file_path"))
if image_path := cls._pick_best_tmdb_image(images.get("logos")):
mediainfo.logo_path = settings.TMDB_IMAGE_URL(image_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 = settings.TMDB_IMAGE_URL(posters[0].get("file_path"))
if image_path := cls._pick_best_tmdb_image(images.get("posters")):
mediainfo.poster_path = settings.TMDB_IMAGE_URL(image_path)
return mediainfo
def obtain_images(self, mediainfo: MediaInfo) -> Optional[MediaInfo]:
@@ -1011,9 +1022,15 @@ class TheMovieDbModule(_ModuleBase):
# 调用TMDB图片接口
if mediainfo.type == MediaType.MOVIE:
images = self.tmdb.get_movie_images(mediainfo.tmdb_id)
images = self.tmdb.get_movie_images(
mediainfo.tmdb_id,
original_language=mediainfo.original_language,
)
else:
images = self.tmdb.get_tv_images(mediainfo.tmdb_id)
images = self.tmdb.get_tv_images(
mediainfo.tmdb_id,
original_language=mediainfo.original_language,
)
if not images:
return mediainfo
@@ -1033,9 +1050,15 @@ class TheMovieDbModule(_ModuleBase):
# 调用TMDB图片接口
if mediainfo.type == MediaType.MOVIE:
images = await self.tmdb.async_get_movie_images(mediainfo.tmdb_id)
images = await self.tmdb.async_get_movie_images(
mediainfo.tmdb_id,
original_language=mediainfo.original_language,
)
else:
images = await self.tmdb.async_get_tv_images(mediainfo.tmdb_id)
images = await self.tmdb.async_get_tv_images(
mediainfo.tmdb_id,
original_language=mediainfo.original_language,
)
if not images:
return mediainfo
+47
View File
@@ -127,6 +127,7 @@ class TmdbScraper:
if poster_name and poster_url:
images[poster_name] = poster_url
else:
self.__ensure_main_images(mediainfo)
# 获取媒体信息中原有图片
for attr_name, attr_value in vars(mediainfo).items():
if (
@@ -155,6 +156,52 @@ class TmdbScraper:
images[image_name] = image_url
return images
def __ensure_main_images(self, mediainfo: MediaInfo) -> None:
"""
主媒体图片缺失时从 TMDB images 接口回填,避免当前语言没有图时只生成 NFO。
"""
if not mediainfo or not mediainfo.tmdb_id:
return
if mediainfo.poster_path and mediainfo.backdrop_path:
return
if mediainfo.type == MediaType.MOVIE:
image_info = self.default_tmdb.get_movie_images(
mediainfo.tmdb_id,
original_language=mediainfo.original_language,
)
else:
image_info = self.default_tmdb.get_tv_images(
mediainfo.tmdb_id,
original_language=mediainfo.original_language,
)
if not image_info:
return
if not mediainfo.poster_path:
poster_path = self.__pick_best_image_path(image_info.get("posters"))
if poster_path:
mediainfo.poster_path = settings.TMDB_IMAGE_URL(poster_path)
if not mediainfo.backdrop_path:
backdrop_path = self.__pick_best_image_path(image_info.get("backdrops"))
if backdrop_path:
mediainfo.backdrop_path = settings.TMDB_IMAGE_URL(backdrop_path)
@staticmethod
def __pick_best_image_path(images: list) -> Optional[str]:
"""
从 TMDB 图片列表中选择评分和投票数最高的一张。
"""
if not images:
return None
images = sorted(
images,
key=lambda item: (
item.get("vote_average") or 0,
item.get("vote_count") or 0,
),
reverse=True,
)
return images[0].get("file_path")
@staticmethod
def get_season_poster(seasoninfo: dict, season: int) -> Tuple[str, str]:
"""
+50 -8
View File
@@ -4,6 +4,7 @@ from typing import Optional, List
import zhconv
from app.core.config import settings
from app.log import logger
from app.schemas.types import MediaType
from app.utils.string import StringUtils
@@ -1232,7 +1233,24 @@ class TmdbApi:
logger.error(str(e))
return []
def get_movie_images(self, tmdbid: int) -> dict:
@staticmethod
def _build_include_image_language(original_language: Optional[str] = None) -> str:
"""
构造图片接口语言回退列表,避免当前语言没有图片时返回空列表。
"""
languages = []
for language in (
getattr(settings, "TMDB_LOCALE", None),
"en",
None,
original_language,
):
language = "null" if language is None else str(language).strip()
if language and language not in languages:
languages.append(language)
return ",".join(languages)
def get_movie_images(self, tmdbid: int, original_language: Optional[str] = None) -> dict:
"""
获取电影的图片
"""
@@ -1240,12 +1258,17 @@ class TmdbApi:
return {}
try:
logger.debug(f"正在获取电影图片:{tmdbid}...")
return self.movie.images(movie_id=tmdbid) or {}
return self.movie.images(
movie_id=tmdbid,
include_image_language=self._build_include_image_language(
original_language
),
) or {}
except Exception as e:
logger.error(str(e))
return {}
def get_tv_images(self, tmdbid: int) -> dict:
def get_tv_images(self, tmdbid: int, original_language: Optional[str] = None) -> dict:
"""
获取电视剧的图片
"""
@@ -1253,7 +1276,12 @@ class TmdbApi:
return {}
try:
logger.debug(f"正在获取电视剧图片:{tmdbid}...")
return self.tv.images(tv_id=tmdbid) or {}
return self.tv.images(
tv_id=tmdbid,
include_image_language=self._build_include_image_language(
original_language
),
) or {}
except Exception as e:
logger.error(str(e))
return {}
@@ -1965,7 +1993,9 @@ class TmdbApi:
logger.error(str(e))
return []
async def async_get_movie_images(self, tmdbid: int) -> dict:
async def async_get_movie_images(
self, tmdbid: int, original_language: Optional[str] = None
) -> dict:
"""
获取电影的图片(异步版本)
"""
@@ -1973,12 +2003,19 @@ class TmdbApi:
return {}
try:
logger.debug(f"正在获取电影图片:{tmdbid}...")
return await self.movie.async_images(movie_id=tmdbid) or {}
return await self.movie.async_images(
movie_id=tmdbid,
include_image_language=self._build_include_image_language(
original_language
),
) or {}
except Exception as e:
logger.error(str(e))
return {}
async def async_get_tv_images(self, tmdbid: int) -> dict:
async def async_get_tv_images(
self, tmdbid: int, original_language: Optional[str] = None
) -> dict:
"""
获取电视剧的图片(异步版本)
"""
@@ -1986,7 +2023,12 @@ class TmdbApi:
return {}
try:
logger.debug(f"正在获取电视剧图片:{tmdbid}...")
return await self.tv.async_images(tv_id=tmdbid) or {}
return await self.tv.async_images(
tv_id=tmdbid,
include_image_language=self._build_include_image_language(
original_language
),
) or {}
except Exception as e:
logger.error(str(e))
return {}
+65 -1
View File
@@ -1,11 +1,13 @@
import asyncio
from unittest import TestCase
from unittest.mock import AsyncMock, Mock
from unittest.mock import AsyncMock, Mock, patch
from app.core.context import MediaInfo
from app.core.meta import MetaBase
from app.modules.douban import DoubanModule
from app.modules.themoviedb import TheMovieDbModule
from app.modules.themoviedb.scraper import TmdbScraper
from app.modules.themoviedb.tmdbapi import TmdbApi
from app.schemas.types import MediaType
@@ -96,6 +98,68 @@ class MediaRecognizeModulesTest(TestCase):
module._async_search_by_name.assert_called()
module.tmdb.async_match_web.assert_not_called()
def test_tmdb_image_language_fallback_includes_current_en_null_and_original(self):
"""TMDB 图片查询应带上语言回退,避免当前语言没有图片时直接返回空。"""
with patch("app.modules.themoviedb.tmdbapi.settings") as mock_settings:
mock_settings.TMDB_LOCALE = "zh"
result = TmdbApi._build_include_image_language("ja")
self.assertEqual(result, "zh,en,null,ja")
def test_tmdb_obtain_images_uses_language_fallback_and_picks_best(self):
"""obtain_images 应从图片接口回填缺失的海报和背景图。"""
module = TheMovieDbModule()
module.tmdb = Mock()
module.tmdb.get_movie_images.return_value = {
"posters": [
{"file_path": "/low-poster.jpg", "vote_average": 2, "vote_count": 10},
{"file_path": "/best-poster.jpg", "vote_average": 8, "vote_count": 1},
],
"backdrops": [
{"file_path": "/best-backdrop.jpg", "vote_average": 7, "vote_count": 2},
],
}
mediainfo = MediaInfo(
tmdb_id=100,
type=MediaType.MOVIE,
original_language="ja",
)
result = module.obtain_images(mediainfo)
self.assertIs(result, mediainfo)
module.tmdb.get_movie_images.assert_called_once_with(100, original_language="ja")
self.assertTrue(mediainfo.poster_path.endswith("/best-poster.jpg"))
self.assertTrue(mediainfo.backdrop_path.endswith("/best-backdrop.jpg"))
def test_tmdb_scraper_metadata_img_fetches_missing_main_images(self):
"""主媒体图片缺失时,刮削图片列表应先从 TMDB images 接口补齐。"""
scraper = TmdbScraper()
scraper._meta_tmdb = Mock()
scraper._meta_tmdb.get_movie_images.return_value = {
"posters": [
{"file_path": "/fallback-poster.jpg", "vote_average": 5},
],
"backdrops": [
{"file_path": "/fallback-backdrop.jpg", "vote_average": 4},
],
}
mediainfo = MediaInfo(
tmdb_id=200,
type=MediaType.MOVIE,
original_language="en",
)
images = scraper.get_metadata_img(mediainfo)
scraper._meta_tmdb.get_movie_images.assert_called_once_with(
200,
original_language="en",
)
self.assertIn("poster.jpg", images)
self.assertIn("backdrop.jpg", images)
def test_douban_prepare_search_names_deduplicates_simplified_name(self):
"""豆瓣候选名称应保留顺序,并去掉繁简转换后的重复项。"""
meta = MetaBase("流浪地球")
+94 -4
View File
@@ -15,11 +15,20 @@ from app.core.metainfo import MetaInfo
from app.schemas.types import EventType, MediaType, ScrapingTarget, ScrapingMetadata, ScrapingPolicy
def reset_media_chain_singleton():
"""清理 MediaChain 单例,避免测试间复用被 mock 的实例。"""
MediaChain._instances.pop((MediaChain, (), frozenset()), None)
class TestMediaScrapingPaths(unittest.TestCase):
def setUp(self):
reset_media_chain_singleton()
self.media_chain = MediaChain()
self.media_chain.storagechain = MagicMock()
def tearDown(self):
reset_media_chain_singleton()
def test_movie_file_nfo_path(self):
fileitem = schemas.FileItem(path="/movies/avatar.mkv", name="avatar.mkv", type="file", storage="local")
parent_item = schemas.FileItem(path="/movies", name="movies", type="dir", storage="local")
@@ -75,6 +84,25 @@ class TestMediaScrapingPaths(unittest.TestCase):
self.assertEqual(target_item, fileitem)
self.assertEqual(target_path, Path("/tv/Show/Season 1/poster.jpg"))
def test_season_dir_poster_paths_include_root_and_season_dir(self):
"""季海报应同时写剧集根目录和季目录,兼容不同媒体库。"""
parent_item = schemas.FileItem(path="/tv/Show", name="Show", type="dir", storage="local")
fileitem = schemas.FileItem(path="/tv/Show/Season 1", name="Season 1", type="dir", storage="local")
targets = self.media_chain._get_target_fileitems_and_paths(
current_fileitem=fileitem,
item_type=ScrapingTarget.SEASON,
metadata_type=ScrapingMetadata.POSTER,
filename_hint="season01-poster.jpg",
parent_fileitem=parent_item,
)
self.assertEqual(
targets,
[
(parent_item, Path("/tv/Show/season01-poster.jpg")),
(fileitem, Path("/tv/Show/Season 1/poster.jpg")),
],
)
def test_season_dir_specials_poster_path(self):
fileitem = schemas.FileItem(path="/tv/Show/Specials", name="Specials", type="dir", storage="local")
target_item, target_path = self.media_chain._get_target_fileitem_and_path(
@@ -86,6 +114,20 @@ class TestMediaScrapingPaths(unittest.TestCase):
self.assertEqual(target_item, fileitem)
self.assertEqual(target_path, Path("/tv/Show/Specials/poster.jpg"))
def test_movie_file_image_path_uses_parent_dir(self):
"""直接刮削电影文件时,图片应保存到父目录。"""
fileitem = schemas.FileItem(path="/movies/Avatar/Avatar.mkv", name="Avatar.mkv", type="file", storage="local")
parent_item = schemas.FileItem(path="/movies/Avatar", name="Avatar", type="dir", storage="local")
target_item, target_path = self.media_chain._get_target_fileitem_and_path(
current_fileitem=fileitem,
item_type=ScrapingTarget.MOVIE,
metadata_type=ScrapingMetadata.POSTER,
filename_hint="poster.jpg",
parent_fileitem=parent_item,
)
self.assertEqual(target_item, parent_item)
self.assertEqual(target_path, Path("/movies/Avatar/poster.jpg"))
def test_episode_file_nfo_path(self):
fileitem = schemas.FileItem(path="/tv/Show/Season 1/S01E01.mp4", name="S01E01.mp4", type="file", storage="local")
parent_item = schemas.FileItem(path="/tv/Show/Season 1", name="Season 1", type="dir", storage="local")
@@ -101,6 +143,7 @@ class TestMediaScrapingPaths(unittest.TestCase):
class TestMediaScrapingNFO(unittest.TestCase):
def setUp(self):
reset_media_chain_singleton()
self.media_chain = MediaChain()
self.media_chain.storagechain = MagicMock()
self.media_chain.metadata_nfo = MagicMock(return_value="<nfo></nfo>")
@@ -111,6 +154,9 @@ class TestMediaScrapingNFO(unittest.TestCase):
self.meta = MetaInfo("Avatar (2009)")
self.mediainfo = MediaInfo()
def tearDown(self):
reset_media_chain_singleton()
def test_scrape_nfo_off(self):
self.media_chain.scraping_policies.option.return_value = ScrapingOption("movie", "nfo", ScrapingPolicy.SKIP)
self.media_chain._scrape_nfo_generic(self.fileitem, self.meta, self.mediainfo, ScrapingTarget.MOVIE)
@@ -147,6 +193,7 @@ class TestMediaScrapingNFO(unittest.TestCase):
class TestMediaScrapingImages(unittest.TestCase):
def setUp(self):
reset_media_chain_singleton()
self.media_chain = MediaChain()
self.original_download = self.media_chain._download_and_save_image
self.media_chain.storagechain = MagicMock()
@@ -156,6 +203,7 @@ class TestMediaScrapingImages(unittest.TestCase):
def tearDown(self):
self.media_chain._download_and_save_image = self.original_download
reset_media_chain_singleton()
def test_scrape_images_mapping(self):
fileitem = schemas.FileItem(path="/movies/Avatar", name="Avatar", type="dir", storage="local")
@@ -191,9 +239,43 @@ class TestMediaScrapingImages(unittest.TestCase):
self.media_chain._scrape_images_generic(fileitem, mediainfo, ScrapingTarget.SEASON, season_number=1)
calls = self.media_chain._download_and_save_image.call_args_list
self.assertEqual(len(calls), 1)
self.assertEqual(calls[0].kwargs["url"], "http://season01")
self.assertEqual(calls[0].kwargs["path"], Path("/tv/Show/Season 1/poster.jpg"))
self.assertEqual(len(calls), 2)
self.assertTrue(all(call.kwargs["url"] == "http://season01" for call in calls))
self.assertEqual(
[call.kwargs["path"] for call in calls],
[
Path("/tv/Show/season01-poster.jpg"),
Path("/tv/Show/Season 1/poster.jpg"),
],
)
def test_scrape_movie_file_images_when_initialized_directly(self):
"""直接初始化刮削电影文件时,应生成同级 poster/backdrop。"""
fileitem = schemas.FileItem(path="/movies/Avatar/Avatar.mkv", name="Avatar.mkv", type="file", storage="local")
parent_item = schemas.FileItem(path="/movies/Avatar", name="Avatar", type="dir", storage="local")
mediainfo = MediaInfo()
self.media_chain.metadata_img.return_value = {
"poster.jpg": "http://poster",
"backdrop.jpg": "http://backdrop",
}
self.media_chain.scraping_policies.option.return_value = ScrapingOption("movie", "poster", ScrapingPolicy.OVERWRITE)
self.media_chain.storagechain.get_file_item.return_value = None
self.media_chain._scrape_images_generic(
fileitem,
mediainfo,
ScrapingTarget.MOVIE,
parent_fileitem=parent_item,
)
paths = [call.kwargs["path"] for call in self.media_chain._download_and_save_image.call_args_list]
self.assertEqual(
paths,
[
Path("/movies/Avatar/poster.jpg"),
Path("/movies/Avatar/backdrop.jpg"),
],
)
def test_scrape_episode_thumb_image_path(self):
fileitem = schemas.FileItem(path="/tv/Show/Season 1/S01E01.mp4", name="S01E01.mp4", type="file", storage="local")
@@ -295,11 +377,15 @@ class TestMediaScrapingImages(unittest.TestCase):
class TestMediaScrapingTVDirectory(unittest.TestCase):
def setUp(self):
reset_media_chain_singleton()
self.media_chain = MediaChain()
self.media_chain.storagechain = MagicMock()
self.media_chain._scrape_nfo_generic = MagicMock()
self.media_chain._scrape_images_generic = MagicMock()
def tearDown(self):
reset_media_chain_singleton()
@patch("app.chain.media.settings")
def test_initialize_tv_directory_specials(self, mock_settings):
# mock specials directory recognition
@@ -366,9 +452,13 @@ class TestMediaScrapingTVDirectory(unittest.TestCase):
class TestMediaScrapeEvents(unittest.TestCase):
def setUp(self):
reset_media_chain_singleton()
self.media_chain = MediaChain()
self.media_chain.storagechain = MagicMock()
def tearDown(self):
reset_media_chain_singleton()
@patch("app.chain.media.MediaChain.scrape_metadata")
def test_scrape_metadata_event_file(
self, mock_scrape_metadata
@@ -394,7 +484,7 @@ class TestMediaScrapeEvents(unittest.TestCase):
mock_scrape_metadata.assert_called_once_with(
fileitem=fileitem,
mediainfo=mediainfo,
init_folder=False,
init_folder=True,
parent=parent_item,
overwrite=True
)