Compare commits

..

20 Commits

Author SHA1 Message Date
a59bcf054a Bump version to 0.13.2 2023-04-02 23:58:16 -04:00
fe4faee7aa Add an end timestamp 2023-04-02 23:57:55 -04:00
5db8bf0329 Move check for finish up a level 2023-04-02 23:44:07 -04:00
d085bf2153 Don't bother with authors in book metadata 2023-04-02 23:21:34 -04:00
a133e7a30c Someone fixed an NPR typo 2023-04-02 23:01:00 -04:00
ca59605afc Dont bail on stop if not in progress 2023-04-02 22:46:58 -04:00
597ac2c7b8 Add save game data for video game scrobbles 2023-04-02 22:43:58 -04:00
f04f8b04c0 Ooops, need to pop after updating 2023-04-02 22:37:56 -04:00
2215976571 Fix jellyfin duping scrobbles after complete 2023-04-02 18:48:55 -04:00
e6bb52702c Fix migration with timezone issue 2023-03-30 02:00:14 -04:00
0dc0102bb6 Refactor long play finishing 2023-03-30 01:55:36 -04:00
7d1e070ee6 Fix scrobbles same song over and over error 2023-03-30 01:48:20 -04:00
6c060f24ec Default timezone to one that respects DST 2023-03-30 00:34:17 -04:00
b6a0f0d3fb Fix koreader importing 2023-03-30 00:34:09 -04:00
1c6f28bae3 Fix bad lookup of covers in long play template 2023-03-29 15:40:44 -04:00
845ee7d4e9 Revert "First run at adding thumbnailing to images"
This reverts commit c00343abfe.
2023-03-28 15:33:53 -04:00
dadc5db0f9 Revert "Fix bug in thumbnail tag"
This reverts commit c39430e987.
2023-03-28 15:33:47 -04:00
c4ddb4b51c Revert "Let's also thumbnail the now playing widget"
This reverts commit 76cc1f7b1c.
2023-03-28 15:33:37 -04:00
76cc1f7b1c Let's also thumbnail the now playing widget 2023-03-28 15:24:04 -04:00
c39430e987 Fix bug in thumbnail tag 2023-03-28 14:45:23 -04:00
20 changed files with 218 additions and 787 deletions

14
poetry.lock generated
View File

