From 3c71cf813fd6b2a70d5fbbd4f71a121834450b6c Mon Sep 17 00:00:00 2001 From: EstrellaXD Date: Mon, 26 Jan 2026 13:51:55 +0100 Subject: [PATCH] fix(offset): only suggest episode_offset for virtual seasons MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- backend/src/module/database/combine.py | 6 ++- .../module/parser/analyser/offset_detector.py | 40 ++++++++++++++----- 2 files changed, 35 insertions(+), 11 deletions(-) diff --git a/backend/src/module/database/combine.py b/backend/src/module/database/combine.py index eddbfc14..eb4ca17a 100644 --- a/backend/src/module/database/combine.py +++ b/backend/src/module/database/combine.py @@ -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: diff --git a/backend/src/module/parser/analyser/offset_detector.py b/backend/src/module/parser/analyser/offset_detector.py index e54e5ad6..13da2194 100644 --- a/backend/src/module/parser/analyser/offset_detector.py +++ b/backend/src/module/parser/analyser/offset_detector.py @@ -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