mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-09-07 08:26:53 +08:00
fix: preserve tv bluray disc folders (#5788)
This commit is contained in:
@@ -34,6 +34,49 @@ class TransHandler:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def __normalize_disc_folder_name(value: Optional[str]) -> Optional[str]:
|
||||||
|
"""
|
||||||
|
从 Disc/Disk/DVD/CD 标识中提取盘号并统一为 Disc N。
|
||||||
|
"""
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
match = re.search(
|
||||||
|
r"(?:disc|disk|dvd|cd)[\s._-]*0*(\d{1,3})",
|
||||||
|
value,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if not match:
|
||||||
|
return None
|
||||||
|
return f"Disc {int(match.group(1))}"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def __get_tv_bluray_dir_path(
|
||||||
|
cls,
|
||||||
|
rendered_path: Path,
|
||||||
|
source_item: FileItem,
|
||||||
|
meta: MetaBase,
|
||||||
|
) -> Path:
|
||||||
|
"""
|
||||||
|
电视剧原盘目录没有单集文件名,保留季目录并追加盘片目录。
|
||||||
|
"""
|
||||||
|
disc_folder = cls.__normalize_disc_folder_name(getattr(meta, "part", None))
|
||||||
|
if not disc_folder and source_item:
|
||||||
|
source_name = source_item.name or Path(source_item.path).name
|
||||||
|
disc_folder = cls.__normalize_disc_folder_name(source_name)
|
||||||
|
if not disc_folder:
|
||||||
|
match = re.search(
|
||||||
|
r"(?:^|[^A-Za-z0-9])S\d{1,3}D0*(\d{1,3})(?:[^A-Za-z0-9]|$)",
|
||||||
|
source_name,
|
||||||
|
re.IGNORECASE,
|
||||||
|
)
|
||||||
|
if match:
|
||||||
|
disc_folder = f"Disc {int(match.group(1))}"
|
||||||
|
if not disc_folder:
|
||||||
|
disc_folder = source_name
|
||||||
|
|
||||||
|
return rendered_path.parent / (disc_folder or "Disc")
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __update_result(result: TransferInfo, **kwargs):
|
def __update_result(result: TransferInfo, **kwargs):
|
||||||
"""
|
"""
|
||||||
@@ -156,7 +199,7 @@ class TransHandler:
|
|||||||
if fileitem.type == "dir":
|
if fileitem.type == "dir":
|
||||||
# 整理整个目录,一般为蓝光原盘
|
# 整理整个目录,一般为蓝光原盘
|
||||||
if need_rename:
|
if need_rename:
|
||||||
new_path = self.get_rename_path(
|
rendered_path = self.get_rename_path(
|
||||||
path=target_path,
|
path=target_path,
|
||||||
template_string=rename_format,
|
template_string=rename_format,
|
||||||
rename_dict=self.get_naming_dict(
|
rename_dict=self.get_naming_dict(
|
||||||
@@ -165,9 +208,16 @@ class TransHandler:
|
|||||||
source_path=fileitem.path,
|
source_path=fileitem.path,
|
||||||
source_item=fileitem,
|
source_item=fileitem,
|
||||||
)
|
)
|
||||||
new_path = DirectoryHelper.get_media_root_path(
|
if mediainfo.type == MediaType.TV:
|
||||||
rename_format, rename_path=new_path
|
new_path = self.__get_tv_bluray_dir_path(
|
||||||
)
|
rendered_path=rendered_path,
|
||||||
|
source_item=fileitem,
|
||||||
|
meta=in_meta,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
new_path = DirectoryHelper.get_media_root_path(
|
||||||
|
rename_format, rename_path=rendered_path
|
||||||
|
)
|
||||||
if not new_path:
|
if not new_path:
|
||||||
self.__update_result(
|
self.__update_result(
|
||||||
result=result,
|
result=result,
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
from unittest.mock import patch
|
from unittest.mock import patch
|
||||||
|
|
||||||
|
sys.modules.setdefault("app.helper.sites", ModuleType("app.helper.sites"))
|
||||||
|
setattr(sys.modules["app.helper.sites"], "SitesHelper", object)
|
||||||
|
|
||||||
from app import schemas
|
from app import schemas
|
||||||
from app.chain.media import MediaChain
|
from app.chain.media import MediaChain
|
||||||
from app.chain.storage import StorageChain
|
from app.chain.storage import StorageChain
|
||||||
@@ -174,7 +179,7 @@ class BluRayTest(TestCase):
|
|||||||
# 刮削电影目录
|
# 刮削电影目录
|
||||||
__test_scrape_metadata("/FOLDER", excepted_nfo_count=2)
|
__test_scrape_metadata("/FOLDER", excepted_nfo_count=2)
|
||||||
|
|
||||||
@patch("app.chain.ChainBase.metadata_img", return_value=None) # 避免获取图片
|
@patch("app.chain.media.MediaChain.metadata_img", return_value=None) # 避免获取图片
|
||||||
@patch("app.chain.ChainBase.__init__", return_value=None) # 避免不必要的模块初始化
|
@patch("app.chain.ChainBase.__init__", return_value=None) # 避免不必要的模块初始化
|
||||||
@patch("app.db.transferhistory_oper.TransferHistoryOper.get_by_src")
|
@patch("app.db.transferhistory_oper.TransferHistoryOper.get_by_src")
|
||||||
@patch("app.chain.storage.StorageChain.list_files")
|
@patch("app.chain.storage.StorageChain.list_files")
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import sys
|
||||||
|
from types import ModuleType
|
||||||
|
|
||||||
|
sys.modules.setdefault("app.helper.sites", ModuleType("app.helper.sites"))
|
||||||
|
setattr(sys.modules["app.helper.sites"], "SitesHelper", object)
|
||||||
|
|
||||||
from app.core.context import MediaInfo
|
from app.core.context import MediaInfo
|
||||||
from app.core.metainfo import MetaInfoPath
|
from app.core.meta import MetaBase
|
||||||
from app.modules.filemanager import FileManagerModule
|
from app.modules.filemanager import FileManagerModule
|
||||||
from app.schemas import FileItem, TransferDirectoryConf
|
from app.schemas import FileItem, TransferDirectoryConf
|
||||||
from app.schemas.types import MediaType
|
from app.schemas.types import MediaType
|
||||||
@@ -31,6 +36,23 @@ class GuardedStorage:
|
|||||||
raise AssertionError("预览不应删除文件")
|
raise AssertionError("预览不应删除文件")
|
||||||
|
|
||||||
|
|
||||||
|
def _build_meta(
|
||||||
|
title: str,
|
||||||
|
media_type: MediaType = MediaType.TV,
|
||||||
|
season: int = 1,
|
||||||
|
episode: int = 1,
|
||||||
|
part: str = None,
|
||||||
|
):
|
||||||
|
meta = MetaBase(title)
|
||||||
|
meta.type = media_type
|
||||||
|
meta.name = "Breaking Bad" if media_type == MediaType.TV else "Test Movie"
|
||||||
|
meta.year = "2008" if media_type == MediaType.TV else "2026"
|
||||||
|
meta.begin_season = season
|
||||||
|
meta.begin_episode = episode
|
||||||
|
meta.part = part
|
||||||
|
return meta
|
||||||
|
|
||||||
|
|
||||||
def test_cloud_storage_preview_only_calculates_target_path():
|
def test_cloud_storage_preview_only_calculates_target_path():
|
||||||
fileitem = FileItem(
|
fileitem = FileItem(
|
||||||
storage="alist",
|
storage="alist",
|
||||||
@@ -41,7 +63,7 @@ def test_cloud_storage_preview_only_calculates_target_path():
|
|||||||
extension="mkv",
|
extension="mkv",
|
||||||
size=1024,
|
size=1024,
|
||||||
)
|
)
|
||||||
meta = MetaInfoPath(Path(fileitem.path))
|
meta = _build_meta("Test.Show.S01E01.mkv", season=1, episode=1)
|
||||||
mediainfo = MediaInfo(
|
mediainfo = MediaInfo(
|
||||||
type=MediaType.TV,
|
type=MediaType.TV,
|
||||||
title="Test Show",
|
title="Test Show",
|
||||||
@@ -94,7 +116,7 @@ def test_local_storage_preview_skips_target_conflict_checks(tmp_path):
|
|||||||
extension="mkv",
|
extension="mkv",
|
||||||
size=source_file.stat().st_size,
|
size=source_file.stat().st_size,
|
||||||
)
|
)
|
||||||
meta = MetaInfoPath(source_file)
|
meta = _build_meta(source_file.name, season=1, episode=2)
|
||||||
mediainfo = MediaInfo(
|
mediainfo = MediaInfo(
|
||||||
type=MediaType.TV,
|
type=MediaType.TV,
|
||||||
title="Test Show",
|
title="Test Show",
|
||||||
@@ -131,3 +153,99 @@ def test_local_storage_preview_skips_target_conflict_checks(tmp_path):
|
|||||||
assert transferinfo.target_item.path.endswith(".mkv")
|
assert transferinfo.target_item.path.endswith(".mkv")
|
||||||
assert transferinfo.file_list == [fileitem.path]
|
assert transferinfo.file_list == [fileitem.path]
|
||||||
assert transferinfo.file_list_new == [transferinfo.target_item.path]
|
assert transferinfo.file_list_new == [transferinfo.target_item.path]
|
||||||
|
|
||||||
|
|
||||||
|
def _build_bluray_dir_preview(
|
||||||
|
source_name: str,
|
||||||
|
media_type: MediaType,
|
||||||
|
season: int = None,
|
||||||
|
part: str = None,
|
||||||
|
):
|
||||||
|
fileitem = FileItem(
|
||||||
|
storage="alist",
|
||||||
|
path=f"/downloads/{source_name}",
|
||||||
|
type="dir",
|
||||||
|
name=source_name,
|
||||||
|
basename=source_name,
|
||||||
|
)
|
||||||
|
meta = _build_meta(
|
||||||
|
fileitem.name,
|
||||||
|
media_type=media_type,
|
||||||
|
season=season,
|
||||||
|
episode=None,
|
||||||
|
part=part,
|
||||||
|
)
|
||||||
|
mediainfo = MediaInfo(
|
||||||
|
type=media_type,
|
||||||
|
title="Breaking Bad" if media_type == MediaType.TV else "Test Movie",
|
||||||
|
year="2008" if media_type == MediaType.TV else "2026",
|
||||||
|
tmdb_id=1396,
|
||||||
|
)
|
||||||
|
target_directory = TransferDirectoryConf(
|
||||||
|
name="cloud-library",
|
||||||
|
transfer_type="copy",
|
||||||
|
overwrite_mode="never",
|
||||||
|
library_path="/library",
|
||||||
|
library_storage="alist",
|
||||||
|
renaming=True,
|
||||||
|
scraping=True,
|
||||||
|
notify=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
return FileManagerModule().transfer(
|
||||||
|
fileitem=fileitem,
|
||||||
|
meta=meta,
|
||||||
|
mediainfo=mediainfo,
|
||||||
|
target_directory=target_directory,
|
||||||
|
source_oper=GuardedStorage(),
|
||||||
|
target_oper=GuardedStorage(),
|
||||||
|
preview=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_tv_bluray_dir_preview_preserves_disk_folder_from_meta_part():
|
||||||
|
transferinfo = _build_bluray_dir_preview(
|
||||||
|
source_name="Breaking Bad Season 2 - Disk 1",
|
||||||
|
media_type=MediaType.TV,
|
||||||
|
season=2,
|
||||||
|
part="Disk1",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert transferinfo.success is True
|
||||||
|
assert transferinfo.target_item.path == "/library/Breaking Bad (2008)/Season 2/Disc 1"
|
||||||
|
assert transferinfo.target_diritem.path == transferinfo.target_item.path
|
||||||
|
assert transferinfo.file_list_new == [transferinfo.target_item.path]
|
||||||
|
|
||||||
|
|
||||||
|
def test_tv_bluray_dir_preview_preserves_disc_folder_from_source_name():
|
||||||
|
transferinfo = _build_bluray_dir_preview(
|
||||||
|
source_name="BREAKING_BAD_S01D01",
|
||||||
|
media_type=MediaType.TV,
|
||||||
|
season=1,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert transferinfo.success is True
|
||||||
|
assert transferinfo.target_item.path == "/library/Breaking Bad (2008)/Season 1/Disc 1"
|
||||||
|
|
||||||
|
|
||||||
|
def test_tv_bluray_dir_preview_falls_back_to_source_name_without_disc_number():
|
||||||
|
source_name = "Breaking Bad Season 3 Bonus Disc"
|
||||||
|
transferinfo = _build_bluray_dir_preview(
|
||||||
|
source_name=source_name,
|
||||||
|
media_type=MediaType.TV,
|
||||||
|
season=3,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert transferinfo.success is True
|
||||||
|
assert transferinfo.target_item.path == f"/library/Breaking Bad (2008)/Season 3/{source_name}"
|
||||||
|
|
||||||
|
|
||||||
|
def test_movie_bluray_dir_preview_keeps_movie_root_layout():
|
||||||
|
transferinfo = _build_bluray_dir_preview(
|
||||||
|
source_name="Test Movie Disc 1",
|
||||||
|
media_type=MediaType.MOVIE,
|
||||||
|
part="Disc1",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert transferinfo.success is True
|
||||||
|
assert transferinfo.target_item.path == "/library/Test Movie (2026)"
|
||||||
|
|||||||
Reference in New Issue
Block a user