From f4a879b3de5e13735f044a2b27f1b64d61b02008 Mon Sep 17 00:00:00 2001 From: jxxghp Date: Wed, 19 Aug 2026 17:26:35 +0800 Subject: [PATCH] =?UTF-8?q?fix(rename):=20=E5=89=A5=E7=A6=BB=E4=B8=93?= =?UTF-8?q?=E8=BE=91=E5=90=8D=E5=B0=BE=E9=83=A8=E5=B9=B4=E4=BB=BD=EF=BC=8C?= =?UTF-8?q?=E9=81=BF=E5=85=8D=E9=87=8D=E5=91=BD=E5=90=8D=E7=9B=AE=E5=BD=95?= =?UTF-8?q?=E9=87=8D=E5=A4=8D=E5=B9=B4=E4=BB=BD=20(#6355)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 音频标签或下载目录常把发行年份写进专辑名(如 "欲望反光 (2018)"), 而音乐重命名模板会独立追加 ({{year}}),两者叠加生成 "欲望反光 (2018) (2018)" 这类重复年份目录,并把同一专辑拆到多个目录。 在模板上下文中统一剥离专辑名尾部的括号年份;仅当存在可独立渲染的 年份时才剥离,避免丢失只存在于专辑名中的年份信息。 --- app/application/messaging/message.py | 50 +++++++++++++++++++---- tests/test_template_context_builder.py | 56 ++++++++++++++++++++++++++ 2 files changed, 98 insertions(+), 8 deletions(-) diff --git a/app/application/messaging/message.py b/app/application/messaging/message.py index 756421fbb..ae3c02f44 100644 --- a/app/application/messaging/message.py +++ b/app/application/messaging/message.py @@ -29,6 +29,13 @@ from app.foundation import size as size_tools from app.foundation.crypto import HashUtils +# 专辑名尾部的括号年份标记;重命名模板会独立追加 `({{year}})`, +# 标签或目录名中自带的尾部年份若不剥离,会生成重复年份的目录名(issue #6355) +_ALBUM_TRAILING_YEAR_RE = re.compile( + r"(?:[\s\u3000]*[\(\[(【]\s*(?:19|20)\d{2}\s*[\)\])】])+$" +) + + class AsyncMessageQueryRepository(Protocol): """消息查询用例依赖的异步持久化端口。""" @@ -169,23 +176,32 @@ class TemplateContextBuilder: aggregate_music_album and mediainfo.music_type == MUSIC_ENTITY_ALBUM ) + # 专辑场景以识别结果的专辑名为标题;整专年份以识别结果为准, + # 逐文件场景沿用 meta 解析年份,保证文件级年份优先。 + year = ( + mediainfo.year + if (is_album_context and mediainfo.year) + else (context.get("year") or mediainfo.year) + ) if is_album_context and mediainfo.album: - title = cls.__convert_invalid_characters(mediainfo.album) + title = cls.__strip_album_trailing_year( + cls.__convert_invalid_characters(mediainfo.album), year + ) else: title = context.get("title") or cls.__convert_invalid_characters(mediainfo.title) artists = context.get("artists") or [ cls.__convert_invalid_characters(item) for item in mediainfo.artists ] artist = context.get("artist") or cls.__convert_invalid_characters(mediainfo.artist) - album = context.get("album") or cls.__convert_invalid_characters(mediainfo.album) + # 标签/目录名自带的尾部年份会被重命名模板的 `({{year}})` 再次追加, + # 统一剥离避免生成 "专辑 (2018) (2018)" 这类重复年份目录(issue #6355) + album = cls.__strip_album_trailing_year( + context.get("album") or cls.__convert_invalid_characters(mediainfo.album), + year, + ) album_artist = context.get("album_artist") or cls.__convert_invalid_characters( mediainfo.album_artist ) - year = ( - mediainfo.year - if (is_album_context and mediainfo.year) - else (context.get("year") or mediainfo.year) - ) disc_number = context.get("disc_number") or mediainfo.disc_number track_number = ( None @@ -305,7 +321,10 @@ class TemplateContextBuilder: "title": cls.__convert_invalid_characters(meta.title), "artists": [cls.__convert_invalid_characters(item) for item in meta.artists], "artist": cls.__convert_invalid_characters(meta.artist), - "album": cls.__convert_invalid_characters(meta.album), + # 标签专辑名常自带尾部年份,与模板独立追加的年份去重(issue #6355) + "album": cls.__strip_album_trailing_year( + cls.__convert_invalid_characters(meta.album), meta.year + ), "album_artist": cls.__convert_invalid_characters(meta.album_artist), "year": meta.year, "disc_number": meta.disc_number, @@ -513,6 +532,21 @@ class TemplateContextBuilder: filename = filename.replace(char, char.translate(translation_table)) return filename + @staticmethod + def __strip_album_trailing_year( + album: Optional[str], year: Optional[int] + ) -> Optional[str]: + """ + 去除专辑名尾部的括号年份标记(如 ``欲望反光 (2018)`` -> ``欲望反光``)。 + + 音频标签或下载目录常把发行年份写进专辑名,而重命名模板会独立追加 + ``({{year}})``,两者叠加会生成 "专辑 (2018) (2018)" 这类重复年份目录; + 仅当存在可独立渲染的年份时才剥离,避免丢失只存在于专辑名中的年份信息。 + """ + if not album or not year: + return album + return _ALBUM_TRAILING_YEAR_RE.sub("", album) or album + class TemplateHelper(metaclass=SingletonClass): """ diff --git a/tests/test_template_context_builder.py b/tests/test_template_context_builder.py index 291785d03..9c1d77a20 100644 --- a/tests/test_template_context_builder.py +++ b/tests/test_template_context_builder.py @@ -189,3 +189,59 @@ def test_build_exposes_music_audio_specs_for_notifications() -> None: assert context["audio_specs"] == "FLAC · 24-bit · 96 kHz · 2,304 kbps" assert context["bitrate_kbps"] == 2304 assert context["sample_rate_khz"] == "96" + + +def _build_music_context(album, year): + """ + 构造音乐重命名上下文:标签专辑名带年份、文件/目录年份可独立渲染的场景。 + """ + meta = MetaMusic( + title="欲望反光", + album=album, + artists=["萧敬腾"], + album_artist="萧敬腾", + year=year, + track_number=1, + audio_format="FLAC", + ) + return TemplateContextBuilder().build(meta=meta) + + +def test_music_rename_strips_duplicate_album_year() -> None: + """专辑名尾部的年份标记不应与模板追加的年份重复(issue #6355)。""" + context = _build_music_context(album="欲望反光 (2018)", year=2018) + + assert context["album"] == "欲望反光" + assert context["year"] == 2018 + + +def test_music_rename_strips_album_year_different_from_media_year() -> None: + """标签专辑名年份与识别年份不一致时,仅保留模板追加的识别年份。""" + context = _build_music_context( + album="洛克先生Mr.Rock演唱会Live纪实 (2010)", year=2009 + ) + + assert context["album"] == "洛克先生Mr.Rock演唱会Live纪实" + assert context["year"] == 2009 + + +def test_music_rename_strips_repeated_album_year_suffixes() -> None: + """历史整理已生成的重复年份目录再次重命名时,所有尾部年份都应被剥离。""" + context = _build_music_context(album="爱的时刻自选辑 (2009) (2009)", year=2015) + + assert context["album"] == "爱的时刻自选辑" + assert context["year"] == 2015 + + +def test_music_rename_keeps_album_year_when_no_standalone_year() -> None: + """没有独立年份可渲染时保留专辑名自带的年份,避免信息丢失。""" + context = _build_music_context(album="欲望反光 (2018)", year=None) + + assert context["album"] == "欲望反光 (2018)" + + +def test_music_rename_keeps_plain_album_title() -> None: + """不含年份的普通专辑名不受影响。""" + context = _build_music_context(album="叶惠美", year=2003) + + assert context["album"] == "叶惠美"