From 8a375e022c643079757edb3e1332c1927d597433 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 20 May 2026 18:20:18 +0800 Subject: [PATCH] feat: add video bit rename template field --- app/core/meta/metaanime.py | 2 ++ app/core/meta/metabase.py | 21 ++++++++++++++++ app/core/meta/metavideo.py | 27 +++++++++++++++++++++ app/helper/message.py | 2 ++ tests/test_metainfo.py | 15 ++++++++++++ tests/test_template_context_builder.py | 33 ++++++++++++++++++++++++++ 6 files changed, 100 insertions(+) diff --git a/app/core/meta/metaanime.py b/app/core/meta/metaanime.py index 74d82e3b..240caf3b 100644 --- a/app/core/meta/metaanime.py +++ b/app/core/meta/metaanime.py @@ -170,6 +170,8 @@ class MetaAnime(MetaBase): self.video_encode = anitopy_info.get("video_term") if isinstance(self.video_encode, list): self.video_encode = self.video_encode[0] + # 视频位深 + self.video_bit = self.extract_video_bit(original_title) or self.extract_video_bit(self.video_encode) # 音频编码 self.audio_encode = anitopy_info.get("audio_term") if isinstance(self.audio_encode, list): diff --git a/app/core/meta/metabase.py b/app/core/meta/metabase.py index 3736d613..f073e4bd 100644 --- a/app/core/meta/metabase.py +++ b/app/core/meta/metabase.py @@ -61,6 +61,8 @@ class MetaBase(object): web_source: Optional[str] = None # 视频编码 video_encode: Optional[str] = None + # 视频位深 + video_bit: Optional[str] = None # 音频编码 audio_encode: Optional[str] = None # 应用的识别词信息 @@ -460,6 +462,22 @@ class MetaBase(object): """ return self.fps or None + @staticmethod + def extract_video_bit(value: Optional[str]) -> Optional[str]: + """ + 从标题或编码文本中提取视频位深标签。 + """ + if not value: + return None + bit_match = re.search( + r"(?8|10|12|16)[\s._-]*bits?(?![A-Za-z0-9])", + value, + re.IGNORECASE, + ) + if not bit_match: + return None + return f"{bit_match.group('bit')}bit" + def is_in_season(self, season: Union[list, int, str]) -> bool: """ 是否包含季 @@ -593,6 +611,9 @@ class MetaBase(object): # 视频编码 if not self.video_encode: self.video_encode = meta.video_encode + # 视频位深 + if not self.video_bit: + self.video_bit = meta.video_bit # 音频编码 if not self.audio_encode: self.audio_encode = meta.audio_encode diff --git a/app/core/meta/metavideo.py b/app/core/meta/metavideo.py index c9da5ad8..ccc91699 100644 --- a/app/core/meta/metavideo.py +++ b/app/core/meta/metavideo.py @@ -54,6 +54,7 @@ class MetaVideo(MetaBase): _video_encode_re = r"^(H26[45])$|^(x26[45])$|^AVC$|^HEVC$|^VC\d?$|^MPEG\d?$|^Xvid$|^DivX$|^AV1$|^HDR\d*$|^AVS(\+|[23])$" _audio_encode_re = r"^DTS\d?$|^DTSHD$|^DTSHDMA$|^Atmos$|^TrueHD\d?$|^AC3$|^\dAudios?$|^DDP\d?$|^DD\+\d?$|^DD\d?$|^LPCM\d?$|^AAC\d?$|^FLAC\d?$|^HD\d?$|^MA\d?$|^HR\d?$|^Opus\d?$|^Vorbis\d?$|^AV[3S]A$" _fps_re = r"(\d{2,3})(?=FPS)" + def __init__(self, title: str, subtitle: str = None, isfile: bool = False): """ 初始化 @@ -136,6 +137,9 @@ class MetaVideo(MetaBase): # 视频编码 if self._continue_flag: self.__init_video_encode(token) + # 视频位深 + if self._continue_flag: + self.__init_video_bit(token) # 音频编码 if self._continue_flag: self.__init_audio_encode(token) @@ -178,6 +182,8 @@ class MetaVideo(MetaBase): self.resource_team = ReleaseGroupsMatcher().match(title=original_title) or None # 自定义占位符 self.customization = CustomizationMatcher().match(title=original_title) or None + if not self.video_bit: + self.video_bit = self.extract_video_bit(self.video_encode) @staticmethod def __get_title_from_description(description: str) -> Optional[str]: @@ -693,6 +699,27 @@ class MetaVideo(MetaBase): else: self.video_encode = f"{self.video_encode} 10bit" + def __init_video_bit(self, token: str): + """ + 识别视频位深。 + """ + if not self.name: + return + if not self.year \ + and not self.resource_pix \ + and not self.resource_type \ + and not self.begin_season \ + and not self.begin_episode: + return + video_bit = self.extract_video_bit(token) + if not video_bit: + return + self._continue_flag = False + self._stop_name_flag = True + self._last_token_type = "videobit" + if not self.video_bit: + self.video_bit = video_bit + def __init_audio_encode(self, token: str): """ 识别音频编码 diff --git a/app/helper/message.py b/app/helper/message.py index c1c2cc09..c99ce7dd 100644 --- a/app/helper/message.py +++ b/app/helper/message.py @@ -204,6 +204,8 @@ class TemplateContextBuilder: "releaseGroup": meta.resource_team, # 视频编码 "videoCodec": meta.video_encode, + # 视频位深 + "videoBit": meta.video_bit, # 音频编码 "audioCodec": meta.audio_encode, # 流媒体平台 diff --git a/tests/test_metainfo.py b/tests/test_metainfo.py index c77ffe4f..ed985f80 100644 --- a/tests/test_metainfo.py +++ b/tests/test_metainfo.py @@ -132,6 +132,21 @@ class MetaInfoTest(TestCase): self.assertEqual(meta.episode, "E04") self.assertEqual(meta.apply_words, custom_words) + def test_video_bit_extracted_for_video_title(self): + """测试普通影视标题中的视频位深可单独识别""" + meta = MetaInfo(title="The 355 2022 BluRay 1080p DTS-HD MA5.1 X265.10bit-BeiTai") + self.assertEqual(meta.video_encode, "x265 10bit") + self.assertEqual(meta.video_bit, "10bit") + + def test_video_bit_extracted_for_anime_title(self): + """测试动漫标题中的视频位深可单独识别""" + meta = MetaInfo( + title="[云歌字幕组][7月新番][欢迎来到实力至上主义的教室 第二季][01]" + "[X264 10bit][1080p][简体中文].mp4" + ) + self.assertEqual(meta.video_encode, "X264") + self.assertEqual(meta.video_bit, "10bit") + def test_emby_tmdbid_overrides_braced_metainfo_tmdbid(self): """ 同时存在内嵌元信息和 Emby [tmdbid] 标签时,保持历史上的 [tmdbid] 优先级。 diff --git a/tests/test_template_context_builder.py b/tests/test_template_context_builder.py index b0c9442d..701950fb 100644 --- a/tests/test_template_context_builder.py +++ b/tests/test_template_context_builder.py @@ -67,6 +67,39 @@ class TemplateContextBuilderConcurrencyTest(unittest.TestCase): # 第二次调用不应反向污染第一次的结果 self.assertEqual(first.get("marker"), 1) + def test_build_exposes_video_bit_from_meta(self): + """ + 模板上下文应提供独立 videoBit 字段,避免用户只能从 videoCodec 中手工拆位深。 + """ + meta = type("FakeMeta", (), {})() + meta.begin_episode = None + meta.title = "Movie.2024.1080p.x265.10bit.mkv" + meta.name = "Movie" + meta.en_name = "Movie" + meta.year = "2024" + meta.season_seq = "" + meta.season = "" + meta.episode_seqs = "" + meta.episode = "" + meta.part = None + meta.customization = None + meta.fps = None + meta.resource_type = None + meta.resource_effect = None + meta.edition = "" + meta.resource_pix = "1080p" + meta.resource_term = "1080p" + meta.resource_team = None + meta.video_encode = "x265 10bit" + meta.video_bit = "10bit" + meta.audio_encode = "AAC" + meta.web_source = None + + context = TemplateContextBuilder().build(meta=meta) + + self.assertEqual(context.get("videoCodec"), "x265 10bit") + self.assertEqual(context.get("videoBit"), "10bit") + if __name__ == "__main__": unittest.main()