Add scrobble log option and CLI tool
This commit is contained in:
@ -62,9 +62,7 @@ combine_as_imports = true
|
|||||||
exclude_dirs = ["*/tests/*", "*/migrations/*"]
|
exclude_dirs = ["*/tests/*", "*/migrations/*"]
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
server = 'scripts:server'
|
vrobbler = "vrobbler.cli:main"
|
||||||
migrate = 'scripts:migrate'
|
|
||||||
shell = 'scripts:shell'
|
|
||||||
|
|
||||||
[build-system]
|
[build-system]
|
||||||
requires = ["poetry-core>=1.0.0"]
|
requires = ["poetry-core>=1.0.0"]
|
||||||
|
|||||||
18
scrobbles/migrations/0004_scrobble_scrobble_log.py
Normal file
18
scrobbles/migrations/0004_scrobble_scrobble_log.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 4.1.5 on 2023-01-05 17:50
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('scrobbles', '0003_remove_scrobble_counted_scrobble_in_progress'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='scrobble',
|
||||||
|
name='scrobble_log',
|
||||||
|
field=models.TextField(blank=True, null=True),
|
||||||
|
),
|
||||||
|
]
|
||||||
@ -21,6 +21,7 @@ class Scrobble(TimeStampedModel):
|
|||||||
source = models.CharField(max_length=255, **BNULL)
|
source = models.CharField(max_length=255, **BNULL)
|
||||||
source_id = models.TextField(**BNULL)
|
source_id = models.TextField(**BNULL)
|
||||||
in_progress = models.BooleanField(default=True)
|
in_progress = models.BooleanField(default=True)
|
||||||
|
scrobble_log = models.TextField(**BNULL)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def percent_played(self) -> int:
|
def percent_played(self) -> int:
|
||||||
|
|||||||
@ -34,7 +34,9 @@ class RecentScrobbleList(ListView):
|
|||||||
model = Scrobble
|
model = Scrobble
|
||||||
|
|
||||||
def get_queryset(self):
|
def get_queryset(self):
|
||||||
return Scrobble.objects.filter(in_progress=False)
|
return Scrobble.objects.filter(in_progress=False).order_by(
|
||||||
|
'-timestamp'
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@csrf_exempt
|
@csrf_exempt
|
||||||
@ -93,8 +95,16 @@ def jellyfin_websocket(request):
|
|||||||
.order_by('-modified')
|
.order_by('-modified')
|
||||||
.first()
|
.first()
|
||||||
)
|
)
|
||||||
|
existing_in_progress_scrobble = (
|
||||||
|
Scrobble.objects.filter(
|
||||||
|
video=video, user_id=request.user.id, in_progress=True
|
||||||
|
)
|
||||||
|
.order_by('-modified')
|
||||||
|
.first()
|
||||||
|
)
|
||||||
|
|
||||||
minutes_from_now = timezone.now() + timedelta(minutes=15)
|
minutes_from_now = timezone.now() + timedelta(minutes=15)
|
||||||
|
a_day_from_now = timezone.now() + timedelta(days=1)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
existing_finished_scrobble
|
existing_finished_scrobble
|
||||||
@ -105,6 +115,19 @@ def jellyfin_websocket(request):
|
|||||||
)
|
)
|
||||||
return Response(video_dict, status=status.HTTP_204_NO_CONTENT)
|
return Response(video_dict, status=status.HTTP_204_NO_CONTENT)
|
||||||
|
|
||||||
|
# Check if found in progress scrobble is more than a day old
|
||||||
|
if not (
|
||||||
|
existing_in_progress_scrobble
|
||||||
|
and existing_in_progress_scrobble.modified < a_day_from_now
|
||||||
|
):
|
||||||
|
logger.info(
|
||||||
|
'Found a scrobble for this video more than a day old, creating a new scrobble'
|
||||||
|
)
|
||||||
|
scrobble = existing_in_progress_scrobble
|
||||||
|
scrobble_created = False
|
||||||
|
else:
|
||||||
|
if getattr(settings, "DELETE_STALE_SCROBBLES", True):
|
||||||
|
existing_in_progress_scrobble.delete()
|
||||||
scrobble, scrobble_created = Scrobble.objects.get_or_create(
|
scrobble, scrobble_created = Scrobble.objects.get_or_create(
|
||||||
**scrobble_dict
|
**scrobble_dict
|
||||||
)
|
)
|
||||||
@ -113,6 +136,7 @@ def jellyfin_websocket(request):
|
|||||||
# If we newly created this, capture the client we're watching from
|
# If we newly created this, capture the client we're watching from
|
||||||
scrobble.source = data_dict['ClientName']
|
scrobble.source = data_dict['ClientName']
|
||||||
scrobble.source_id = data_dict['MediaSourceId']
|
scrobble.source_id = data_dict['MediaSourceId']
|
||||||
|
scrobble.scrobble_log = ""
|
||||||
else:
|
else:
|
||||||
last_tick = scrobble.playback_position_ticks
|
last_tick = scrobble.playback_position_ticks
|
||||||
|
|
||||||
@ -121,7 +145,14 @@ def jellyfin_websocket(request):
|
|||||||
scrobble.playback_position = data_dict["PlaybackPosition"]
|
scrobble.playback_position = data_dict["PlaybackPosition"]
|
||||||
scrobble.timestamp = parse(data_dict["UtcTimestamp"])
|
scrobble.timestamp = parse(data_dict["UtcTimestamp"])
|
||||||
scrobble.is_paused = data_dict["IsPaused"] in TRUTHY_VALUES
|
scrobble.is_paused = data_dict["IsPaused"] in TRUTHY_VALUES
|
||||||
scrobble.save()
|
scrobble.save(
|
||||||
|
update_fields=[
|
||||||
|
'playback_position_ticks',
|
||||||
|
'playback_position',
|
||||||
|
'timestamp',
|
||||||
|
'is_paused',
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# If we hit our completion threshold, save it and get ready
|
# If we hit our completion threshold, save it and get ready
|
||||||
# to scrobble again if we re-watch this.
|
# to scrobble again if we re-watch this.
|
||||||
@ -133,6 +164,9 @@ def jellyfin_websocket(request):
|
|||||||
scrobble.save()
|
scrobble.save()
|
||||||
|
|
||||||
if scrobble.percent_played % 5 == 0:
|
if scrobble.percent_played % 5 == 0:
|
||||||
logger.info(f"You are {scrobble.percent_played}% through {video}")
|
if getattr(settings, "KEEP_DETAILED_SCROBBLE_LOGS", False):
|
||||||
|
scrobble.scrobble_log += f"\n{str(scrobble.timestamp)} - {scrobble.playback_position} - {str(scrobble.playback_position_ticks)} - {str(scrobble.percent_played)}%"
|
||||||
|
scrobble.save(update_fields=['scrobble_log'])
|
||||||
|
logger.debug(f"You are {scrobble.percent_played}% through {video}")
|
||||||
|
|
||||||
return Response(video_dict, status=status.HTTP_201_CREATED)
|
return Response(video_dict, status=status.HTTP_201_CREATED)
|
||||||
|
|||||||
34
vrobbler/cli.py
Normal file
34
vrobbler/cli.py
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
# cli.py
|
||||||
|
import logging
|
||||||
|
import sys
|
||||||
|
from os import environ as env
|
||||||
|
|
||||||
|
|
||||||
|
if not 'DJANGO_SETTINGS_MODULE' in env:
|
||||||
|
from vrobbler import settings
|
||||||
|
env.setdefault('DJANGO_SETTINGS_MODULE', settings.__name__)
|
||||||
|
|
||||||
|
|
||||||
|
import django
|
||||||
|
django.setup()
|
||||||
|
|
||||||
|
# this line must be after django.setup() for logging configure
|
||||||
|
logger = logging.getLogger('vrobbler')
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# to get configured settings
|
||||||
|
from django.conf import settings
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@ -32,6 +32,12 @@ DEBUG = os.getenv("VROBBLER_DEBUG", False)
|
|||||||
|
|
||||||
TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
|
TESTING = len(sys.argv) > 1 and sys.argv[1] == "test"
|
||||||
|
|
||||||
|
KEEP_DETAILED_SCROBBLE_LOGS = os.getenv(
|
||||||
|
"VROBBLER_KEEP_DETAILED_SCROBBLE_LOGS", False
|
||||||
|
)
|
||||||
|
|
||||||
|
DELETE_STALE_SCROBBLES = os.getenv("VROBBLER_DELETE_STALE_SCROBBLES", True)
|
||||||
|
|
||||||
TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "")
|
TMDB_API_KEY = os.getenv("VROBBLER_TMDB_API_KEY", "")
|
||||||
|
|
||||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||||
@ -61,7 +67,6 @@ INSTALLED_APPS = [
|
|||||||
"django_filters",
|
"django_filters",
|
||||||
"django_extensions",
|
"django_extensions",
|
||||||
'rest_framework.authtoken',
|
'rest_framework.authtoken',
|
||||||
"vrobbler",
|
|
||||||
"scrobbles",
|
"scrobbles",
|
||||||
"videos",
|
"videos",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
|
|||||||
Reference in New Issue
Block a user