Compare commits

...

3 Commits
0.2.0 ... 0.2.1

Author SHA1 Message Date
d0c88ce271 Bump version to 0.2.1 2023-01-10 16:14:33 -05:00
f8c9df3b9a Fix artist aggregation so it works 2023-01-10 16:13:52 -05:00
8b61dab1bc Fix aggregator and blacken things up 2023-01-10 15:21:12 -05:00
8 changed files with 210 additions and 128 deletions

View File

@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "vrobbler" name = "vrobbler"
version = "0.2.0" version = "0.2.1"
description = "" description = ""
authors = ["Colin Powell <colin@unbl.ink>"] authors = ["Colin Powell <colin@unbl.ink>"]

View File

@ -2,6 +2,7 @@ from django.contrib import admin
from music.models import Artist, Album, Track from music.models import Artist, Album, Track
@admin.register(Album) @admin.register(Album)
class AlbumAdmin(admin.ModelAdmin): class AlbumAdmin(admin.ModelAdmin):
date_hierarchy = "created" date_hierarchy = "created"
@ -9,12 +10,14 @@ class AlbumAdmin(admin.ModelAdmin):
list_filter = ("year",) list_filter = ("year",)
ordering = ("name",) ordering = ("name",)
@admin.register(Artist) @admin.register(Artist)
class ArtistAdmin(admin.ModelAdmin): class ArtistAdmin(admin.ModelAdmin):
date_hierarchy = "created" date_hierarchy = "created"
list_display = ("name", "musicbrainz_id") list_display = ("name", "musicbrainz_id")
ordering = ("name",) ordering = ("name",)
@admin.register(Track) @admin.register(Track)
class TrackAdmin(admin.ModelAdmin): class TrackAdmin(admin.ModelAdmin):
date_hierarchy = "created" date_hierarchy = "created"

View File

