From 1bee3a4675a63156eee4f56e8a63be4acd308d53 Mon Sep 17 00:00:00 2001
From: Eliot Berriot <contact@eliotberriot.com>
Date: Sun, 23 Sep 2018 12:38:42 +0000
Subject: [PATCH] Import trust source

---
 api/funkwhale_api/federation/serializers.py   |  90 ++--
 api/funkwhale_api/music/metadata.py           |  57 ++-
 api/funkwhale_api/music/models.py             |  33 +-
 api/funkwhale_api/music/tasks.py              | 459 +++++++++++-------
 .../management/commands/import_files.py       |  47 +-
 api/requirements/local.txt                    |   1 +
 api/tests/conftest.py                         |   9 +-
 api/tests/federation/test_serializers.py      | 128 +----
 api/tests/music/test.mp3                      | Bin 297745 -> 297745 bytes
 api/tests/music/test_metadata.py              |  62 ++-
 api/tests/music/test_tasks.py                 | 351 +++++++++++---
 api/tests/test_import_audio_file.py           |  35 +-
 dev.yml                                       |   1 +
 .../views/content/libraries/FilesTable.vue    |   4 +
 14 files changed, 860 insertions(+), 417 deletions(-)

diff --git a/api/funkwhale_api/federation/serializers.py b/api/funkwhale_api/federation/serializers.py
index 99ed708f1..71cd7a831 100644
--- a/api/funkwhale_api/federation/serializers.py
+++ b/api/funkwhale_api/federation/serializers.py
@@ -4,7 +4,6 @@ import urllib.parse
 
 from django.core.exceptions import ObjectDoesNotExist
 from django.core.paginator import Paginator
-from django.db.models import F, Q
 from rest_framework import serializers
 
 from funkwhale_api.common import utils as funkwhale_utils
@@ -21,6 +20,31 @@ AP_CONTEXT = [
 logger = logging.getLogger(__name__)
 
 
+class LinkSerializer(serializers.Serializer):
+    type = serializers.ChoiceField(choices=["Link"])
+    href = serializers.URLField(max_length=500)
+    mediaType = serializers.CharField()
+
+    def __init__(self, *args, **kwargs):
+        self.allowed_mimetypes = kwargs.pop("allowed_mimetypes", [])
+        super().__init__(*args, **kwargs)
+
+    def validate_mediaType(self, v):
+        if not self.allowed_mimetypes:
+            # no restrictions
+            return v
+        for mt in self.allowed_mimetypes:
+            if mt.endswith("/*"):
+                if v.startswith(mt.replace("*", "")):
+                    return v
+            else:
+                if v == mt:
+                    return v
+        raise serializers.ValidationError(
+            "Invalid mimetype {}. Allowed: {}".format(v, self.allowed_mimetypes)
+        )
+
+
 class ActorSerializer(serializers.Serializer):
     id = serializers.URLField(max_length=500)
     outbox = serializers.URLField(max_length=500)
@@ -626,32 +650,8 @@ class MusicEntitySerializer(serializers.Serializer):
     musicbrainzId = serializers.UUIDField(allow_null=True, required=False)
     name = serializers.CharField(max_length=1000)
 
-    def create(self, validated_data):
-        mbid = validated_data.get("musicbrainzId")
-        candidates = self.model.objects.filter(
-            Q(mbid=mbid) | Q(fid=validated_data["id"])
-        ).order_by(F("fid").desc(nulls_last=True))
-
-        existing = candidates.first()
-        if existing:
-            return existing
-
-        # nothing matching in our database, let's create a new object
-        return self.model.objects.create(**self.get_create_data(validated_data))
-
-    def get_create_data(self, validated_data):
-        return {
-            "mbid": validated_data.get("musicbrainzId"),
-            "fid": validated_data["id"],
-            "name": validated_data["name"],
-            "creation_date": validated_data["published"],
-            "from_activity": self.context.get("activity"),
-        }
-
 
 class ArtistSerializer(MusicEntitySerializer):
-    model = music_models.Artist
-
     def to_representation(self, instance):
         d = {
             "type": "Artist",
@@ -667,9 +667,11 @@ class ArtistSerializer(MusicEntitySerializer):
 
 
 class AlbumSerializer(MusicEntitySerializer):
-    model = music_models.Album
     released = serializers.DateField(allow_null=True, required=False)
     artists = serializers.ListField(child=ArtistSerializer(), min_length=1)
+    cover = LinkSerializer(
+        allowed_mimetypes=["image/*"], allow_null=True, required=False
+    )
 
     def to_representation(self, instance):
         d = {
@@ -688,7 +690,12 @@ class AlbumSerializer(MusicEntitySerializer):
             ],
         }
         if instance.cover:
-            d["cover"] = {"type": "Image", "url": utils.full_url(instance.cover.url)}
+            d["cover"] = {
+                "type": "Link",
+                "href": utils.full_url(instance.cover.url),
+                "mediaType": mimetypes.guess_type(instance.cover.path)[0]
+                or "image/jpeg",
+            }
         if self.context.get("include_ap_context", self.parent is None):
             d["@context"] = AP_CONTEXT
         return d
@@ -711,7 +718,6 @@ class AlbumSerializer(MusicEntitySerializer):
 
 
 class TrackSerializer(MusicEntitySerializer):
-    model = music_models.Track
     position = serializers.IntegerField(min_value=0, allow_null=True, required=False)
     artists = serializers.ListField(child=ArtistSerializer(), min_length=1)
     album = AlbumSerializer()
@@ -738,32 +744,22 @@ class TrackSerializer(MusicEntitySerializer):
             d["@context"] = AP_CONTEXT
         return d
 
-    def get_create_data(self, validated_data):
-        artist_data = validated_data["artists"][0]
-        artist = ArtistSerializer(
-            context={"activity": self.context.get("activity")}
-        ).create(artist_data)
-        album = AlbumSerializer(
-            context={"activity": self.context.get("activity")}
-        ).create(validated_data["album"])
+    def create(self, validated_data):
+        from funkwhale_api.music import tasks as music_tasks
 
-        return {
-            "mbid": validated_data.get("musicbrainzId"),
-            "fid": validated_data["id"],
-            "title": validated_data["name"],
-            "position": validated_data.get("position"),
-            "creation_date": validated_data["published"],
-            "artist": artist,
-            "album": album,
-            "from_activity": self.context.get("activity"),
-        }
+        metadata = music_tasks.federation_audio_track_to_metadata(validated_data)
+        from_activity = self.context.get("activity")
+        if from_activity:
+            metadata["from_activity_id"] = from_activity.pk
+        track = music_tasks.get_track_from_import_metadata(metadata)
+        return track
 
 
 class UploadSerializer(serializers.Serializer):
     type = serializers.ChoiceField(choices=["Audio"])
     id = serializers.URLField(max_length=500)
     library = serializers.URLField(max_length=500)
-    url = serializers.JSONField()
+    url = LinkSerializer(allowed_mimetypes=["audio/*"])
     published = serializers.DateTimeField()
     updated = serializers.DateTimeField(required=False, allow_null=True)
     bitrate = serializers.IntegerField(min_value=0)
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 4c754ae05..21daf2747 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -93,9 +93,9 @@ def convert_track_number(v):
 class FirstUUIDField(forms.UUIDField):
     def to_python(self, value):
         try:
-            # sometimes, Picard leaves to uuids in the field, separated
-            # by a slash
-            value = value.split("/")[0]
+            # sometimes, Picard leaves two uuids in the field, separated
+            # by a slash or a ;
+            value = value.split(";")[0].split("/")[0].strip()
         except (AttributeError, IndexError, TypeError):
             pass
 
@@ -107,10 +107,18 @@ def get_date(value):
     return datetime.date(parsed.year, parsed.month, parsed.day)
 
 
+def split_and_return_first(separator):
+    def inner(v):
+        return v.split(separator)[0].strip()
+
+    return inner
+
+
 VALIDATION = {
     "musicbrainz_artistid": FirstUUIDField(),
     "musicbrainz_albumid": FirstUUIDField(),
     "musicbrainz_recordingid": FirstUUIDField(),
+    "musicbrainz_albumartistid": FirstUUIDField(),
 }
 
 CONF = {
@@ -123,10 +131,15 @@ CONF = {
             },
             "title": {},
             "artist": {},
+            "album_artist": {
+                "field": "albumartist",
+                "to_application": split_and_return_first(";"),
+            },
             "album": {},
             "date": {"field": "date", "to_application": get_date},
             "musicbrainz_albumid": {},
             "musicbrainz_artistid": {},
+            "musicbrainz_albumartistid": {},
             "musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
         },
     },
@@ -139,10 +152,15 @@ CONF = {
             },
             "title": {},
             "artist": {},
+            "album_artist": {
+                "field": "albumartist",
+                "to_application": split_and_return_first(";"),
+            },
             "album": {},
             "date": {"field": "date", "to_application": get_date},
             "musicbrainz_albumid": {},
             "musicbrainz_artistid": {},
