From 39f3a31847711e226fab14fe991367ff1f24d074 Mon Sep 17 00:00:00 2001 From: Colin Powell Date: Wed, 6 Nov 2024 10:50:39 -0500 Subject: [PATCH] [videos] Update youtubes stuff --- vrobbler/apps/scrobbles/tsv.py | 15 +++- ...e_url_video_youtube_id_channel_and_more.py | 88 +++++++++++++++++++ vrobbler/apps/videos/models.py | 61 ++++++++++++- 3 files changed, 158 insertions(+), 6 deletions(-) create mode 100644 vrobbler/apps/videos/migrations/0019_rename_youtube_url_video_youtube_id_channel_and_more.py diff --git a/vrobbler/apps/scrobbles/tsv.py b/vrobbler/apps/scrobbles/tsv.py index 1338511..9167512 100644 --- a/vrobbler/apps/scrobbles/tsv.py +++ b/vrobbler/apps/scrobbles/tsv.py @@ -12,8 +12,9 @@ from music.utils import ( ) from scrobbles.constants import AsTsvColumn from scrobbles.models import Scrobble +from music.constants import MOPIDY_POST_KEYS -from vrobbler.apps.scrobbles.utils import timestamp_user_tz_to_utc +from scrobbles.utils import timestamp_user_tz_to_utc logger = logging.getLogger(__name__) @@ -43,8 +44,8 @@ def process_audioscrobbler_tsv_file(file_path, user_id, user_tz=None): rockbox_info += row[0] + "\n" continue if len(row) > 8: - logger.warning( - "Improper row length during Audioscrobbler import", + logger.info( + "[skip] too many columns in row", extra={"row": row}, ) continue @@ -73,7 +74,13 @@ def process_audioscrobbler_tsv_file(file_path, user_id, user_tz=None): # TODO Set all this up as constants if row[AsTsvColumn["COMPLETE"].value] == "S": - logger.info(f"Skipping track {track} because not finished") + logger.info( + "[skip] track not finished", + extra={ + "album_name": row[AsTsvColumn["ALBUM_NAME"].value], + "artist_name": row[AsTsvColumn["ARTIST_NAME"].value], + } + ) continue timestamp = timestamp_user_tz_to_utc( diff --git a/vrobbler/apps/videos/migrations/0019_rename_youtube_url_video_youtube_id_channel_and_more.py b/vrobbler/apps/videos/migrations/0019_rename_youtube_url_video_youtube_id_channel_and_more.py new file mode 100644 index 0000000..61cbaca --- /dev/null +++ b/vrobbler/apps/videos/migrations/0019_rename_youtube_url_video_youtube_id_channel_and_more.py @@ -0,0 +1,88 @@ +# Generated by Django 4.2.16 on 2024-11-06 15:49 + +from django.db import migrations, models +import django.db.models.deletion +import django_extensions.db.fields +import taggit.managers +import uuid + + +class Migration(migrations.Migration): + + dependencies = [ + ("scrobbles", "0066_scrobble_beer_alter_scrobble_media_type"), + ("videos", "0018_video_youtube_url_alter_video_video_type"), + ] + + operations = [ + migrations.RenameField( + model_name="video", + old_name="youtube_url", + new_name="youtube_id", + ), + migrations.CreateModel( + name="Channel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "created", + django_extensions.db.fields.CreationDateTimeField( + auto_now_add=True, verbose_name="created" + ), + ), + ( + "modified", + django_extensions.db.fields.ModificationDateTimeField( + auto_now=True, verbose_name="modified" + ), + ), + ( + "uuid", + models.UUIDField( + blank=True, + default=uuid.uuid4, + editable=False, + null=True, + ), + ), + ("name", models.CharField(max_length=255)), + ( + "cover_image", + models.ImageField( + blank=True, null=True, upload_to="videos/channels/" + ), + ), + ( + "genre", + taggit.managers.TaggableManager( + help_text="A comma-separated list of tags.", + through="scrobbles.ObjectWithGenres", + to="scrobbles.Genre", + verbose_name="Tags", + ), + ), + ], + options={ + "get_latest_by": "modified", + "abstract": False, + }, + ), + migrations.AddField( + model_name="video", + name="channel", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + to="videos.channel", + ), + ), + ] diff --git a/vrobbler/apps/videos/models.py b/vrobbler/apps/videos/models.py index ef81b66..d52d238 100644 --- a/vrobbler/apps/videos/models.py +++ b/vrobbler/apps/videos/models.py @@ -23,6 +23,56 @@ from videos.imdb import lookup_video_from_imdb logger = logging.getLogger(__name__) BNULL = {"blank": True, "null": True} +class Channel(TimeStampedModel): + uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) + name = models.CharField(max_length=255) + cover_image = models.ImageField(upload_to="videos/channels/", **BNULL) + cover_small = ImageSpecField( + source="cover_image", + processors=[ResizeToFit(100, 100)], + format="JPEG", + options={"quality": 60}, + ) + cover_medium = ImageSpecField( + source="cover_image", + processors=[ResizeToFit(300, 300)], + format="JPEG", + options={"quality": 75}, + ) + genre = TaggableManager(through=ObjectWithGenres) + + def __str__(self): + return self.name + + def get_absolute_url(self): + return reverse("videos:channel_detail", kwargs={"slug": self.uuid}) + + def youtube_link(self): + return f"https://www.youtube.com/user/t{self.yt_username}" + + @property + def primary_image_url(self) -> str: + url = "" + if self.cover_image: + url = self.cover_image_medium.url + return url + + def scrobbles_for_user(self, user_id: int, include_playing=False): + from scrobbles.models import Scrobble + + played_query = models.Q(played_to_completion=True) + if include_playing: + played_query = models.Q() + return Scrobble.objects.filter( + played_query, + video__channel=self, + user=user_id, + ).order_by("-timestamp") + + def fix_metadata(self, force: bool = False): + # TODO Scrape channel info from Youtube + logger.warning("Not implemented yet") + return class Series(TimeStampedModel): uuid = models.UUIDField(default=uuid4, editable=False, **BNULL) @@ -141,6 +191,7 @@ class Video(ScrobblableMixin): # TV show specific fields tv_series = models.ForeignKey(Series, on_delete=models.DO_NOTHING, **BNULL) + channel = models.ForeignKey(Channel, on_delete=models.DO_NOTHING, **BNULL) season_number = models.IntegerField(**BNULL) episode_number = models.IntegerField(**BNULL) next_imdb_id = models.CharField(max_length=20, **BNULL) @@ -162,7 +213,7 @@ class Video(ScrobblableMixin): tvrage_id = models.CharField(max_length=20, **BNULL) tvdb_id = models.CharField(max_length=20, **BNULL) tmdb_id = models.CharField(max_length=20, **BNULL) - youtube_url = models.CharField(max_length=255, **BNULL) + youtube_id = models.CharField(max_length=255, **BNULL) plot = models.TextField(**BNULL) year = models.IntegerField(**BNULL) @@ -171,7 +222,9 @@ class Video(ScrobblableMixin): def __str__(self): if self.video_type == self.VideoType.TV_EPISODE: - return f"{self.tv_series} - Season {self.season_number}, Episode {self.episode_number}" + return f"{self.title} - {self.tv_series} - Season {self.season_number}, Episode {self.episode_number}" + if self.video_type == self.VideoType.YOUTUBE: + return f"{self.title} - {self.channel}" return self.title def get_absolute_url(self): @@ -195,6 +248,10 @@ class Video(ScrobblableMixin): def link(self): return self.imdb_link + @property + def youtube_link(self): + return f"https://www.youtube.com/watch?v={self.youtube_id}" + @property def primary_image_url(self) -> str: url = ""