Jelajahi Sumber

Add very rudimentary fetching of art and metadata from MB

Colin Powell 2 tahun lalu
induk
melakukan
f435e60b80

+ 20 - 1
poetry.lock

@@ -625,6 +625,14 @@ category = "dev"
 optional = false
 python-versions = ">=3.6"
 
+[[package]]
+name = "musicbrainzngs"
+version = "0.7.1"
+description = "Python bindings for the MusicBrainz NGS and the Cover Art Archive webservices"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*"
+
 [[package]]
 name = "mypy"
 version = "0.961"
@@ -1369,7 +1377,7 @@ testing = ["flake8 (<5)", "func-timeout", "jaraco.functools", "jaraco.itertools"
 [metadata]
 lock-version = "1.1"
 python-versions = "^3.8"
-content-hash = "6105971e3adba942edffa16bd54f5822cdcabcd1e55dfecfc67410cf486a1a71"
+content-hash = "f8d6b69cfb8ac53b7e03533e8fede68d323363bd73b9c7902d958dc433b1210e"
 
 [metadata.files]
 amqp = [
@@ -1719,6 +1727,10 @@ mccabe = [
     {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"},
     {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"},
 ]
+musicbrainzngs = [
+    {file = "musicbrainzngs-0.7.1-py2.py3-none-any.whl", hash = "sha256:e841a8f975104c0a72290b09f59326050194081a5ae62ee512f41915090e1a10"},
+    {file = "musicbrainzngs-0.7.1.tar.gz", hash = "sha256:ab1c0100fd0b305852e65f2ed4113c6de12e68afd55186987b8ed97e0f98e627"},
+]
 mypy = [
     {file = "mypy-0.961-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:697540876638ce349b01b6786bc6094ccdaba88af446a9abb967293ce6eaa2b0"},
     {file = "mypy-0.961-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:b117650592e1782819829605a193360a08aa99f1fc23d1d71e1a75a142dc7e15"},
@@ -1769,6 +1781,13 @@ pbr = [
     {file = "pbr-5.11.0.tar.gz", hash = "sha256:b97bc6695b2aff02144133c2e7399d5885223d42b7912ffaec2ca3898e673bfe"},
 ]
 pillow = [
+    {file = "Pillow-9.4.0-1-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:1b4b4e9dda4f4e4c4e6896f93e84a8f0bcca3b059de9ddf67dac3c334b1195e1"},
+    {file = "Pillow-9.4.0-1-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:fb5c1ad6bad98c57482236a21bf985ab0ef42bd51f7ad4e4538e89a997624e12"},
+    {file = "Pillow-9.4.0-1-cp37-cp37m-macosx_10_10_x86_64.whl", hash = "sha256:f0caf4a5dcf610d96c3bd32932bfac8aee61c96e60481c2a0ea58da435e25acd"},
+    {file = "Pillow-9.4.0-1-cp38-cp38-macosx_10_10_x86_64.whl", hash = "sha256:3f4cc516e0b264c8d4ccd6b6cbc69a07c6d582d8337df79be1e15a5056b258c9"},
+    {file = "Pillow-9.4.0-1-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:b8c2f6eb0df979ee99433d8b3f6d193d9590f735cf12274c108bd954e30ca858"},
+    {file = "Pillow-9.4.0-1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:b70756ec9417c34e097f987b4d8c510975216ad26ba6e57ccb53bc758f490dab"},
+    {file = "Pillow-9.4.0-1-pp39-pypy39_pp73-macosx_10_10_x86_64.whl", hash = "sha256:43521ce2c4b865d385e78579a082b6ad1166ebed2b1a2293c3be1d68dd7ca3b9"},
     {file = "Pillow-9.4.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:2968c58feca624bb6c8502f9564dd187d0e1389964898f5e9e1fbc8533169157"},
     {file = "Pillow-9.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c5c1362c14aee73f50143d74389b2c158707b4abce2cb055b7ad37ce60738d47"},
     {file = "Pillow-9.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd752c5ff1b4a870b7661234694f24b1d2b9076b8bf337321a814c612665f343"},

+ 1 - 0
pyproject.toml

@@ -27,6 +27,7 @@ django-markdownify = "^0.9.1"
 gunicorn = "^20.1.0"
 django-simple-history = "^3.1.1"
 whitenoise = "^6.3.0"
+musicbrainzngs = "^0.7.1"
 
 [tool.poetry.dev-dependencies]
 Werkzeug = "2.0.3"

+ 3 - 0
vrobbler/apps/music/admin.py

@@ -9,6 +9,9 @@ class AlbumAdmin(admin.ModelAdmin):
     list_display = ("name", "year", "musicbrainz_id")
     list_filter = ("year",)
     ordering = ("name",)
+    filter_horizontal = [
+        'artists',
+    ]
 
 
 @admin.register(Artist)

+ 23 - 0
vrobbler/apps/music/migrations/0005_album_cover_image.py

@@ -0,0 +1,23 @@
+# Generated by Django 4.1.5 on 2023-01-12 04:22
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        (
+            'music',
+            '0004_alter_artist_options_alter_album_musicbrainz_id_and_more',
+        ),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='album',
+            name='cover_image',
+            field=models.ImageField(
+                blank=True, null=True, upload_to='albums/'
+            ),
+        ),
+    ]

+ 20 - 0
vrobbler/apps/music/migrations/0006_album_artists.py

@@ -0,0 +1,20 @@
+# Generated by Django 4.1.5 on 2023-01-12 05:04
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('music', '0005_album_cover_image'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='album',
+            name='artists',
+            field=models.ManyToManyField(
+                blank=True, null=True, to='music.artist'
+            ),
+        ),
+    ]

+ 54 - 14
vrobbler/apps/music/models.py

@@ -1,9 +1,10 @@
 import logging
 from typing import Dict, Optional
 from uuid import uuid4
+import musicbrainzngs
 
 from django.apps.config import cached_property
-
+from django.core.files.base import ContentFile
 from django.db import models
 from django.utils.translation import gettext_lazy as _
 from django_extensions.db.models import TimeStampedModel
@@ -12,36 +13,73 @@ logger = logging.getLogger(__name__)
 BNULL = {"blank": True, "null": True}
 
 
-class Album(TimeStampedModel):
+class Artist(TimeStampedModel):
     uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
     name = models.CharField(max_length=255)
-    year = models.IntegerField(**BNULL)
-    musicbrainz_id = models.CharField(max_length=255, unique=True, **BNULL)
-    musicbrainz_releasegroup_id = models.CharField(max_length=255, **BNULL)
-    musicbrainz_albumartist_id = models.CharField(max_length=255, **BNULL)
+    musicbrainz_id = models.CharField(max_length=255, **BNULL)
+
+    class Meta:
+        unique_together = [['name', 'musicbrainz_id']]
 
     def __str__(self):
         return self.name
 
     @property
     def mb_link(self):
-        return f"https://musicbrainz.org/release/{self.musicbrainz_id}"
+        return f"https://musicbrainz.org/artist/{self.musicbrainz_id}"
 
 
-class Artist(TimeStampedModel):
+class Album(TimeStampedModel):
     uuid = models.UUIDField(default=uuid4, editable=False, **BNULL)
     name = models.CharField(max_length=255)
-    musicbrainz_id = models.CharField(max_length=255, **BNULL)
-
-    class Meta:
-        unique_together=[['name', 'musicbrainz_id']]
+    artists = models.ManyToManyField(Artist, **BNULL)
+    year = models.IntegerField(**BNULL)
+    musicbrainz_id = models.CharField(max_length=255, unique=True, **BNULL)
+    musicbrainz_releasegroup_id = models.CharField(max_length=255, **BNULL)
+    musicbrainz_albumartist_id = models.CharField(max_length=255, **BNULL)
+    cover_image = models.ImageField(upload_to="albums/", **BNULL)
 
     def __str__(self):
         return self.name
 
+    @property
+    def primary_artist(self):
+        return self.artists.first()
+
+    def fix_metadata(self):
+        musicbrainzngs.set_useragent('vrobbler', '0.3.0')
+        mb_data = musicbrainzngs.get_release_by_id(
+            self.musicbrainz_id, includes=['artists']
+        )
+        if not self.musicbrainz_albumartist_id:
+            self.musicbrainz_albumartist_id = mb_data['release'][
+                'artist-credit'
+            ][0]['artist']['id']
+        if not self.year:
+            self.year = mb_data['release']['date'][0:4]
+        self.save(update_fields=['musicbrainz_albumartist_id', 'year'])
+
+        new_artist = Artist.objects.filter(
+            musicbrainz_id=self.musicbrainz_albumartist_id
+        ).first()
+        if self.musicbrainz_albumartist_id and new_artist:
+            self.artists.add(new_artist)
+        if not new_artist:
+            for t in self.track_set.all():
+                self.artists.add(t.artist)
+
+    def fetch_artwork(self):
+        try:
+            img_data = musicbrainzngs.get_image_front(self.musicbrainz_id)
+            name = f"{self.name}_{self.uuid}.jpg"
+            self.cover_image = ContentFile(img_data, name=name)
+            self.save()
+        except musicbrainzngs.ResponseError:
+            logger.warning(f'No cover art found for {self.name}')
+
     @property
     def mb_link(self):
-        return f"https://musicbrainz.org/artist/{self.musicbrainz_id}"
+        return f"https://musicbrainz.org/release/{self.musicbrainz_id}"
 
 
 class Track(TimeStampedModel):
@@ -83,7 +121,7 @@ class Track(TimeStampedModel):
             'musicbrainz_id'
         ):
             logger.warning(
-                f"No artist or artist musicbrainz ID found in message from Jellyfin, not scrobbling"
+                f"No artist or artist musicbrainz ID found in message from source, not scrobbling"
             )
             return
         artist, artist_created = Artist.objects.get_or_create(**artist_dict)
@@ -97,6 +135,8 @@ class Track(TimeStampedModel):
             logger.debug(f"Created new album {album}")
         else:
             logger.debug(f"Found album {album}")
+        album.fix_metadata()
+        album.fetch_artwork()
 
         track_dict['album_id'] = getattr(album, "id", None)
         track_dict['artist_id'] = artist.id