diff --git a/api/funkwhale_api/common/utils.py b/api/funkwhale_api/common/utils.py
index 57bcba932f89008be946ea62e429659c45a12426..7763e9b7f810090018fb9845a7fb390df812c0d9 100644
--- a/api/funkwhale_api/common/utils.py
+++ b/api/funkwhale_api/common/utils.py
@@ -113,6 +113,9 @@ def chunk_queryset(source_qs, chunk_size):
 
 
 def join_url(start, end):
+    if end.startswith("http://") or end.startswith("https://"):
+        # alread a full URL, joining makes no sense
+        return end
     if start.endswith("/") and end.startswith("/"):
         return start + end[1:]
 
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 387b8ffe7de425ecca879999e39d671d619caff8..55044dbc7f98e88d2decc1c756a7f0488bcfdac9 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -481,14 +481,26 @@ class PermissiveDateField(serializers.CharField):
         return None
 
 
+class MBIDField(serializers.UUIDField):
+    def __init__(self, *args, **kwargs):
+        kwargs.setdefault("allow_null", True)
+        kwargs.setdefault("required", False)
+        super().__init__(*args, **kwargs)
+
+    def to_internal_value(self, v):
+        if v in ["", None]:
+            return None
+        return super().to_internal_value(v)
+
+
 class ArtistSerializer(serializers.Serializer):
     name = serializers.CharField()
-    mbid = serializers.UUIDField(required=False, allow_null=True)
+    mbid = MBIDField()
 
 
 class AlbumSerializer(serializers.Serializer):
     title = serializers.CharField()
-    mbid = serializers.UUIDField(required=False, allow_null=True)
+    mbid = MBIDField()
     release_date = PermissiveDateField(required=False, allow_null=True)
 
 
@@ -512,16 +524,35 @@ class PositionField(serializers.CharField):
 
 class TrackMetadataSerializer(serializers.Serializer):
     title = serializers.CharField()
-    position = PositionField(allow_null=True, required=False)
-    disc_number = PositionField(allow_null=True, required=False)
-    copyright = serializers.CharField(allow_null=True, required=False)
-    license = serializers.CharField(allow_null=True, required=False)
-    mbid = serializers.UUIDField(allow_null=True, required=False)
+    position = PositionField(allow_blank=True, allow_null=True, required=False)
+    disc_number = PositionField(allow_blank=True, allow_null=True, required=False)
+    copyright = serializers.CharField(allow_blank=True, allow_null=True, required=False)
+    license = serializers.CharField(allow_blank=True, allow_null=True, required=False)
+    mbid = MBIDField()
 
     album = AlbumField()
     artists = ArtistField()
     cover_data = CoverDataField()
 
+    remove_blank_null_fields = [
+        "copyright",
+        "license",
+        "position",
+        "disc_number",
+        "mbid",
+    ]
+
+    def validate(self, validated_data):
+        validated_data = super().validate(validated_data)
+        for field in self.remove_blank_null_fields:
+            try:
+                v = validated_data[field]
+            except KeyError:
+                continue
+            if v in ["", None]:
+                validated_data.pop(field)
+        return validated_data
+
 
 class FakeMetadata(Mapping):
     def __init__(self, data, picture=None):
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 309eb1266dd5fd8db1446ff8981a89de4a1fecf9..cf6a0b7ba310cbbe7354a20072de3a6f352333e4 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -870,7 +870,14 @@ class UploadVersion(models.Model):
 
     @property
     def filename(self):
-        return self.upload.filename
+        try:
+            return (
+                self.upload.track.full_name
+                + "."
+                + utils.MIMETYPE_TO_EXTENSION[self.mimetype]
+            )
+        except KeyError:
+            return self.upload.filename
 
     @property
     def audio_file_path(self):
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 391a4b333fe45628626383c4479d4a952c9df4b5..4f8f35ced2b2e7a0f6319b568fad511fb48dd711 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -285,6 +285,11 @@ def should_transcode(upload, format, max_bitrate=None):
     return format_need_transcoding or bitrate_need_transcoding
 
 
+def get_content_disposition(filename):
+    filename = "filename*=UTF-8''{}".format(urllib.parse.quote(filename))
+    return "attachment; {}".format(filename)
+
+
 def handle_serve(upload, user, format=None, max_bitrate=None, proxy_media=True):
     f = upload
     # we update the accessed_date
@@ -342,8 +347,7 @@ def handle_serve(upload, user, format=None, max_bitrate=None, proxy_media=True):
     mapping = {"nginx": "X-Accel-Redirect", "apache2": "X-Sendfile"}
     file_header = mapping[settings.REVERSE_PROXY_TYPE]
     response[file_header] = file_path
-    filename = "filename*=UTF-8''{}".format(urllib.parse.quote(filename))
-    response["Content-Disposition"] = "attachment; {}".format(filename)
+    response["Content-Disposition"] = get_content_disposition(filename)
     if mt:
         response["Content-Type"] = mt
 
