feat: properly normalize episodes

This commit is contained in:
Benexl
2025-07-22 17:25:33 +03:00
parent 5e45fba66d
commit 3092ef0887
3 changed files with 126 additions and 6 deletions

View File

View File

@@ -0,0 +1,65 @@
import re
from typing import Dict, List, Optional, Union
def extract_episode_number(title: str) -> Optional[float]:
"""
Extracts the episode number (supports floats) from a title like:
"Episode 2.5 - Some Title". Returns None if no match.
"""
match = re.search(r"Episode\s+([0-9]+(?:\.[0-9]+)?)", title, re.IGNORECASE)
if match:
return round(float(match.group(1)), 3)
return None
def strip_original_episode_prefix(title: str) -> str:
"""
Removes the original 'Episode X' prefix from the title.
"""
return re.sub(
r"^Episode\s+[0-9]+(?:\.[0-9]+)?\s*[-:]?\s*", "", title, flags=re.IGNORECASE
)
def renumber_titles(titles: List[str]) -> Dict[str, Union[int, float, None]]:
"""
Extracts and renumbers episode numbers from titles starting at 1.
Preserves fractional spacing and leaves titles without episode numbers untouched.
Returns a dict: {original_title: new_episode_number or None}
"""
# Separate titles with and without numbers
with_numbers = [(t, extract_episode_number(t)) for t in titles]
with_numbers = [(t, n) for t, n in with_numbers if n is not None]
without_numbers = [t for t in titles if extract_episode_number(t) is None]
# Sort numerically
with_numbers.sort(key=lambda x: x[1])
renumbered = {}
base_map = {}
next_index = 1
for title, orig_ep in with_numbers:
int_part = int(orig_ep)
is_whole = orig_ep == int_part
if is_whole:
base_map[int_part] = next_index
renumbered_val = next_index
next_index += 1
else:
base_val = base_map.get(int_part, next_index - 1)
offset = round(orig_ep - int_part, 3)
renumbered_val = round(base_val + offset, 3)
renumbered[title] = (
int(renumbered_val) if renumbered_val.is_integer() else renumbered_val
)
# Add back the unnumbered titles with `None`
for t in without_numbers:
renumbered[t] = None
return renumbered

View File

@@ -2,6 +2,7 @@ import logging
from datetime import datetime
from typing import Dict, List, Optional
from ....core.utils.formatting import renumber_titles, strip_original_episode_prefix
from ..types import (
AiringSchedule,
MediaImage,
@@ -131,15 +132,69 @@ def _to_generic_tags(anilist_tags: list[AnilistMediaTag]) -> List[MediaTag]:
]
# def _to_generic_streaming_episodes(
# anilist_episodes: list[AnilistStreamingEpisode],
# ) -> List[StreamingEpisode]:
# """Maps a list of AniList streaming episodes to generic StreamingEpisode objects."""
# return [
# StreamingEpisode(title=episode["title"], thumbnail=episode.get("thumbnail"))
# for episode in anilist_episodes
# if episode.get("title")
# ]
# def _to_generic_streaming_episodes(
# anilist_episodes: list[dict],
# ) -> List[StreamingEpisode]:
# """Maps a list of AniList streaming episodes to generic StreamingEpisode objects with renumbered episode titles."""
# # Extract titles
# titles = [ep["title"] for ep in anilist_episodes if "title" in ep]
# # Generate mapping: title -> renumbered_ep
# renumbered_map = renumber_titles(titles)
# # Apply renumbering
# return [
# StreamingEpisode(
# title=f"{renumbered_map[ep['title']]} - {ep['title']}",
# thumbnail=ep.get("thumbnail"),
# )
# for ep in anilist_episodes
# if ep.get("title")
# ]
def _to_generic_streaming_episodes(
anilist_episodes: list[AnilistStreamingEpisode],
) -> List[StreamingEpisode]:
"""Maps a list of AniList streaming episodes to generic StreamingEpisode objects."""
return [
StreamingEpisode(title=episode["title"], thumbnail=episode.get("thumbnail"))
for episode in anilist_episodes
if episode.get("title")
]
"""Maps a list of AniList streaming episodes to generic StreamingEpisode objects,
renumbering them fresh if they contain episode numbers."""
titles = [ep["title"] for ep in anilist_episodes if "title" in ep and ep["title"]]
renumber_map = renumber_titles(titles)
result = []
for ep in anilist_episodes:
title = ep.get("title")
if not title:
continue
renumbered_ep = renumber_map.get(title)
display_title = (
f"Episode {renumbered_ep} - {strip_original_episode_prefix(title)}"
if renumbered_ep is not None
else title
)
result.append(
StreamingEpisode(
title=display_title,
thumbnail=ep.get("thumbnail"),
)
)
return result
def _to_generic_user_status(