|
@@ -1,4 +1,5 @@
|
|
|
from dataclasses import dataclass
|
|
from dataclasses import dataclass
|
|
|
|
|
+import re
|
|
|
import logging
|
|
import logging
|
|
|
from typing import Optional
|
|
from typing import Optional
|
|
|
from uuid import uuid4
|
|
from uuid import uuid4
|
|
@@ -27,6 +28,8 @@ from vrobbler.apps.scrobbles.dataclasses import BaseLogData, WithPeopleLogData
|
|
|
|
|
|
|
|
YOUTUBE_VIDEO_URL = "https://www.youtube.com/watch?v="
|
|
YOUTUBE_VIDEO_URL = "https://www.youtube.com/watch?v="
|
|
|
YOUTUBE_CHANNEL_URL = "https://www.youtube.com/channel/"
|
|
YOUTUBE_CHANNEL_URL = "https://www.youtube.com/channel/"
|
|
|
|
|
+YOUTUBE_ID_PATTERN = re.compile(r'^[A-Za-z0-9_-]{11}$')
|
|
|
|
|
+
|
|
|
IMDB_VIDEO_URL = "https://www.imdb.com/title/tt"
|
|
IMDB_VIDEO_URL = "https://www.imdb.com/title/tt"
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
logger = logging.getLogger(__name__)
|
|
@@ -136,6 +139,13 @@ class Series(TimeStampedModel):
|
|
|
url = self.cover_image_medium.url
|
|
url = self.cover_image_medium.url
|
|
|
return url
|
|
return url
|
|
|
|
|
|
|
|
|
|
+ def save_image_from_url(self, url: str, force_update: bool = False):
|
|
|
|
|
+ if not self.cover_image or (force_update and url):
|
|
|
|
|
+ r = requests.get(url)
|
|
|
|
|
+ if r.status_code == 200:
|
|
|
|
|
+ fname = f"{self.title}_{self.uuid}.jpg"
|
|
|
|
|
+ self.cover_image.save(fname, ContentFile(r.content), save=True)
|
|
|
|
|
+
|
|
|
def scrobbles_for_user(self, user_id: int, include_playing=False):
|
|
def scrobbles_for_user(self, user_id: int, include_playing=False):
|
|
|
from scrobbles.models import Scrobble
|
|
from scrobbles.models import Scrobble
|
|
|
|
|
|
|
@@ -184,6 +194,31 @@ class Series(TimeStampedModel):
|
|
|
if genres := imdb_dict.get("genres"):
|
|
if genres := imdb_dict.get("genres"):
|
|
|
self.genre.add(*genres)
|
|
self.genre.add(*genres)
|
|
|
|
|
|
|
|
|
|
+ @classmethod
|
|
|
|
|
+ def find_or_create(cls, imdb_id: str, overwrite: bool = True):
|
|
|
|
|
+ series, created = cls.objects.get_or_create(imdb_id=imdb_id)
|
|
|
|
|
+
|
|
|
|
|
+ if not (created or overwrite):
|
|
|
|
|
+ return series
|
|
|
|
|
+
|
|
|
|
|
+ vdict, _, cover, genres = lookup_video_from_imdb(
|
|
|
|
|
+ imdb_id
|
|
|
|
|
+ ).as_dict_with_cover_and_genres()
|
|
|
|
|
+ vdict.pop("video_type")
|
|
|
|
|
+
|
|
|
|
|
+ vdict["name"] = vdict.pop("title")
|
|
|
|
|
+ for k, v in vdict.items():
|
|
|
|
|
+ setattr(series, k, v)
|
|
|
|
|
+ series.save()
|
|
|
|
|
+
|
|
|
|
|
+ if cover:
|
|
|
|
|
+ series.save_image_from_url(cover)
|
|
|
|
|
+ if genres:
|
|
|
|
|
+ series.genre.add(*genres)
|
|
|
|
|
+
|
|
|
|
|
+ return series
|
|
|
|
|
+
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class Video(ScrobblableMixin):
|
|
class Video(ScrobblableMixin):
|
|
|
COMPLETION_PERCENT = getattr(settings, "VIDEO_COMPLETION_PERCENT", 90)
|
|
COMPLETION_PERCENT = getattr(settings, "VIDEO_COMPLETION_PERCENT", 90)
|
|
@@ -304,7 +339,7 @@ class Video(ScrobblableMixin):
|
|
|
if not created and not overwrite:
|
|
if not created and not overwrite:
|
|
|
return video
|
|
return video
|
|
|
|
|
|
|
|
- vdict, cover, genres = lookup_video_from_youtube(
|
|
|
|
|
|
|
+ vdict, _, cover, genres = lookup_video_from_youtube(
|
|
|
youtube_id
|
|
youtube_id
|
|
|
).as_dict_with_cover_and_genres()
|
|
).as_dict_with_cover_and_genres()
|
|
|
if created or overwrite:
|
|
if created or overwrite:
|
|
@@ -320,28 +355,38 @@ class Video(ScrobblableMixin):
|
|
|
def get_from_imdb_id(
|
|
def get_from_imdb_id(
|
|
|
cls, imdb_id: str, overwrite: bool = False
|
|
cls, imdb_id: str, overwrite: bool = False
|
|
|
) -> "Video":
|
|
) -> "Video":
|
|
|
- if "tt" in imdb_id:
|
|
|
|
|
- imdb_id = imdb_id[2:]
|
|
|
|
|
video, created = cls.objects.get_or_create(imdb_id=imdb_id)
|
|
video, created = cls.objects.get_or_create(imdb_id=imdb_id)
|
|
|
if not created and not overwrite:
|
|
if not created and not overwrite:
|
|
|
return video
|
|
return video
|
|
|
|
|
|
|
|
- vdict, cover, genres = lookup_video_from_tmdb(
|
|
|
|
|
|
|
+ vdict, series_id, cover, genres = lookup_video_from_imdb(
|
|
|
imdb_id
|
|
imdb_id
|
|
|
).as_dict_with_cover_and_genres()
|
|
).as_dict_with_cover_and_genres()
|
|
|
|
|
+
|
|
|
if created or overwrite:
|
|
if created or overwrite:
|
|
|
for k, v in vdict.items():
|
|
for k, v in vdict.items():
|
|
|
setattr(video, k, v)
|
|
setattr(video, k, v)
|
|
|
|
|
+
|
|
|
|
|
+ if series_id:
|
|
|
|
|
+ video.tv_series = Series.find_or_create(imdb_id=series_id)
|
|
|
|
|
+
|
|
|
video.save()
|
|
video.save()
|
|
|
|
|
|
|
|
- video.save_image_from_url(cover)
|
|
|
|
|
- video.genre.add(*genres)
|
|
|
|
|
|
|
+ if cover:
|
|
|
|
|
+ video.save_image_from_url(cover)
|
|
|
|
|
+ if genres:
|
|
|
|
|
+ video.genre.add(*genres)
|
|
|
|
|
+
|
|
|
return video
|
|
return video
|
|
|
|
|
|
|
|
@classmethod
|
|
@classmethod
|
|
|
- def find_or_create(
|
|
|
|
|
- cls, data_dict: dict, post_keys: dict = JELLYFIN_POST_KEYS
|
|
|
|
|
- ) -> Optional["Video"]:
|
|
|
|
|
- """Thes smallest of wrappers around our actual get or create utility."""
|
|
|
|
|
- imdb_key = post_keys.get("IMDB_ID", "").replace("tt", "")
|
|
|
|
|
- return cls.get_from_imdb_id(data_dict.get(imdb_key))
|
|
|
|
|
|
|
+ def find_or_create(cls, source_id: str, overwrite: bool = True) -> "Video":
|
|
|
|
|
+ if "tt" in source_id:
|
|
|
|
|
+ return cls.get_from_imdb_id(source_id)
|
|
|
|
|
+ if bool(YOUTUBE_ID_PATTERN.match(source_id)):
|
|
|
|
|
+ return cls.get_from_youtube_id(source_id)
|
|
|
|
|
+
|
|
|
|
|
+ #TODO scrobble but without a video obj?
|
|
|
|
|
+ logger.warning("Video ID not recognized, not scrobbling")
|
|
|
|
|
+
|
|
|
|
|
+ return
|