+            "musicbrainz_albumartistid": {},
             "musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
         },
     },
@@ -155,10 +173,12 @@ CONF = {
             },
             "title": {},
             "artist": {},
+            "album_artist": {"field": "albumartist"},
             "album": {},
             "date": {"field": "date", "to_application": get_date},
             "musicbrainz_albumid": {"field": "MusicBrainz Album Id"},
             "musicbrainz_artistid": {"field": "MusicBrainz Artist Id"},
+            "musicbrainz_albumartistid": {"field": "MusicBrainz Album Artist Id"},
             "musicbrainz_recordingid": {"field": "MusicBrainz Track Id"},
         },
     },
@@ -169,10 +189,12 @@ CONF = {
             "track_number": {"field": "TRCK", "to_application": convert_track_number},
             "title": {"field": "TIT2"},
             "artist": {"field": "TPE1"},
+            "album_artist": {"field": "TPE2"},
             "album": {"field": "TALB"},
             "date": {"field": "TDRC", "to_application": get_date},
             "musicbrainz_albumid": {"field": "MusicBrainz Album Id"},
             "musicbrainz_artistid": {"field": "MusicBrainz Artist Id"},
+            "musicbrainz_albumartistid": {"field": "MusicBrainz Album Artist Id"},
             "musicbrainz_recordingid": {
                 "field": "UFID",
                 "getter": get_mp3_recording_id,
@@ -190,10 +212,12 @@ CONF = {
             },
             "title": {},
             "artist": {},
+            "album_artist": {"field": "albumartist"},
             "album": {},
             "date": {"field": "date", "to_application": get_date},
             "musicbrainz_albumid": {},
             "musicbrainz_artistid": {},
+            "musicbrainz_albumartistid": {},
             "musicbrainz_recordingid": {"field": "musicbrainz_trackid"},
             "test": {},
             "pictures": {},
@@ -201,6 +225,19 @@ CONF = {
     },
 }
 
+ALL_FIELDS = [
+    "track_number",
+    "title",
+    "artist",
+    "album_artist",
+    "album",
+    "date",
+    "musicbrainz_albumid",
+    "musicbrainz_artistid",
+    "musicbrainz_albumartistid",
+    "musicbrainz_recordingid",
+]
+
 
 class Metadata(object):
     def __init__(self, path):
@@ -238,6 +275,20 @@ class Metadata(object):
             v = field.to_python(v)
         return v
 
+    def all(self):
+        """
+        Return a dict containing all metadata of the file
+        """
+
+        data = {}
+        for field in ALL_FIELDS:
+            try:
+                data[field] = self.get(field, None)
+            except (TagNotFound, forms.ValidationError):
+                data[field] = None
+
+        return data
+
     def get_picture(self, picture_type="cover_front"):
         ptype = getattr(mutagen.id3.PictureType, picture_type.upper())
         try:
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 51f1d4286..55f1c77b8 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -1,4 +1,5 @@
 import datetime
+import logging
 import os
 import tempfile
 import uuid
@@ -21,11 +22,14 @@ from versatileimagefield.image_warmer import VersatileImageFieldWarmer
 
 from funkwhale_api import musicbrainz
 from funkwhale_api.common import fields
+from funkwhale_api.common import session
 from funkwhale_api.common import utils as common_utils
 from funkwhale_api.federation import models as federation_models
 from funkwhale_api.federation import utils as federation_utils
 from . import importers, metadata, utils
 
+logger = logging.getLogger(__file__)
+
 
 def empty_dict():
     return {}
@@ -240,14 +244,35 @@ class Album(APIModelMixin):
 
     def get_image(self, data=None):
         if data:
-            f = ContentFile(data["content"])
             extensions = {"image/jpeg": "jpg", "image/png": "png", "image/gif": "gif"}
             extension = extensions.get(data["mimetype"], "jpg")
-            self.cover.save("{}.{}".format(self.uuid, extension), f)
-        else:
+            if data.get("content"):
+                # we have to cover itself
+                f = ContentFile(data["content"])
+            elif data.get("url"):
+                # we can fetch from a url
+                try:
+                    response = session.get_session().get(
+                        data.get("url"),
+                        timeout=3,
+                        verify=settings.EXTERNAL_REQUESTS_VERIFY_SSL,
+                    )
+                    response.raise_for_status()
+                except Exception as e:
+                    logger.warn(
+                        "Cannot download cover at url %s: %s", data.get("url"), e
+                    )
+                    return
+                else:
+                    f = ContentFile(response.content)
+            self.cover.save("{}.{}".format(self.uuid, extension), f, save=False)
+            self.save(update_fields=["cover"])
+            return self.cover.file
+        if self.mbid:
             image_data = musicbrainz.api.images.get_front(str(self.mbid))
             f = ContentFile(image_data)
-            self.cover.save("{0}.jpg".format(self.mbid), f)
+            self.cover.save("{0}.jpg".format(self.mbid), f, save=False)
+            self.save(update_fields=["cover"])
         return self.cover.file
 
     def __str__(self):
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index 61ee15585..0a4c04225 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -1,9 +1,10 @@
+import collections
 import logging
 import os
 
 from django.utils import timezone
 from django.db import transaction
-from django.db.models import F
+from django.db.models import F, Q
 from django.dispatch import receiver
 
 from musicbrainzngs import ResponseError
@@ -14,7 +15,6 @@ from funkwhale_api.common import preferences
 from funkwhale_api.federation import activity, actors, routes
 from funkwhale_api.federation import library as lb
 from funkwhale_api.federation import library as federation_serializers
-from funkwhale_api.providers.acoustid import get_acoustid_client
 from funkwhale_api.taskapp import celery
 
 from . import lyrics as lyrics_utils
@@ -26,102 +26,32 @@ from . import serializers
 logger = logging.getLogger(__name__)
 
 
-@celery.app.task(name="acoustid.set_on_upload")
-@celery.require_instance(models.Upload, "upload")
-def set_acoustid_on_upload(upload):
-    client = get_acoustid_client()
-    result = client.get_best_match(upload.audio_file.path)
-
-    def update(id):
-        upload.acoustid_track_id = id
-        upload.save(update_fields=["acoustid_track_id"])
-        return id
-
-    if result:
-        return update(result["id"])
-
-
-def import_track_from_remote(metadata):
-    try:
-        track_mbid = metadata["recording"]["musicbrainz_id"]
-        assert track_mbid  # for null/empty values
-    except (KeyError, AssertionError):
-        pass
-    else:
-        return models.Track.get_or_create_from_api(mbid=track_mbid)[0]
-
-    try:
-        album_mbid = metadata["release"]["musicbrainz_id"]
-        assert album_mbid  # for null/empty values
-    except (KeyError, AssertionError):
-        pass
-    else:
-        album, _ = models.Album.get_or_create_from_api(mbid=album_mbid)
-        return models.Track.get_or_create_from_title(
-            metadata["title"], artist=album.artist, album=album
-        )[0]
-
-    try:
-        artist_mbid = metadata["artist"]["musicbrainz_id"]
-        assert artist_mbid  # for null/empty values
-    except (KeyError, AssertionError):
-        pass
-    else:
-        artist, _ = models.Artist.get_or_create_from_api(mbid=artist_mbid)
-        album, _ = models.Album.get_or_create_from_title(
-            metadata["album_title"], artist=artist
-        )
-        return models.Track.get_or_create_from_title(
-            metadata["title"], artist=artist, album=album
-        )[0]
-
-    # worst case scenario, we have absolutely no way to link to a
-    # musicbrainz resource, we rely on the name/titles
-    artist, _ = models.Artist.get_or_create_from_name(metadata["artist_name"])
-    album, _ = models.Album.get_or_create_from_title(
-        metadata["album_title"], artist=artist
-    )
-    return models.Track.get_or_create_from_title(
-        metadata["title"], artist=artist, album=album
-    )[0]
-
-
-def update_album_cover(album, upload, replace=False):
+def update_album_cover(album, source=None, cover_data=None, replace=False):
     if album.cover and not replace:
         return
 
-    if upload:
-        # maybe the file has a cover embedded?
+    if cover_data:
+        return album.get_image(data=cover_data)
+
+    if source and source.startswith("file://"):
+        # let's look for a cover in the same directory
+        path = os.path.dirname(source.replace("file://", "", 1))
+        logger.info("[Album %s] scanning covers from %s", album.pk, path)
+        cover = get_cover_from_fs(path)
+        if cover:
+            return album.get_image(data=cover)
+    if album.mbid:
         try:
-            metadata = upload.get_metadata()
-        except FileNotFoundError:
-            metadata = None
-        if metadata:
-            cover = metadata.get_picture("cover_front")
-            if cover:
-                # best case scenario, cover is embedded in the track
-                logger.info("[Album %s] Using cover embedded in file", album.pk)
-                return album.get_image(data=cover)
-        if upload.source and upload.source.startswith("file://"):
-            # let's look for a cover in the same directory
-            path = os.path.dirname(upload.source.replace("file://", "", 1))
-            logger.info("[Album %s] scanning covers from %s", album.pk, path)
-            cover = get_cover_from_fs(path)
-            if cover:
-                return album.get_image(data=cover)
-    if not album.mbid:
-        return
-    try:
-        logger.info(
-            "[Album %s] Fetching cover from musicbrainz release %s",
-            album.pk,
-            str(album.mbid),
-        )
-        return album.get_image()
-    except ResponseError as exc:
-        logger.warning(
-            "[Album %s] cannot fetch cover from musicbrainz: %s", album.pk, str(exc)
-        )
+            logger.info(
+                "[Album %s] Fetching cover from musicbrainz release %s",
+                album.pk,
+                str(album.mbid),
+            )
+            return album.get_image()
+        except ResponseError as exc:
+            logger.warning(
+                "[Album %s] cannot fetch cover from musicbrainz: %s", album.pk, str(exc)
+            )
 
 
 IMAGE_TYPES = [("jpg", "image/jpeg"), ("png", "image/png")]
@@ -244,15 +174,15 @@ def scan_library_page(library_scan, page_url):
         scan_library_page.delay(library_scan_id=library_scan.pk, page_url=next_page)
 
 
-def getter(data, *keys):
+def getter(data, *keys, default=None):
     if not data:
-        return
+        return default
     v = data
     for k in keys:
         try:
             v = v[k]
         except KeyError:
-            return
+            return default
 
     return v
 
@@ -269,12 +199,17 @@ def fail_import(upload, error_code):
     upload.import_details = {"error_code": error_code}
     upload.import_date = timezone.now()
     upload.save(update_fields=["import_details", "import_status", "import_date"])
-    signals.upload_import_status_updated.send(
-        old_status=old_status,
-        new_status=upload.import_status,
-        upload=upload,
-        sender=None,
+
+    broadcast = getter(
+        upload.import_metadata, "funkwhale", "config", "broadcast", default=True
     )
+    if broadcast:
+        signals.upload_import_status_updated.send(
+            old_status=old_status,
+            new_status=upload.import_status,
+            upload=upload,
+            sender=None,
+        )
 
 
 @celery.app.task(name="music.process_upload")
@@ -285,22 +220,29 @@ def fail_import(upload, error_code):
     "upload",
 )
 def process_upload(upload):
-    data = upload.import_metadata or {}
+    import_metadata = upload.import_metadata or {}
     old_status = upload.import_status
+    audio_file = upload.get_audio_file()
     try:
-        track = get_track_from_import_metadata(upload.import_metadata or {})
-        if not track and upload.audio_file:
-            # easy ways did not work. Now we have to be smart and use
-            # metadata from the file itself if any
-            track = import_track_data_from_file(upload.audio_file.file, hints=data)
-        if not track and upload.metadata:
-            # we can try to import using federation metadata
-            track = import_track_from_remote(upload.metadata)
+        additional_data = {}
+        if not audio_file:
+            # we can only rely on user proveded data
+            final_metadata = import_metadata
+        else:
+            # we use user provided data and data from the file itself
+            m = metadata.Metadata(audio_file)
+            file_metadata = m.all()
+            final_metadata = collections.ChainMap(
+                additional_data, import_metadata, file_metadata
+            )
+            additional_data["cover_data"] = m.get_picture("cover_front")
+        additional_data["upload_source"] = upload.source
+        track = get_track_from_import_metadata(final_metadata)
     except UploadImportError as e:
         return fail_import(upload, e.code)
     except Exception:
-        fail_import(upload, "unknown_error")
-        raise
+        return fail_import(upload, "unknown_error")
+
     # under some situations, we want to skip the import (
     # for instance if the user already owns the files)
     owned_duplicates = get_owned_duplicates(upload, track)
@@ -342,33 +284,69 @@ def process_upload(upload):
             "bitrate",
         ]
     )
