252 lines
8.7 KiB
Python
252 lines
8.7 KiB
Python
import logging
|
|
|
|
from rest_framework import serializers
|
|
|
|
from funkwhale_api import musicbrainz
|
|
from funkwhale_api.tags import models as tags_models
|
|
|
|
from . import client
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ArtistSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for Musicbrainz artist data.
|
|
"""
|
|
|
|
id = serializers.CharField()
|
|
name = serializers.CharField()
|
|
|
|
def create(self, validated_data):
|
|
from funkwhale_api.music.models import Artist
|
|
|
|
data = {
|
|
"name": validated_data["name"],
|
|
"mbid": validated_data["id"],
|
|
}
|
|
artist, created = Artist.objects.get_or_create(**data)
|
|
return artist
|
|
|
|
|
|
class ArtistCreditSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for Musicbrainz artist data.
|
|
"""
|
|
|
|
name = serializers.CharField()
|
|
joinphrase = serializers.CharField(allow_blank=True)
|
|
artist = ArtistSerializer()
|
|
|
|
def create(self, validated_data):
|
|
from funkwhale_api.music.models import ArtistCredit
|
|
|
|
data = {
|
|
"credit": validated_data["name"],
|
|
"joinphrase": validated_data.get("joinphrase", ""),
|
|
"artist": ArtistSerializer().create(validated_data["artist"]),
|
|
}
|
|
artist_credit, created = ArtistCredit.objects.get_or_create(**data)
|
|
return artist_credit
|
|
|
|
|
|
class ReleaseForRecordingSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for Musicbrainz release data when returned in a recording object.
|
|
"""
|
|
|
|
id = serializers.CharField()
|
|
title = serializers.CharField()
|
|
artist_credit = ArtistCreditSerializer(many=True)
|
|
tags = serializers.ListField(child=serializers.CharField(), allow_empty=True)
|
|
date = serializers.DateField(input_formats=["%Y", "%Y/%m/%d", "%Y-%m-%d"])
|
|
|
|
def create(self, validated_data):
|
|
from funkwhale_api.music.models import Album
|
|
|
|
data = {
|
|
"title": validated_data["title"],
|
|
"mbid": validated_data["id"],
|
|
"release_date": validated_data.get("date", None),
|
|
}
|
|
album, created = Album.objects.get_or_create(**data)
|
|
artist_credit = ArtistCreditSerializer(many=True).create(
|
|
validated_data["artist_credit"]
|
|
)
|
|
album.artist_credit.set(artist_credit)
|
|
album.save()
|
|
tags_models.add_tags(album, *validated_data.get("tags", []))
|
|
return album
|
|
|
|
def update(self, instance, validated_data):
|
|
instance.title = validated_data["title"]
|
|
instance.release_date = validated_data.get("date")
|
|
instance.save()
|
|
tags_models.add_tags(instance, *validated_data.get("tags", []))
|
|
|
|
return instance
|
|
|
|
|
|
class RecordingSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for Musicbrainz track data.
|
|
"""
|
|
|
|
id = serializers.CharField()
|
|
title = serializers.CharField()
|
|
artist_credit = ArtistCreditSerializer(many=True)
|
|
releases = ReleaseForRecordingSerializer(many=True, required=False)
|
|
tags = serializers.ListField(child=serializers.CharField(), allow_empty=True)
|
|
position = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
def create(self, validated_data):
|
|
from funkwhale_api.music.models import Track
|
|
|
|
data = {"mbid": validated_data["id"]}
|
|
defaults = {
|
|
"title": validated_data["title"],
|
|
"mbid": validated_data["id"],
|
|
# In mb a recording can have various releases, we take the fist one
|
|
"album": (
|
|
ReleaseForRecordingSerializer(many=True).create(
|
|
validated_data["releases"]
|
|
)[0]
|
|
if validated_data.get("releases")
|
|
else None
|
|
),
|
|
# this will be none if the recording is not fetched from the release endpoint
|
|
"position": validated_data.get("position", None),
|
|
}
|
|
|
|
if defaults["album"] is not None and defaults["position"] is None:
|
|
result = client.api.releases.get(
|
|
id=validated_data["releases"][0]["id"],
|
|
includes=["tags", "artists", "recordings"],
|
|
)
|
|
tracks = result["media"][0]["tracks"]
|
|
defaults["position"] = next(
|
|
(o for o in tracks if o["recording"]["id"] == data["mbid"]), {}
|
|
).get("position", None)
|
|
|
|
track, created = Track.objects.get_or_create(**data, defaults=defaults)
|
|
artist_credit = ArtistCreditSerializer(many=True).create(
|
|
validated_data["artist_credit"]
|
|
)
|
|
track.artist_credit.set(artist_credit)
|
|
track.save()
|
|
|
|
tags_models.add_tags(track, *validated_data.get("tags", []))
|
|
|
|
return track
|
|
|
|
def update(self, instance, validated_data):
|
|
instance.title = validated_data["title"]
|
|
instance.save()
|
|
tags_models.add_tags(instance, *validated_data.get("tags", []))
|
|
|
|
return instance
|
|
|
|
|
|
class RecordingForReleaseSerializer(serializers.Serializer):
|
|
id = serializers.CharField()
|
|
title = serializers.CharField()
|
|
# not in Musicbrainz recording object, but used to store the position of the track in the album
|
|
position = serializers.IntegerField(required=False, allow_null=True)
|
|
|
|
def create(self, validated_data):
|
|
def replace_hyphens_in_keys(obj):
|
|
if isinstance(obj, dict):
|
|
return {
|
|
k.replace("-", "_"): replace_hyphens_in_keys(v)
|
|
for k, v in obj.items()
|
|
}
|
|
elif isinstance(obj, list):
|
|
return [replace_hyphens_in_keys(item) for item in obj]
|
|
else:
|
|
return obj
|
|
|
|
recordings_data = musicbrainz.api.recordings.get(
|
|
id=validated_data["id"], includes=["tags", "artists"]
|
|
)
|
|
recordings_data = replace_hyphens_in_keys(recordings_data)
|
|
recordings_data["position"] = validated_data.get("position", None)
|
|
serializer = RecordingSerializer(data=recordings_data)
|
|
serializer.is_valid(raise_exception=True)
|
|
track = serializer.save()
|
|
track.album = validated_data["album"]
|
|
track.save()
|
|
return track
|
|
|
|
def update(self, instance, validated_data):
|
|
instance.title = validated_data["title"]
|
|
tags_models.add_tags(instance, *validated_data.get("tags", []))
|
|
instance.album = validated_data["album"]
|
|
instance.save()
|
|
return instance
|
|
|
|
|
|
class TrackSerializer(serializers.Serializer):
|
|
recording = RecordingForReleaseSerializer()
|
|
position = serializers.IntegerField()
|
|
|
|
|
|
class MediaSerializer(serializers.Serializer):
|
|
tracks = TrackSerializer(many=True)
|
|
|
|
|
|
class ReleaseSerializer(serializers.Serializer):
|
|
"""
|
|
Serializer for Musicbrainz release data.
|
|
"""
|
|
|
|
id = serializers.CharField()
|
|
title = serializers.CharField()
|
|
artist_credit = ArtistCreditSerializer(many=True)
|
|
tags = serializers.ListField(child=serializers.CharField(), allow_empty=True)
|
|
date = serializers.DateField(input_formats=["%Y", "%Y/%m/%d", "%Y-%m-%d"])
|
|
media = serializers.ListField(child=MediaSerializer())
|
|
|
|
def create(self, validated_data):
|
|
from funkwhale_api.music.models import Album
|
|
|
|
data = {
|
|
"title": validated_data["title"],
|
|
"mbid": validated_data["id"],
|
|
"release_date": validated_data.get("date"),
|
|
}
|
|
album, created = Album.objects.get_or_create(**data)
|
|
artist_credit = ArtistCreditSerializer(many=True).create(
|
|
validated_data["artist_credit"]
|
|
)
|
|
album.artist_credit.set(artist_credit)
|
|
album.save()
|
|
tags_models.add_tags(album, *validated_data.get("tags", []))
|
|
|
|
# an album can have various media/physical representation, we take the first one
|
|
tracks = [t for t in validated_data["media"][0]["tracks"]]
|
|
recordings = []
|
|
for t in tracks:
|
|
t["recording"]["position"] = t["position"]
|
|
recordings.append(t["recording"])
|
|
|
|
for r in recordings:
|
|
r["album"] = album
|
|
RecordingForReleaseSerializer().create(r)
|
|
|
|
return album
|
|
|
|
# this will never be used while FetchViewSet and third_party_fetch filter out finished fetch
|
|
# Would be nice to have a way to manually update releases from Musicbrainz
|
|
def update(self, instance, validated_data):
|
|
logger.info(f"Updating release {instance} with data: {validated_data}")
|
|
instance.title = validated_data["title"]
|
|
instance.release_date = validated_data.get("date")
|
|
instance.save()
|
|
tags_models.add_tags(instance, *validated_data.get("tags", []))
|
|
|
|
recordings = [t["recording"] for t in validated_data["media"][0]["tracks"]]
|
|
for r in recordings:
|
|
r["album"] = instance
|
|
RecordingForReleaseSerializer().update(r)
|
|
return instance
|