@ -10,7 +10,9 @@ from datetime import datetime, timedelta
NOW = timezone.now() NOW = timezone.now()
START_OF_TODAY = datetime.combine(NOW.date(), datetime.min.time(), NOW.tzinfo) 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_WEEK = NOW.date() - timedelta(
days=NOW.today().isoweekday() % 7
)
STARTING_DAY_OF_CURRENT_MONTH = NOW.date().replace(day=1) STARTING_DAY_OF_CURRENT_MONTH = NOW.date().replace(day=1)
STARTING_DAY_OF_CURRENT_YEAR = NOW.date().replace(month=1, day=1) STARTING_DAY_OF_CURRENT_YEAR = NOW.date().replace(month=1, day=1)
@ -18,30 +20,48 @@ STARTING_DAY_OF_CURRENT_YEAR = NOW.date().replace(month=1, day=1)
def scrobble_counts(): def scrobble_counts():
finished_scrobbles_qs = Scrobble.objects.filter(in_progress=False) finished_scrobbles_qs = Scrobble.objects.filter(in_progress=False)
data = {} data = {}
data['today'] = finished_scrobbles_qs.filter(timestamp__gte=START_OF_TODAY).count() data['today'] = finished_scrobbles_qs.filter(
data['week'] = finished_scrobbles_qs.filter(timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK).count() timestamp__gte=START_OF_TODAY
data['month'] = finished_scrobbles_qs.filter(timestamp__gte=STARTING_DAY_OF_CURRENT_MONTH).count() ).count()
data['year'] = finished_scrobbles_qs.filter(timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR).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() data['alltime'] = finished_scrobbles_qs.count()
return data return data
def week_of_scrobbles(media: str='tracks') -> dict[str, int]:
scrobble_day_dict= {}
media_filter = Q(track__isnull=True)
for day in range(1,8): def week_of_scrobbles(media: str = 'tracks') -> dict[str, int]:
scrobble_day_dict = {}
media_filter = Q(track__isnull=False)
for day in range(6, -1, -1):
start = START_OF_TODAY - timedelta(days=day) start = START_OF_TODAY - timedelta(days=day)
end = datetime.combine(start, datetime.max.time(), NOW.tzinfo) end = datetime.combine(start, datetime.max.time(), NOW.tzinfo)
day_of_week = start.strftime('%A') day_of_week = start.strftime('%A')
if media == 'movies': if media == 'movies':
media_filter = Q(video__videotype=Video.VideoType.MOVIE) media_filter = Q(video__videotype=Video.VideoType.MOVIE)
if media == 'series': if media == 'series':
media_filter = Q(video__videotype=Video.VideoType.MOVIE) media_filter = Q(video__videotype=Video.VideoType.TV_EPISODE)
scrobble_day_dict[day_of_week] = Scrobble.objects.filter(media_filter).filter(timestamp__lte=START_OF_TODAY, timestamp__gt=end, in_progress=False).count() scrobble_day_dict[day_of_week] = (
Scrobble.objects.filter(media_filter)
.filter(
timestamp__gte=start,
timestamp__lte=end,
in_progress=False,
)
.count()
)
return scrobble_day_dict return scrobble_day_dict
def top_tracks(filter: str="today", limit: int=15) -> List["Track"]:
def top_tracks(filter: str = "today", limit: int = 15) -> List["Track"]:
time_filter = Q(scrobble__timestamp__gte=START_OF_TODAY) time_filter = Q(scrobble__timestamp__gte=START_OF_TODAY)
if filter == "week": if filter == "week":
time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK) time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK)
@ -50,21 +70,36 @@ def top_tracks(filter: str="today", limit: int=15) -> List["Track"]:
if filter == "year": if filter == "year":
time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR) time_filter = Q(scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR)
return Track.objects.annotate(num_scrobbles=Count("scrobble", distinct=True)).filter(time_filter).order_by("-num_scrobbles")[:limit] return (
Track.objects.annotate(num_scrobbles=Count("scrobble", distinct=True))
.filter(time_filter)
.order_by("-num_scrobbles")[:limit]
)
def top_artists(filter: str="today", limit: int=15) -> List["Artist"]:
def top_artists(filter: str = "today", limit: int = 15) -> List["Artist"]:
time_filter = Q(track__scrobble__timestamp__gte=START_OF_TODAY) time_filter = Q(track__scrobble__timestamp__gte=START_OF_TODAY)
if filter == "week": if filter == "week":
time_filter = Q(track__scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK) time_filter = Q(
track__scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_WEEK
)
if filter == "month": if filter == "month":
time_filter = Q(track__scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_MONTH) time_filter = Q(
track__scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_MONTH
)
if filter == "year": if filter == "year":
time_filter = Q(track__scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR) time_filter = Q(
track__scrobble__timestamp__gte=STARTING_DAY_OF_CURRENT_YEAR
)
return (
Artist.objects.annotate(
num_scrobbles=Count("track__scrobble", distinct=True)
)
.filter(time_filter)
.order_by("-num_scrobbles")[:limit]
)
return Artist.objects.annotate(num_scrobbles=Sum("track__scrobble", distinct=True)).filter(time_filter).order_by("-num_scrobbles")[:limit]
def artist_scrobble_count(artist_id: int, filter: str = "today") -> int: def artist_scrobble_count(artist_id: int, filter: str = "today") -> int:
return ( return Scrobble.objects.filter(track__artist=artist_id).count()
Scrobble.objects.filter(track__artist=artist_id)
.count()
)

View File

@ -13,7 +13,7 @@ class ScrobbleAdmin(admin.ModelAdmin):
"playback_position", "playback_position",
"in_progress", "in_progress",
) )
list_filter = ("in_progress", "source") list_filter = ("in_progress", "source", "track__artist")
ordering = ("-timestamp",) ordering = ("-timestamp",)

View File

