fix(offset): only suggest episode_offset for virtual seasons

- Change episode_offset type from int to int | None
- Only set episode_offset when virtual season split is detected
- For simple season mismatches (e.g., RSS S2 → TMDB S1), episode_offset is now None
- Improve reason messages to clarify when episode offset is/isn't needed
- Update database migration version to 7 and add migration check

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
EstrellaXD
2026-01-26 13:51:55 +01:00
parent 01a1a79a33
commit 3c71cf813f
2 changed files with 35 additions and 11 deletions

View File

@@ -23,7 +23,7 @@ logger = logging.getLogger(__name__)
TABLE_MODELS: list[type[SQLModel]] = [Bangumi, RSSItem, Torrent, User, Passkey]
# Increment this when adding new migrations to MIGRATIONS list.
CURRENT_SCHEMA_VERSION = 6
CURRENT_SCHEMA_VERSION = 7
# Each migration is a tuple of (version, description, list of SQL statements).
# Migrations are applied in order. A migration at index i brings the schema
@@ -183,6 +183,10 @@ class Database(Session):
columns = [col["name"] for col in inspector.get_columns("torrent")]
if "qb_hash" in columns:
needs_run = False
if "bangumi" in tables and version == 7:
columns = [col["name"] for col in inspector.get_columns("bangumi")]
if "suggested_season_offset" in columns:
needs_run = False
if needs_run:
with self.engine.connect() as conn:
for stmt in statements:

View File

@@ -14,7 +14,7 @@ class OffsetSuggestion:
"""Suggested offsets to align RSS parsed data with TMDB."""
season_offset: int
episode_offset: int
episode_offset: int | None # None means no episode offset needed
reason: str
confidence: Literal["high", "medium", "low"]
@@ -36,12 +36,17 @@ def detect_offset_mismatch(
Returns:
OffsetSuggestion if a mismatch is detected, None otherwise
Note:
When only season_offset is needed (simple season mismatch), episode_offset
will be None. Episode offset is only set when there's a virtual season split
where episodes need to be renumbered (e.g., RSS S2E01 → TMDB S1E25).
"""
if not tmdb_info or not tmdb_info.last_season:
return None
suggested_season_offset = 0
suggested_episode_offset = 0
suggested_episode_offset: int | None = None # Only set when virtual season detected
reasons = []
confidence: Literal["high", "medium", "low"] = "high"
@@ -52,27 +57,42 @@ def detect_offset_mismatch(
target_season = parsed_season + suggested_season_offset
# Check if this season has virtual season breakpoints (detected from air date gaps)
if tmdb_info.virtual_season_starts and target_season in tmdb_info.virtual_season_starts:
if (
tmdb_info.virtual_season_starts
and target_season in tmdb_info.virtual_season_starts
):
vs_starts = tmdb_info.virtual_season_starts[target_season]
# Calculate which virtual season the parsed_season maps to
# e.g., if vs_starts = [1, 29] and parsed_season = 2, we're in the 2nd virtual season
virtual_season_index = parsed_season - target_season # 0-indexed from target
virtual_season_index = (
parsed_season - target_season
) # 0-indexed from target
if virtual_season_index < len(vs_starts):
# Episode offset is the start of this virtual season minus 1
if virtual_season_index > 0 and virtual_season_index < len(vs_starts):
# Only set episode offset for 2nd+ virtual season (index > 0)
# First virtual season (index 0) starts at episode 1, no offset needed
suggested_episode_offset = vs_starts[virtual_season_index] - 1
reasons.append(
f"RSS显示S{parsed_season}但TMDB只有{tmdb_info.last_season}"
f"(检测到第{virtual_season_index + 1}部分从第{vs_starts[virtual_season_index]}集开始"
f"(检测到第{virtual_season_index + 1}部分从第{vs_starts[virtual_season_index]}集开始"
f"建议集数偏移+{suggested_episode_offset}"
)
logger.debug(
f"[OffsetDetector] Virtual season detected: S{parsed_season} maps to "
f"TMDB S{target_season} starting at episode {vs_starts[virtual_season_index]}"
)
else:
reasons.append(f"RSS显示S{parsed_season}但TMDB只有{tmdb_info.last_season}")
# Simple season mismatch, no episode offset needed
reasons.append(
f"RSS显示S{parsed_season}但TMDB只有{tmdb_info.last_season}"
f"(建议季度偏移{suggested_season_offset},无需调整集数)"
)
else:
reasons.append(f"RSS显示S{parsed_season}但TMDB只有{tmdb_info.last_season}")
# Simple season mismatch, no episode offset needed
reasons.append(
f"RSS显示S{parsed_season}但TMDB只有{tmdb_info.last_season}"
f"(建议季度偏移{suggested_season_offset},无需调整集数)"
)
logger.debug(
f"[OffsetDetector] Season mismatch: parsed S{parsed_season}, "
@@ -83,7 +103,7 @@ def detect_offset_mismatch(
target_season = parsed_season + suggested_season_offset
if tmdb_info.season_episode_counts:
season_ep_count = tmdb_info.season_episode_counts.get(target_season, 0)
adjusted_episode = parsed_episode + suggested_episode_offset
adjusted_episode = parsed_episode + (suggested_episode_offset or 0)
if season_ep_count > 0 and adjusted_episode > season_ep_count:
# Episode exceeds the count for this season