mirror of
https://github.com/Benexl/FastAnime.git
synced 2025-12-12 15:50:01 -08:00
feat: properly normalize episodes
This commit is contained in:
0
fastanime/core/utils/converters.py
Normal file
0
fastanime/core/utils/converters.py
Normal file
65
fastanime/core/utils/formatting.py
Normal file
65
fastanime/core/utils/formatting.py
Normal 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
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user