@ -21,7 +21,12 @@ from scrobbles.models import Scrobble
from scrobbles.serializers import ScrobbleSerializer from scrobbles.serializers import ScrobbleSerializer
from scrobbles.utils import convert_to_seconds from scrobbles.utils import convert_to_seconds
from videos.models import Video from videos.models import Video
from vrobbler.apps.music.aggregators import scrobble_counts, top_tracks, week_of_scrobbles from vrobbler.apps.music.aggregators import (
scrobble_counts,
top_artists,
top_tracks,
week_of_scrobbles,
)
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -52,18 +57,25 @@ class RecentScrobbleList(ListView):
timestamp__gte=last_eight_minutes, timestamp__gte=last_eight_minutes,
timestamp__lte=now, timestamp__lte=now,
) )
data['video_scrobble_list'] = Scrobble.objects.filter(video__isnull=False, in_progress=False).order_by('-timestamp')[:10] data['video_scrobble_list'] = Scrobble.objects.filter(
video__isnull=False, in_progress=False
).order_by('-timestamp')[:10]
data['top_daily_tracks'] = top_tracks() data['top_daily_tracks'] = top_tracks()
data['top_weekly_tracks'] = top_tracks(filter='week') data['top_weekly_tracks'] = top_tracks(filter='week')
data['top_monthly_tracks'] = top_tracks(filter='month') data['top_monthly_tracks'] = top_tracks(filter='month')
data['top_daily_artists'] = top_artists()
data['top_weekly_artists'] = top_artists(filter='week')
data['top_monthly_artists'] = top_artists(filter='month')
data["weekly_data"] = week_of_scrobbles() data["weekly_data"] = week_of_scrobbles()
data['counts'] = scrobble_counts() data['counts'] = scrobble_counts()
return data return data
def get_queryset(self): def get_queryset(self):
return Scrobble.objects.filter(track__isnull=False, in_progress=False).order_by( return Scrobble.objects.filter(
'-timestamp' track__isnull=False, in_progress=False
)[:25] ).order_by('-timestamp')[:25]
@csrf_exempt @csrf_exempt

View File

@ -6,15 +6,18 @@ from os import environ as env
if not 'DJANGO_SETTINGS_MODULE' in env: if not 'DJANGO_SETTINGS_MODULE' in env:
from vrobbler import settings from vrobbler import settings
env.setdefault('DJANGO_SETTINGS_MODULE', settings.__name__) env.setdefault('DJANGO_SETTINGS_MODULE', settings.__name__)
import django import django
django.setup() django.setup()
# this line must be after django.setup() for logging configure # this line must be after django.setup() for logging configure
logger = logging.getLogger('vrobbler') logger = logging.getLogger('vrobbler')
def main(): def main():
# to get configured settings # to get configured settings
from django.conf import settings from django.conf import settings

View File

@ -280,35 +280,35 @@
var myChart = new Chart(ctx, { var myChart = new Chart(ctx, {
type: 'line', type: 'line',
data: { data: {
labels: [ labels: [
{% for day in weekly_data.keys %} {% for day in weekly_data.keys %}
"{{day}}"{% if not forloop.last %},{% endif %} "{{day}}"{% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
], ],
datasets: [{ datasets: [{
data: [ data: [
{% for count in weekly_data.values %} {% for count in weekly_data.values %}
{{count}}{% if not forloop.last %},{% endif %} {{count}}{% if not forloop.last %},{% endif %}
{% endfor %} {% endfor %}
], ],
lineTension: 0, lineTension: 0,
backgroundColor: 'transparent', backgroundColor: 'transparent',
borderColor: '#007bff', borderColor: '#007bf0',
borderWidth: 4, borderWidth: 4,
pointBackgroundColor: '#007bff' pointBackgroundColor: '#007bff'
}] }]
}, },
options: { options: {
scales: { scales: {
yAxes: [{ yAxes: [{
ticks: { ticks: {
beginAtZero: false beginAtZero: false
} }
}] }]
}, },
legend: { legend: {
display: false display: false
} }
} }
}) })
})() })()

View File

