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"), }