From 44eb0850f0c391511c1d2e1460102267af0d1877 Mon Sep 17 00:00:00 2001 From: Felix Stupp Date: Thu, 18 Aug 2022 10:11:21 +0000 Subject: [PATCH] Add thumbnail support to MediaElements --- server/entertainment_decider/models.py | 65 ++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/server/entertainment_decider/models.py b/server/entertainment_decider/models.py index c8097bf..cea5e28 100644 --- a/server/entertainment_decider/models.py +++ b/server/entertainment_decider/models.py @@ -10,12 +10,34 @@ import math import logging from typing import Callable, Dict, Iterable, List, Optional, Set, Tuple, TypeVar, Union +import magic +import requests from pony import orm from pony.orm.core import Query db = orm.Database() +THUMBNAIL_ALLOWED_TYPES = [ + "image/avif", + "image/jpeg", + "image/png", + "image/webp", +] +THUMBNAIL_HEADERS = { + "Accept": ",".join(THUMBNAIL_ALLOWED_TYPES) + ",*/*;q=0.9", + "User-Agent": "Mozilla/5.0 (Windows NT 10.0; rv:91.0) Gecko/20100101 Firefox/91.0", +} +THUMBNAIL_TARGET = 16 / 9 + + +def thumbnail_sort_key(width: int, height: int) -> Tuple: + return ( + abs((width / height) - THUMBNAIL_TARGET), + width * height, + ) + + #### ## Model Extensions #### @@ -367,6 +389,7 @@ class MediaElement(db.Entity, Tagable): uri: str = orm.Required(str, unique=True) title: str = orm.Optional(str) + thumbnail: MediaThumbnail = orm.Optional(lambda: MediaThumbnail) notes: str = orm.Optional(str) release_date: datetime = orm.Optional(datetime) @@ -468,6 +491,48 @@ class MediaElement(db.Entity, Tagable): return f"/media/{self.id}" +class MediaThumbnail(db.Entity): + + id: int = orm.PrimaryKey(int, auto=True) + uri: str = orm.Required(str, unique=True) + last_downloaded: datetime = orm.Optional(datetime, default=None, nullable=True) + last_accessed: datetime = orm.Optional(datetime, default=None, nullable=True) + mime_type: str = orm.Optional(str, default="") + data: bytes = orm.Optional(bytes, default=None, nullable=True, lazy=True) + + elements: Set[MediaElement] = orm.Set(lambda: MediaElement) + + @classmethod + def from_uri(cls, uri: str) -> MediaThumbnail: + return cls.get(uri=uri) or MediaThumbnail(uri=uri) + + def access(self): + self.last_accessed = datetime.now() + + def download(self) -> bool: + res = requests.get(url=self.uri, headers=THUMBNAIL_HEADERS) + mime = magic.from_buffer(res.content, mime=True) + if mime not in THUMBNAIL_ALLOWED_TYPES: + return False + self.mime_type = mime + self.data = res.content + self.last_downloaded = datetime.now() + return True + + def prepare(self) -> bool: + if self.last_downloaded is not None: + return True + return self.download() + + def access_data(self): + self.prepare() + self.access() + + def receive_data(self) -> bytes: + self.access_data() + return self.data + + class MediaUriMapping(db.Entity): id: int = orm.PrimaryKey(int, auto=True)