-    signals.upload_import_status_updated.send(
-        old_status=old_status,
-        new_status=upload.import_status,
-        upload=upload,
-        sender=None,
+    broadcast = getter(
+        import_metadata, "funkwhale", "config", "broadcast", default=True
     )
-    routes.outbox.dispatch(
-        {"type": "Create", "object": {"type": "Audio"}}, context={"upload": upload}
+    if broadcast:
+        signals.upload_import_status_updated.send(
+            old_status=old_status,
+            new_status=upload.import_status,
+            upload=upload,
+            sender=None,
+        )
+    dispatch_outbox = getter(
+        import_metadata, "funkwhale", "config", "dispatch_outbox", default=True
     )
-    if not track.album.cover:
-        update_album_cover(track.album, upload)
-
+    if dispatch_outbox:
+        routes.outbox.dispatch(
+            {"type": "Create", "object": {"type": "Audio"}}, context={"upload": upload}
+        )
 
-def get_track_from_import_metadata(data):
-    track_mbid = getter(data, "track", "mbid")
-    track_uuid = getter(data, "track", "uuid")
 
-    if track_mbid:
-        # easiest case: there is a MBID provided in the import_metadata
-        return models.Track.get_or_create_from_api(mbid=track_mbid)[0]
-    if track_uuid:
-        # another easy case, we have a reference to a uuid of a track that
-        # already exists in our database
-        try:
-            return models.Track.objects.get(uuid=track_uuid)
-        except models.Track.DoesNotExist:
-            raise UploadImportError(code="track_uuid_not_found")
+def federation_audio_track_to_metadata(payload):
+    """
+    Given a valid payload as returned by federation.serializers.TrackSerializer.validated_data,
+    returns a correct metadata payload for use with get_track_from_import_metadata.
+    """
+    musicbrainz_recordingid = payload.get("musicbrainzId")
+    musicbrainz_artistid = payload["artists"][0].get("musicbrainzId")
+    musicbrainz_albumartistid = payload["album"]["artists"][0].get("musicbrainzId")
+    musicbrainz_albumid = payload["album"].get("musicbrainzId")
+
+    new_data = {
+        "title": payload["name"],
+        "album": payload["album"]["name"],
+        "track_number": payload["position"],
+        "artist": payload["artists"][0]["name"],
+        "album_artist": payload["album"]["artists"][0]["name"],
+        "date": payload["album"].get("released"),
+        # musicbrainz
+        "musicbrainz_recordingid": str(musicbrainz_recordingid)
+        if musicbrainz_recordingid
+        else None,
+        "musicbrainz_artistid": str(musicbrainz_artistid)
+        if musicbrainz_artistid
+        else None,
+        "musicbrainz_albumartistid": str(musicbrainz_albumartistid)
+        if musicbrainz_albumartistid
+        else None,
+        "musicbrainz_albumid": str(musicbrainz_albumid)
+        if musicbrainz_albumid
+        else None,
+        # federation
+        "fid": payload["id"],
+        "artist_fid": payload["artists"][0]["id"],
+        "album_artist_fid": payload["album"]["artists"][0]["id"],
+        "album_fid": payload["album"]["id"],
+        "fdate": payload["published"],
+        "album_fdate": payload["album"]["published"],
+        "album_artist_fdate": payload["album"]["artists"][0]["published"],
+        "artist_fdate": payload["artists"][0]["published"],
+    }
+    cover = payload["album"].get("cover")
+    if cover:
+        new_data["cover_data"] = {"mimetype": cover["mediaType"], "url": cover["href"]}
+    return new_data
 
 
 def get_owned_duplicates(upload, track):
@@ -385,45 +363,191 @@ def get_owned_duplicates(upload, track):
     )
 
 
+def get_best_candidate_or_create(model, query, defaults, sort_fields):
+    """
+    Like queryset.get_or_create() but does not crash if multiple objects
+    are returned on the get() call
+    """
+    candidates = model.objects.filter(query)
+    if candidates:
+
+        return sort_candidates(candidates, sort_fields)[0], False
+
+    return model.objects.create(**defaults), True
+
+
+def sort_candidates(candidates, important_fields):
+    """
+    Given a list of objects and a list of fields,
+    will return a sorted list of those objects by score.
+
+    Score is higher for objects that have a non-empty attribute
+    that is also present in important fields::
+
+        artist1 = Artist(mbid=None, fid=None)
+        artist2 = Artist(mbid="something", fid=None)
+
+        # artist2 has a mbid, so is sorted first
+        assert sort_candidates([artist1, artist2], ['mbid'])[0] == artist2
+
+    Only supports string fields.
+    """
+
+    # map each fields to its score, giving a higher score to first fields
+    fields_scores = {f: i + 1 for i, f in enumerate(sorted(important_fields))}
+    candidates_with_scores = []
+    for candidate in candidates:
+        current_score = 0
+        for field, score in fields_scores.items():
+            v = getattr(candidate, field, "")
+            if v:
+                current_score += score
+
+        candidates_with_scores.append((candidate, current_score))
+
+    return [c for c, s in reversed(sorted(candidates_with_scores, key=lambda v: v[1]))]
+
+
 @transaction.atomic
-def import_track_data_from_file(file, hints={}):
-    data = metadata.Metadata(file)
-    album = None
+def get_track_from_import_metadata(data):
+    track_uuid = getter(data, "funkwhale", "track", "uuid")
+
+    if track_uuid:
+        # easy case, we have a reference to a uuid of a track that
+        # already exists in our database
+        try:
+            track = models.Track.objects.get(uuid=track_uuid)
+        except models.Track.DoesNotExist:
+            raise UploadImportError(code="track_uuid_not_found")
+
+        if not track.album.cover:
+            update_album_cover(
+                track.album,
+                source=data.get("upload_source"),
+                cover_data=data.get("cover_data"),
+            )
+        return track
+
+    from_activity_id = data.get("from_activity_id", None)
     track_mbid = data.get("musicbrainz_recordingid", None)
     album_mbid = data.get("musicbrainz_albumid", None)
+    track_fid = getter(data, "fid")
+
+    query = None
 
     if album_mbid and track_mbid:
-        # to gain performance and avoid additional mb lookups,
-        # we import from the release data, which is already cached
-        return models.Track.get_or_create_from_release(album_mbid, track_mbid)[0]
-    elif track_mbid:
-        return models.Track.get_or_create_from_api(track_mbid)[0]
-    elif album_mbid:
-        album = models.Album.get_or_create_from_api(album_mbid)[0]
-
-    artist = album.artist if album else None
+        query = Q(mbid=track_mbid, album__mbid=album_mbid)
+
+    if track_fid:
+        query = query | Q(fid=track_fid) if query else Q(fid=track_fid)
+
+    if query:
+        # second easy case: we have a (track_mbid, album_mbid) pair or
+        # a federation uuid we can check on
+        try:
+            return sort_candidates(models.Track.objects.filter(query), ["mbid", "fid"])[
+                0
+            ]
+        except IndexError:
+            pass
+
+    # get / create artist and album artist
     artist_mbid = data.get("musicbrainz_artistid", None)
-    if not artist:
-        if artist_mbid:
-            artist = models.Artist.get_or_create_from_api(artist_mbid)[0]
-        else:
-            artist = models.Artist.objects.get_or_create(
-                name__iexact=data.get("artist"), defaults={"name": data.get("artist")}
-            )[0]
-
-    release_date = data.get("date", default=None)
-    if not album:
-        album = models.Album.objects.get_or_create(
-            title__iexact=data.get("album"),
-            artist=artist,
-            defaults={"title": data.get("album"), "release_date": release_date},
+    artist_fid = data.get("artist_fid", None)
+    artist_name = data["artist"]
+    query = Q(name__iexact=artist_name)
+    if artist_mbid:
+        query |= Q(mbid=artist_mbid)
+    if artist_fid:
+        query |= Q(fid=artist_fid)
+    defaults = {
+        "name": artist_name,
+        "mbid": artist_mbid,
+        "fid": artist_fid,
+        "from_activity_id": from_activity_id,
+    }
+    if data.get("artist_fdate"):
+        defaults["creation_date"] = data.get("artist_fdate")
+
+    artist = get_best_candidate_or_create(
+        models.Artist, query, defaults=defaults, sort_fields=["mbid", "fid"]
+    )[0]
+
+    album_artist_name = data.get("album_artist", artist_name)
+    if album_artist_name == artist_name:
+        album_artist = artist
+    else:
+        query = Q(name__iexact=album_artist_name)
+        album_artist_mbid = data.get("musicbrainz_albumartistid", None)
+        album_artist_fid = data.get("album_artist_fid", None)
+        if album_artist_mbid:
+            query |= Q(mbid=album_artist_mbid)
+        if album_artist_fid:
+            query |= Q(fid=album_artist_fid)
+        defaults = {
+            "name": album_artist_name,
+            "mbid": album_artist_mbid,
+            "fid": album_artist_fid,
+            "from_activity_id": from_activity_id,
+        }
+        if data.get("album_artist_fdate"):
+            defaults["creation_date"] = data.get("album_artist_fdate")
+
+        album_artist = get_best_candidate_or_create(
+            models.Artist, query, defaults=defaults, sort_fields=["mbid", "fid"]
         )[0]
-    position = data.get("track_number", default=None)
-    track = models.Track.objects.get_or_create(
-        title__iexact=data.get("title"),
-        album=album,
-        defaults={"title": data.get("title"), "position": position},
+
+    # get / create album
+    album_title = data["album"]
+    album_fid = data.get("album_fid", None)
+    query = Q(title__iexact=album_title, artist=album_artist)
+    if album_mbid:
+        query |= Q(mbid=album_mbid)
+    if album_fid:
+        query |= Q(fid=album_fid)
+    defaults = {
+        "title": album_title,
+        "artist": album_artist,
+        "mbid": album_mbid,
+        "release_date": data.get("date"),
+        "fid": album_fid,
+        "from_activity_id": from_activity_id,
+    }
+    if data.get("album_fdate"):
+        defaults["creation_date"] = data.get("album_fdate")
+
+    album = get_best_candidate_or_create(
+        models.Album, query, defaults=defaults, sort_fields=["mbid", "fid"]
     )[0]
+    if not album.cover:
+        update_album_cover(
+            album, source=data.get("upload_source"), cover_data=data.get("cover_data")
+        )
+
+    # get / create track
+    track_title = data["title"]
+    track_number = data.get("track_number", 1)
+    query = Q(title__iexact=track_title, artist=artist, album=album)
+    if track_mbid:
+        query |= Q(mbid=track_mbid)
+    if track_fid:
+        query |= Q(fid=track_fid)
+    defaults = {
+        "title": track_title,
+        "album": album,
+        "mbid": track_mbid,
+        "artist": artist,
+        "position": track_number,
+        "fid": track_fid,
+        "from_activity_id": from_activity_id,
+    }
+    if data.get("fdate"):
+        defaults["creation_date"] = data.get("fdate")
+
+    track = get_best_candidate_or_create(
+        models.Track, query, defaults=defaults, sort_fields=["mbid", "fid"]
+    )[0]
+
     return track
 
 
@@ -432,6 +556,7 @@ def broadcast_import_status_update_to_owner(old_status, new_status, upload, **kw
     user = upload.library.actor.get_user()
     if not user:
         return
+
     group = "user.{}.imports".format(user.pk)
     channels.group_send(
         group,
diff --git a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
index bc1c9af0a..d4917be5e 100644
--- a/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
+++ b/api/funkwhale_api/providers/audiofile/management/commands/import_files.py
@@ -77,6 +77,29 @@ class Command(BaseCommand):
                 "with their newest version."
             ),
         )
+        parser.add_argument(
+            "--outbox",
+            action="store_true",
+            dest="outbox",
+            default=False,
+            help=(
+                "Use this flag to notify library followers of newly imported files. "
+                "You'll likely want to keep this disabled for CLI imports, especially if"
+                "you plan to import hundreds or thousands of files, as it will cause a lot "
+                "of overhead on your server and on servers you are federating with."
+            ),
+        )
+
+        parser.add_argument(
+            "--broadcast",
+            action="store_true",
+            dest="broadcast",
+            default=False,
+            help=(
+                "Use this flag to enable realtime updates about the import in the UI. "
+                "This causes some overhead, so it's disabled by default."
+            ),
+        )
 
         parser.add_argument(
             "--reference",
@@ -261,6 +284,8 @@ class Command(BaseCommand):
                     async_,
                     options["replace"],
                     options["in_place"],
+                    options["outbox"],
+                    options["broadcast"],
                 )
             except Exception as e:
                 if options["exit_on_failure"]:
@@ -272,11 +297,29 @@ class Command(BaseCommand):
                 errors.append((path, "{} {}".format(e.__class__.__name__, e)))
         return errors
 
-    def create_upload(self, path, reference, library, async_, replace, in_place):
+    def create_upload(
+        self,
+        path,
+        reference,
+        library,
+        async_,
+        replace,
+        in_place,
+        dispatch_outbox,
+        broadcast,
+    ):
         import_handler = tasks.process_upload.delay if async_ else tasks.process_upload
         upload = models.Upload(library=library, import_reference=reference)
         upload.source = "file://" + path
-        upload.import_metadata = {"replace": replace}
+        upload.import_metadata = {
+            "funkwhale": {
+                "config": {
+                    "replace": replace,
+                    "dispatch_outbox": dispatch_outbox,
+                    "broadcast": broadcast,
+                }
+            }
+        }
         if not in_place:
             name = os.path.basename(path)
             with open(path, "rb") as f:
diff --git a/api/requirements/local.txt b/api/requirements/local.txt
index f11f976b8..c12f1ecb8 100644
--- a/api/requirements/local.txt
+++ b/api/requirements/local.txt
@@ -10,3 +10,4 @@ django-debug-toolbar>=1.9,<1.10
 # improved REPL
 ipdb==0.8.1
 black
+profiling
diff --git a/api/tests/conftest.py b/api/tests/conftest.py
index 1694e5623..a1688127c 100644
--- a/api/tests/conftest.py
+++ b/api/tests/conftest.py
@@ -11,7 +11,7 @@ import uuid
 from faker.providers import internet as internet_provider
 import factory
 import pytest
-import requests_mock
+
 from django.contrib.auth.models import AnonymousUser
 from django.core.cache import cache as django_cache
 from django.core.files import uploadedfile
@@ -271,14 +271,13 @@ def media_root(settings):
     shutil.rmtree(tmp_dir)
 
 
-@pytest.fixture
-def r_mock():
+@pytest.fixture(autouse=True)
+def r_mock(requests_mock):
     """
     Returns a requests_mock.mock() object you can use to mock HTTP calls made
     using python-requests
     """
-    with requests_mock.mock() as m:
-        yield m
+    yield requests_mock
 
 
 @pytest.fixture
diff --git a/api/tests/federation/test_serializers.py b/api/tests/federation/test_serializers.py
index 00bb011f2..54e044c31 100644
--- a/api/tests/federation/test_serializers.py
+++ b/api/tests/federation/test_serializers.py
@@ -1,3 +1,4 @@
+import io
 import pytest
 import uuid
 
@@ -588,42 +589,6 @@ def test_music_library_serializer_from_private(factories, mocker):
     )
 
 
-@pytest.mark.parametrize(
-    "model,serializer_class",
-    [
-        ("music.Artist", serializers.ArtistSerializer),
-        ("music.Album", serializers.AlbumSerializer),
-        ("music.Track", serializers.TrackSerializer),
-    ],
-)
-def test_music_entity_serializer_create_existing_mbid(
-    model, serializer_class, factories
-):
-    entity = factories[model]()
-    data = {"musicbrainzId": str(entity.mbid), "id": "https://noop"}
-    serializer = serializer_class()
-
-    assert serializer.create(data) == entity
-
-
-@pytest.mark.parametrize(
-    "model,serializer_class",
-    [
-        ("music.Artist", serializers.ArtistSerializer),
-        ("music.Album", serializers.AlbumSerializer),
-        ("music.Track", serializers.TrackSerializer),
-    ],
-)
-def test_music_entity_serializer_create_existing_fid(
-    model, serializer_class, factories
-):
-    entity = factories[model](fid="https://entity.url")
-    data = {"musicbrainzId": None, "id": "https://entity.url"}
-    serializer = serializer_class()
-
-    assert serializer.create(data) == entity
-
-
 def test_activity_pub_artist_serializer_to_ap(factories):
     artist = factories["music.Artist"]()
     expected = {
@@ -639,30 +604,6 @@ def test_activity_pub_artist_serializer_to_ap(factories):
     assert serializer.data == expected
 
 
-def test_activity_pub_artist_serializer_from_ap(factories):
-    activity = factories["federation.Activity"]()
-
-    published = timezone.now()
-    data = {
-        "type": "Artist",
-        "id": "http://hello.artist",
-        "name": "John Smith",
-        "musicbrainzId": str(uuid.uuid4()),
-        "published": published.isoformat(),
-    }
-    serializer = serializers.ArtistSerializer(data=data, context={"activity": activity})
-
-    assert serializer.is_valid(raise_exception=True)
-
-    artist = serializer.save()
-
-    assert artist.from_activity == activity
-    assert artist.name == data["name"]
-    assert artist.fid == data["id"]
-    assert str(artist.mbid) == data["musicbrainzId"]
-    assert artist.creation_date == published
-
-
 def test_activity_pub_album_serializer_to_ap(factories):
     album = factories["music.Album"]()
 
@@ -671,7 +612,11 @@ def test_activity_pub_album_serializer_to_ap(factories):
         "type": "Album",
         "id": album.fid,
         "name": album.title,
-        "cover": {"type": "Image", "url": utils.full_url(album.cover.url)},
+        "cover": {
+            "type": "Link",
+            "mediaType": "image/jpeg",
+            "href": utils.full_url(album.cover.url),
+        },
         "musicbrainzId": album.mbid,
         "published": album.creation_date.isoformat(),
         "released": album.release_date.isoformat(),
@@ -686,49 +631,6 @@ def test_activity_pub_album_serializer_to_ap(factories):
     assert serializer.data == expected
 
 
-def test_activity_pub_album_serializer_from_ap(factories):
-    activity = factories["federation.Activity"]()
-
-    published = timezone.now()
-    released = timezone.now().date()
-    data = {
-        "type": "Album",
-        "id": "http://hello.album",
-        "name": "Purple album",
-        "musicbrainzId": str(uuid.uuid4()),
-        "published": published.isoformat(),
-        "released": released.isoformat(),
-        "artists": [
-            {
-                "type": "Artist",
-                "id": "http://hello.artist",
-                "name": "John Smith",
-                "musicbrainzId": str(uuid.uuid4()),
-                "published": published.isoformat(),
-            }
-        ],
-    }
-    serializer = serializers.AlbumSerializer(data=data, context={"activity": activity})
-
-    assert serializer.is_valid(raise_exception=True)
-
-    album = serializer.save()
-    artist = album.artist
-
-    assert album.from_activity == activity
-    assert album.title == data["name"]
-    assert album.fid == data["id"]
-    assert str(album.mbid) == data["musicbrainzId"]
-    assert album.creation_date == published
-    assert album.release_date == released
-
-    assert artist.from_activity == activity
-    assert artist.name == data["artists"][0]["name"]
-    assert artist.fid == data["artists"][0]["id"]
-    assert str(artist.mbid) == data["artists"][0]["musicbrainzId"]
-    assert artist.creation_date == published
-
-
 def test_activity_pub_track_serializer_to_ap(factories):
     track = factories["music.Track"]()
     expected = {
@@ -753,7 +655,7 @@ def test_activity_pub_track_serializer_to_ap(factories):
     assert serializer.data == expected
 
 
-def test_activity_pub_track_serializer_from_ap(factories):
+def test_activity_pub_track_serializer_from_ap(factories, r_mock):
     activity = factories["federation.Activity"]()
     published = timezone.now()
     released = timezone.now().date()
@@ -771,6 +673,11 @@ def test_activity_pub_track_serializer_from_ap(factories):
             "musicbrainzId": str(uuid.uuid4()),
             "published": published.isoformat(),
             "released": released.isoformat(),
+            "cover": {
+                "type": "Link",
+                "href": "https://cover.image/test.png",
+                "mediaType": "image/png",
+            },
             "artists": [
                 {
                     "type": "Artist",
@@ -791,12 +698,14 @@ def test_activity_pub_track_serializer_from_ap(factories):
             }
         ],
     }
+    r_mock.get(data["album"]["cover"]["href"], body=io.BytesIO(b"coucou"))
     serializer = serializers.TrackSerializer(data=data, context={"activity": activity})
     assert serializer.is_valid(raise_exception=True)
 
     track = serializer.save()
     album = track.album
     artist = track.artist
+    album_artist = track.album.artist
 
     assert track.from_activity == activity
     assert track.fid == data["id"]
@@ -806,7 +715,8 @@ def test_activity_pub_track_serializer_from_ap(factories):
     assert str(track.mbid) == data["musicbrainzId"]
 
     assert album.from_activity == activity
-
+    assert album.cover.read() == b"coucou"
+    assert album.cover.path.endswith(".png")
     assert album.title == data["album"]["name"]
     assert album.fid == data["album"]["id"]
     assert str(album.mbid) == data["album"]["musicbrainzId"]
@@ -819,6 +729,12 @@ def test_activity_pub_track_serializer_from_ap(factories):
     assert str(artist.mbid) == data["artists"][0]["musicbrainzId"]
     assert artist.creation_date == published
 
+    assert album_artist.from_activity == activity
+    assert album_artist.name == data["album"]["artists"][0]["name"]
+    assert album_artist.fid == data["album"]["artists"][0]["id"]
+    assert str(album_artist.mbid) == data["album"]["artists"][0]["musicbrainzId"]
+    assert album_artist.creation_date == published
+
 
 def test_activity_pub_upload_serializer_from_ap(factories, mocker):
     activity = factories["federation.Activity"]()
diff --git a/api/tests/music/test.mp3 b/api/tests/music/test.mp3
index 8502de71b8284e9f30a397f58401d96fc42dbb17..6c1f52a35f665306af4959bb5ed024d2abea544a 100644
GIT binary patch
delta 24122
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

delta 24097
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
F{{RANqQ(FK

diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
index f105b6b7e..82c991c0b 100644
--- a/api/tests/music/test_metadata.py
+++ b/api/tests/music/test_metadata.py
@@ -9,21 +9,46 @@ from funkwhale_api.music import metadata
 DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 
+def test_get_all_metadata_at_once():
+    path = os.path.join(DATA_DIR, "test.ogg")
+    data = metadata.Metadata(path)
+
+    expected = {
+        "title": "Peer Gynt Suite no. 1, op. 46: I. Morning",
+        "artist": "Edvard Grieg",
+        "album_artist": "Edvard Grieg",
+        "album": "Peer Gynt Suite no. 1, op. 46",
+        "date": datetime.date(2012, 8, 15),
+        "track_number": 1,
+        "musicbrainz_albumid": uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75"),
+        "musicbrainz_recordingid": uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656"),
+        "musicbrainz_artistid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"),
+        "musicbrainz_albumartistid": uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"),
+    }
+
+    assert data.all() == expected
+
+
 @pytest.mark.parametrize(
     "field,value",
     [
         ("title", "Peer Gynt Suite no. 1, op. 46: I. Morning"),
         ("artist", "Edvard Grieg"),
+        ("album_artist", "Edvard Grieg"),
         ("album", "Peer Gynt Suite no. 1, op. 46"),
         ("date", datetime.date(2012, 8, 15)),
         ("track_number", 1),
         ("musicbrainz_albumid", uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75")),
         ("musicbrainz_recordingid", uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656")),
         ("musicbrainz_artistid", uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823")),
+        (
+            "musicbrainz_albumartistid",
+            uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"),
+        ),
     ],
 )
-def test_can_get_metadata_from_opus_file(field, value):
-    path = os.path.join(DATA_DIR, "test.opus")
+def test_can_get_metadata_from_ogg_file(field, value):
+    path = os.path.join(DATA_DIR, "test.ogg")
     data = metadata.Metadata(path)
 
     assert data.get(field) == value
@@ -34,16 +59,21 @@ def test_can_get_metadata_from_opus_file(field, value):
     [
         ("title", "Peer Gynt Suite no. 1, op. 46: I. Morning"),
         ("artist", "Edvard Grieg"),
+        ("album_artist", "Edvard Grieg"),
         ("album", "Peer Gynt Suite no. 1, op. 46"),
         ("date", datetime.date(2012, 8, 15)),
         ("track_number", 1),
         ("musicbrainz_albumid", uuid.UUID("a766da8b-8336-47aa-a3ee-371cc41ccc75")),
         ("musicbrainz_recordingid", uuid.UUID("bd21ac48-46d8-4e78-925f-d9cc2a294656")),
         ("musicbrainz_artistid", uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823")),
+        (
+            "musicbrainz_albumartistid",
+            uuid.UUID("013c8e5b-d72a-4cd3-8dee-6c64d6125823"),
+        ),
     ],
 )
-def test_can_get_metadata_from_ogg_file(field, value):
-    path = os.path.join(DATA_DIR, "test.ogg")
+def test_can_get_metadata_from_opus_file(field, value):
+    path = os.path.join(DATA_DIR, "test.opus")
     data = metadata.Metadata(path)
 
     assert data.get(field) == value
@@ -54,12 +84,17 @@ def test_can_get_metadata_from_ogg_file(field, value):
     [
         ("title", "Drei Kreuze (dass wir hier sind)"),
         ("artist", "Die Toten Hosen"),
+        ("album_artist", "Die Toten Hosen"),
         ("album", "Ballast der Republik"),
         ("date", datetime.date(2012, 5, 4)),
         ("track_number", 1),
         ("musicbrainz_albumid", uuid.UUID("1f0441ad-e609-446d-b355-809c445773cf")),
         ("musicbrainz_recordingid", uuid.UUID("124d0150-8627-46bc-bc14-789a3bc960c8")),
         ("musicbrainz_artistid", uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1")),
+        (
+            "musicbrainz_albumartistid",
+            uuid.UUID("c3bc80a6-1f4a-4e17-8cf0-6b1efe8302f1"),
+        ),
     ],
 )
 def test_can_get_metadata_from_ogg_theora_file(field, value):
@@ -73,13 +108,18 @@ def test_can_get_metadata_from_ogg_theora_file(field, value):
     "field,value",
     [
         ("title", "Bend"),
-        ("artist", "Bindrpilot"),
+        ("artist", "Binärpilot"),
+        ("album_artist", "Binärpilot"),
         ("album", "You Can't Stop Da Funk"),
         ("date", datetime.date(2006, 2, 7)),
         ("track_number", 2),
         ("musicbrainz_albumid", uuid.UUID("ce40cdb1-a562-4fd8-a269-9269f98d4124")),
         ("musicbrainz_recordingid", uuid.UUID("f269d497-1cc0-4ae4-a0c4-157ec7d73fcb")),
         ("musicbrainz_artistid", uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13")),
+        (
+            "musicbrainz_albumartistid",
+            uuid.UUID("9c6bddde-6228-4d9f-ad0d-03f6fcb19e13"),
+        ),
     ],
 )
 def test_can_get_metadata_from_id3_mp3_file(field, value):
@@ -108,12 +148,17 @@ def test_can_get_pictures(name):
     [
         ("title", "999,999"),
         ("artist", "Nine Inch Nails"),
+        ("album_artist", "Nine Inch Nails"),
         ("album", "The Slip"),
         ("date", datetime.date(2008, 5, 5)),
         ("track_number", 1),
         ("musicbrainz_albumid", uuid.UUID("12b57d46-a192-499e-a91f-7da66790a1c1")),
         ("musicbrainz_recordingid", uuid.UUID("30f3f33e-8d0c-4e69-8539-cbd701d18f28")),
         ("musicbrainz_artistid", uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da")),
+        (
+            "musicbrainz_albumartistid",
+            uuid.UUID("b7ffd2af-418f-4be2-bdd1-22f8b48613da"),
+        ),
     ],
 )
 def test_can_get_metadata_from_flac_file(field, value):
@@ -133,7 +178,12 @@ def test_can_get_metadata_from_flac_file_not_crash_if_empty():
 
 @pytest.mark.parametrize(
     "field_name",
-    ["musicbrainz_artistid", "musicbrainz_albumid", "musicbrainz_recordingid"],
+    [
+        "musicbrainz_artistid",
+        "musicbrainz_albumid",
+        "musicbrainz_recordingid",
+        "musicbrainz_albumartistid",
+    ],
 )
 def test_mbid_clean_keeps_only_first(field_name):
     u1 = str(uuid.uuid4())
diff --git a/api/tests/music/test_tasks.py b/api/tests/music/test_tasks.py
index c58bce7db..de5e0310f 100644
--- a/api/tests/music/test_tasks.py
+++ b/api/tests/music/test_tasks.py
@@ -1,12 +1,14 @@
 import datetime
+import io
 import os
 import pytest
 import uuid
 
 from django.core.paginator import Paginator
+from django.utils import timezone
 
 from funkwhale_api.federation import serializers as federation_serializers
-from funkwhale_api.music import signals, tasks
+from funkwhale_api.music import metadata, signals, tasks
 
 DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
@@ -16,84 +18,163 @@ DATA_DIR = os.path.dirname(os.path.abspath(__file__))
 
 def test_can_create_track_from_file_metadata_no_mbid(db, mocker):
     metadata = {
-        "artist": ["Test artist"],
-        "album": ["Test album"],
-        "title": ["Test track"],
-        "TRACKNUMBER": ["4"],
-        "date": ["2012-08-15"],
+        "title": "Test track",
+        "artist": "Test artist",
+        "album": "Test album",
+        "date": datetime.date(2012, 8, 15),
+        "track_number": 4,
     }
-    mocker.patch("mutagen.File", return_value=metadata)
-    mocker.patch(
-        "funkwhale_api.music.metadata.Metadata.get_file_type", return_value="OggVorbis"
-    )
-    track = tasks.import_track_data_from_file(os.path.join(DATA_DIR, "dummy_file.ogg"))
+    mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata)
+
+    track = tasks.get_track_from_import_metadata(metadata)
 
-    assert track.title == metadata["title"][0]
+    assert track.title == metadata["title"]
     assert track.mbid is None
     assert track.position == 4
-    assert track.album.title == metadata["album"][0]
+    assert track.album.title == metadata["album"]
     assert track.album.mbid is None
     assert track.album.release_date == datetime.date(2012, 8, 15)
-    assert track.artist.name == metadata["artist"][0]
+    assert track.artist.name == metadata["artist"]
     assert track.artist.mbid is None
 
 
 def test_can_create_track_from_file_metadata_mbid(factories, mocker):
-    album = factories["music.Album"]()
-    artist = factories["music.Artist"]()
-    mocker.patch(
-        "funkwhale_api.music.models.Album.get_or_create_from_api",
-        return_value=(album, True),
-    )
+    metadata = {
+        "title": "Test track",
+        "artist": "Test artist",
+        "album_artist": "Test album artist",
+        "album": "Test album",
+        "date": datetime.date(2012, 8, 15),
+        "track_number": 4,
+        "musicbrainz_albumid": "ce40cdb1-a562-4fd8-a269-9269f98d4124",
+        "musicbrainz_recordingid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb",
+        "musicbrainz_artistid": "9c6bddde-6228-4d9f-ad0d-03f6fcb19e13",
+        "musicbrainz_albumartistid": "9c6bddde-6478-4d9f-ad0d-03f6fcb19e13",
+    }
 
-    album_data = {
-        "release": {
-            "id": album.mbid,
-            "medium-list": [
-                {
-                    "track-list": [
-                        {
-                            "id": "03baca8b-855a-3c05-8f3d-d3235287d84d",
-                            "position": "4",
-                            "number": "4",
-                            "recording": {
-                                "id": "2109e376-132b-40ad-b993-2bb6812e19d4",
-                                "title": "Teen Age Riot",
-                                "artist-credit": [
-                                    {"artist": {"id": artist.mbid, "name": artist.name}}
-                                ],
-                            },
-                        }
-                    ],
-                    "track-count": 1,
-                }
-            ],
-        }
+    mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata)
+
+    track = tasks.get_track_from_import_metadata(metadata)
+
+    assert track.title == metadata["title"]
+    assert track.mbid == metadata["musicbrainz_recordingid"]
+    assert track.position == 4
+    assert track.album.title == metadata["album"]
+    assert track.album.mbid == metadata["musicbrainz_albumid"]
+    assert track.album.artist.mbid == metadata["musicbrainz_albumartistid"]
+    assert track.album.artist.name == metadata["album_artist"]
+    assert track.album.release_date == datetime.date(2012, 8, 15)
+    assert track.artist.name == metadata["artist"]
+    assert track.artist.mbid == metadata["musicbrainz_artistid"]
+
+
+def test_can_create_track_from_file_metadata_mbid_existing_album_artist(
+    factories, mocker
+):
+    artist = factories["music.Artist"]()
+    album = factories["music.Album"]()
+    metadata = {
+        "artist": "",
+        "album": "",
+        "title": "Hello",
+        "track_number": 4,
+        "musicbrainz_albumid": album.mbid,
+        "musicbrainz_recordingid": "f269d497-1cc0-4ae4-a0c4-157ec7d73fcb",
+        "musicbrainz_artistid": artist.mbid,
+        "musicbrainz_albumartistid": album.artist.mbid,
     }
-    mocker.patch("funkwhale_api.musicbrainz.api.releases.get", return_value=album_data)
-    track_data = album_data["release"]["medium-list"][0]["track-list"][0]
+
+    mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata)
+
+    track = tasks.get_track_from_import_metadata(metadata)
+
+    assert track.title == metadata["title"]
+    assert track.mbid == metadata["musicbrainz_recordingid"]
+    assert track.position == 4
+    assert track.album == album
+    assert track.artist == artist
+
+
+def test_can_create_track_from_file_metadata_fid_existing_album_artist(
+    factories, mocker
+):
+    artist = factories["music.Artist"]()
+    album = factories["music.Album"]()
     metadata = {
-        "musicbrainz_albumid": [album.mbid],
-        "musicbrainz_trackid": [track_data["recording"]["id"]],
+        "artist": "",
+        "album": "",
+        "title": "Hello",
+        "track_number": 4,
+        "fid": "https://hello",
+        "album_fid": album.fid,
+        "artist_fid": artist.fid,
+        "album_artist_fid": album.artist.fid,
     }
-    mocker.patch("mutagen.File", return_value=metadata)
-    mocker.patch(
-        "funkwhale_api.music.metadata.Metadata.get_file_type", return_value="OggVorbis"
-    )
-    track = tasks.import_track_data_from_file(os.path.join(DATA_DIR, "dummy_file.ogg"))
 
-    assert track.title == track_data["recording"]["title"]
-    assert track.mbid == track_data["recording"]["id"]
+    mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata)
+
+    track = tasks.get_track_from_import_metadata(metadata)
+
+    assert track.title == metadata["title"]
+    assert track.fid == metadata["fid"]
     assert track.position == 4
     assert track.album == album
     assert track.artist == artist
 
 
-def test_upload_import_mbid(now, factories, temp_signal, mocker):
+def test_can_create_track_from_file_metadata_federation(factories, mocker, r_mock):
+    metadata = {
+        "artist": "Artist",
+        "album": "Album",
+        "album_artist": "Album artist",
+        "title": "Hello",
+        "track_number": 4,
+        "fid": "https://hello",
+        "album_fid": "https://album.fid",
+        "artist_fid": "https://artist.fid",
+        "album_artist_fid": "https://album.artist.fid",
+        "fdate": timezone.now(),
+        "album_fdate": timezone.now(),
+        "album_artist_fdate": timezone.now(),
+        "artist_fdate": timezone.now(),
+        "cover_data": {"url": "https://cover/hello.png", "mimetype": "image/png"},
+    }
+    r_mock.get(metadata["cover_data"]["url"], body=io.BytesIO(b"coucou"))
+    mocker.patch("funkwhale_api.music.metadata.Metadata.all", return_value=metadata)
+
+    track = tasks.get_track_from_import_metadata(metadata)
+
+    assert track.title == metadata["title"]
+    assert track.fid == metadata["fid"]
+    assert track.creation_date == metadata["fdate"]
+    assert track.position == 4
+    assert track.album.cover.read() == b"coucou"
+    assert track.album.cover.path.endswith(".png")
+    assert track.album.fid == metadata["album_fid"]
+    assert track.album.title == metadata["album"]
+    assert track.album.creation_date == metadata["album_fdate"]
+    assert track.album.artist.fid == metadata["album_artist_fid"]
+    assert track.album.artist.name == metadata["album_artist"]
+    assert track.album.artist.creation_date == metadata["album_artist_fdate"]
+    assert track.artist.fid == metadata["artist_fid"]
+    assert track.artist.name == metadata["artist"]
+    assert track.artist.creation_date == metadata["artist_fdate"]
+
+
+def test_sort_candidates(factories):
+    artist1 = factories["music.Artist"].build(fid=None, mbid=None)
+    artist2 = factories["music.Artist"].build(fid=None)
+    artist3 = factories["music.Artist"].build(mbid=None)
+    result = tasks.sort_candidates([artist1, artist2, artist3], ["mbid", "fid"])
+
+    assert result == [artist2, artist3, artist1]
+
+
+def test_upload_import(now, factories, temp_signal, mocker):
     outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
     track = factories["music.Track"]()
     upload = factories["music.Upload"](
-        track=None, import_metadata={"track": {"mbid": track.mbid}}
+        track=None, import_metadata={"funkwhale": {"track": {"uuid": str(track.uuid)}}}
     )
 
     with temp_signal(signals.upload_import_status_updated) as handler:
@@ -123,7 +204,29 @@ def test_upload_import_get_audio_data(factories, mocker):
     )
     track = factories["music.Track"]()
     upload = factories["music.Upload"](
-        track=None, import_metadata={"track": {"mbid": track.mbid}}
+        track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
+    )
+
+    tasks.process_upload(upload_id=upload.pk)
+
+    upload.refresh_from_db()
+    assert upload.size == 23
+    assert upload.duration == 42
+    assert upload.bitrate == 66
+
+
+def test_upload_import_in_place(factories, mocker):
+    mocker.patch(
+        "funkwhale_api.music.models.Upload.get_audio_data",
+        return_value={"size": 23, "duration": 42, "bitrate": 66},
+    )
+    track = factories["music.Track"]()
+    path = os.path.join(DATA_DIR, "test.ogg")
+    upload = factories["music.Upload"](
+        track=None,
+        audio_file=None,
+        source="file://{}".format(path),
+        import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}},
     )
 
     tasks.process_upload(upload_id=upload.pk)
@@ -141,13 +244,13 @@ def test_upload_import_skip_existing_track_in_own_library(factories, temp_signal
         track=track,
         import_status="finished",
         library=library,
-        import_metadata={"track": {"mbid": track.mbid}},
+        import_metadata={"funkwhale": {"track": {"uuid": track.mbid}}},
     )
     duplicate = factories["music.Upload"](
         track=track,
         import_status="pending",
         library=library,
-        import_metadata={"track": {"mbid": track.mbid}},
+        import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}},
     )
     with temp_signal(signals.upload_import_status_updated) as handler:
         tasks.process_upload(upload_id=duplicate.pk)
