diff --git a/VERSION b/VERSION index 5b09c67..a970716 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -1.0.14 +1.0.15 diff --git a/engine/episode_match.py b/engine/episode_match.py index 46660be..527d3a6 100644 --- a/engine/episode_match.py +++ b/engine/episode_match.py @@ -170,6 +170,7 @@ def rewrite_episode_stem( span = target.episode_end - target.episode + 1 else: span = parsed["span"] + title = parsed.get("title") or "" if parsed["format"] == "nxnn": s_pad = max(parsed["season_pad"], len(str(new_season))) @@ -218,6 +219,33 @@ def _similarity(a: str, b: str) -> float: return SequenceMatcher(None, a, b).ratio() +def _title_match_score(fnorm: str, enorm: str) -> float: + """Fuzzy title match; handles filenames that use a shortened episode title.""" + if not fnorm or not enorm: + return 0.0 + if fnorm == enorm: + return 1.0 + score = _similarity(fnorm, enorm) + if len(fnorm) >= 4 and (fnorm in enorm or enorm.startswith(fnorm)): + score = max(score, 0.78) + ftokens = [t for t in fnorm.split() if len(t) > 2] + if ftokens: + etokens = set(enorm.split()) + overlap = sum(1 for t in ftokens if t in etokens) / len(ftokens) + if overlap >= 0.75: + score = max(score, 0.66 + overlap * 0.3) + return score + + +def _episode_number_boost(parsed: dict, season: int, ep_num: int, title_score: float) -> float: + """Nudge score up when NxNN/SxxExx in the filename agrees with this episode.""" + if title_score < 0.45: + return 0.0 + if parsed.get("season") == season and parsed.get("old_first") == ep_num: + return 0.12 + return 0.0 + + def _coerce_target( value: EpisodeTarget | tuple[int, ...] | int, parsed: dict, @@ -278,7 +306,7 @@ def _combined_title_variants(name: str) -> list[str]: def _combined_match_score(fnorm: str, combined_name: str, file_title: str = "") -> float: variants = _combined_title_variants(combined_name) - best = max((_similarity(fnorm, v) for v in variants), default=0.0) + best = max((_title_match_score(fnorm, v) for v in variants), default=0.0) if file_title and _dual_title_matches_combined(file_title, combined_name): best = max(best, 0.96) return best @@ -417,8 +445,10 @@ def match_filenames_to_episodes( for season, ep_num, enorm, ep_name in season_eps: if not fnorm: continue + title_score = _title_match_score(fnorm, enorm) + score = title_score + _episode_number_boost(parsed, season, ep_num, title_score) score = _apply_season_hint( - _similarity(fnorm, enorm), + score, EpisodeTarget(season=season, episode=ep_num), parsed, ) diff --git a/packaging/release-stamp-1.0.15.txt b/packaging/release-stamp-1.0.15.txt new file mode 100644 index 0000000..6ffff0e --- /dev/null +++ b/packaging/release-stamp-1.0.15.txt @@ -0,0 +1,3 @@ +HSRename 1.0.15 +- Fix nxnn preview crash (undefined title in rewrite_episode_stem) +- Improve matching for shortened episode titles (prefix/token overlap scoring) diff --git a/release_gitea.sh b/release_gitea.sh index cef627a..72afec7 100755 --- a/release_gitea.sh +++ b/release_gitea.sh @@ -76,7 +76,8 @@ API="${GITEA_HOST}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases" NOTES="$(cat <