mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-11-06 07:29:09 +08:00
fix(bangumi): inherit year for same-title entries across subtitle groups
When a new bangumi entry is created for an already-known show (same official_title, different group/quality so no semantic merge), it now inherits the existing entry's year. The save path is built as `title (year)/Season N`, so a Mikan-parsed entry (which never fills year) previously diverged into a separate library folder from a TMDB-parsed sibling that had one. `_inherit_year` in add()/add_all() copies the year from any non-deleted same-title row when the incoming entry has none; a parsed year is never overwritten. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FAxVyRwrY7z5NotTtgnwVs
This commit is contained in:
@@ -126,6 +126,21 @@ def _find_semantic_match(data: Bangumi, candidates: list[Bangumi]) -> Optional[B
|
||||
return None
|
||||
|
||||
|
||||
def _inherit_year(data: Bangumi, candidates: list[Bangumi]) -> None:
|
||||
"""新条目缺年份时继承同名已有条目的年份。
|
||||
|
||||
save path 由 ``official_title (year)`` 组成(downloader/path.py),同一部
|
||||
番剧的不同订阅条目(不同字幕组/清晰度)若年份不一致,会被拆进两个媒体库
|
||||
目录。Mikan 解析路径不填 year,因此从已有同名条目补齐。
|
||||
"""
|
||||
if data.year:
|
||||
return
|
||||
for candidate in candidates:
|
||||
if candidate.year:
|
||||
data.year = candidate.year
|
||||
return
|
||||
|
||||
|
||||
def match_bangumi_in_list(
|
||||
torrent_name: str, bangumi_list: list[Bangumi]
|
||||
) -> Optional[Bangumi]:
|
||||
@@ -152,6 +167,17 @@ class BangumiDatabase:
|
||||
def __init__(self, session: AsyncSession):
|
||||
self.session = session
|
||||
|
||||
async def _same_title_candidates(self, official_title: str) -> list[Bangumi]:
|
||||
"""加载与给定 official_title 同名的所有未删除条目。"""
|
||||
statement = select(Bangumi).where(
|
||||
and_(
|
||||
Bangumi.official_title == official_title,
|
||||
Bangumi.deleted == false(),
|
||||
)
|
||||
)
|
||||
result = await self.session.execute(statement)
|
||||
return list(result.scalars().all())
|
||||
|
||||
async def find_semantic_duplicate(self, data: Bangumi) -> Optional[Bangumi]:
|
||||
"""
|
||||
Find existing bangumi that semantically matches the new one.
|
||||
@@ -166,15 +192,7 @@ class BangumiDatabase:
|
||||
|
||||
Returns the matching Bangumi if found, None otherwise.
|
||||
"""
|
||||
statement = select(Bangumi).where(
|
||||
and_(
|
||||
Bangumi.official_title == data.official_title,
|
||||
Bangumi.deleted == false(),
|
||||
)
|
||||
)
|
||||
result = await self.session.execute(statement)
|
||||
candidates = list(result.scalars().all())
|
||||
|
||||
candidates = await self._same_title_candidates(data.official_title)
|
||||
match = _find_semantic_match(data, candidates)
|
||||
if match:
|
||||
logger.debug(
|
||||
@@ -249,7 +267,8 @@ class BangumiDatabase:
|
||||
return False
|
||||
|
||||
# Check for semantic duplicate (same anime, different naming pattern)
|
||||
semantic_match = await self.find_semantic_duplicate(data)
|
||||
candidates = await self._same_title_candidates(data.official_title)
|
||||
semantic_match = _find_semantic_match(data, candidates)
|
||||
if semantic_match:
|
||||
# Add as alias instead of creating new entry
|
||||
await self.add_title_alias(semantic_match.id, data.title_raw)
|
||||
@@ -259,6 +278,7 @@ class BangumiDatabase:
|
||||
)
|
||||
return False # Return False since we didn't add a new entry
|
||||
|
||||
_inherit_year(data, candidates)
|
||||
self.session.add(data)
|
||||
await self.session.commit()
|
||||
logger.debug("Insert %s into database.", data.official_title)
|
||||
@@ -328,6 +348,7 @@ class BangumiDatabase:
|
||||
f"'{semantic_match.title_raw}' (official: {d.official_title})"
|
||||
)
|
||||
else:
|
||||
_inherit_year(d, candidates_by_title.get(d.official_title, []))
|
||||
really_to_add.append(d)
|
||||
|
||||
# Also deduplicate within the batch itself
|
||||
|
||||
@@ -455,6 +455,106 @@ async def test_add_with_semantic_duplicate_creates_alias(db_session):
|
||||
assert "Frieren Beyond Journey's End" in aliases
|
||||
|
||||
|
||||
async def test_add_same_title_new_entry_inherits_year(db_session):
|
||||
"""A new entry for an already-known anime (same official_title, different
|
||||
group/quality so no semantic merge) must inherit the existing year, so both
|
||||
entries build the same save path (title (year)/Season N)."""
|
||||
db = BangumiDatabase(db_session)
|
||||
|
||||
existing = Bangumi(
|
||||
official_title="躲在超市后门抽烟的两人",
|
||||
year="2026",
|
||||
title_raw="躲在超市后门抽烟的两人",
|
||||
group_name="樱都字幕组",
|
||||
dpi="1080p",
|
||||
source="Baha",
|
||||
subtitle="CHS",
|
||||
rss_link="test1",
|
||||
)
|
||||
await db.add(existing)
|
||||
|
||||
new_entry = Bangumi(
|
||||
official_title="躲在超市后门抽烟的两人",
|
||||
year=None,
|
||||
title_raw="Super no Ura de Yani Suu Futari",
|
||||
group_name="Dynamis One",
|
||||
dpi="1080p",
|
||||
source="ABEMA",
|
||||
subtitle="JPSC",
|
||||
rss_link="test2",
|
||||
)
|
||||
result = await db.add(new_entry)
|
||||
|
||||
assert result is True # different group/source: a new entry, not a merge
|
||||
assert new_entry.year == "2026"
|
||||
|
||||
|
||||
async def test_add_same_title_keeps_own_year(db_session):
|
||||
"""An explicitly parsed year must not be overwritten by an existing entry's."""
|
||||
db = BangumiDatabase(db_session)
|
||||
|
||||
await db.add(
|
||||
Bangumi(
|
||||
official_title="Frieren",
|
||||
year="2023",
|
||||
title_raw="Sousou no Frieren",
|
||||
group_name="LoliHouse",
|
||||
dpi="1080p",
|
||||
source="Web",
|
||||
subtitle="CHT",
|
||||
rss_link="test1",
|
||||
)
|
||||
)
|
||||
|
||||
new_entry = Bangumi(
|
||||
official_title="Frieren",
|
||||
year="2024",
|
||||
title_raw="Frieren S2",
|
||||
group_name="OtherGroup",
|
||||
dpi="720p",
|
||||
source="Baha",
|
||||
subtitle="CHS",
|
||||
rss_link="test2",
|
||||
)
|
||||
result = await db.add(new_entry)
|
||||
|
||||
assert result is True
|
||||
assert new_entry.year == "2024"
|
||||
|
||||
|
||||
async def test_add_all_same_title_new_entries_inherit_year(db_session):
|
||||
"""add_all() applies the same year inheritance as add()."""
|
||||
db = BangumiDatabase(db_session)
|
||||
|
||||
await db.add(
|
||||
Bangumi(
|
||||
official_title="躲在超市后门抽烟的两人",
|
||||
year="2026",
|
||||
title_raw="躲在超市后门抽烟的两人",
|
||||
group_name="樱都字幕组",
|
||||
dpi="1080p",
|
||||
source="Baha",
|
||||
subtitle="CHS",
|
||||
rss_link="test1",
|
||||
)
|
||||
)
|
||||
|
||||
new_entry = Bangumi(
|
||||
official_title="躲在超市后门抽烟的两人",
|
||||
year=None,
|
||||
title_raw="Super no Ura de Yani Suu Futari",
|
||||
group_name="Dynamis One",
|
||||
dpi="1080p",
|
||||
source="ABEMA",
|
||||
subtitle="JPSC",
|
||||
rss_link="test2",
|
||||
)
|
||||
added = await db.add_all([new_entry])
|
||||
|
||||
assert added == 1
|
||||
assert new_entry.year == "2026"
|
||||
|
||||
|
||||
async def test_add_all_semantic_duplicates_across_batch_all_merged_as_aliases(
|
||||
db_session,
|
||||
):
|
||||
|
||||
Reference in New Issue
Block a user