Skip to content

Commit 3e77176

Browse files
elematedclaude
andcommitted
Share one duration formatter instead of seven copies
- Adds `helpers.seconds_to_hm` next to the existing `minutes_to_hhmm`, and points the seven hand-rolled `Xh Ym` blocks in podcast views, media details, both statistics aggregators and the podcast lookup API at it. - Output is unchanged except for a zero-second duration in the statistics "longest episodes" lists, which now renders blank rather than "0m", matching what every view already did. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 5560f64 commit 3e77176

6 files changed

Lines changed: 21 additions & 50 deletions

File tree

src/api/fork_views_podcast.py

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
from rest_framework import views as drf_views
99
from rest_framework.response import Response
1010

11-
from app import fork_services_podcast
11+
from app import fork_services_podcast, helpers
1212
from app.forms import PodcastShowTrackerForm
1313
from app.log_safety import safe_url
1414
from app.models import PodcastEpisode, PodcastShow, PodcastShowTracker, Status
@@ -174,20 +174,6 @@ class PodcastLookupView(drf_views.APIView):
174174
# week in pocketcasts.lookup_by_itunes_id.
175175
_FEED_CACHE_SECONDS = 15 * 60
176176

177-
@staticmethod
178-
def _format_duration(seconds):
179-
"""Format a duration in seconds as "1h 9m" or "46m".
180-
181-
Matches the format `podcast_views.py` sends for every already-imported
182-
episode, so a preview episode's duration reads the same as a tracked
183-
one's.
184-
"""
185-
if not seconds:
186-
return ""
187-
hours = seconds // 3600
188-
minutes = (seconds % 3600) // 60
189-
return f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m"
190-
191177
def _read_feed(self, itunes_id, itunes_data, rss_feed_url):
192178
"""Return (metadata, episodes) for a feed, caching the pair briefly."""
193179
cache_key = f"podcast_lookup_feed_{itunes_id}"
@@ -215,7 +201,7 @@ def _feed_episodes(self, rss_feed_url):
215201
{
216202
"title": episode.get("title", ""),
217203
"published": episode.get("published"),
218-
"duration": self._format_duration(episode.get("duration")),
204+
"duration": helpers.seconds_to_hm(episode.get("duration")),
219205
"duration_seconds": episode.get("duration"),
220206
"episode_number": episode.get("episode_number"),
221207
"season_number": episode.get("season_number"),

src/app/helpers.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ def minutes_to_hhmm(total_minutes):
7575
return f"{hours}h {minutes:02d}min"
7676

7777

78+
def seconds_to_hm(total_seconds):
79+
"""Convert a duration in seconds to "1h 9m", or to "46m" under an hour.
80+
81+
Returns "" for a missing or zero duration.
82+
"""
83+
if not total_seconds:
84+
return ""
85+
hours = total_seconds // 3600
86+
minutes = (total_seconds % 3600) // 60
87+
return f"{hours}h {minutes}m" if hours else f"{minutes}m"
88+
89+
7890
def has_real_image(image):
7991
"""Return whether the value is a usable artwork URL."""
8092
return bool(image and image != settings.IMG_NONE)

src/app/media_details_views.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -488,15 +488,7 @@ def _best_effort_detail_followup(
488488
for episode_obj, enriched in zip(
489489
episodes[:initial_limit], enriched_episodes, strict=False
490490
):
491-
# Format duration
492-
duration_str = ""
493-
if episode_obj.duration:
494-
hours = episode_obj.duration // 3600
495-
minutes = (episode_obj.duration % 3600) // 60
496-
if hours > 0:
497-
duration_str = f"{hours}h {minutes}m"
498-
else:
499-
duration_str = f"{minutes}m"
491+
duration_str = helpers.seconds_to_hm(episode_obj.duration)
500492

501493
# Get user's podcast media for this episode
502494
episode_media = enriched["media"]

src/app/podcast_views.py

Lines changed: 3 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -174,11 +174,7 @@ def podcast_episodes_api(request, show_id):
174174
enriched = e
175175
break
176176

177-
duration_str = ""
178-
if episode_obj.duration:
179-
hours = episode_obj.duration // 3600
180-
minutes = (episode_obj.duration % 3600) // 60
181-
duration_str = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m"
177+
duration_str = helpers.seconds_to_hm(episode_obj.duration)
182178

183179
user_podcast = episode_podcast_map.get(episode_obj.id)
184180

@@ -308,11 +304,7 @@ def count(self):
308304
enriched = e
309305
break
310306

311-
duration_str = ""
312-
if episode_obj.duration:
313-
hours = episode_obj.duration // 3600
314-
minutes = (episode_obj.duration % 3600) // 60
315-
duration_str = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m"
307+
duration_str = helpers.seconds_to_hm(episode_obj.duration)
316308

317309
user_podcast = episode_podcast_map.get(episode_obj.id)
318310
status = user_podcast.status if user_podcast else None
@@ -585,11 +577,7 @@ def podcast_save(request):
585577
else {"item": {"media_id": episode_obj.episode_uuid}, "media": None}
586578
)
587579

588-
duration_str = ""
589-
if episode_obj.duration:
590-
hours = episode_obj.duration // 3600
591-
minutes = (episode_obj.duration % 3600) // 60
592-
duration_str = f"{hours}h {minutes}m" if hours > 0 else f"{minutes}m"
580+
duration_str = helpers.seconds_to_hm(episode_obj.duration)
593581

594582
all_history = []
595583
if user_podcast:

src/app/statistics_aggregator.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1380,9 +1380,7 @@ def _top_items(values, key_fields=("minutes", "plays"), limit=STATISTICS_TOP_N):
13801380
for item in most_played + most_listened:
13811381
item["formatted_duration"] = helpers.minutes_to_hhmm(item["minutes"])
13821382
for item in longest_episodes:
1383-
hours = item["duration_seconds"] // 3600
1384-
minutes = (item["duration_seconds"] % 3600) // 60
1385-
item["formatted_duration"] = f"{hours}h {minutes}m" if hours else f"{minutes}m"
1383+
item["formatted_duration"] = helpers.seconds_to_hm(item["duration_seconds"])
13861384
podcast_consumption.update(
13871385
{
13881386
"most_played": most_played,

src/app/stats_podcast.py

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ def _compute_podcast_top_lists(play_details, limit=STATISTICS_TOP_N):
156156
Returns:
157157
dict with most_played (by show), most_listened (by show), longest_episodes lists
158158
"""
159-
from app.helpers import minutes_to_hhmm
159+
from app.helpers import minutes_to_hhmm, seconds_to_hm
160160

161161
# Aggregate by show for most_played and most_listened
162162
show_stats = defaultdict(
@@ -298,12 +298,7 @@ def _compute_podcast_top_lists(play_details, limit=STATISTICS_TOP_N):
298298

299299
# Format longest episodes duration (from seconds)
300300
for item in longest_episodes:
301-
hours = item["duration_seconds"] // 3600
302-
minutes = (item["duration_seconds"] % 3600) // 60
303-
if hours > 0:
304-
item["formatted_duration"] = f"{hours}h {minutes}m"
305-
else:
306-
item["formatted_duration"] = f"{minutes}m"
301+
item["formatted_duration"] = seconds_to_hm(item["duration_seconds"])
307302

308303
return {
309304
"most_played": most_played,

0 commit comments

Comments
 (0)