From 268aff9918f2f2dc9f4e57297bdea29d71bd2bf9 Mon Sep 17 00:00:00 2001 From: Claudio Ortolina Date: Sat, 14 Jun 2025 09:38:44 +0300 Subject: [PATCH] Add top albums to the stats page Authored with Claude. > I'd like to have a new section in the Stats page. In this section I want to see the top albums I listened to in the past 30, 90, 365 days. Use the data in `scrobbled_tracks` to determine which albums need to be displayed. > This all looks great as a starter. I'd like to make some modifications. 1. Invert the position of the artist name and the album title (so that it's consistent with the Scrobble Activity) 2. Remove the border and shadow from around each album 3. Rename the "Last 365 days" section to "Last Year" --- lib/music_library/scrobble_activity.ex | 48 ++++++++++- .../live/stats_live/index.ex | 4 +- .../live/stats_live/index.html.heex | 86 +++++++++++++++++++ priv/gettext/default.pot | 20 +++++ priv/gettext/en/LC_MESSAGES/default.po | 20 +++++ 5 files changed, 175 insertions(+), 3 deletions(-) diff --git a/lib/music_library/scrobble_activity.ex b/lib/music_library/scrobble_activity.ex index 512b976f..7bba0bbf 100644 --- a/lib/music_library/scrobble_activity.ex +++ b/lib/music_library/scrobble_activity.ex @@ -1,7 +1,9 @@ defmodule MusicLibrary.ScrobbleActivity do - alias LastFm.Scrobble + import Ecto.Query + + alias LastFm.{Scrobble, Track} alias MusicBrainz.Release - alias MusicLibrary.{Artists, Collection, Secrets, Wishlist} + alias MusicLibrary.{Artists, Collection, Repo, Secrets, Wishlist} def can_scrobble? do Secrets.get("last_fm_session_key") !== nil @@ -200,4 +202,46 @@ defmodule MusicLibrary.ScrobbleActivity do |> Enum.reject(fn musicbrainz_id -> musicbrainz_id == "" end) |> MapSet.new() end + + @doc """ + Gets the top albums by scrobble count for the given number of days. + Returns a list of maps with album information and play counts. + """ + def get_top_albums_by_days(days, limit \\ 10) do + cutoff_timestamp = + DateTime.utc_now() + |> DateTime.add(-days, :day) + |> DateTime.to_unix() + + query = + from t in Track, + where: t.scrobbled_at_uts >= ^cutoff_timestamp, + group_by: [ + fragment("json_extract(album, '$.title')"), + fragment("json_extract(artist, '$.name')") + ], + select: %{ + album_title: fragment("json_extract(album, '$.title')"), + artist_name: fragment("json_extract(artist, '$.name')"), + play_count: count(t.scrobbled_at_uts), + cover_url: fragment("max(?)", t.cover_url), + album_mbid: fragment("json_extract(album, '$.musicbrainz_id')") + }, + order_by: [desc: count(t.scrobbled_at_uts)], + limit: ^limit + + Repo.all(query) + end + + @doc """ + Gets top albums for multiple time periods (30, 90, 365 days). + Returns a map with the results for each period. + """ + def get_top_albums_by_periods(limit \\ 10) do + %{ + last_30_days: get_top_albums_by_days(30, limit), + last_90_days: get_top_albums_by_days(90, limit), + last_365_days: get_top_albums_by_days(365, limit) + } + end end diff --git a/lib/music_library_web/live/stats_live/index.ex b/lib/music_library_web/live/stats_live/index.ex index efd9e974..605cbc8f 100644 --- a/lib/music_library_web/live/stats_live/index.ex +++ b/lib/music_library_web/live/stats_live/index.ex @@ -76,6 +76,7 @@ defmodule MusicLibraryWeb.StatsLive.Index do recent_tracks = LastFm.get_scrobbled_tracks() records_by_artists = Collection.count_records_by_artist(limit: 20) records_by_genre = Collection.count_records_by_genre(limit: 20) + top_albums = ScrobbleActivity.get_top_albums_by_periods(10) if connected?(socket) do LastFm.subscribe_to_feed() @@ -98,7 +99,8 @@ defmodule MusicLibraryWeb.StatsLive.Index do page_title: gettext("Stats"), current_section: :stats, records_by_artist: records_by_artists, - records_by_genre: records_by_genre + records_by_genre: records_by_genre, + top_albums: top_albums )} end diff --git a/lib/music_library_web/live/stats_live/index.html.heex b/lib/music_library_web/live/stats_live/index.html.heex index 988b9ba5..9f92196d 100644 --- a/lib/music_library_web/live/stats_live/index.html.heex +++ b/lib/music_library_web/live/stats_live/index.html.heex @@ -112,6 +112,92 @@ +
+

+ {gettext("Top Albums")} +

+ +
+
+

+ {gettext("Last 30 days")} +

+
+
+ {album.album_title} +
+

+ {album.artist_name} +

+

+ {album.album_title} +

+
+
+ {album.play_count} +
+
+
+
+ +
+

+ {gettext("Last 90 days")} +

+
+
+ {album.album_title} +
+

+ {album.artist_name} +

+

+ {album.album_title} +

+
+
+ {album.play_count} +
+
+
+
+ +
+

+ {gettext("Last Year")} +

+
+
+ {album.album_title} +
+

+ {album.artist_name} +

+

+ {album.album_title} +

+
+
+ {album.play_count} +
+
+
+
+
+
+

diff --git a/priv/gettext/default.pot b/priv/gettext/default.pot index e3a9adb3..5d1df700 100644 --- a/priv/gettext/default.pot +++ b/priv/gettext/default.pot @@ -885,3 +885,23 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Record deleted" msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Last 90 days" +msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Last Year" +msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Top Albums" +msgstr "" diff --git a/priv/gettext/en/LC_MESSAGES/default.po b/priv/gettext/en/LC_MESSAGES/default.po index 7813cd49..251c57d6 100644 --- a/priv/gettext/en/LC_MESSAGES/default.po +++ b/priv/gettext/en/LC_MESSAGES/default.po @@ -885,3 +885,23 @@ msgstr "" #, elixir-autogen, elixir-format msgid "Record deleted" msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Last 30 days" +msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Last 90 days" +msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format +msgid "Last Year" +msgstr "" + +#: lib/music_library_web/live/stats_live/index.html.heex +#, elixir-autogen, elixir-format, fuzzy +msgid "Top Albums" +msgstr ""