Add templates and clean up views for scrobbling

Biggest thing here is adding the ability to scrobble until the video is
95% done and then not scrobble the same video file again for 15 minutes.

This seems hacky, but in practice works pretty well, so long as you
don't monkey around with the 95% completion thing.  Unlike music, videos
are generally watched all the way through.
This commit is contained in:
2023-01-05 01:35:12 -05:00
parent ece9a68165
commit fe47d916e9
14 changed files with 194 additions and 76 deletions

View File

@ -5,7 +5,13 @@ from scrobbles.models import Scrobble
class ScrobbleAdmin(admin.ModelAdmin):
date_hierarchy = "timestamp"
list_display = ("video", "timestamp", "source", "playback_position")
list_display = (
"video",
"timestamp",
"source",
"playback_position",
"in_progress",
)
list_filter = ("video",)
ordering = ("-timestamp",)

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.5 on 2023-01-04 23:11
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scrobbles', '0001_initial'),
]
operations = [
migrations.AddField(
model_name='scrobble',
name='counted',
field=models.BooleanField(default=False),
),
]

View File

@ -0,0 +1,22 @@
# Generated by Django 4.1.5 on 2023-01-04 23:51
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('scrobbles', '0002_scrobble_counted'),
]
operations = [
migrations.RemoveField(
model_name='scrobble',
name='counted',
),
migrations.AddField(
model_name='scrobble',
name='in_progress',
field=models.BooleanField(default=True),
),
]

View File

@ -20,6 +20,13 @@ class Scrobble(TimeStampedModel):
played_to_completion = models.BooleanField(default=False)
source = models.CharField(max_length=255, **BNULL)
source_id = models.TextField(**BNULL)
in_progress = models.BooleanField(default=True)
def percent_played(self):
return int((self.playback_position_ticks / video.run_time_ticks) * 100)
@property
def percent_played(self) -> int:
return int(
(self.playback_position_ticks / self.video.run_time_ticks) * 100
)
def __str__(self):
return f"Scrobble of {self.video} {self.timestamp.year}-{self.timestamp.month}"

View File

@ -1,25 +0,0 @@
<!doctype html>
<html class="no-js" lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<title>Untitled</title>
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
<!-- Place favicon.ico in the root directory -->
</head>
<body>
<!--[if lt IE 8]>
<p class="browserupgrade">
You are using an <strong>outdated</strong> browser. Please
<a href="http://browsehappy.com/">upgrade your browser</a> to improve
your experience.
</p>
<![endif]-->
</body>
</html>

View File

@ -1,7 +1,10 @@
import json
import logging
from datetime import datetime, timedelta
import dateutil
from dateutil.parser import parse
from django.conf import settings
from django.db.models.fields import timezone
from django.views.decorators.csrf import csrf_exempt
from django.views.generic.list import ListView
from rest_framework import status
@ -30,6 +33,9 @@ TRUTHY_VALUES = [
class RecentScrobbleList(ListView):
model = Scrobble
def get_queryset(self):
return Scrobble.objects.filter(in_progress=False)
@csrf_exempt
@api_view(['GET', 'POST'])
@ -41,58 +47,92 @@ def scrobble_list(request):
return Response(serializer.data)
elif request.method == 'POST':
data_dict = json.loads(request.data["_content"])
data_dict = request.data
media_type = data_dict["ItemType"]
# Check if it's a TV Episode
video_dict = {
"title": data_dict["Name"],
"imdb_id": data_dict["Provider_imdb"],
"video_type": Video.VideoType.MOVIE,
"year": data_dict["Year"],
}
if media_type == 'Episode':
series_name = data_dict["SeriesName"]
series = Series.objects.get_or_create(name=series_name)
series, series_created = Series.objects.get_or_create(
name=series_name
)
video_dict['video_type'] = Video.VideoType.TV_EPISODE
video_dict["tv_series_id"] = series.id
video_dict["episode_num"] = data_dict["EpisodeNumber"]
video_dict["season_num"] = data_dict["SeasonNumber"]
video_dict["tvdb_id"] = data_dict["Provider_tvdb"]
video_dict["tvrage_id"] = data_dict["Provider_tvrage"]
video_dict["episode_number"] = data_dict["EpisodeNumber"]
video_dict["season_number"] = data_dict["SeasonNumber"]
video_dict["tvdb_id"] = data_dict.get("Provider_tvdb", None)
video_dict["tvrage_id"] = data_dict.get("Provider_tvrage", None)
video, _created = Video.objects.get_or_create(**video_dict)
video, video_created = Video.objects.get_or_create(**video_dict)
video.year = data_dict["Year"]
video.overview = data_dict["Overview"]
video.tagline = data_dict["Tagline"]
video.run_time_ticks = data_dict["RunTimeTicks"]
video.run_time = data_dict["RunTime"]
video.save()
if video_created:
video.overview = data_dict["Overview"]
video.tagline = data_dict["Tagline"]
video.run_time_ticks = data_dict["RunTimeTicks"]
video.run_time = data_dict["RunTime"]
video.save()
# Now we run off a scrobble
timestamp = parse(data_dict["UtcTimestamp"])
scrobble_dict = {
'video_id': video.id,
'user_id': request.user.id,
'in_progress': True,
}
existing_finished_scrobble = (
Scrobble.objects.filter(
video=video, user_id=request.user.id, in_progress=False
)
.order_by('-modified')
.first()
)
minutes_from_now = timezone.now() + timedelta(minutes=15)
if (
existing_finished_scrobble
and existing_finished_scrobble.modified < minutes_from_now
):
logger.info(
'Found a scrobble for this video less than 15 minutes ago, holding off scrobbling again'
)
return Response(video_dict, status=status.HTTP_204_NO_CONTENT)
scrobble, scrobble_created = Scrobble.objects.get_or_create(
**scrobble_dict
)
if scrobble_created:
# If we newly created this, capture the client we're watching from
scrobble.source = data_dict['ClientName']
scrobble.source_id = data_dict['MediaSourceId']
else:
last_tick = scrobble.playback_position_ticks
# Update a found scrobble with new position and timestamp
scrobble.playback_position_ticks = data_dict["PlaybackPositionTicks"]
scrobble.playback_position = data_dict["PlaybackPosition"]
scrobble.timestamp = dateutil.parser.parse(data_dict["UtcTimestamp"])
scrobble.timestamp = parse(data_dict["UtcTimestamp"])
scrobble.is_paused = data_dict["IsPaused"] in TRUTHY_VALUES
scrobble.played_to_completion = (
data_dict["PlayedToCompletion"] in TRUTHY_VALUES
)
scrobble.save()
logger.info(f"You are {scrobble.percent_played}% through {video}")
# If we hit our completion threshold, save it and get ready
# to scrobble again if we re-watch this.
if scrobble.percent_played >= getattr(
settings, "PERCENT_FOR_COMPLETION", 95
):
scrobble.in_progress = False
scrobble.playback_position_ticks = video.run_time_ticks
scrobble.save()
if scrobble.percent_played % 5 == 0:
logger.info(f"You are {scrobble.percent_played}% through {video}")
return Response(video_dict, status=status.HTTP_201_CREATED)
# return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)