Files
vrobbler/vrobbler/apps/music/aggregators.py
Colin Powell 22956c7c7f
All checks were successful
build & deploy / test (push) Successful in 1m54s
build & deploy / build-and-deploy (push) Successful in 33s
[music] Tracks can have multiple artists
2026-05-28 23:37:46 -04:00

371 lines
12 KiB
Python

from datetime import datetime, timedelta
from django.apps import apps
from django.db.models import Count, Q, QuerySet
from django.utils import timezone
from profiles.utils import now_user_timezone
from scrobbles.models import Scrobble
from videos.models import Video
def scrobble_counts(user=None):
now = timezone.now()
user_filter = Q()
if user and user.is_authenticated:
now = now_user_timezone(user.profile)
user_filter = Q(user=user)
start_of_today = datetime.combine(now.date(), datetime.min.time(), now.tzinfo)
starting_day_of_current_week = now.date() - timedelta(
days=now.today().isoweekday() % 7
)
starting_day_of_current_month = now.date().replace(day=1)
starting_day_of_current_year = now.date().replace(month=1, day=1)
finished_scrobbles_qs = Scrobble.objects.filter(
user_filter,
played_to_completion=True,
media_type=Scrobble.MediaType.TRACK,
)
data = {}
data["today"] = finished_scrobbles_qs.filter(timestamp__gte=start_of_today).count()
data["week"] = finished_scrobbles_qs.filter(
timestamp__gte=starting_day_of_current_week
).count()
data["month"] = finished_scrobbles_qs.filter(
timestamp__gte=starting_day_of_current_month
).count()
data["year"] = finished_scrobbles_qs.filter(
timestamp__gte=starting_day_of_current_year
).count()
data["alltime"] = finished_scrobbles_qs.count()
return data
def week_of_scrobbles(user=None, start=None, media: str = "tracks") -> dict[str, int]:
now = timezone.now()
user_filter = Q()
if user and user.is_authenticated:
now = now_user_timezone(user.profile)
user_filter = Q(user=user)
if not start:
start = datetime.combine(now.date(), datetime.min.time(), now.tzinfo)
scrobble_day_dict = {}
base_qs = Scrobble.objects.filter(user_filter, played_to_completion=True)
media_filter = Q(track__isnull=False)
if media == "movies":
media_filter = Q(video__video_type=Video.VideoType.MOVIE)
if media == "series":
media_filter = Q(video__video_type=Video.VideoType.TV_EPISODE)
for day in range(6, -1, -1):
start_day = start - timedelta(days=day)
end = datetime.combine(start_day, datetime.max.time(), now.tzinfo)
day_of_week = start_day.strftime("%A")
scrobble_day_dict[day_of_week] = base_qs.filter(
media_filter,
timestamp__gte=start_day,
timestamp__lte=end,
played_to_completion=True,
).count()
return scrobble_day_dict
def live_charts(
user: "User",
media_type: str = "Track",
chart_period: str = "all",
limit: int = 15,
app_label: str = "music",
as_of: datetime = None,
) -> QuerySet:
if as_of is not None:
now = as_of
tzinfo = now.tzinfo
now = now.date()
else:
now = timezone.now()
tzinfo = now.tzinfo
now = now.date()
if user.is_authenticated:
now = now_user_timezone(user.profile)
tzinfo = now.tzinfo
now = now.date()
seven_days_ago = now - timedelta(days=7)
thirty_days_ago = now - timedelta(days=30)
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
start_day_of_month = now.replace(day=1)
start_day_of_year = now.replace(month=1, day=1)
media_model = apps.get_model(app_label=app_label, model_name=media_type)
period_queries = {
"today": {"scrobble__timestamp__gte": start_of_today},
"week": {"scrobble__timestamp__gte": start_day_of_week},
"last7": {"scrobble__timestamp__gte": seven_days_ago},
"last30": {"scrobble__timestamp__gte": thirty_days_ago},
"month": {"scrobble__timestamp__gte": start_day_of_month},
"year": {"scrobble__timestamp__gte": start_day_of_year},
"all": {},
}
time_filter = Q()
completion_filter = Q(scrobble__user=user, scrobble__played_to_completion=True)
user_filter = Q(scrobble__user=user)
count_field = "scrobble"
if media_type == "Artist":
for period, query_dict in period_queries.items():
period_queries[period] = {"track__" + k: v for k, v in query_dict.items()}
completion_filter = Q(
track__scrobble__user=user,
track__scrobble__played_to_completion=True,
)
count_field = "track__scrobble"
user_filter = Q(track__scrobble__user=user)
time_filter = Q(**period_queries[chart_period])
return (
media_model.objects.filter(user_filter, time_filter)
.annotate(
num_scrobbles=Count(
count_field,
filter=completion_filter,
distinct=True,
)
)
.order_by("-num_scrobbles")[:limit]
)
def artist_scrobble_count(artist_id: int, filter: str = "today") -> int:
return Scrobble.objects.filter(track__artists=artist_id).count()
def live_tv_charts(
user: "User",
chart_period: str = "all",
limit: int = 15,
) -> QuerySet:
from django.db.models import OuterRef, Subquery
from videos.models import Series, Video
now = timezone.now()
tzinfo = now.tzinfo
now = now.date()
if user.is_authenticated:
now = now_user_timezone(user.profile)
tzinfo = now.tzinfo
seven_days_ago = now - timedelta(days=7)
thirty_days_ago = now - timedelta(days=30)
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
start_day_of_month = now.replace(day=1)
start_day_of_year = now.replace(month=1, day=1)
period_queries = {
"today": Q(timestamp__gte=start_of_today),
"week": Q(timestamp__gte=start_day_of_week),
"last7": Q(timestamp__gte=seven_days_ago),
"last30": Q(timestamp__gte=thirty_days_ago),
"month": Q(timestamp__gte=start_day_of_month),
"year": Q(timestamp__gte=start_day_of_year),
"all": Q(),
}
time_filter = period_queries[chart_period]
completion_filter = Q(
user=user,
played_to_completion=True,
video__video_type=Video.VideoType.TV_EPISODE,
)
scrobble_counts = (
Scrobble.objects.filter(
completion_filter,
time_filter,
video__tv_series=OuterRef("pk"),
)
.values("video__tv_series")
.annotate(c=Count("id"))
.values("c")
)
return (
Series.objects.annotate(num_scrobbles=Subquery(scrobble_counts))
.filter(num_scrobbles__gt=0)
.order_by("-num_scrobbles")[:limit]
)
def live_youtube_channel_charts(
user: "User",
chart_period: str = "all",
limit: int = 15,
) -> QuerySet:
from django.db.models import OuterRef, Subquery
from videos.models import Channel, Video
now = timezone.now()
tzinfo = now.tzinfo
now = now_user_timezone(user.profile)
tzinfo = now.tzinfo
seven_days_ago = now - timedelta(days=7)
thirty_days_ago = now - timedelta(days=30)
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
start_day_of_week = start_of_today - timedelta(days=now.today().isoweekday() % 7)
start_day_of_year = now.replace(month=1, day=1)
period_queries = {
"today": Q(timestamp__gte=start_of_today),
"week": Q(timestamp__gte=start_day_of_week),
"last7": Q(timestamp__gte=seven_days_ago),
"last30": Q(timestamp__gte=thirty_days_ago),
"year": Q(timestamp__gte=start_day_of_year),
"all": Q(),
}
time_filter = period_queries[chart_period]
completion_filter = Q(
user=user,
played_to_completion=True,
video__video_type=Video.VideoType.YOUTUBE,
)
scrobble_counts = (
Scrobble.objects.filter(
completion_filter,
time_filter,
video__channel=OuterRef("pk"),
)
.values("video__channel")
.annotate(c=Count("id"))
.values("c")
)
return (
Channel.objects.annotate(num_scrobbles=Subquery(scrobble_counts))
.filter(num_scrobbles__gt=0)
.order_by("-num_scrobbles")[:limit]
)
def batch_live_charts(
user: "User",
media_type: str = "Track",
limit: int = 15,
app_label: str = "music",
) -> dict[str, list]:
from django.db.models import Count, Q
now = timezone.now()
tzinfo = now.tzinfo
now = now.date()
if user.is_authenticated:
now = now_user_timezone(user.profile)
tzinfo = now.tzinfo
seven_days_ago = now - timedelta(days=7)
thirty_days_ago = now - timedelta(days=30)
start_of_today = datetime.combine(now, datetime.min.time(), tzinfo)
start_day_of_year = now.replace(month=1, day=1)
media_model = apps.get_model(app_label=app_label, model_name=media_type)
if media_type == "Artist":
base_filter = Q(
track__scrobble__user=user, track__scrobble__played_to_completion=True
)
count_field = "track__scrobble"
else:
base_filter = Q(scrobble__user=user, scrobble__played_to_completion=True)
count_field = "scrobble"
scrobble_filter_prefix = (
"track__scrobble__" if media_type == "Artist" else "scrobble__"
)
queryset = media_model.objects.filter(
Q(track__scrobble__user=user)
if media_type == "Artist"
else Q(scrobble__user=user)
).annotate(
num_scrobbles_today=Count(
count_field,
filter=Q(
**{
f"{scrobble_filter_prefix}played_to_completion": True,
f"{scrobble_filter_prefix}timestamp__gte": start_of_today,
}
),
distinct=True,
),
num_scrobbles_last7=Count(
count_field,
filter=Q(
**{
f"{scrobble_filter_prefix}played_to_completion": True,
f"{scrobble_filter_prefix}timestamp__gte": seven_days_ago,
}
),
distinct=True,
),
num_scrobbles_last30=Count(
count_field,
filter=Q(
**{
f"{scrobble_filter_prefix}played_to_completion": True,
f"{scrobble_filter_prefix}timestamp__gte": thirty_days_ago,
}
),
distinct=True,
),
num_scrobbles_year=Count(
count_field,
filter=Q(
**{
f"{scrobble_filter_prefix}played_to_completion": True,
f"{scrobble_filter_prefix}timestamp__gte": start_day_of_year,
}
),
distinct=True,
),
num_scrobbles=Count(
count_field,
filter=Q(
**{
f"{scrobble_filter_prefix}played_to_completion": True,
}
),
distinct=True,
),
)
items = list(queryset)
for item in items:
item.count = item.num_scrobbles
def filter_by_count_attr(attr_name):
filtered = [item for item in items if getattr(item, attr_name) > 0]
return sorted(filtered, key=lambda x: -getattr(x, attr_name))[:limit]
return {
"today": filter_by_count_attr("num_scrobbles_today"),
"last7": filter_by_count_attr("num_scrobbles_last7"),
"last30": filter_by_count_attr("num_scrobbles_last30"),
"year": filter_by_count_attr("num_scrobbles_year"),
"all": filter_by_count_attr("num_scrobbles"),
}