mirror of
https://github.com/jxxghp/MoviePilot.git
synced 2026-06-27 18:52:23 +08:00
359 lines
11 KiB
Python
359 lines
11 KiB
Python
from pathlib import Path
|
|
from types import SimpleNamespace
|
|
|
|
from app.chain.transfer import JobManager, TransferChain
|
|
from app.core.config import settings
|
|
from app.schemas import FileItem
|
|
from app.schemas.types import MediaType
|
|
|
|
|
|
class FakeMeta:
|
|
"""
|
|
构造整理链路所需的最小剧集元数据。
|
|
"""
|
|
|
|
def __init__(self, episode: int):
|
|
self.name = "Test Show"
|
|
self.title = f"Test Show S01E{episode:02d}"
|
|
self.year = "2026"
|
|
self.type = MediaType.TV
|
|
self.begin_season = 1
|
|
self.end_season = None
|
|
self.total_season = 1
|
|
self.begin_episode = episode
|
|
self.end_episode = None
|
|
self.total_episode = 1
|
|
self.part = None
|
|
|
|
@property
|
|
def episode_list(self) -> list[int]:
|
|
"""
|
|
返回当前文件覆盖的集数列表。
|
|
"""
|
|
return [self.begin_episode]
|
|
|
|
|
|
def make_transfer_chain() -> TransferChain:
|
|
"""
|
|
构造不启动后台线程的整理链实例。
|
|
"""
|
|
chain = object.__new__(TransferChain)
|
|
chain.jobview = JobManager()
|
|
chain._media_exts = settings.RMT_MEDIAEXT
|
|
chain._subtitle_exts = settings.RMT_SUBEXT
|
|
chain._audio_exts = settings.RMT_AUDIOEXT
|
|
chain._allowed_exts = (
|
|
chain._media_exts + chain._audio_exts + chain._subtitle_exts
|
|
)
|
|
chain._success_target_files = {}
|
|
chain._scrape_batches = {}
|
|
return chain
|
|
|
|
|
|
def make_fileitem(path: str) -> FileItem:
|
|
"""
|
|
根据路径构造文件项。
|
|
"""
|
|
file_path = Path(path)
|
|
return FileItem(
|
|
storage="local",
|
|
path=file_path.as_posix(),
|
|
type="file",
|
|
name=file_path.name,
|
|
basename=file_path.stem,
|
|
extension=file_path.suffix.lstrip("."),
|
|
size=1024,
|
|
)
|
|
|
|
|
|
def test_sync_extra_subtitle_inherits_matching_video_episode(monkeypatch):
|
|
"""
|
|
同名随片字幕应继承对应视频集数,避免字幕自身识别错误时全部落到第一集。
|
|
"""
|
|
chain = make_transfer_chain()
|
|
planned = []
|
|
main_ep1_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E01.2026.mkv"
|
|
)
|
|
main_ep2_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.mkv"
|
|
)
|
|
ep1_subtitle_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E01.2026.zh-cn.srt"
|
|
)
|
|
ep2_subtitle_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.zh-cn.srt"
|
|
)
|
|
parent_fileitem = FileItem(
|
|
storage="local",
|
|
path="/downloads/Test Show (2026)/",
|
|
type="dir",
|
|
name="Test Show (2026)",
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__get_trans_fileitems",
|
|
lambda fileitem, predicate: [
|
|
(main_ep1_fileitem, False),
|
|
(main_ep2_fileitem, False),
|
|
(ep1_subtitle_fileitem, False),
|
|
(ep2_subtitle_fileitem, False),
|
|
],
|
|
)
|
|
monkeypatch.setattr(chain, "_TransferChain__put_to_jobview", lambda task: True)
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__register_scrape_batch_task",
|
|
lambda task: None,
|
|
)
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__close_scrape_batch",
|
|
lambda batch_id: None,
|
|
)
|
|
|
|
def fake_handle_transfer(task, callback=None):
|
|
"""
|
|
记录实际创建的整理任务集数。
|
|
"""
|
|
planned.append((task.fileitem.path, task.meta.begin_episode))
|
|
return True, ""
|
|
|
|
def fake_meta_info_path(path, custom_words=None):
|
|
"""
|
|
模拟字幕文件自身会被误识别为第一集的场景。
|
|
"""
|
|
file_name = Path(path).name
|
|
if file_name.endswith(".mkv") and "S01E02" in file_name:
|
|
return FakeMeta(2)
|
|
return FakeMeta(1)
|
|
|
|
monkeypatch.setattr(chain, "_TransferChain__handle_transfer", fake_handle_transfer)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.TransferHistoryOper",
|
|
lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.DownloadHistoryOper",
|
|
lambda: SimpleNamespace(
|
|
get_by_hash=lambda download_hash: None,
|
|
get_file_by_fullpath=lambda fullpath: None,
|
|
get_files_by_savepath=lambda savepath: [],
|
|
get_by_path=lambda path: None,
|
|
),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.SystemConfigOper",
|
|
lambda: SimpleNamespace(get=lambda key: None),
|
|
)
|
|
monkeypatch.setattr("app.chain.transfer.MetaInfoPath", fake_meta_info_path)
|
|
|
|
state, errmsg = TransferChain.do_transfer(
|
|
chain,
|
|
fileitem=parent_fileitem,
|
|
background=False,
|
|
sync_extra_files=True,
|
|
)
|
|
|
|
assert state is True
|
|
assert errmsg == ""
|
|
assert planned == [
|
|
(main_ep1_fileitem.path, 1),
|
|
(ep1_subtitle_fileitem.path, 1),
|
|
(main_ep2_fileitem.path, 2),
|
|
(ep2_subtitle_fileitem.path, 2),
|
|
]
|
|
|
|
|
|
def test_single_subtitle_transfer_reuses_same_name_video_episode(monkeypatch):
|
|
"""
|
|
单独整理同名字幕时应复用主视频识别结果,不受 sync_extra_files 开关影响。
|
|
"""
|
|
chain = make_transfer_chain()
|
|
planned = []
|
|
main_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.mkv"
|
|
)
|
|
subtitle_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.zh-cn.srt"
|
|
)
|
|
parent_fileitem = FileItem(
|
|
storage="local",
|
|
path="/downloads/Test Show (2026)/",
|
|
type="dir",
|
|
name="Test Show (2026)",
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__get_trans_fileitems",
|
|
lambda fileitem, predicate: [(subtitle_fileitem, False)],
|
|
)
|
|
monkeypatch.setattr(chain, "_TransferChain__put_to_jobview", lambda task: True)
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__register_scrape_batch_task",
|
|
lambda task: None,
|
|
)
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__close_scrape_batch",
|
|
lambda batch_id: None,
|
|
)
|
|
|
|
def fake_handle_transfer(task, callback=None):
|
|
"""
|
|
记录单独字幕整理时使用的集数。
|
|
"""
|
|
planned.append((task.fileitem.path, task.meta.begin_episode))
|
|
return True, ""
|
|
|
|
def fake_meta_info_path(path, custom_words=None):
|
|
"""
|
|
模拟字幕自身会被误识别为第一集,主视频可正确识别为第二集。
|
|
"""
|
|
file_name = Path(path).name
|
|
if file_name.endswith(".mkv"):
|
|
return FakeMeta(2)
|
|
return FakeMeta(1)
|
|
|
|
monkeypatch.setattr(chain, "_TransferChain__handle_transfer", fake_handle_transfer)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.TransferHistoryOper",
|
|
lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.DownloadHistoryOper",
|
|
lambda: SimpleNamespace(
|
|
get_by_hash=lambda download_hash: None,
|
|
get_file_by_fullpath=lambda fullpath: None,
|
|
get_files_by_savepath=lambda savepath: [],
|
|
get_by_path=lambda path: None,
|
|
),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.SystemConfigOper",
|
|
lambda: SimpleNamespace(get=lambda key: None),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.StorageChain",
|
|
lambda: SimpleNamespace(
|
|
get_parent_item=lambda fileitem: parent_fileitem,
|
|
list_files=lambda fileitem, recursion=False: [
|
|
main_fileitem,
|
|
subtitle_fileitem,
|
|
],
|
|
),
|
|
)
|
|
monkeypatch.setattr("app.chain.transfer.MetaInfoPath", fake_meta_info_path)
|
|
|
|
state, errmsg = TransferChain.do_transfer(
|
|
chain,
|
|
fileitem=subtitle_fileitem,
|
|
background=False,
|
|
sync_extra_files=False,
|
|
)
|
|
|
|
assert state is True
|
|
assert errmsg == ""
|
|
assert planned == [(subtitle_fileitem.path, 2)]
|
|
|
|
|
|
def test_single_video_transfer_lists_parent_once_for_same_name_extra(monkeypatch):
|
|
"""
|
|
单文件视频整理只读取一次父目录,并只附带同名附加文件。
|
|
"""
|
|
chain = make_transfer_chain()
|
|
planned = []
|
|
list_files_calls = []
|
|
main_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.mkv"
|
|
)
|
|
subtitle_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Test.Show.S01E02.2026.zh-cn.srt"
|
|
)
|
|
other_subtitle_fileitem = make_fileitem(
|
|
"/downloads/Test Show (2026)/Other.Show.S01E02.2026.zh-cn.srt"
|
|
)
|
|
parent_fileitem = FileItem(
|
|
storage="local",
|
|
path="/downloads/Test Show (2026)/",
|
|
type="dir",
|
|
name="Test Show (2026)",
|
|
)
|
|
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__get_trans_fileitems",
|
|
lambda fileitem, predicate: [(main_fileitem, False)],
|
|
)
|
|
monkeypatch.setattr(chain, "_TransferChain__put_to_jobview", lambda task: True)
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__register_scrape_batch_task",
|
|
lambda task: None,
|
|
)
|
|
monkeypatch.setattr(
|
|
chain,
|
|
"_TransferChain__close_scrape_batch",
|
|
lambda batch_id: None,
|
|
)
|
|
|
|
def fake_handle_transfer(task, callback=None):
|
|
"""
|
|
记录单视频整理时实际附带的文件。
|
|
"""
|
|
planned.append(task.fileitem.path)
|
|
return True, ""
|
|
|
|
def fake_list_files(fileitem, recursion=False):
|
|
"""
|
|
记录父目录读取次数。
|
|
"""
|
|
list_files_calls.append((fileitem.path, recursion))
|
|
return [
|
|
main_fileitem,
|
|
subtitle_fileitem,
|
|
other_subtitle_fileitem,
|
|
]
|
|
|
|
monkeypatch.setattr(chain, "_TransferChain__handle_transfer", fake_handle_transfer)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.TransferHistoryOper",
|
|
lambda: SimpleNamespace(get_by_src=lambda src, storage=None: None),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.DownloadHistoryOper",
|
|
lambda: SimpleNamespace(
|
|
get_by_hash=lambda download_hash: None,
|
|
get_file_by_fullpath=lambda fullpath: None,
|
|
get_files_by_savepath=lambda savepath: [],
|
|
get_by_path=lambda path: None,
|
|
),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.SystemConfigOper",
|
|
lambda: SimpleNamespace(get=lambda key: None),
|
|
)
|
|
monkeypatch.setattr(
|
|
"app.chain.transfer.StorageChain",
|
|
lambda: SimpleNamespace(
|
|
get_parent_item=lambda fileitem: parent_fileitem,
|
|
list_files=fake_list_files,
|
|
),
|
|
)
|
|
monkeypatch.setattr("app.chain.transfer.MetaInfoPath", lambda path, custom_words=None: FakeMeta(2))
|
|
|
|
state, errmsg = TransferChain.do_transfer(
|
|
chain,
|
|
fileitem=main_fileitem,
|
|
background=False,
|
|
sync_extra_files=False,
|
|
)
|
|
|
|
assert state is True
|
|
assert errmsg == ""
|
|
assert planned == [main_fileitem.path, subtitle_fileitem.path]
|
|
assert list_files_calls == [(parent_fileitem.path, False)]
|