From a5553966de93da942756aedfd934b10bb596d000 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Wed, 31 Jan 2024 01:19:58 -0500 Subject: [PATCH] Add script for building scrobbles from past pages --- .../commands/migrate_koreader_data_to_json.py | 129 +++++++++++++++++- 1 file changed, 128 insertions(+), 1 deletion(-) diff --git a/vrobbler/apps/books/management/commands/migrate_koreader_data_to_json.py b/vrobbler/apps/books/management/commands/migrate_koreader_data_to_json.py index 5aea114..109671f 100644 --- a/vrobbler/apps/books/management/commands/migrate_koreader_data_to_json.py +++ b/vrobbler/apps/books/management/commands/migrate_koreader_data_to_json.py @@ -1,9 +1,22 @@ -from django.core.management.base import BaseCommand +import logging +import pytz +from datetime import datetime from books.models import Book +from django.core.management.base import BaseCommand +from scrobbles.models import Scrobble +from vrobbler.apps.scrobbles.utils import timestamp_user_tz_to_utc + + +logger = logging.getLogger(__name__) + +# Grace period between page reads for it to be a new scrobble +SESSION_GAP_SECONDS = 1800 # a half hour class Command(BaseCommand): def handle(self, *args, **options): + scrobbles_to_create = [] + for book in Book.objects.all(): koreader_data = book.koreader_data_by_hash or {} if book.koreader_md5: @@ -15,3 +28,117 @@ class Command(BaseCommand): } book.koreader_data_by_hash = koreader_data book.save(update_fields=["koreader_data_by_hash"]) + + # Next parse all this book's pages into new scrobbles + should_create_scrobble = False + scrobble_page_data = {} + playback_position_seconds = 0 + prev_page = None + + pages_processed = 0 + total_pages = book.pages + for page in book.page_set.order_by("number"): + user = page.user + book_id = page.book.id + pages_processed += 1 + scrobble_page_data[page.number] = { + "duration": page.duration_seconds, + "start_ts": page.start_time.timestamp(), + "end_ts": page.end_time.timestamp(), + } + seconds_from_last_page = 0 + if prev_page: + seconds_from_last_page = ( + page.end_time.timestamp() + - prev_page.start_ts.timestamp() + ) + playback_position_seconds = ( + playback_position_seconds + page.duration_seconds + ) + + end_of_reading = pages_processed == total_pages + if ( + seconds_from_last_page > SESSION_GAP_SECONDS + or end_of_reading + ): + should_create_scrobble = True + + if should_create_scrobble: + first_page = scrobble_page_data.get( + list(scrobble_page_data.keys())[0] + ) + last_page = scrobble_page_data.get( + list(scrobble_page_data.keys())[-1] + ) + start_ts = int(first_page.get("start_ts")) + end_ts = start_ts + playback_position_seconds + + timestamp = datetime.fromtimestamp(start_ts).replace( + tzinfo=user.profile.tzinfo + ) + stop_timestamp = datetime.fromtimestamp(end_ts).replace( + tzinfo=user.profile.tzinfo + ) + # Add a shim here temporarily to fix imports while we were in France + # if date is between 10/15 and 12/15, cast it to Europe/Central + if ( + datetime(2023, 10, 15).replace( + tzinfo=pytz.timezone("Europe/Paris") + ) + <= timestamp + <= datetime(2023, 12, 15).replace( + tzinfo=pytz.timezone("Europe/Paris") + ) + ): + timestamp.replace(tzinfo=pytz.timezone("Europe/Paris")) + + scrobble = Scrobble.objects.filter( + timestamp=timestamp, + book_id=book_id, + user_id=user.id, + ).first() + if scrobble: + logger.info( + f"Found existing scrobble {scrobble}, updating" + ) + scrobble.book_page_data = scrobble_page_data + scrobble.playback_position_seconds = ( + scrobble.calc_reading_duration() + ) + scrobble.save( + update_fields=[ + "book_page_data", + "playback_position_seconds", + ] + ) + if not scrobble: + logger.info( + f"Queueing scrobble for {book_id}, page {page.number}" + ) + scrobbles_to_create.append( + Scrobble( + book_id=book_id, + user_id=user.id, + source="KOReader", + media_type=Scrobble.MediaType.BOOK, + timestamp=timestamp, + stop_timestamp=stop_timestamp, + playback_position_seconds=playback_position_seconds, + book_koreader_hash=book.koreader_data_by_hash.keys()[ + 0 + ], + book_page_data=scrobble_page_data, + book_pages_read=page.number, + in_progress=False, + played_to_completion=True, + long_play_complete=False, + ) + ) + # Then start over + should_create_scrobble = False + playback_position_seconds = 0 + scrobble_page_data = {} + + prev_page = page + + created = Scrobble.objects.bulk_create(scrobbles_to_create)