From 7e3be3325ab22c21fadab7f48eddc5c8d0db6f5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=AF=E5=A4=A7=E4=BE=A0?= Date: Sat, 31 May 2025 01:52:31 +0800 Subject: [PATCH 1/3] =?UTF-8?q?fix=20#4294=20=E6=9B=B4=E6=96=B0=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/cases/meta.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/cases/meta.py b/tests/cases/meta.py index fd7ef153..79ef9cb0 100644 --- a/tests/cases/meta.py +++ b/tests/cases/meta.py @@ -139,7 +139,7 @@ meta_cases = [{ "episode": "E06", "restype": "WEB-DL", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "AAC" } }, { @@ -155,7 +155,7 @@ meta_cases = [{ "episode": "E02", "restype": "WEB-DL", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "AAC" } }, { @@ -235,7 +235,7 @@ meta_cases = [{ "episode": "", "restype": "BluRay", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "Atmos TrueHD 7.1" } }, { @@ -363,7 +363,7 @@ meta_cases = [{ "episode": "E01", "restype": "UHD BluRay", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "" } }, { @@ -523,7 +523,7 @@ meta_cases = [{ "episode": "E02", "restype": "BDRIP", "pix": "1080p", - "video_codec": "X265", + "video_codec": "x265", "audio_codec": "FLAC" } }, { @@ -571,7 +571,7 @@ meta_cases = [{ "episode": "E05", "restype": "WEB-DL", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "AAC" } }, { @@ -667,7 +667,7 @@ meta_cases = [{ "episode": "", "restype": "UHD BluRay DoVi", "pix": "1080p", - "video_codec": "X265", + "video_codec": "x265", "audio_codec": "DD+ 7.1" } }, { @@ -683,7 +683,7 @@ meta_cases = [{ "episode": "E16", "restype": "WEB-DL", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "AAC" } }, { @@ -763,7 +763,7 @@ meta_cases = [{ "episode": "", "restype": "BluRay", "pix": "1080p", - "video_codec": "X265 10bit", + "video_codec": "x265 10bit", "audio_codec": "DTS-HD MA 5.1" } }, { @@ -779,7 +779,7 @@ meta_cases = [{ "episode": "", "restype": "WEB-DL", "pix": "1080p", - "video_codec": "X265", + "video_codec": "x265", "audio_codec": "" } }, { @@ -827,7 +827,7 @@ meta_cases = [{ "episode": "", "restype": "BluRay", "pix": "4k", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "DTS" } }, { @@ -859,7 +859,7 @@ meta_cases = [{ "episode": "E01", "restype": "WEB-DL", "pix": "1080p", - "video_codec": "X265", + "video_codec": "x265", "audio_codec": "" } }, { @@ -923,7 +923,7 @@ meta_cases = [{ "episode": "E06", "restype": "WEBRip", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "DD 5.1" } }, { @@ -939,7 +939,7 @@ meta_cases = [{ "episode": "E05", "restype": "WEBRip", "pix": "1080p", - "video_codec": "X264", + "video_codec": "x264", "audio_codec": "DD 5.1" } }, { From b74c7531d9555ddda3e7a32fd95f7390a45b198d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=AF=E5=A4=A7=E4=BE=A0?= Date: Sat, 31 May 2025 02:11:24 +0800 Subject: [PATCH 2/3] =?UTF-8?q?fix=20#4371=20=E9=80=92=E5=BD=92=E5=88=A4?= =?UTF-8?q?=E6=96=AD=E8=93=9D=E5=85=89=E7=9B=AE=E5=BD=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/chain/transfer.py | 30 ++-- tests/run.py | 4 + tests/test_bluray.py | 336 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 356 insertions(+), 14 deletions(-) create mode 100644 tests/test_bluray.py diff --git a/app/chain/transfer.py b/app/chain/transfer.py index 118b839f..e78e3622 100755 --- a/app/chain/transfer.py +++ b/app/chain/transfer.py @@ -866,19 +866,22 @@ class TransferChain(ChainBase, metaclass=Singleton): logger.info("所有下载器中下载完成的文件已整理完成") return True - def __get_trans_fileitems(self, fileitem: FileItem) -> List[Tuple[FileItem, bool]]: + def __get_trans_fileitems( + self, fileitem: FileItem, depth: int = 1 + ) -> List[Tuple[FileItem, bool]]: """ 获取整理目录或文件列表 + :param fileitem: 文件项 + :param depth: 递归深度,默认为1 """ - def __is_bluray_dir(_fileitem: FileItem) -> bool: + def __contains_bluray_sub(_fileitems: List[FileItem]) -> bool: """ - 判断是不是蓝光目录 + 判断是否包含蓝光子目录 """ - subs = self.storagechain.list_files(_fileitem) - if subs: - for sub in subs: + if _fileitems: + for sub in _fileitems: if sub.type == "dir" and sub.name in ["BDMV", "CERTIFICATE"]: return True return False @@ -913,25 +916,22 @@ class TransferChain(ChainBase, metaclass=Singleton): return [(fileitem, False)] # 蓝光原盘根目录 - if __is_bluray_dir(fileitem): + sub_items = self.storagechain.list_files(fileitem) or [] + if __contains_bluray_sub(sub_items): return [(fileitem, True)] # 需要整理的文件项列表 trans_items = [] # 先检查当前目录的下级目录,以支持合集的情况 - for sub_dir in self.storagechain.list_files(fileitem): + for sub_dir in sub_items if depth >= 1 else []: if sub_dir.type == "dir": - if __is_bluray_dir(sub_dir): - trans_items.append((sub_dir, True)) - else: - trans_items.append((sub_dir, False)) + trans_items.extend(self.__get_trans_fileitems(sub_dir, depth=depth - 1)) if not trans_items: # 没有有效子目录,直接整理当前目录 trans_items.append((fileitem, False)) else: # 有子目录时,把当前目录的文件添加到整理任务中 - sub_items = self.storagechain.list_files(fileitem) if sub_items: trans_items.extend([(f, False) for f in sub_items if f.type == "file"]) @@ -997,7 +997,9 @@ class TransferChain(ChainBase, metaclass=Singleton): # 汇总错误信息 err_msgs: List[str] = [] # 待整理目录或文件项 - trans_items = self.__get_trans_fileitems(fileitem) + trans_items = self.__get_trans_fileitems( + fileitem, depth=2 # 为解决 issue#4371 深度至少需要>=2 + ) # 待整理的文件列表 file_items: List[Tuple[FileItem, bool]] = [] diff --git a/tests/run.py b/tests/run.py index 5aa65968..6c98e14a 100644 --- a/tests/run.py +++ b/tests/run.py @@ -1,5 +1,6 @@ import unittest +from tests.test_bluray import BluRayTest from tests.test_metainfo import MetaInfoTest if __name__ == '__main__': @@ -9,6 +10,9 @@ if __name__ == '__main__': suite.addTest(MetaInfoTest('test_metainfo')) suite.addTest(MetaInfoTest('test_emby_format_ids')) + # 测试蓝光目录识别 + suite.addTest(BluRayTest()) + # 运行测试 runner = unittest.TextTestRunner() runner.run(suite) diff --git a/tests/test_bluray.py b/tests/test_bluray.py new file mode 100644 index 00000000..899e7f1b --- /dev/null +++ b/tests/test_bluray.py @@ -0,0 +1,336 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +from pathlib import Path +from typing import List, Optional +from unittest import TestCase + +from app import schemas +from app.chain.storage import StorageChain +from app.chain.transfer import TransferChain +from app.db.models.transferhistory import TransferHistory +from app.db.systemconfig_oper import SystemConfigOper +from app.db.transferhistory_oper import TransferHistoryOper + + +class MockTransferHistoryOper(TransferHistoryOper): + def __init__(self): + # pylint: disable=super-init-not-called + self.history = [] + + def get_by_src(self, src, storage=None): + self.history.append(src) + return TransferHistory() + + +class MockStorage(StorageChain): + def __init__(self, files: list): + # pylint: disable=super-init-not-called + 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[dict]): + parent.children = [] + for item in files: + children = item.get("children") + sep = "" if parent.path.endswith("/") else "/" + name: str = item["name"] + file_item = schemas.FileItem( + path=f"{parent.path}{sep}{name}", + name=name, + extension=Path(name).suffix[1:], + basename=Path(name).stem, + type="file" if children is None else "dir", + size=item.get("size", 0), + ) + parent.children.append(file_item) + self.__all[file_item.path] = file_item + if children is not None: + __build_child(file_item, children) + + __build_child(self.__root, files) + + def list_files( + self, fileitem: schemas.FileItem, recursion: bool = False + ) -> Optional[List[schemas.FileItem]]: + 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_file_item(self, storage: str, path: Path) -> Optional[schemas.FileItem]: + """ + 根据路径获取文件项 + """ + path_posix = path.as_posix() + return self.__all.get(path_posix) + + +class MockTransferChain(TransferChain): + def __init__(self, storage: MockStorage): + # pylint: disable=super-init-not-called + + self.transferhis = MockTransferHistoryOper() + self.systemconfig = SystemConfigOper() + self.storagechain = storage + + def test(self, path: str): + self.transferhis.history.clear() + self.do_transfer( + force=False, + background=False, + fileitem=self.storagechain.get_file_item(None, Path(path)), + ) + return self.transferhis.history + + +class BluRayTest(TestCase): + def __init__(self, methodName="test"): + super().__init__(methodName) + + def setUp(self) -> None: + pass + + def tearDown(self) -> None: + pass + + def test(self): + 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, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "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, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "name": "Digimon (2199)", + "children": [ + { + "name": "Digimon.2199.mp4", + "size": 104857600, + }, + ], + }, + ], + }, + { + "name": "Pokemon (2016)", + "children": [ + { + "name": "BDMV", + "children": [ + { + "name": "STREAM", + "children": [ + { + "name": "00000.m2ts", + "size": 104857600, + }, + { + "name": "00001.m2ts", + "size": 104857600, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "name": "Pokemon (2021)", + "children": [ + { + "name": "BDMV", + "children": [ + { + "name": "STREAM", + "children": [ + { + "name": "00000.m2ts", + "size": 104857600, + }, + { + "name": "00001.m2ts", + "size": 104857600, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "name": "Pokemon (2028)", + "children": [ + { + "name": "Pokemon.2028.mkv", + "size": 104857600, + }, + { + "name": "Pokemon.2028.hdr.mkv.!qB", + "size": 104857600, + }, + ], + }, + { + "name": "Pokemon.2029.mp4", + "size": 104857600, + }, + { + "name": "Pokemon (2030)", + "children": [ + { + "name": "S", + "size": 104857600, + }, + ], + }, + ], + }, + ] + transfer = MockTransferChain(MockStorage(files)) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2055)", + "/FOLDER/Digimon/Digimon (2099)", + "/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4", + ], + transfer.test("/FOLDER/Digimon"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2055)", + ], + transfer.test("/FOLDER/Digimon/Digimon (2055)"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2055)", + ], + transfer.test("/FOLDER/Digimon/Digimon (2055)/BDMV"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2055)", + ], + transfer.test("/FOLDER/Digimon/Digimon (2055)/BDMV/STREAM"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2055)", + ], + transfer.test("/FOLDER/Digimon/Digimon (2055)/BDMV/STREAM/00001.m2ts"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4", + ], + transfer.test("/FOLDER/Digimon/Digimon (2199)"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4", + ], + transfer.test("/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4"), + ) + + self.assertEqual( + [ + "/FOLDER/Pokemon.2029.mp4", + ], + transfer.test("/FOLDER/Pokemon.2029.mp4"), + ) + + self.assertEqual( + [ + "/FOLDER/Digimon/Digimon (2055)", + "/FOLDER/Digimon/Digimon (2099)", + "/FOLDER/Digimon/Digimon (2199)/Digimon.2199.mp4", + "/FOLDER/Pokemon (2016)", + "/FOLDER/Pokemon (2021)", + "/FOLDER/Pokemon (2028)/Pokemon.2028.mkv", + "/FOLDER/Pokemon.2029.mp4", + ], + transfer.test("/FOLDER"), + ) From fc1ade32d7d4f7c63fe157387cd97af89a56eeda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=99=AF=E5=A4=A7=E4=BE=A0?= Date: Sat, 31 May 2025 10:56:57 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E8=93=9D=E5=85=89?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/cases/files.py | 161 ++++++++++++++++++++++++++++++++++++++++++ tests/test_bluray.py | 162 +------------------------------------------ 2 files changed, 163 insertions(+), 160 deletions(-) create mode 100644 tests/cases/files.py diff --git a/tests/cases/files.py b/tests/cases/files.py new file mode 100644 index 00000000..5781898e --- /dev/null +++ b/tests/cases/files.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python +# -*- coding:utf-8 -*- +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, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "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, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "name": "Digimon (2199)", + "children": [ + { + "name": "Digimon.2199.mp4", + "size": 104857600, + }, + ], + }, + ], + }, + { + "name": "Pokemon (2016)", + "children": [ + { + "name": "BDMV", + "children": [ + { + "name": "STREAM", + "children": [ + { + "name": "00000.m2ts", + "size": 104857600, + }, + { + "name": "00001.m2ts", + "size": 104857600, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "name": "Pokemon (2021)", + "children": [ + { + "name": "BDMV", + "children": [ + { + "name": "STREAM", + "children": [ + { + "name": "00000.m2ts", + "size": 104857600, + }, + { + "name": "00001.m2ts", + "size": 104857600, + }, + ], + }, + ], + }, + { + "name": "CERTIFICATE", + "children": [], + }, + ], + }, + { + "name": "Pokemon (2028)", + "children": [ + { + "name": "Pokemon.2028.mkv", + "size": 104857600, + }, + { + "name": "Pokemon.2028.hdr.mkv.!qB", + "size": 104857600, + }, + ], + }, + { + "name": "Pokemon.2029.mp4", + "size": 104857600, + }, + { + "name": "Pokemon (2030)", + "children": [ + { + "name": "S", + "size": 104857600, + }, + ], + }, + ], + }, +] diff --git a/tests/test_bluray.py b/tests/test_bluray.py index 899e7f1b..5d571382 100644 --- a/tests/test_bluray.py +++ b/tests/test_bluray.py @@ -10,6 +10,7 @@ from app.chain.transfer import TransferChain from app.db.models.transferhistory import TransferHistory from app.db.systemconfig_oper import SystemConfigOper from app.db.transferhistory_oper import TransferHistoryOper +from tests.cases.files import bluray_files class MockTransferHistoryOper(TransferHistoryOper): @@ -103,166 +104,7 @@ class BluRayTest(TestCase): pass def test(self): - 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, - }, - ], - }, - ], - }, - { - "name": "CERTIFICATE", - "children": [], - }, - ], - }, - { - "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, - }, - ], - }, - ], - }, - { - "name": "CERTIFICATE", - "children": [], - }, - ], - }, - { - "name": "Digimon (2199)", - "children": [ - { - "name": "Digimon.2199.mp4", - "size": 104857600, - }, - ], - }, - ], - }, - { - "name": "Pokemon (2016)", - "children": [ - { - "name": "BDMV", - "children": [ - { - "name": "STREAM", - "children": [ - { - "name": "00000.m2ts", - "size": 104857600, - }, - { - "name": "00001.m2ts", - "size": 104857600, - }, - ], - }, - ], - }, - { - "name": "CERTIFICATE", - "children": [], - }, - ], - }, - { - "name": "Pokemon (2021)", - "children": [ - { - "name": "BDMV", - "children": [ - { - "name": "STREAM", - "children": [ - { - "name": "00000.m2ts", - "size": 104857600, - }, - { - "name": "00001.m2ts", - "size": 104857600, - }, - ], - }, - ], - }, - { - "name": "CERTIFICATE", - "children": [], - }, - ], - }, - { - "name": "Pokemon (2028)", - "children": [ - { - "name": "Pokemon.2028.mkv", - "size": 104857600, - }, - { - "name": "Pokemon.2028.hdr.mkv.!qB", - "size": 104857600, - }, - ], - }, - { - "name": "Pokemon.2029.mp4", - "size": 104857600, - }, - { - "name": "Pokemon (2030)", - "children": [ - { - "name": "S", - "size": 104857600, - }, - ], - }, - ], - }, - ] - transfer = MockTransferChain(MockStorage(files)) + transfer = MockTransferChain(MockStorage(bluray_files)) self.assertEqual( [