@ -20,80 +20,109 @@
<canvas class="my-4 w-100" id="myChart" width="900" height="380"></canvas> <canvas class="my-4 w-100" id="myChart" width="900" height="380"></canvas>
<div class="container">
<h2>Top this week</h2> <div class="row">
<div class="table-responsive"> <div class="col-md">
<table class="table table-striped table-sm"> <h2>Top artists this week</h2>
<thead> <div class="table-responsive">
<tr> <table class="table table-striped table-sm">
<th scope="col">#</th> <thead>
<th scope="col">Track</th> <tr>
<th scope="col">Artist</th> <th scope="col">#</th>
<th scope="col">Album</th> <th scope="col">Artist</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for track in top_weekly_tracks %} {% for artist in top_weekly_artists %}
<tr> <tr>
<td>{{track.num_scrobbles}}</td> <td>{{artist.num_scrobbles}}</td>
<td>{{track.title}}</td> <td>{{artist.name}}</td>
<td>{{track.artist.name}}</td> </tr>
<td>{{track.album.name}}</td> {% endfor %}
</tr> </tbody>
{% endfor %} </table>
</tbody> </div>
</table> </div>
<div class="col-lg">
<h2>Top tracks this week</h2>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">Track</th>
<th scope="col">Artist</th>
</tr>
</thead>
<tbody>
{% for track in top_weekly_tracks %}
<tr>
<td>{{track.num_scrobbles}}</td>
<td>{{track.title}}</td>
<td>{{track.artist.name}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div> </div>
<div class="container">
<div class="row">
<div class="col-lg">
<h2>Latest listened</h2>
<p>Today <b>{{counts.today}}</b> | This Week <b>{{counts.week}}</b> | This Month <b>{{counts.month}}</b> | This Year <b>{{counts.year}}</b> | All Time <b>{{counts.alltime}}</b></p>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Time</th>
<th scope="col">Track</th>
<th scope="col">Artist</th>
<th scope="col">Source</th>
</tr>
</thead>
<tbody>
{% for scrobble in object_list %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.track.title}}</td>
<td>{{scrobble.track.artist.name}}</td>
<td>{{scrobble.source}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<h2>Latest scrobbles</h2> <div class="col-lg">
<p>Today <b>{{counts.today}}</b> | This Week <b>{{counts.week}}</b> | This Month <b>{{counts.month}}</b> | This Year <b>{{counts.year}}</b> | All Time <b>{{counts.alltime}}</b></p> <h2>Latest watched</h2>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-striped table-sm"> <table class="table table-striped table-sm">
<thead> <thead>
<tr> <tr>
<th scope="col">Time</th> <th scope="col">Time</th>
<th scope="col">Track</th> <th scope="col">Title</th>
<th scope="col">Artist</th> <th scope="col">Series</th>
<th scope="col">Source</th> <th scope="col">Source</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for scrobble in object_list %} {% for scrobble in video_scrobble_list %}
<tr> <tr>
<td>{{scrobble.timestamp|naturaltime}}</td> <td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.track.title}}</td> <td>{% if scrobble.video.tv_series %}E{{scrobble.video.season_number}}S{{scrobble.video.season_number}} -{% endif %} {{scrobble.video.title}}</td>
<td>{{scrobble.track.artist.name}}</td> <td>{% if scrobble.video.tv_series %}{{scrobble.video.tv_series}}{% endif %}</td>
<td>{{scrobble.source}}</td> <td>{{scrobble.source}}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
</div>
<h2>Latest watched</h2> </div>
<div class="table-responsive">
<table class="table table-striped table-sm">
<thead>
<tr>
<th scope="col">Time</th>
<th scope="col">Title</th>
<th scope="col">Series</th>
<th scope="col">Season & Episode</th>
<th scope="col">Source</th>
</tr>
</thead>
<tbody>
{% for scrobble in video_scrobble_list %}
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
<td>{{scrobble.video.title}}</td>
<td>{% if scrobble.video.tv_series %}{{scrobble.video.tv_series}}{% endif %}</td>
<td>{% if scrobble.video.tv_series %}{{scrobble.video.season_number}}, {{scrobble.video.episode_number}}{% endif %}</td>
<td>{{scrobble.source}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</main> </main>
{% endblock %} {% endblock %}