@@ -172,7 +275,7 @@ def test_upload_import_skip_existing_track_in_own_library(factories, temp_signal
 def test_upload_import_track_uuid(now, factories):
     track = factories["music.Track"]()
     upload = factories["music.Upload"](
-        track=None, import_metadata={"track": {"uuid": track.uuid}}
+        track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
     )
 
     tasks.process_upload(upload_id=upload.pk)
@@ -184,9 +287,43 @@ def test_upload_import_track_uuid(now, factories):
     assert upload.import_date == now
 
 
+def test_upload_import_skip_federation(now, factories, mocker):
+    outbox = mocker.patch("funkwhale_api.federation.routes.outbox.dispatch")
+    track = factories["music.Track"]()
+    upload = factories["music.Upload"](
+        track=None,
+        import_metadata={
+            "funkwhale": {
+                "track": {"uuid": track.uuid},
+                "config": {"dispatch_outbox": False},
+            }
+        },
+    )
+
+    tasks.process_upload(upload_id=upload.pk)
+
+    outbox.assert_not_called()
+
+
+def test_upload_import_skip_broadcast(now, factories, mocker):
+    group_send = mocker.patch("funkwhale_api.common.channels.group_send")
+    track = factories["music.Track"]()
+    upload = factories["music.Upload"](
+        library__actor__local=True,
+        track=None,
+        import_metadata={
+            "funkwhale": {"track": {"uuid": track.uuid}, "config": {"broadcast": False}}
+        },
+    )
+
+    tasks.process_upload(upload_id=upload.pk)
+
+    group_send.assert_not_called()
+
+
 def test_upload_import_error(factories, now, temp_signal):
     upload = factories["music.Upload"](
-        import_metadata={"track": {"uuid": uuid.uuid4()}}
+        import_metadata={"funkwhale": {"track": {"uuid": uuid.uuid4()}}}
     )
     with temp_signal(signals.upload_import_status_updated) as handler:
         tasks.process_upload(upload_id=upload.pk)
@@ -209,32 +346,26 @@ def test_upload_import_updates_cover_if_no_cover(factories, mocker, now):
     album = factories["music.Album"](cover="")
     track = factories["music.Track"](album=album)
     upload = factories["music.Upload"](
-        track=None, import_metadata={"track": {"uuid": track.uuid}}
+        track=None, import_metadata={"funkwhale": {"track": {"uuid": track.uuid}}}
     )
     tasks.process_upload(upload_id=upload.pk)
-    mocked_update.assert_called_once_with(album, upload)
+    mocked_update.assert_called_once_with(album, source=None, cover_data=None)
 
 
 def test_update_album_cover_mbid(factories, mocker):
     album = factories["music.Album"](cover="")
 
     mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
-    tasks.update_album_cover(album=album, upload=None)
+    tasks.update_album_cover(album=album)
 
     mocked_get.assert_called_once_with()
 
 
 def test_update_album_cover_file_data(factories, mocker):
     album = factories["music.Album"](cover="", mbid=None)
-    upload = factories["music.Upload"](track__album=album)
 
     mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
-    mocker.patch(
-        "funkwhale_api.music.metadata.Metadata.get_picture",
-        return_value={"hello": "world"},
-    )
-    tasks.update_album_cover(album=album, upload=upload)
-    upload.get_metadata()
+    tasks.update_album_cover(album=album, cover_data={"hello": "world"})
     mocked_get.assert_called_once_with(data={"hello": "world"})
 
 
@@ -245,19 +376,87 @@ def test_update_album_cover_file_cover_separate_file(ext, mimetype, factories, m
     with open(image_path, "rb") as f:
         image_content = f.read()
     album = factories["music.Album"](cover="", mbid=None)
-    upload = factories["music.Upload"](
-        track__album=album, source="file://" + image_path
-    )
 
     mocked_get = mocker.patch("funkwhale_api.music.models.Album.get_image")
     mocker.patch("funkwhale_api.music.metadata.Metadata.get_picture", return_value=None)
-    tasks.update_album_cover(album=album, upload=upload)
-    upload.get_metadata()
+    tasks.update_album_cover(album=album, source="file://" + image_path)
     mocked_get.assert_called_once_with(
         data={"mimetype": mimetype, "content": image_content}
     )
 
 
+def test_federation_audio_track_to_metadata(now):
+    published = now
+    released = now.date()
+    payload = {
+        "type": "Track",
+        "id": "http://hello.track",
+        "musicbrainzId": str(uuid.uuid4()),
+        "name": "Black in back",
+        "position": 5,
+        "published": published.isoformat(),
+        "album": {
+            "published": published.isoformat(),
+            "type": "Album",
+            "id": "http://hello.album",
+            "name": "Purple album",
+            "musicbrainzId": str(uuid.uuid4()),
+            "released": released.isoformat(),
+            "artists": [
+                {
+                    "type": "Artist",
+                    "published": published.isoformat(),
+                    "id": "http://hello.artist",
+                    "name": "John Smith",
+                    "musicbrainzId": str(uuid.uuid4()),
+                }
+            ],
+        },
+        "artists": [
+            {
+                "published": published.isoformat(),
+                "type": "Artist",
+                "id": "http://hello.trackartist",
+                "name": "Bob Smith",
+                "musicbrainzId": str(uuid.uuid4()),
+            }
+        ],
+    }
+    serializer = federation_serializers.TrackSerializer(data=payload)
+    serializer.is_valid(raise_exception=True)
+    expected = {
+        "artist": payload["artists"][0]["name"],
+        "album": payload["album"]["name"],
+        "album_artist": payload["album"]["artists"][0]["name"],
+        "title": payload["name"],
+        "date": released,
+        "track_number": payload["position"],
+        # musicbrainz
+        "musicbrainz_albumid": payload["album"]["musicbrainzId"],
+        "musicbrainz_recordingid": payload["musicbrainzId"],
+        "musicbrainz_artistid": payload["artists"][0]["musicbrainzId"],
+        "musicbrainz_albumartistid": payload["album"]["artists"][0]["musicbrainzId"],
+        # federation
+        "fid": payload["id"],
+        "album_fid": payload["album"]["id"],
+        "artist_fid": payload["artists"][0]["id"],
+        "album_artist_fid": payload["album"]["artists"][0]["id"],
+        "fdate": serializer.validated_data["published"],
+        "artist_fdate": serializer.validated_data["artists"][0]["published"],
+        "album_artist_fdate": serializer.validated_data["album"]["artists"][0][
+            "published"
+        ],
+        "album_fdate": serializer.validated_data["album"]["published"],
+    }
+
+    result = tasks.federation_audio_track_to_metadata(serializer.validated_data)
+    assert result == expected
+
+    # ensure we never forget to test a mandatory field
+    for k in metadata.ALL_FIELDS:
+        assert k in result
+
+
 def test_scan_library_fetches_page_and_calls_scan_page(now, mocker, factories, r_mock):
     scan = factories["music.LibraryScan"]()
     collection_conf = {
diff --git a/api/tests/test_import_audio_file.py b/api/tests/test_import_audio_file.py
index a7b2380ed..ad4b4be0e 100644
--- a/api/tests/test_import_audio_file.py
+++ b/api/tests/test_import_audio_file.py
@@ -54,6 +54,39 @@ def test_import_files_stores_proper_data(factories, mocker, now, path):
     assert upload.import_reference == "cli-{}".format(now.isoformat())
     assert upload.import_status == "pending"
     assert upload.source == "file://{}".format(path)
+    assert upload.import_metadata == {
+        "funkwhale": {
+            "config": {"replace": False, "dispatch_outbox": False, "broadcast": False}
+        }
+    }
+
+    mocked_process.assert_called_once_with(upload_id=upload.pk)
+
+
+def test_import_with_outbox_flag(factories, mocker):
+    library = factories["music.Library"](actor__local=True)
+    path = os.path.join(DATA_DIR, "dummy_file.ogg")
+    mocked_process = mocker.patch("funkwhale_api.music.tasks.process_upload")
+    call_command(
+        "import_files", str(library.uuid), path, outbox=True, interactive=False
+    )
+    upload = library.uploads.last()
+
+    assert upload.import_metadata["funkwhale"]["config"]["dispatch_outbox"] is True
+
+    mocked_process.assert_called_once_with(upload_id=upload.pk)
+
+
+def test_import_with_broadcast_flag(factories, mocker):
+    library = factories["music.Library"](actor__local=True)
+    path = os.path.join(DATA_DIR, "dummy_file.ogg")
+    mocked_process = mocker.patch("funkwhale_api.music.tasks.process_upload")
+    call_command(
+        "import_files", str(library.uuid), path, broadcast=True, interactive=False
+    )
+    upload = library.uploads.last()
+
+    assert upload.import_metadata["funkwhale"]["config"]["broadcast"] is True
 
     mocked_process.assert_called_once_with(upload_id=upload.pk)
 
@@ -67,7 +100,7 @@ def test_import_with_replace_flag(factories, mocker):
     )
     upload = library.uploads.last()
 
-    assert upload.import_metadata["replace"] is True
+    assert upload.import_metadata["funkwhale"]["config"]["replace"] is True
 
     mocked_process.assert_called_once_with(upload_id=upload.pk)
 
diff --git a/dev.yml b/dev.yml
index a67085e44..5ac74424c 100644
--- a/dev.yml
+++ b/dev.yml
@@ -30,6 +30,7 @@ services:
       - .env.dev
       - .env
     image: postgres
+    command: postgres -c log_min_duration_statement=0
     volumes:
       - "./data/${COMPOSE_PROJECT_NAME-node1}/postgres:/var/lib/postgresql/data"
     networks:
diff --git a/front/src/views/content/libraries/FilesTable.vue b/front/src/views/content/libraries/FilesTable.vue
index c657cc7f9..ef34b3983 100644
--- a/front/src/views/content/libraries/FilesTable.vue
+++ b/front/src/views/content/libraries/FilesTable.vue
@@ -282,6 +282,7 @@ export default {
     'search.tokens': {
       handler (newValue) {
         this.search.query = compileTokens(newValue)
+        this.page = 1
         this.fetchData()
       },
       deep: true
@@ -290,6 +291,9 @@ export default {
       this.page = 1
       this.fetchData()
     },
+    page: function () {
+      this.fetchData()
+    },
     ordering: function () {
       this.page = 1
       this.fetchData()
-- 
GitLab