@ -1580,14 +1580,6 @@ category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "sorl-thumbnail"
version = "12.9.0"
description = "Thumbnails for Django"
category = "main"
optional = false
python-versions = ">=3.7"
[[package]]
name = "sortedcontainers"
version = "2.4.0"
@ -1893,7 +1885,7 @@ testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more
[metadata]
lock-version = "1.1"
python-versions = "^3.8"
content-hash = "b355c69693f7e45bf42429bcb195dceae3f2057e0b46fd937188cb649f972a24"
content-hash = "2076d7c876e5086dfa822014940f0921879f505f38f9175a77320082b4439430"
[metadata.files]
aiohttp = [
@ -3152,10 +3144,6 @@ sniffio = [
{file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"},
{file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"},
]
sorl-thumbnail = [
{file = "sorl-thumbnail-12.9.0.tar.gz", hash = "sha256:0cbc2f52152e7f2266e3c2cb4ae5d83afd2e96fd5e1c42e5667362baaa3d2db3"},
{file = "sorl_thumbnail-12.9.0-py3-none-any.whl", hash = "sha256:ec586724bea7dc8c53561ce18335ea641fcd0e560d6387b2353a2afdd06705a4"},
]
sortedcontainers = [
{file = "sortedcontainers-2.4.0-py2.py3-none-any.whl", hash = "sha256:a163dcaede0f1c021485e957a39245190e74249897e2ae4b2aa38595db237ee0"},
{file = "sortedcontainers-2.4.0.tar.gz", hash = "sha256:25caa5a06cc30b6b83d11423433f65d1f9d76c4c6a0c90e3379eaa43b9bfdb88"},

View File

@ -40,7 +40,6 @@ beautifulsoup4 = "^4.11.2"
django-storages = "^1.13.2"
boto3 = "^1.26.98"
stream-sqlite = "^0.0.41"
sorl-thumbnail = "^12.9.0"
[tool.poetry.dev-dependencies]
Werkzeug = "2.0.3"

View File

@ -4,7 +4,7 @@ expected_desc = (
"NPR's Up First is the news you need to start your day. "
"The three biggest stories of the day, with reporting and analysis "
"from NPR News — in 10 minutes. Available weekdays by 6 a.m. ET, "
"with hosts Leila Fadel, Steve Inskeep, Rachel Martin and A Martinez. "
"with hosts Leila Fadel, Steve Inskeep, Michel Martin and A Martinez. "
"Also available on Saturdays by 8 a.m. ET, with Ayesha Rascoe and "
"Scott Simon. On Sundays, hear a longer exploration behind the "
"headlines with Rachel Martin, available by 8 a.m. ET. Subscribe "

View File

@ -3,7 +3,8 @@
A fun way to keep track of things in the project to fix or improve.
* Version 1.0.0
** TODO Add a "finished_timestamp" so we don't rely on content length :improvement:scrobbling:
** DONE Add a "stop_timestamp" so we don't rely on content length :improvement:scrobbling:
CLOSED: [2023-04-02 Sun 23:58]
Essentially, we currently have the timestamp as when the content began
scrobbling and then calculate the finish time from the length of the content.

View File

@ -1,7 +1,7 @@
import re
import codecs
import logging
import os
import re
import sqlite3
from datetime import datetime
from enum import Enum
@ -10,10 +10,11 @@ from typing import Iterable, List
import pytz
import requests
from books.models import Author, Book, Page
from books.openlibrary import get_author_openlibrary_id
from django.db.models import Sum
from pylast import httpx, tempfile
from scrobbles.models import Scrobble
from stream_sqlite import stream_sqlite
from vrobbler.apps.books.openlibrary import get_author_openlibrary_id
logger = logging.getLogger(__name__)
@ -105,22 +106,15 @@ def get_book_map_from_sqlite(rows: Iterable) -> dict:
# This will try to fix metadata by looking it up on OL
book.fix_metadata()
playback_position_seconds = 0
if book_row[KoReaderBookColumn.TOTAL_READ_TIME.value]:
playback_position_seconds = book_row[
KoReaderBookColumn.TOTAL_READ_TIME.value
]
pages_read = 0
if book_row[KoReaderBookColumn.TOTAL_READ_PAGES.value]:
pages_read = int(
book_row[KoReaderBookColumn.TOTAL_READ_PAGES.value]
)
timestamp = datetime.utcfromtimestamp(
book_row[KoReaderBookColumn.LAST_OPEN.value]
).replace(tzinfo=pytz.utc)
book.refresh_from_db()
book_id_map[book_row[KoReaderBookColumn.ID.value]] = book.id
total_seconds = 0
if book_row[KoReaderBookColumn.TOTAL_READ_TIME.value]:
total_seconds = book_row[KoReaderBookColumn.TOTAL_READ_TIME.value]
book_id_map[book_row[KoReaderBookColumn.ID.value]] = (
book.id,
total_seconds,
)
return book_id_map
@ -131,12 +125,14 @@ def build_scrobbles_from_pages(
new_scrobbles = []
new_scrobbles = []
pages_created = []
pages_found = []
book_read_time_map = {}
for page_row in rows:
koreader_id = page_row[KoReaderPageStatColumn.ID_BOOK.value]
page_number = page_row[KoReaderPageStatColumn.PAGE.value]
ts = page_row[KoReaderPageStatColumn.START_TIME.value]
book_id = book_id_map[koreader_id]
book_id = book_id_map[koreader_id][0]
book_read_time_map[book_id] = book_id_map[koreader_id][1]
page, page_created = Page.objects.get_or_create(
book_id=book_id, number=page_number, user_id=user_id
@ -149,25 +145,39 @@ def build_scrobbles_from_pages(
KoReaderPageStatColumn.DURATION.value
]
page.save(update_fields=["start_time", "duration_seconds"])
pages_created.append(page)
pages_found.append(page)
for page in pages_created:
playback_position_seconds = 0
for page in set(pages_found):
# Add up page seconds to set the aggregate time of all pages to reading time
playback_position_seconds = (
playback_position_seconds + page.duration_seconds
)
if page.is_scrobblable:
# Page number is a placeholder, we'll re-preocess this after creation
logger.debug(
f"Queueing scrobble for {page.book}, page {page.number}"
)
new_scrobble = Scrobble(
# Check to see if a scrobble with this timestamp, book and user already exists
scrobble = Scrobble.objects.filter(
timestamp=page.start_time,
book_id=page.book_id,
user_id=user_id,
source="KOReader",
timestamp=page.start_time,
played_to_completion=True,
in_progress=False,
book_pages_read=page.number,
long_play_complete=False,
)
new_scrobbles.append(new_scrobble)
).first()
if not scrobble:
logger.debug(
f"Queueing scrobble for {page.book}, page {page.number}"
)
new_scrobble = Scrobble(
book_id=page.book_id,
user_id=user_id,
source="KOReader",
timestamp=page.start_time,
played_to_completion=True,
playback_position_seconds=playback_position_seconds,
in_progress=False,
book_pages_read=page.number,
long_play_complete=False,
)
new_scrobbles.append(new_scrobble)
# After setting a scrobblable page, reset our accumulator
playback_position_seconds = 0
return new_scrobbles
@ -176,15 +186,16 @@ def enrich_koreader_scrobbles(scrobbles: list) -> None:
for media completion"""
for scrobble in scrobbles:
scrobble.book_pages_read = scrobble.book.page_set.last().number
# But if there's a next scrobble, set pages read to their starting page
#
if scrobble.next:
# Set pages read to the starting page of the next scrobble minus one, if it exists
scrobble.book_pages_read = scrobble.next.book_pages_read - 1
scrobble.save(update_fields=["book_pages_read"])
else:
# Set pages read to the last page we have
scrobble.book_pages_read = scrobble.book.page_set.last().number
scrobble.long_play_seconds = scrobble.book.page_set.filter(
number__lte=scrobble.book_pages_read
).aggregate(Sum("duration_seconds"))["duration_seconds__sum"]
scrobble.save(update_fields=["book_pages_read", "long_play_complete"])
scrobble.save(update_fields=["book_pages_read", "long_play_seconds"])
def process_koreader_sqlite_url(file_url, user_id) -> list:
@ -194,6 +205,7 @@ def process_koreader_sqlite_url(file_url, user_id) -> list:
for table_name, pragma_table_info, rows in stream_sqlite(
_sqlite_bytes(file_url), max_buffer_size=1_048_576
):
logger.debug(f"Found table {table_name} - processing")
if table_name == "book":
book_id_map = get_book_map_from_sqlite(rows)
@ -201,6 +213,7 @@ def process_koreader_sqlite_url(file_url, user_id) -> list:
new_scrobbles = build_scrobbles_from_pages(
rows, book_id_map, user_id
)
logger.debug(f"Creating {len(new_scrobbles)} new scrobbles")
created = []
if new_scrobbles:

View File

@ -112,7 +112,7 @@ class Book(LongPlayScrobblableMixin):
if book_dict.get("pages") == None:
book_dict.pop("pages")
ol_title = book_dict.get("title")
ol_title = book_dict.get("title", "")
if ol_title.lower() != self.title.lower():
logger.warn(
@ -122,21 +122,6 @@ class Book(LongPlayScrobblableMixin):
Book.objects.filter(pk=self.id).update(**book_dict)
self.refresh_from_db()
# Process authors
author = None
author_created = False
if ol_author_id:
author, author_created = Author.objects.get_or_create(
openlibrary_id=ol_author_id
)
if author_created or force_update:
author.fix_metadata()
if not author and ol_author_name:
author, author_created = Author.objects.get_or_create(
name=ol_author_name
)
self.authors.add(author)
if cover_url:
r = requests.get(cover_url)
if r.status_code == 200:

View File

@ -17,7 +17,6 @@ from music.models import Album, Artist, Track
def get_or_create_artist(name: str, mbid: str = None) -> Artist:
artist = None
logger.debug(f"Got artist {name} and mbid: {mbid}")
if "feat." in name.lower():
name = re.split("feat.", name, flags=re.IGNORECASE)[0].strip()
@ -29,13 +28,9 @@ def get_or_create_artist(name: str, mbid: str = None) -> Artist:
artist_dict = lookup_artist_from_mb(name)
mbid = mbid or artist_dict["id"]
logger.debug(f"Looking up artist {name} and mbid: {mbid}")
artist = Artist.objects.filter(musicbrainz_id=mbid).first()
if not artist:
artist = Artist.objects.create(name=name, musicbrainz_id=mbid)
logger.debug(
f"Created artist {artist.name} ({artist.musicbrainz_id}) "
)
artist.fix_metadata()
return artist
@ -48,9 +43,6 @@ def get_or_create_album(
album_dict = lookup_album_dict_from_mb(name, artist_name=artist.name)
name = name or album_dict["title"]
logger.debug(f"Looking up album {name} and mbid: {mbid}")
album = Album.objects.filter(artists__in=[artist], name=name).first()
if not album and name:

View File

@ -1,6 +1,7 @@
# Generated by Django 4.1.5 on 2023-03-22 22:26
from django.db import migrations, models
from profiles.constants import PRETTY_TIMEZONE_CHOICES
class Migration(migrations.Migration):
@ -15,586 +16,7 @@ class Migration(migrations.Migration):
name="timezone",
field=models.CharField(
blank=True,
choices=[
("Pacific/Midway", "(GMT-1100) Pacific/Midway"),
("Pacific/Niue", "(GMT-1100) Pacific/Niue"),
("Pacific/Pago_Pago", "(GMT-1100) Pacific/Pago_Pago"),
("Pacific/Honolulu", "(GMT-1000) Pacific/Honolulu"),
("Pacific/Rarotonga", "(GMT-1000) Pacific/Rarotonga"),
("Pacific/Tahiti", "(GMT-1000) Pacific/Tahiti"),
("US/Hawaii", "(GMT-1000) US/Hawaii"),
("Pacific/Marquesas", "(GMT-0930) Pacific/Marquesas"),
("America/Adak", "(GMT-0900) America/Adak"),
("Pacific/Gambier", "(GMT-0900) Pacific/Gambier"),
("America/Anchorage", "(GMT-0800) America/Anchorage"),
("America/Juneau", "(GMT-0800) America/Juneau"),
("America/Metlakatla", "(GMT-0800) America/Metlakatla"),
("America/Nome", "(GMT-0800) America/Nome"),
("America/Sitka", "(GMT-0800) America/Sitka"),
("America/Yakutat", "(GMT-0800) America/Yakutat"),
("Pacific/Pitcairn", "(GMT-0800) Pacific/Pitcairn"),
("US/Alaska", "(GMT-0800) US/Alaska"),
("America/Creston", "(GMT-0700) America/Creston"),
("America/Dawson", "(GMT-0700) America/Dawson"),
(
"America/Dawson_Creek",
"(GMT-0700) America/Dawson_Creek",
),
("America/Fort_Nelson", "(GMT-0700) America/Fort_Nelson"),
("America/Hermosillo", "(GMT-0700) America/Hermosillo"),
("America/Los_Angeles", "(GMT-0700) America/Los_Angeles"),
("America/Mazatlan", "(GMT-0700) America/Mazatlan"),
("America/Phoenix", "(GMT-0700) America/Phoenix"),
("America/Tijuana", "(GMT-0700) America/Tijuana"),
("America/Vancouver", "(GMT-0700) America/Vancouver"),
("America/Whitehorse", "(GMT-0700) America/Whitehorse"),
("Canada/Pacific", "(GMT-0700) Canada/Pacific"),
("US/Arizona", "(GMT-0700) US/Arizona"),
("US/Pacific", "(GMT-0700) US/Pacific"),
(
"America/Bahia_Banderas",
"(GMT-0600) America/Bahia_Banderas",
),
("America/Belize", "(GMT-0600) America/Belize"),
("America/Boise", "(GMT-0600) America/Boise"),
(
"America/Cambridge_Bay",
"(GMT-0600) America/Cambridge_Bay",
),
("America/Chihuahua", "(GMT-0600) America/Chihuahua"),
(
"America/Ciudad_Juarez",
"(GMT-0600) America/Ciudad_Juarez",
),
("America/Costa_Rica", "(GMT-0600) America/Costa_Rica"),
("America/Denver", "(GMT-0600) America/Denver"),
("America/Edmonton", "(GMT-0600) America/Edmonton"),
("America/El_Salvador", "(GMT-0600) America/El_Salvador"),
("America/Guatemala", "(GMT-0600) America/Guatemala"),
("America/Inuvik", "(GMT-0600) America/Inuvik"),
("America/Managua", "(GMT-0600) America/Managua"),
("America/Merida", "(GMT-0600) America/Merida"),
("America/Mexico_City", "(GMT-0600) America/Mexico_City"),
("America/Monterrey", "(GMT-0600) America/Monterrey"),
("America/Regina", "(GMT-0600) America/Regina"),
(
"America/Swift_Current",
"(GMT-0600) America/Swift_Current",
),
("America/Tegucigalpa", "(GMT-0600) America/Tegucigalpa"),
("America/Yellowknife", "(GMT-0600) America/Yellowknife"),
("Canada/Mountain", "(GMT-0600) Canada/Mountain"),
("Pacific/Galapagos", "(GMT-0600) Pacific/Galapagos"),
("US/Mountain", "(GMT-0600) US/Mountain"),
("America/Atikokan", "(GMT-0500) America/Atikokan"),
("America/Bogota", "(GMT-0500) America/Bogota"),
("America/Cancun", "(GMT-0500) America/Cancun"),
("America/Cayman", "(GMT-0500) America/Cayman"),
("America/Chicago", "(GMT-0500) America/Chicago"),
("America/Eirunepe", "(GMT-0500) America/Eirunepe"),
("America/Guayaquil", "(GMT-0500) America/Guayaquil"),
(
"America/Indiana/Knox",
"(GMT-0500) America/Indiana/Knox",
),
(
"America/Indiana/Tell_City",
"(GMT-0500) America/Indiana/Tell_City",
),
("America/Jamaica", "(GMT-0500) America/Jamaica"),
("America/Lima", "(GMT-0500) America/Lima"),
("America/Matamoros", "(GMT-0500) America/Matamoros"),
("America/Menominee", "(GMT-0500) America/Menominee"),
(
"America/North_Dakota/Beulah",
"(GMT-0500) America/North_Dakota/Beulah",
),
(
"America/North_Dakota/Center",
"(GMT-0500) America/North_Dakota/Center",
),
(
"America/North_Dakota/New_Salem",
"(GMT-0500) America/North_Dakota/New_Salem",
),
("America/Ojinaga", "(GMT-0500) America/Ojinaga"),
("America/Panama", "(GMT-0500) America/Panama"),
(
"America/Rankin_Inlet",
"(GMT-0500) America/Rankin_Inlet",
),
("America/Resolute", "(GMT-0500) America/Resolute"),
("America/Rio_Branco", "(GMT-0500) America/Rio_Branco"),
("America/Winnipeg", "(GMT-0500) America/Winnipeg"),
("Canada/Central", "(GMT-0500) Canada/Central"),
("Pacific/Easter", "(GMT-0500) Pacific/Easter"),
("US/Central", "(GMT-0500) US/Central"),
("America/Anguilla", "(GMT-0400) America/Anguilla"),
("America/Antigua", "(GMT-0400) America/Antigua"),
("America/Aruba", "(GMT-0400) America/Aruba"),
("America/Barbados", "(GMT-0400) America/Barbados"),
(
"America/Blanc-Sablon",
"(GMT-0400) America/Blanc-Sablon",
),
("America/Boa_Vista", "(GMT-0400) America/Boa_Vista"),
(
"America/Campo_Grande",
"(GMT-0400) America/Campo_Grande",
),
("America/Caracas", "(GMT-0400) America/Caracas"),
("America/Cuiaba", "(GMT-0400) America/Cuiaba"),
("America/Curacao", "(GMT-0400) America/Curacao"),
("America/Detroit", "(GMT-0400) America/Detroit"),
("America/Dominica", "(GMT-0400) America/Dominica"),
("America/Grand_Turk", "(GMT-0400) America/Grand_Turk"),
("America/Grenada", "(GMT-0400) America/Grenada"),
("America/Guadeloupe", "(GMT-0400) America/Guadeloupe"),
("America/Guyana", "(GMT-0400) America/Guyana"),
("America/Havana", "(GMT-0400) America/Havana"),
(
"America/Indiana/Indianapolis",
"(GMT-0400) America/Indiana/Indianapolis",
),
(
"America/Indiana/Marengo",
"(GMT-0400) America/Indiana/Marengo",
),
(
"America/Indiana/Petersburg",
"(GMT-0400) America/Indiana/Petersburg",
),
(
"America/Indiana/Vevay",
"(GMT-0400) America/Indiana/Vevay",
),
(
"America/Indiana/Vincennes",
"(GMT-0400) America/Indiana/Vincennes",
),
(
"America/Indiana/Winamac",
"(GMT-0400) America/Indiana/Winamac",
),
("America/Iqaluit", "(GMT-0400) America/Iqaluit"),
(
"America/Kentucky/Louisville",
"(GMT-0400) America/Kentucky/Louisville",
),
(
"America/Kentucky/Monticello",
"(GMT-0400) America/Kentucky/Monticello",
),
("America/Kralendijk", "(GMT-0400) America/Kralendijk"),
("America/La_Paz", "(GMT-0400) America/La_Paz"),
(
"America/Lower_Princes",
"(GMT-0400) America/Lower_Princes",
),
("America/Manaus", "(GMT-0400) America/Manaus"),
("America/Marigot", "(GMT-0400) America/Marigot"),
("America/Martinique", "(GMT-0400) America/Martinique"),
("America/Montserrat", "(GMT-0400) America/Montserrat"),
("America/Nassau", "(GMT-0400) America/Nassau"),
("America/New_York", "(GMT-0400) America/New_York"),
(
"America/Port-au-Prince",
"(GMT-0400) America/Port-au-Prince",
),
(
"America/Port_of_Spain",
"(GMT-0400) America/Port_of_Spain",
),
("America/Porto_Velho", "(GMT-0400) America/Porto_Velho"),
("America/Puerto_Rico", "(GMT-0400) America/Puerto_Rico"),
(
"America/Santo_Domingo",
"(GMT-0400) America/Santo_Domingo",
),
(
"America/St_Barthelemy",
"(GMT-0400) America/St_Barthelemy",
),
("America/St_Kitts", "(GMT-0400) America/St_Kitts"),
("America/St_Lucia", "(GMT-0400) America/St_Lucia"),
("America/St_Thomas", "(GMT-0400) America/St_Thomas"),
("America/St_Vincent", "(GMT-0400) America/St_Vincent"),
("America/Toronto", "(GMT-0400) America/Toronto"),
("America/Tortola", "(GMT-0400) America/Tortola"),
("Canada/Eastern", "(GMT-0400) Canada/Eastern"),
("US/Eastern", "(GMT-0400) US/Eastern"),
("America/Araguaina", "(GMT-0300) America/Araguaina"),
(
"America/Argentina/Buenos_Aires",
"(GMT-0300) America/Argentina/Buenos_Aires",
),
(
"America/Argentina/Catamarca",
"(GMT-0300) America/Argentina/Catamarca",
),
(
"America/Argentina/Cordoba",
"(GMT-0300) America/Argentina/Cordoba",
),
(
"America/Argentina/Jujuy",
"(GMT-0300) America/Argentina/Jujuy",
),
(
"America/Argentina/La_Rioja",
"(GMT-0300) America/Argentina/La_Rioja",
),
(
"America/Argentina/Mendoza",
"(GMT-0300) America/Argentina/Mendoza",
),
(
"America/Argentina/Rio_Gallegos",
"(GMT-0300) America/Argentina/Rio_Gallegos",
),
(
"America/Argentina/Salta",
"(GMT-0300) America/Argentina/Salta",
),
(
"America/Argentina/San_Juan",
"(GMT-0300) America/Argentina/San_Juan",
),
(
"America/Argentina/San_Luis",
"(GMT-0300) America/Argentina/San_Luis",
),
(
"America/Argentina/Tucuman",
"(GMT-0300) America/Argentina/Tucuman",
),
(
"America/Argentina/Ushuaia",
"(GMT-0300) America/Argentina/Ushuaia",
),
("America/Asuncion", "(GMT-0300) America/Asuncion"),
("America/Bahia", "(GMT-0300) America/Bahia"),
("America/Belem", "(GMT-0300) America/Belem"),
("America/Cayenne", "(GMT-0300) America/Cayenne"),
("America/Fortaleza", "(GMT-0300) America/Fortaleza"),
("America/Glace_Bay", "(GMT-0300) America/Glace_Bay"),
("America/Goose_Bay", "(GMT-0300) America/Goose_Bay"),
("America/Halifax", "(GMT-0300) America/Halifax"),
("America/Maceio", "(GMT-0300) America/Maceio"),
("America/Moncton", "(GMT-0300) America/Moncton"),
("America/Montevideo", "(GMT-0300) America/Montevideo"),
("America/Nuuk", "(GMT-0300) America/Nuuk"),
("America/Paramaribo", "(GMT-0300) America/Paramaribo"),
(
"America/Punta_Arenas",
"(GMT-0300) America/Punta_Arenas",
),
("America/Recife", "(GMT-0300) America/Recife"),
("America/Santarem", "(GMT-0300) America/Santarem"),
("America/Santiago", "(GMT-0300) America/Santiago"),
("America/Sao_Paulo", "(GMT-0300) America/Sao_Paulo"),
("America/Thule", "(GMT-0300) America/Thule"),
("Antarctica/Palmer", "(GMT-0300) Antarctica/Palmer"),
("Antarctica/Rothera", "(GMT-0300) Antarctica/Rothera"),
("Atlantic/Bermuda", "(GMT-0300) Atlantic/Bermuda"),
("Atlantic/Stanley", "(GMT-0300) Atlantic/Stanley"),
("Canada/Atlantic", "(GMT-0300) Canada/Atlantic"),
("America/St_Johns", "(GMT-0230) America/St_Johns"),
("Canada/Newfoundland", "(GMT-0230) Canada/Newfoundland"),
("America/Miquelon", "(GMT-0200) America/Miquelon"),
("America/Noronha", "(GMT-0200) America/Noronha"),
(
"Atlantic/South_Georgia",
"(GMT-0200) Atlantic/South_Georgia",
),
(
"America/Scoresbysund",
"(GMT-0100) America/Scoresbysund",
),
("Atlantic/Azores", "(GMT-0100) Atlantic/Azores"),
("Atlantic/Cape_Verde", "(GMT-0100) Atlantic/Cape_Verde"),
("Africa/Abidjan", "(GMT+0000) Africa/Abidjan"),
("Africa/Accra", "(GMT+0000) Africa/Accra"),
("Africa/Bamako", "(GMT+0000) Africa/Bamako"),
("Africa/Banjul", "(GMT+0000) Africa/Banjul"),
("Africa/Bissau", "(GMT+0000) Africa/Bissau"),
("Africa/Casablanca", "(GMT+0000) Africa/Casablanca"),
("Africa/Conakry", "(GMT+0000) Africa/Conakry"),
("Africa/Dakar", "(GMT+0000) Africa/Dakar"),
("Africa/El_Aaiun", "(GMT+0000) Africa/El_Aaiun"),
("Africa/Freetown", "(GMT+0000) Africa/Freetown"),
("Africa/Lome", "(GMT+0000) Africa/Lome"),
("Africa/Monrovia", "(GMT+0000) Africa/Monrovia"),
("Africa/Nouakchott", "(GMT+0000) Africa/Nouakchott"),
("Africa/Ouagadougou", "(GMT+0000) Africa/Ouagadougou"),
("Africa/Sao_Tome", "(GMT+0000) Africa/Sao_Tome"),
(
"America/Danmarkshavn",
"(GMT+0000) America/Danmarkshavn",
),
("Antarctica/Troll", "(GMT+0000) Antarctica/Troll"),
("Atlantic/Canary", "(GMT+0000) Atlantic/Canary"),
("Atlantic/Faroe", "(GMT+0000) Atlantic/Faroe"),
("Atlantic/Madeira", "(GMT+0000) Atlantic/Madeira"),
("Atlantic/Reykjavik", "(GMT+0000) Atlantic/Reykjavik"),
("Atlantic/St_Helena", "(GMT+0000) Atlantic/St_Helena"),
("Europe/Dublin", "(GMT+0000) Europe/Dublin"),
("Europe/Guernsey", "(GMT+0000) Europe/Guernsey"),
("Europe/Isle_of_Man", "(GMT+0000) Europe/Isle_of_Man"),
("Europe/Jersey", "(GMT+0000) Europe/Jersey"),
("Europe/Lisbon", "(GMT+0000) Europe/Lisbon"),
("Europe/London", "(GMT+0000) Europe/London"),
("GMT", "(GMT+0000) GMT"),
("UTC", "(GMT+0000) UTC"),
("Africa/Algiers", "(GMT+0100) Africa/Algiers"),
("Africa/Bangui", "(GMT+0100) Africa/Bangui"),
("Africa/Brazzaville", "(GMT+0100) Africa/Brazzaville"),
("Africa/Ceuta", "(GMT+0100) Africa/Ceuta"),
("Africa/Douala", "(GMT+0100) Africa/Douala"),
("Africa/Kinshasa", "(GMT+0100) Africa/Kinshasa"),
("Africa/Lagos", "(GMT+0100) Africa/Lagos"),
("Africa/Libreville", "(GMT+0100) Africa/Libreville"),
("Africa/Luanda", "(GMT+0100) Africa/Luanda"),
("Africa/Malabo", "(GMT+0100) Africa/Malabo"),
("Africa/Ndjamena", "(GMT+0100) Africa/Ndjamena"),
("Africa/Niamey", "(GMT+0100) Africa/Niamey"),
("Africa/Porto-Novo", "(GMT+0100) Africa/Porto-Novo"),
("Africa/Tunis", "(GMT+0100) Africa/Tunis"),
("Arctic/Longyearbyen", "(GMT+0100) Arctic/Longyearbyen"),
("Europe/Amsterdam", "(GMT+0100) Europe/Amsterdam"),
("Europe/Andorra", "(GMT+0100) Europe/Andorra"),
("Europe/Belgrade", "(GMT+0100) Europe/Belgrade"),
("Europe/Berlin", "(GMT+0100) Europe/Berlin"),
("Europe/Bratislava", "(GMT+0100) Europe/Bratislava"),
("Europe/Brussels", "(GMT+0100) Europe/Brussels"),
("Europe/Budapest", "(GMT+0100) Europe/Budapest"),
("Europe/Busingen", "(GMT+0100) Europe/Busingen"),
("Europe/Copenhagen", "(GMT+0100) Europe/Copenhagen"),
("Europe/Gibraltar", "(GMT+0100) Europe/Gibraltar"),
("Europe/Ljubljana", "(GMT+0100) Europe/Ljubljana"),
("Europe/Luxembourg", "(GMT+0100) Europe/Luxembourg"),
("Europe/Madrid", "(GMT+0100) Europe/Madrid"),
("Europe/Malta", "(GMT+0100) Europe/Malta"),
("Europe/Monaco", "(GMT+0100) Europe/Monaco"),
("Europe/Oslo", "(GMT+0100) Europe/Oslo"),
("Europe/Paris", "(GMT+0100) Europe/Paris"),
("Europe/Podgorica", "(GMT+0100) Europe/Podgorica"),
("Europe/Prague", "(GMT+0100) Europe/Prague"),
("Europe/Rome", "(GMT+0100) Europe/Rome"),
("Europe/San_Marino", "(GMT+0100) Europe/San_Marino"),
("Europe/Sarajevo", "(GMT+0100) Europe/Sarajevo"),
("Europe/Skopje", "(GMT+0100) Europe/Skopje"),
("Europe/Stockholm", "(GMT+0100) Europe/Stockholm"),
("Europe/Tirane", "(GMT+0100) Europe/Tirane"),
("Europe/Vaduz", "(GMT+0100) Europe/Vaduz"),
("Europe/Vatican", "(GMT+0100) Europe/Vatican"),
("Europe/Vienna", "(GMT+0100) Europe/Vienna"),
("Europe/Warsaw", "(GMT+0100) Europe/Warsaw"),
("Europe/Zagreb", "(GMT+0100) Europe/Zagreb"),
("Europe/Zurich", "(GMT+0100) Europe/Zurich"),
("Africa/Blantyre", "(GMT+0200) Africa/Blantyre"),
("Africa/Bujumbura", "(GMT+0200) Africa/Bujumbura"),
("Africa/Cairo", "(GMT+0200) Africa/Cairo"),
("Africa/Gaborone", "(GMT+0200) Africa/Gaborone"),
("Africa/Harare", "(GMT+0200) Africa/Harare"),
("Africa/Johannesburg", "(GMT+0200) Africa/Johannesburg"),
("Africa/Juba", "(GMT+0200) Africa/Juba"),
("Africa/Khartoum", "(GMT+0200) Africa/Khartoum"),
("Africa/Kigali", "(GMT+0200) Africa/Kigali"),
("Africa/Lubumbashi", "(GMT+0200) Africa/Lubumbashi"),
("Africa/Lusaka", "(GMT+0200) Africa/Lusaka"),
("Africa/Maputo", "(GMT+0200) Africa/Maputo"),
("Africa/Maseru", "(GMT+0200) Africa/Maseru"),
("Africa/Mbabane", "(GMT+0200) Africa/Mbabane"),
("Africa/Tripoli", "(GMT+0200) Africa/Tripoli"),
("Africa/Windhoek", "(GMT+0200) Africa/Windhoek"),
("Asia/Beirut", "(GMT+0200) Asia/Beirut"),
("Asia/Famagusta", "(GMT+0200) Asia/Famagusta"),
("Asia/Gaza", "(GMT+0200) Asia/Gaza"),
("Asia/Hebron", "(GMT+0200) Asia/Hebron"),
("Asia/Jerusalem", "(GMT+0200) Asia/Jerusalem"),
("Asia/Nicosia", "(GMT+0200) Asia/Nicosia"),
("Europe/Athens", "(GMT+0200) Europe/Athens"),
("Europe/Bucharest", "(GMT+0200) Europe/Bucharest"),
("Europe/Chisinau", "(GMT+0200) Europe/Chisinau"),
("Europe/Helsinki", "(GMT+0200) Europe/Helsinki"),
("Europe/Kaliningrad", "(GMT+0200) Europe/Kaliningrad"),
("Europe/Kyiv", "(GMT+0200) Europe/Kyiv"),
("Europe/Mariehamn", "(GMT+0200) Europe/Mariehamn"),
("Europe/Riga", "(GMT+0200) Europe/Riga"),
("Europe/Sofia", "(GMT+0200) Europe/Sofia"),
("Europe/Tallinn", "(GMT+0200) Europe/Tallinn"),
("Europe/Vilnius", "(GMT+0200) Europe/Vilnius"),
("Africa/Addis_Ababa", "(GMT+0300) Africa/Addis_Ababa"),
("Africa/Asmara", "(GMT+0300) Africa/Asmara"),
(
"Africa/Dar_es_Salaam",
"(GMT+0300) Africa/Dar_es_Salaam",
),
("Africa/Djibouti", "(GMT+0300) Africa/Djibouti"),
("Africa/Kampala", "(GMT+0300) Africa/Kampala"),
("Africa/Mogadishu", "(GMT+0300) Africa/Mogadishu"),
("Africa/Nairobi", "(GMT+0300) Africa/Nairobi"),
("Antarctica/Syowa", "(GMT+0300) Antarctica/Syowa"),
("Asia/Aden", "(GMT+0300) Asia/Aden"),
("Asia/Amman", "(GMT+0300) Asia/Amman"),
("Asia/Baghdad", "(GMT+0300) Asia/Baghdad"),
("Asia/Bahrain", "(GMT+0300) Asia/Bahrain"),
("Asia/Damascus", "(GMT+0300) Asia/Damascus"),
("Asia/Kuwait", "(GMT+0300) Asia/Kuwait"),
("Asia/Qatar", "(GMT+0300) Asia/Qatar"),
("Asia/Riyadh", "(GMT+0300) Asia/Riyadh"),
("Europe/Istanbul", "(GMT+0300) Europe/Istanbul"),
("Europe/Kirov", "(GMT+0300) Europe/Kirov"),
("Europe/Minsk", "(GMT+0300) Europe/Minsk"),
("Europe/Moscow", "(GMT+0300) Europe/Moscow"),
("Europe/Simferopol", "(GMT+0300) Europe/Simferopol"),
("Europe/Volgograd", "(GMT+0300) Europe/Volgograd"),
("Indian/Antananarivo", "(GMT+0300) Indian/Antananarivo"),
("Indian/Comoro", "(GMT+0300) Indian/Comoro"),
("Indian/Mayotte", "(GMT+0300) Indian/Mayotte"),
("Asia/Tehran", "(GMT+0330) Asia/Tehran"),
("Asia/Baku", "(GMT+0400) Asia/Baku"),
("Asia/Dubai", "(GMT+0400) Asia/Dubai"),
("Asia/Muscat", "(GMT+0400) Asia/Muscat"),
("Asia/Tbilisi", "(GMT+0400) Asia/Tbilisi"),
("Asia/Yerevan", "(GMT+0400) Asia/Yerevan"),
("Europe/Astrakhan", "(GMT+0400) Europe/Astrakhan"),
("Europe/Samara", "(GMT+0400) Europe/Samara"),
("Europe/Saratov", "(GMT+0400) Europe/Saratov"),
("Europe/Ulyanovsk", "(GMT+0400) Europe/Ulyanovsk"),
("Indian/Mahe", "(GMT+0400) Indian/Mahe"),
("Indian/Mauritius", "(GMT+0400) Indian/Mauritius"),
("Indian/Reunion", "(GMT+0400) Indian/Reunion"),
("Asia/Kabul", "(GMT+0430) Asia/Kabul"),
("Antarctica/Mawson", "(GMT+0500) Antarctica/Mawson"),
("Asia/Aqtau", "(GMT+0500) Asia/Aqtau"),
("Asia/Aqtobe", "(GMT+0500) Asia/Aqtobe"),
("Asia/Ashgabat", "(GMT+0500) Asia/Ashgabat"),
("Asia/Atyrau", "(GMT+0500) Asia/Atyrau"),
("Asia/Dushanbe", "(GMT+0500) Asia/Dushanbe"),
("Asia/Karachi", "(GMT+0500) Asia/Karachi"),
("Asia/Oral", "(GMT+0500) Asia/Oral"),
("Asia/Qyzylorda", "(GMT+0500) Asia/Qyzylorda"),
("Asia/Samarkand", "(GMT+0500) Asia/Samarkand"),
("Asia/Tashkent", "(GMT+0500) Asia/Tashkent"),
("Asia/Yekaterinburg", "(GMT+0500) Asia/Yekaterinburg"),
("Indian/Kerguelen", "(GMT+0500) Indian/Kerguelen"),
("Indian/Maldives", "(GMT+0500) Indian/Maldives"),
("Asia/Colombo", "(GMT+0530) Asia/Colombo"),
("Asia/Kolkata", "(GMT+0530) Asia/Kolkata"),
("Asia/Kathmandu", "(GMT+0545) Asia/Kathmandu"),
("Antarctica/Vostok", "(GMT+0600) Antarctica/Vostok"),
("Asia/Almaty", "(GMT+0600) Asia/Almaty"),
("Asia/Bishkek", "(GMT+0600) Asia/Bishkek"),
("Asia/Dhaka", "(GMT+0600) Asia/Dhaka"),
("Asia/Omsk", "(GMT+0600) Asia/Omsk"),
("Asia/Qostanay", "(GMT+0600) Asia/Qostanay"),
("Asia/Thimphu", "(GMT+0600) Asia/Thimphu"),
("Asia/Urumqi", "(GMT+0600) Asia/Urumqi"),
("Indian/Chagos", "(GMT+0600) Indian/Chagos"),
("Asia/Yangon", "(GMT+0630) Asia/Yangon"),
("Indian/Cocos", "(GMT+0630) Indian/Cocos"),
("Antarctica/Davis", "(GMT+0700) Antarctica/Davis"),
("Asia/Bangkok", "(GMT+0700) Asia/Bangkok"),
("Asia/Barnaul", "(GMT+0700) Asia/Barnaul"),
("Asia/Ho_Chi_Minh", "(GMT+0700) Asia/Ho_Chi_Minh"),
("Asia/Hovd", "(GMT+0700) Asia/Hovd"),
("Asia/Jakarta", "(GMT+0700) Asia/Jakarta"),
("Asia/Krasnoyarsk", "(GMT+0700) Asia/Krasnoyarsk"),
("Asia/Novokuznetsk", "(GMT+0700) Asia/Novokuznetsk"),
("Asia/Novosibirsk", "(GMT+0700) Asia/Novosibirsk"),
("Asia/Phnom_Penh", "(GMT+0700) Asia/Phnom_Penh"),
("Asia/Pontianak", "(GMT+0700) Asia/Pontianak"),
("Asia/Tomsk", "(GMT+0700) Asia/Tomsk"),
("Asia/Vientiane", "(GMT+0700) Asia/Vientiane"),
("Indian/Christmas", "(GMT+0700) Indian/Christmas"),
("Asia/Brunei", "(GMT+0800) Asia/Brunei"),
("Asia/Choibalsan", "(GMT+0800) Asia/Choibalsan"),
("Asia/Hong_Kong", "(GMT+0800) Asia/Hong_Kong"),
("Asia/Irkutsk", "(GMT+0800) Asia/Irkutsk"),
("Asia/Kuala_Lumpur", "(GMT+0800) Asia/Kuala_Lumpur"),
("Asia/Kuching", "(GMT+0800) Asia/Kuching"),
("Asia/Macau", "(GMT+0800) Asia/Macau"),
("Asia/Makassar", "(GMT+0800) Asia/Makassar"),
("Asia/Manila", "(GMT+0800) Asia/Manila"),
("Asia/Shanghai", "(GMT+0800) Asia/Shanghai"),
("Asia/Singapore", "(GMT+0800) Asia/Singapore"),
("Asia/Taipei", "(GMT+0800) Asia/Taipei"),
("Asia/Ulaanbaatar", "(GMT+0800) Asia/Ulaanbaatar"),
("Australia/Perth", "(GMT+0800) Australia/Perth"),
("Australia/Eucla", "(GMT+0845) Australia/Eucla"),
("Asia/Chita", "(GMT+0900) Asia/Chita"),
("Asia/Dili", "(GMT+0900) Asia/Dili"),
("Asia/Jayapura", "(GMT+0900) Asia/Jayapura"),
("Asia/Khandyga", "(GMT+0900) Asia/Khandyga"),
("Asia/Pyongyang", "(GMT+0900) Asia/Pyongyang"),
("Asia/Seoul", "(GMT+0900) Asia/Seoul"),
("Asia/Tokyo", "(GMT+0900) Asia/Tokyo"),
("Asia/Yakutsk", "(GMT+0900) Asia/Yakutsk"),
("Pacific/Palau", "(GMT+0900) Pacific/Palau"),
("Australia/Darwin", "(GMT+0930) Australia/Darwin"),
(
"Antarctica/DumontDUrville",
"(GMT+1000) Antarctica/DumontDUrville",
),
("Asia/Ust-Nera", "(GMT+1000) Asia/Ust-Nera"),
("Asia/Vladivostok", "(GMT+1000) Asia/Vladivostok"),
("Australia/Brisbane", "(GMT+1000) Australia/Brisbane"),
("Australia/Lindeman", "(GMT+1000) Australia/Lindeman"),
("Pacific/Chuuk", "(GMT+1000) Pacific/Chuuk"),
("Pacific/Guam", "(GMT+1000) Pacific/Guam"),
(
"Pacific/Port_Moresby",
"(GMT+1000) Pacific/Port_Moresby",
),
("Pacific/Saipan", "(GMT+1000) Pacific/Saipan"),
("Australia/Adelaide", "(GMT+1030) Australia/Adelaide"),
(
"Australia/Broken_Hill",
"(GMT+1030) Australia/Broken_Hill",
),
("Antarctica/Casey", "(GMT+1100) Antarctica/Casey"),
(
"Antarctica/Macquarie",
"(GMT+1100) Antarctica/Macquarie",
),
("Asia/Magadan", "(GMT+1100) Asia/Magadan"),
("Asia/Sakhalin", "(GMT+1100) Asia/Sakhalin"),
("Asia/Srednekolymsk", "(GMT+1100) Asia/Srednekolymsk"),
("Australia/Hobart", "(GMT+1100) Australia/Hobart"),
("Australia/Lord_Howe", "(GMT+1100) Australia/Lord_Howe"),
("Australia/Melbourne", "(GMT+1100) Australia/Melbourne"),
("Australia/Sydney", "(GMT+1100) Australia/Sydney"),
(
"Pacific/Bougainville",
"(GMT+1100) Pacific/Bougainville",
),
("Pacific/Efate", "(GMT+1100) Pacific/Efate"),
("Pacific/Guadalcanal", "(GMT+1100) Pacific/Guadalcanal"),
("Pacific/Kosrae", "(GMT+1100) Pacific/Kosrae"),
("Pacific/Noumea", "(GMT+1100) Pacific/Noumea"),
("Pacific/Pohnpei", "(GMT+1100) Pacific/Pohnpei"),
("Asia/Anadyr", "(GMT+1200) Asia/Anadyr"),
("Asia/Kamchatka", "(GMT+1200) Asia/Kamchatka"),
("Pacific/Fiji", "(GMT+1200) Pacific/Fiji"),
("Pacific/Funafuti", "(GMT+1200) Pacific/Funafuti"),
("Pacific/Kwajalein", "(GMT+1200) Pacific/Kwajalein"),
("Pacific/Majuro", "(GMT+1200) Pacific/Majuro"),
("Pacific/Nauru", "(GMT+1200) Pacific/Nauru"),
("Pacific/Norfolk", "(GMT+1200) Pacific/Norfolk"),
("Pacific/Tarawa", "(GMT+1200) Pacific/Tarawa"),
("Pacific/Wake", "(GMT+1200) Pacific/Wake"),
("Pacific/Wallis", "(GMT+1200) Pacific/Wallis"),
("Antarctica/McMurdo", "(GMT+1300) Antarctica/McMurdo"),
("Pacific/Apia", "(GMT+1300) Pacific/Apia"),
("Pacific/Auckland", "(GMT+1300) Pacific/Auckland"),
("Pacific/Fakaofo", "(GMT+1300) Pacific/Fakaofo"),
("Pacific/Kanton", "(GMT+1300) Pacific/Kanton"),
("Pacific/Tongatapu", "(GMT+1300) Pacific/Tongatapu"),
("Pacific/Chatham", "(GMT+1345) Pacific/Chatham"),
("Pacific/Kiritimati", "(GMT+1400) Pacific/Kiritimati"),
],
choices=PRETTY_TIMEZONE_CHOICES,
max_length=255,
null=True,
),

View File

@ -0,0 +1,22 @@
# Generated by Django 4.1.7 on 2023-04-03 02:43
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("scrobbles", "0034_alter_chartrecord_rank"),
]
operations = [
migrations.AddField(
model_name="scrobble",
name="videogame_save_data",
field=models.FileField(
blank=True,
null=True,
upload_to="scrobbles/videogame_save_data/",
),
),
]

View File

@ -0,0 +1,18 @@
# Generated by Django 4.1.7 on 2023-04-03 03:52
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("scrobbles", "0035_scrobble_videogame_save_data"),
]
operations = [
migrations.AddField(
model_name="scrobble",
name="stop_timestamp",
field=models.DateTimeField(blank=True, null=True),
),
]

View File

@ -24,7 +24,10 @@ from profiles.utils import (
)
from scrobbles.constants import LONG_PLAY_MEDIA
from scrobbles.stats import build_charts
from scrobbles.utils import check_scrobble_for_finish
from scrobbles.utils import (
check_long_play_for_finish,
check_scrobble_for_finish,
)
from sports.models import SportEvent
from videogames.models import VideoGame
from videos.models import Series, Video
@ -427,6 +430,7 @@ class Scrobble(TimeStampedModel):
# Time keeping
timestamp = models.DateTimeField(**BNULL)
stop_timestamp = models.DateTimeField(**BNULL)
playback_position_ticks = models.PositiveBigIntegerField(**BNULL)
playback_position_seconds = models.IntegerField(**BNULL)
@ -443,6 +447,9 @@ class Scrobble(TimeStampedModel):
# Fields for keeping track long content like books and games
book_pages_read = models.IntegerField(**BNULL)
videogame_save_data = models.FileField(
upload_to="scrobbles/videogame_save_data/", **BNULL
)
long_play_seconds = models.BigIntegerField(**BNULL)
long_play_complete = models.BooleanField(**BNULL)
@ -538,7 +545,7 @@ class Scrobble(TimeStampedModel):
if self.media_obj.__class__.__name__ in LONG_PLAY_MEDIA.values():
logger.info(f"No - Long play media")
updatable = False
if self.percent_played > 100:
if self.percent_played >= 100:
logger.info(f"No - 100% played - {self.id} - {self.source}")
updatable = False
if self.is_stale:
@ -574,8 +581,8 @@ class Scrobble(TimeStampedModel):
media_class = media.__class__.__name__
media_query = models.Q(track=media)
if media_class == "Track":
media_query = models.Q(track=media)
scrobble_data["track_id"] = media.id
if media_class == "Video":
media_query = models.Q(video=media)
@ -601,21 +608,26 @@ class Scrobble(TimeStampedModel):
.order_by("-modified")
.first()
)
if scrobble and scrobble.can_be_updated:
# We want to finish a video after it's reeached 90%
# but we also don't want to create new videos, so in
# this special case, we allow jellyfin resumed status
# to allow a scrobble to be updated.
jellyfin_in_progress = scrobble_data.get("jellyfin_status", None)
if scrobble and (scrobble.can_be_updated or jellyfin_in_progress):
logger.info(
f"Updating {scrobble.id}",
{"scrobble_data": scrobble_data, "media": media},
)
return scrobble.update(scrobble_data)
# Discard status before creating
scrobble_data.pop("mopidy_status", None)
scrobble_data.pop("jellyfin_status", None)
source = scrobble_data["source"]
logger.info(
f"Creating for {media.id} - {source}",
{"scrobble_data": scrobble_data, "media": media},
)
# If creating a new scrobble, we don't need status
scrobble_data.pop("mopidy_status", None)
scrobble_data.pop("jellyfin_status", None)
return cls.create(scrobble_data)
def update(self, scrobble_data: dict) -> "Scrobble":
@ -637,9 +649,19 @@ class Scrobble(TimeStampedModel):
if scrobble_status == "resumed":
self.resume()
check_scrobble_for_finish(self)
if scrobble_status != "resumed":
scrobble_data["stop_timestamp"] = (
scrobble_data.pop("timestamp", None) or timezone.now()
)
update_fields = []
for key, value in scrobble_data.items():
setattr(self, key, value)
self.save()
update_fields.append(key)
self.save(update_fields=update_fields)
return self
@classmethod
@ -654,54 +676,22 @@ class Scrobble(TimeStampedModel):
return scrobble
def stop(self, force_finish=False) -> None:
if not self.in_progress:
return
self.in_progress = False
self.save(update_fields=["in_progress"])
logger.info(f"{self.id} - {self.source}")
logger.info(f"stopping {self.id} from {self.source}")
class_name = self.media_obj.__class__.__name__
if class_name in LONG_PLAY_MEDIA.values():
now = timezone.now()
self.played_to_completion = True
self.playback_position_seconds = (now - self.timestamp).seconds
media_filter = models.Q(video_game=self.video_game)
if class_name == "Book":
media_filter = models.Q(book=self.book)
# Check for last scrobble, and if present, update long play time
last_scrobble = Scrobble.objects.filter(
media_filter,
user_id=self.user,
played_to_completion=True,
long_play_complete=False,
).last()
self.long_play_seconds = self.playback_position_seconds
if last_scrobble:
self.long_play_seconds = (
last_scrobble.long_play_seconds
+ self.playback_position_seconds
)
self.save(
update_fields=[
"playback_position_seconds",
"played_to_completion",
"long_play_seconds",
]
)
check_long_play_for_finish(self)
return
check_scrobble_for_finish(self, force_finish=force_finish)
def pause(self) -> None:
if self.is_paused:
logger.warning(f"{self.id} - already paused - {self.source}")
return
logger.info(f"{self.id} - pausing - {self.source}")
self.is_paused = True
self.save(update_fields=["is_paused"])
logger.info(f"{self.id} - pausing - {self.source}")
check_scrobble_for_finish(self)
def resume(self) -> None:
if self.is_paused or not self.in_progress:

View File

@ -1,6 +1,7 @@
import logging
from urllib.parse import unquote
from django.utils import timezone
from dateutil.parser import ParserError, parse
from django.apps import apps
from django.conf import settings
@ -78,19 +79,11 @@ def check_scrobble_for_finish(
scrobble: "Scrobble", force_to_100=False, force_finish=False
) -> None:
completion_percent = scrobble.media_obj.COMPLETION_PERCENT
if scrobble.percent_played >= completion_percent or force_finish:
if (
scrobble.playback_position_seconds
and scrobble.media_obj.run_time_seconds
):
logger.info(f"{scrobble.id} {completion_percent} met, finishing")
scrobble.playback_position_seconds = (
scrobble.media_obj.run_time_seconds
)
logger.info(
f"{scrobble.playback_position_seconds} set to {scrobble.media_obj.run_time_seconds}"
)
logger.info(f"{scrobble.id} finished at {scrobble.percent_played}")
scrobble.playback_position_seconds = (
scrobble.media_obj.run_time_seconds
)
scrobble.in_progress = False
scrobble.is_paused = False
@ -104,6 +97,44 @@ def check_scrobble_for_finish(
"playback_position_seconds",
]
)
else:
logger.info(
f"{scrobble.id} not complete at {scrobble.percent_played}%"
)
def check_long_play_for_finish(scrobble):
Scrobble = apps.get_model("scrobbles", "Scrobble")
class_name = scrobble.media_obj.__class__.__name__
now = timezone.now()
scrobble.played_to_completion = True
scrobble.playback_position_seconds = (now - scrobble.timestamp).seconds
media_filter = models.Q(video_game=scrobble.video_game)
if class_name == "Book":
media_filter = models.Q(book=scrobble.book)
# Check for last scrobble, and if present, update long play time
last_scrobble = Scrobble.objects.filter(
media_filter,
user_id=scrobble.user,
played_to_completion=True,
long_play_complete=False,
).last()
scrobble.long_play_seconds = scrobble.playback_position_seconds
if last_scrobble:
scrobble.long_play_seconds = (
last_scrobble.long_play_seconds
+ scrobble.playback_position_seconds
)
scrobble.save(
update_fields=[
"playback_position_seconds",
"played_to_completion",
"long_play_seconds",
]
)
def get_scrobbles_for_media(media_obj, user: User) -> models.QuerySet:

View File

@ -101,7 +101,6 @@ INSTALLED_APPS = [
"django.contrib.humanize",
"django_filters",
"django_extensions",
"sorl.thumbnail",
"storages",
"taggit",
"rest_framework.authtoken",
@ -236,7 +235,7 @@ AUTH_PASSWORD_VALIDATORS = [
LANGUAGE_CODE = "en-us"
TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "EST")
TIME_ZONE = os.getenv("VROBBLER_TIME_ZONE", "America/New_York")
USE_I18N = True

View File

@ -2,7 +2,6 @@
{% load mathfilters %}
{% load static %}
{% load naturalduration %}
{% load thumbnail %}
{% block title %}{{object.title}}{% endblock %}
@ -11,11 +10,9 @@
<div class="row">
{% if object.cover%}
{% thumbnail object.cover "400" as im %}
<p style="float:left; width:{{im.width}}px; padding:0; border: 1px solid #ccc">
<img src="{{im.url}}" width={{im.width}} height={{im.height}}/>
<p style="float:left; width:402px; padding:0; border: 1px solid #ccc">
<img src="{{object.cover.url}}" width=400 />
</p>
{% endthumbnail %}
{% endif %}
<div style="float:left; width:600px; margin-left:10px; ">
{% if object.summary %}

View File

@ -1,7 +1,6 @@
{% extends "base_list.html" %}
{% load mathfilters %}
{% load static %}
{% load thumbnail %}
{% block title %}{{object.name}}{% endblock %}
@ -10,11 +9,9 @@
<div class="row">
{% if object.cover_image %}
{% thumbnail object.cover_image "300x300" as im %}
<p style="float:left; width:{{im.width}}px; padding:0;">
<img src="{{im.url}}" width={{im.width}} height={{im.height}} />
<p style="float:left; width:302px; padding:0;">
<img src="{{object.cover_image.url}}" width=300 height=300 />
</p>
{% endthumbnail %}
{% endif %}
<div style="float:left; width:600px; margin-left:10px; ">
{% if object.theaudiodb_description %}

View File

@ -1,7 +1,6 @@
{% extends "base_list.html" %}
{% load mathfilters %}
{% load static %}
{% load thumbnail %}
{% block title %}{{object.name}}{% endblock %}
@ -9,11 +8,9 @@
<div class="row">
{% if object.thumbnail %}
{% thumbnail object.thumbnail "300x300" as im %}
<p style="float:left; width:{{im.width}}; margin-right:10px;">
<img style="border:1px solid #ccc;" src="{{im.url}}" width={{im.width}} height={{im.height}} />
<p style="float:left; width:300px; margin-right:10px;">
<img style="border:1px solid #ccc;" src="{{artist.thumbnail.url}}" width=300 height=300 />
</p>
{% endthumbnail %}
{% else %}
{% if object.album_set.first.cover_image %}
<p style="float:left; width:302px; padding:0; border: 1px solid #ccc">

View File

@ -1,5 +1,4 @@
{% extends "base_list.html" %}
{% load thumbnail %}
{% block title %}Long Plays{% endblock %}
@ -24,13 +23,9 @@
<dl>
<dt><a href="{{media.get_absolute_url}}">{{media.title}}</a></dt>
{% if media.hltb_cover %}
{% thumbnail media.hltb_cover "200" as im %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{im.url}}" width={{im.width}} height={{im.height}} /></a></dd>
{% endthumbnail %}
{% else %}
{% thumbnail media.cover "200" as im %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{im.url}}" width={{im.width}} height={{im.height}} /></a></dd>
{% endthumbnail %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{media.hltb_cover.url}}" width=200 height=200 /></a></dd>
{% elif media.cover %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{media.cover.url}}" style="width: 200px; height: 200px; object-fit:cover; " /></a></dd>
{% endif %}
<dd>
{% if media.is_long_play_in_progress %}Playing{% else %}<a type="button" class="btn btn-sm btn-primary" href="{{media.get_start_url}}">Resume</a>{% endif %}
@ -77,16 +72,12 @@
{% for media in completed %}
{% if media.hltb_cover %}
<dl>
{% thumbnail media.hltb_cover "200" as im %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{im.url}}" width={{im.width}} height={{im.height}} /></a></dd>
{% endthumbnail %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{media.hltb_cover.url}}" width=200 /></a></dd>
<dt><a href="{{media.get_absolute_url}}">{{media.title}}</a></dt>
</dl>
{% elif media.cover %}
<dl>
{% thumbnail media.cover "200" as im %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{url}}" width={{im.width}} height={{im.height}} /></a></dd>
{% endthumbnail %}
<dd><a href="{{media.get_absolute_url}}"><img src="{{media.cover.url}}" width=200 /></a></dd>
<dt><a href="{{media.get_absolute_url}}">{{media.title}}</a></dt>
</dl>
{% endif %}

View File

@ -1,7 +1,6 @@
{% extends "base.html" %}
{% load humanize %}
{% load static %}
{% load thumbnail %}
{% block head_extra %}
<style>
@ -108,7 +107,7 @@
<div class="caption">#1 {{artists.0.name}}</div>
{% if artists.0 %}
{% if artists.0.thumbnail %}
{% thumbnail artists.0.thumbnail "300x" as im %}<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{{artists.0.thumbnail.url}}" width="300px"></a>
{% else %}
<a href="{{artists.0.get_absolute_url}}"><img lt="{{artists.0.name}}" src="{% static "images/not-found.jpg" %}" width="300px"></a>
{% endif %}
@ -121,7 +120,7 @@
<div class="caption-medium">#2 {{artists.1.name}}</div>
{% if artists.1 %}
{% if artists.1.thumbnail %}
{% thumbnail artists.1.thumbnail "150" as im %}<a href="{{artists.1.get_absolute_url}}"><img lt="{{artists.1.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.1.get_absolute_url}}"><img lt="{{artists.1.name}}" src="{{artists.1.thumbnail.url}}" width="150px"></a>
{% else %}
<a href="{{artists.1.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
@ -131,7 +130,7 @@
<div class="caption-medium">#3 {{artists.2.name}}</div>
{% if artists.2 %}
{% if artists.2.thumbnail %}
{% thumbnail artists.2.thumbnail "150" as im %}<a href="{{artists.2.get_absolute_url}}"><img lt="{{artists.2.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.2.get_absolute_url}}"><img src="{{artists.2.thumbnail.url}}" width="150px"></a>
{% else %}
<a href="{{artists.2.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
@ -141,7 +140,7 @@
<div class="caption-medium">#4 {{artists.3.name}}</div>
{% if artists.3 %}
{% if artists.3.thumbnail %}
{% thumbnail artists.3.thumbnail "150" as im %}<a href="{{artists.3.get_absolute_url}}"><img lt="{{artists.3.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.3.get_absolute_url}}"><img src="{{artists.3.thumbnail.url}}" width="150px"></a>
{% else %}
<a href="{{artists.3.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
@ -151,7 +150,7 @@
<div class="caption-medium">#5 {{artists.4.name}}</div>
{% if artists.4 %}
{% if artists.4.thumbnail %}
{% thumbnail artists.4.thumbnail "150" as im %}<a href="{{artists.4.get_absolute_url}}"><img lt="{{artists.4.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.4.get_absolute_url}}"><img src="{{artists.4.thumbnail.url}}" width="150px"></a>
{% else %}
<a href="{{artists.4.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="150px"></a>
{% endif %}
@ -165,7 +164,7 @@
<div class="caption-small">#6 {{artists.5.name}}</div>
{% if artists.5 %}
{% if artists.5.thumbnail %}
{% thumbnail artists.5.thumbnail "100" as im %}<a href="{{artists.5.get_absolute_url}}"><img lt="{{artists.5.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.5.get_absolute_url}}"><img src="{{artists.5.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.5.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -175,7 +174,7 @@
<div class="caption-small">#7 {{artists.6.name}}</div>
{% if artists.6 %}
{% if artists.6.thumbnail %}
{% thumbnail artists.6.thumbnail "100" as im %}<a href="{{artists.6.get_absolute_url}}"><img lt="{{artists.6.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.6.get_absolute_url}}"><img src="{{artists.6.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.6.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -185,7 +184,7 @@
<div class="caption-small">#8 {{artists.7.name}}</div>
{% if artists.7 %}
{% if artists.7.thumbnail %}
{% thumbnail artists.7.thumbnail "100" as im %}<a href="{{artists.7.get_absolute_url}}"><img lt="{{artists.7.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.7.get_absolute_url}}"><img src="{{artists.7.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.7.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -195,7 +194,7 @@
<div class="caption-small">#9 {{artists.8.name}}</div>
{% if artists.8 %}
{% if artists.8.thumbnail %}
{% thumbnail artists.8.thumbnail "100" as im %}<a href="{{artists.8.get_absolute_url}}"><img lt="{{artists.8.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.8.get_absolute_url}}"><img src="{{artists.8.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.8.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -205,7 +204,7 @@
<div class="caption-small">#10 {{artists.9.name}}</div>
{% if artists.9 %}
{% if artists.9.thumbnail %}
{% thumbnail artists.9.thumbnail "100" as im %}<a href="{{artists.9.get_absolute_url}}"><img lt="{{artists.9.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.9.get_absolute_url}}"><img src="{{artists.9.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.9.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -215,7 +214,7 @@
<div class="caption-small">#11 {{artists.10.name}}</div>
{% if artists.10 %}
{% if artists.10.thumbnail %}
{% thumbnail artists.10.thumbnail "100" as im %}<a href="{{artists.10.get_absolute_url}}"><img lt="{{artists.10.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.10.get_absolute_url}}"><img src="{{artists.10.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.10.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -225,7 +224,7 @@
<div class="caption-small">#12 {{artists.11.name}}</div>
{% if artists.11 %}
{% if artists.11.thumbnail %}
{% thumbnail artists.11.thumbnail "100" as im %}<a href="{{artists.11.get_absolute_url}}"><img lt="{{artists.11.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.11.get_absolute_url}}"><img src="{{artists.11.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.11.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -235,7 +234,7 @@
<div class="caption-small">#13 {{artists.12.name}}</div>
{% if artists.12 %}
{% if artists.12.thumbnail %}
{% thumbnail artists.12.thumbnail "100" as im %}<a href="{{artists.12.get_absolute_url}}"><img lt="{{artists.12.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{artists.12.get_absolute_url}}"><img src="{{artists.12.thumbnail.url}}" width="100px"></a>
{% else %}
<a href="{{artists.12.get_absolute_url}}"><img src="{% static "images/not-found.jpg" %}" width="100px"></a>
{% endif %}
@ -279,7 +278,7 @@
<div class="caption">#1 {{tracks.0.title}}</div>
{% if tracks.0 %}
{% if tracks.0.album.cover_image %}
{% thumbnail tracks.0.album.cover_image "300" as im %}<a href="{{tracks.0.get_absolute_url}}"><img lt="{{tracks.0.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.0.get_absolute_url}}"><img src="{{tracks.0.album.cover_image.url}}" width="300px"></a>
{% else %}
<a href="{{tracks.0.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="300px"></a>
{% endif %}
@ -292,7 +291,7 @@
<div class="caption-medium">#2 {{tracks.1.title}}</div>
{% if tracks.1 %}
{% if tracks.1.album.cover_image %}
{% thumbnail tracks.1.album.cover_image "150" as im %}<a href="{{tracks.1.get_absolute_url}}"><img lt="{{tracks.1.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.1.get_absolute_url}}"><img src="{{tracks.1.album.cover_image.url}}" width="150px"></a>
{% else %}
<a href="{{tracks.1.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="150px"></a>
{% endif %}
@ -302,7 +301,7 @@
<div class="caption-medium">#3 {{tracks.2.title}}</div>
{% if tracks.2 %}
{% if tracks.2.album.cover_image %}
{% thumbnail tracks.2.album.cover_image "150" as im %}<a href="{{tracks.2.get_absolute_url}}"><img lt="{{tracks.2.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.2.get_absolute_url}}"><img src="{{tracks.2.album.cover_image.url}}" width="150px"></a>
{% else %}
<a href="{{tracks.2.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="150px"></a>
{% endif %}
@ -312,7 +311,7 @@
<div class="caption-medium">#4 {{tracks.3.title}}</div>
{% if tracks.3 %}
{% if tracks.3.album.cover_image %}
{% thumbnail tracks.3.album.cover_image "150" as im %}<a href="{{tracks.3.get_absolute_url}}"><img lt="{{tracks.3.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.3.get_absolute_url}}"><img src="{{tracks.3.album.cover_image.url}}" width="150px"></a>
{% else %}
<a href="{{tracks.3.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="150px"></a>
{% endif %}
@ -322,7 +321,7 @@
<div class="caption-medium">#5 {{tracks.4.title}}</div>
{% if tracks.4 %}
{% if tracks.4.album.cover_image %}
{% thumbnail tracks.4.album.cover_image "150" as im %}<a href="{{tracks.4.get_absolute_url}}"><img lt="{{tracks.4.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.4.get_absolute_url}}"><img src="{{tracks.4.album.cover_image.url}}" width="150px"></a>
{% else %}
<a href="{{tracks.4.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="150px"></a>
{% endif %}
@ -336,7 +335,7 @@
<div class="caption-small">#6 {{tracks.5.title}}</div>
{% if tracks.5 %}
{% if tracks.5.album.cover_image %}
{% thumbnail tracks.5.album.cover_image "100" as im %}<a href="{{tracks.5.get_absolute_url}}"><img lt="{{tracks.5.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.5.get_absolute_url}}"><img src="{{tracks.5.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.5.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -346,7 +345,7 @@
<div class="caption-small">#7 {{tracks.6.title}}</div>
{% if tracks.6 %}
{% if tracks.6.album.cover_image %}
{% thumbnail tracks.6.album.cover_image "100" as im %}<a href="{{tracks.6.get_absolute_url}}"><img lt="{{tracks.6.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.6.get_absolute_url}}"><img src="{{tracks.6.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.6.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -356,7 +355,7 @@
<div class="caption-small">#8 {{tracks.7.title}}</div>
{% if tracks.7 %}
{% if tracks.7.album.cover_image %}
{% thumbnail tracks.7.album.cover_image "100" as im %}<a href="{{tracks.7.get_absolute_url}}"><img lt="{{tracks.7.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.7.get_absolute_url}}"><img src="{{tracks.7.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.7.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -366,7 +365,7 @@
<div class="caption-small">#9 {{tracks.8.title}}</div>
{% if tracks.8 %}
{% if tracks.8.album.cover_image %}
{% thumbnail tracks.8.album.cover_image "100" as im %}<a href="{{tracks.8.get_absolute_url}}"><img lt="{{tracks.8.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.8.get_absolute_url}}"><img src="{{tracks.8.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.8.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -376,7 +375,7 @@
<div class="caption-small">#10 {{tracks.9.title}}</div>
{% if tracks.9 %}
{% if tracks.9.album.cover_image %}
{% thumbnail tracks.9.album.cover_image "100" as im %}<a href="{{tracks.9.get_absolute_url}}"><img lt="{{tracks.9.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.9.get_absolute_url}}"><img src="{{tracks.9.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.9.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -386,7 +385,7 @@
<div class="caption-small">#11 {{tracks.10.title}}</div>
{% if tracks.10 %}
{% if tracks.10.album.cover_image %}
{% thumbnail tracks.10.album.cover_image "100" as im %}<a href="{{tracks.10.get_absolute_url}}"><img lt="{{tracks.10.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.10.get_absolute_url}}"><img src="{{tracks.10.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.10.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -396,7 +395,7 @@
<div class="caption-small">#12 {{tracks.11.title}}</div>
{% if tracks.11 %}
{% if tracks.11.album.cover_image %}
{% thumbnail tracks.11.album.cover_image "100" as im %}<a href="{{tracks.11.get_absolute_url}}"><img lt="{{tracks.11.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.11.get_absolute_url}}"><img src="{{tracks.11.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.11.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -406,7 +405,7 @@
<div class="caption-small">#13 {{tracks.12.title}}</div>
{% if tracks.12 %}
{% if tracks.12.album.cover_image %}
{% thumbnail tracks.12.album.cover_image "100" as im %}<a href="{{tracks.12.get_absolute_url}}"><img lt="{{tracks.12.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.12.get_absolute_url}}"><img src="{{tracks.12.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.12.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -416,7 +415,7 @@
<div class="caption-small">#14 {{tracks.13.title}}</div>
{% if tracks.13 %}
{% if tracks.13.album.cover_image %}
{% thumbnail tracks.13.album.cover_image "100" as im %}<a href="{{tracks.13.get_absolute_url}}"><img lt="{{tracks.13.name}}" src="{{im.url}}" width="{{im.width}}" height="{{im.height}}"></a>{% endthumbnail %}
<a href="{{tracks.13.get_absolute_url}}"><img src="{{tracks.13.album.cover_image.url}}" width="100px"></a>
{% else %}
<a href="{{tracks.13.get_absolute_url}}"><img src="{% static 'images/not-found.jpg' %}" width="100px"></a>
{% endif %}
@ -472,9 +471,7 @@
<tr>
<td>{{scrobble.timestamp|naturaltime}}</td>
{% if scrobble.track.album.cover_image %}
{% thumbnail scrobble.track.album.cover_image "25" as im %}
<td><a href="{{scrobble.track.album.get_absolute_url}}"><img src="{{im.url}}" width={{im.width}} height={{im.height}} style="border:1px solid black;" /></aa></td>
{% endthumbnail %}
<td><a href="{{scrobble.track.album.get_absolute_url}}"><img src="{{scrobble.track.album.cover_image.url}}" width=25 height=25 style="border:1px solid black;" /></aa></td>
{% else %}
<td><a href="{{scrobble.track.album.get_absolute_url}}">{{scrobble.track.album.name}}</a></td>
{% endif %}

View File

@ -2,7 +2,6 @@
{% load mathfilters %}
{% load static %}
{% load naturalduration %}
{% load thumbnail %}
{% block title %}{{object.title}}{% endblock %}
@ -31,9 +30,7 @@
<div class="row">
{% if object.hltb_cover%}
{% thumbnail object.hltb_cover 400 as im %}
<div class="cover"><img src="{{im.url}}" /></div>
{% endthumbnail %}
<div class="cover"><img src="{{object.hltb_cover.url}}" /></div>
{% endif %}
<div class="summary">
{% if object.summary %}

View File

@ -1,6 +1,5 @@
{% extends "base_list.html" %}
{% load static %}
{% load thumbnail %}
{% block title %}{{object.title}}{% endblock %}
@ -58,11 +57,7 @@ dd {
<div class="row header">
<div class="cover image-wrapper">
{% if object.imdb_rating %}<div class="caption">{{object.imdb_rating}}</div>{% endif %}
{% if object.cover_image %}
{% thumbnail object.cover_image 400 as im %}<img src="{{im.url}}" width="{{im.width}}" height="{{im.height}}" />{% endthumbnail %}
{% else %}
<img src="{% static 'images/no-video-cover.jpg' %}" width="400px" />
{% endif %}
<img src="{% if object.cover_image %}{{object.cover_image.url}}{% else %}{% static 'images/no-video-cover.jpg' %}{% endif %}" width="400px" />
<div class="caption-footer">{{object.year}}{% if object.tv_series %} | <b>S</b>{{object.season_number}} <b>E</b>{{object.episode_number}}{% endif %}</div>
</div>
<div class="summary">