feat: duration

This commit is contained in:
Benexl
2025-07-24 02:28:55 +03:00
parent d3f08ea9c4
commit 3a9be3f699
5 changed files with 52 additions and 3 deletions

View File

@@ -9,6 +9,47 @@ from ...libs.api.types import AiringSchedule, MediaItem
COMMA_REGEX = re.compile(r"([0-9]{3})(?=\d)")
def format_media_duration(total_minutes: Optional[int]) -> str:
"""
Converts a duration in minutes into a more human-readable format
(e.g., "1 hour 30 minutes", "45 minutes", "2 hours").
Args:
total_minutes: The total duration in minutes (integer).
Returns:
A string representing the formatted duration.
"""
if not total_minutes:
return "N/A"
if not isinstance(total_minutes, int) or total_minutes < 0:
raise ValueError("Input must be a non-negative integer representing minutes.")
if total_minutes == 0:
return "0 minutes"
hours = total_minutes // 60
minutes = total_minutes % 60
parts = []
if hours > 0:
parts.append(f"{hours} hour{'s' if hours > 1 else ''}")
if minutes > 0:
parts.append(f"{minutes} minute{'s' if minutes > 1 else ''}")
# Join the parts with " and " if both hours and minutes are present
if len(parts) == 2:
return f"{parts[0]} and {parts[1]}"
elif len(parts) == 1:
return parts[0]
else:
# This case should ideally not be reached if total_minutes > 0
return "0 minutes" # Fallback for safety, though handled by initial check
def format_date(dt: Optional[datetime], format_str: str = "%A, %d %B %Y") -> str:
"""
Formats a datetime object to a readable string.

View File

@@ -82,6 +82,9 @@ def _populate_info_template(item: MediaItem, config: AppConfig) -> str:
else "N/A"
),
"EPISODES": formatters.shell_safe(str(item.episodes)),
"DURATION": formatters.shell_safe(
formatters.format_media_duration(item.duration)
),
"SCORE": formatters.shell_safe(
formatters.format_score_stars_full(item.average_score)
),

View File

@@ -5,10 +5,13 @@ from typing import List, Optional
from ....core.utils.formatting import renumber_titles, strip_original_episode_prefix
from ..types import (
AiringSchedule,
MediaFormat,
MediaGenre,
MediaImage,
MediaItem,
MediaSearchResult,
MediaStatus,
MediaTag,
MediaTagItem,
MediaTitle,
MediaTrailer,
@@ -133,7 +136,7 @@ def _to_generic_studios(anilist_studios: AnilistStudioNodes) -> List[Studio]:
def _to_generic_tags(anilist_tags: list[AnilistMediaTag]) -> List[MediaTagItem]:
"""Maps a list of AniList tags to generic MediaTag objects."""
return [
MediaTagItem(name=t["name"], rank=t.get("rank"))
MediaTagItem(name=MediaTag(t["name"]), rank=t.get("rank"))
for t in anilist_tags
if t.get("name")
]
@@ -244,14 +247,14 @@ def _to_generic_media_item(
type=data.get("type", "ANIME"),
title=_to_generic_media_title(data["title"]),
status=status_map[data["status"]],
format=data.get("format"),
format=MediaFormat(data["format"]),
cover_image=_to_generic_media_image(data["coverImage"]),
banner_image=data.get("bannerImage"),
trailer=_to_generic_media_trailer(data["trailer"]),
description=data.get("description"),
episodes=data.get("episodes"),
duration=data.get("duration"),
genres=data.get("genres", []),
genres=[MediaGenre(genre) for genre in data["genres"]],
tags=_to_generic_tags(data.get("tags")),
studios=_to_generic_studios(data.get("studios")),
synonymns=data.get("synonyms", []),

View File

@@ -85,6 +85,7 @@ query (
}
favourites
averageScore
duration
episodes
genres
synonyms

View File

@@ -69,6 +69,7 @@ draw_rule
print_kv "Episodes" "{EPISODES}"
print_kv "Next Episode" "{NEXT_EPISODE}"
print_kv "Duration" "{DURATION}"
draw_rule