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:
@ -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",)
|
||||
|
||||
|
||||
18
scrobbles/migrations/0002_scrobble_counted.py
Normal file
18
scrobbles/migrations/0002_scrobble_counted.py
Normal 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),
|
||||
),
|
||||
]
|
||||
@ -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),
|
||||
),
|
||||
]
|
||||
@ -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}"
|
||||
|
||||
@ -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>
|
||||
@ -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)
|
||||
|
||||
Reference in New Issue
Block a user