diff --git a/api/tests/common/test_utils.py b/api/tests/common/test_utils.py
index ea64ed9d2834f8d97aee7425fb223548ffab1b52..74a3d0bca61526e1cf7b5e87772a18a17a3a77aa 100644
--- a/api/tests/common/test_utils.py
+++ b/api/tests/common/test_utils.py
@@ -85,3 +85,17 @@ def test_get_updated_fields(conf, mock_args, data, expected, mocker):
     obj = mocker.Mock(**mock_args)
 
     assert utils.get_updated_fields(conf, data, obj) == expected
+
+
+@pytest.mark.parametrize(
+    "start, end, expected",
+    [
+        ("https://domain", "/api", "https://domain/api"),
+        ("https://domain/", "/api", "https://domain/api"),
+        ("https://domain", "api", "https://domain/api"),
+        ("https://domain", "https://api", "https://api"),
+        ("https://domain", "http://api", "http://api"),
+    ],
+)
+def test_join_url(start, end, expected):
+    assert utils.join_url(start, end) == expected
diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
index 1656ece49701159c4215e82c95d384b052925d35..52c4e302685b7bf0aa178676f321c29bf551e1a8 100644
--- a/api/tests/music/test_metadata.py
+++ b/api/tests/music/test_metadata.py
@@ -539,6 +539,33 @@ def test_serializer_album_artist_missing():
     assert serializer.validated_data == expected
 
 
+@pytest.mark.parametrize(
+    "field_name", ["copyright", "license", "mbid", "position", "disc_number"]
+)
+def test_serializer_empty_fields(field_name):
+    data = {
+        "title": "Track Title",
+        "artist": "Track Artist",
+        "album": "Track Album",
+        # empty copyright/license field shouldn't fail, cf #850
+        field_name: "",
+    }
+    expected = {
+        "title": "Track Title",
+        "artists": [{"name": "Track Artist", "mbid": None}],
+        "album": {
+            "title": "Track Album",
+            "mbid": None,
+            "release_date": None,
+            "artists": [],
+        },
+        "cover_data": None,
+    }
+    serializer = metadata.TrackMetadataSerializer(data=metadata.FakeMetadata(data))
+    assert serializer.is_valid(raise_exception=True) is True
+    assert serializer.validated_data == expected
+
+
 def test_artist_field_featuring():
     data = {
         "artist": "Santana feat. Chris Cornell",
diff --git a/api/tests/music/test_views.py b/api/tests/music/test_views.py
index 102b5a790a82d8d6fc174df72b414d0bf64033bf..25845e738b228b1b1e969c04070f2de134d454eb 100644
--- a/api/tests/music/test_views.py
+++ b/api/tests/music/test_views.py
@@ -1,6 +1,7 @@
 import io
 import magic
 import os
+import urllib.parse
 
 import pytest
 from django.urls import reverse
@@ -412,7 +413,7 @@ def test_handle_serve_create_mp3_version(factories, now):
     user = factories["users.User"]()
     upload = factories["music.Upload"](bitrate=42)
     response = views.handle_serve(upload, user, format="mp3")
-
+    expected_filename = upload.track.full_name + ".mp3"
     version = upload.versions.latest("id")
 
     assert version.mimetype == "audio/mpeg"
@@ -421,7 +422,9 @@ def test_handle_serve_create_mp3_version(factories, now):
     assert version.audio_file_path.endswith(".mp3")
     assert version.size == version.audio_file.size
     assert magic.from_buffer(version.audio_file.read(), mime=True) == "audio/mpeg"
-
+    assert response["Content-Disposition"] == "attachment; filename*=UTF-8''{}".format(
+        urllib.parse.quote(expected_filename)
+    )
     assert response.status_code == 200
 
 
diff --git a/changes/changelog.d/848.bugfix b/changes/changelog.d/848.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..478a8d42cc1fc6dbcf217a0bdb1721146c6744a4
--- /dev/null
+++ b/changes/changelog.d/848.bugfix
@@ -0,0 +1 @@
+Fixed invalid file extension for transcoded tracks (#848)
diff --git a/changes/changelog.d/850.bugfix b/changes/changelog.d/850.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..0e26ce773fcfc55a4c1f07708b58b6d6ebea5735
--- /dev/null
+++ b/changes/changelog.d/850.bugfix
@@ -0,0 +1 @@
+Ensure empty but optional fields in file metadata don't error during import (#850)
diff --git a/changes/changelog.d/851.bugfix b/changes/changelog.d/851.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..e6866b3e1f99b5c47ff6d764394da4ef501e1a7c
--- /dev/null
+++ b/changes/changelog.d/851.bugfix
@@ -0,0 +1 @@
+Fixed wrong og:image url when using S3 storage (#851)