diff --git a/CHANGELOG b/CHANGELOG
index 283bcfcfbf686993a0fd27f1e60b1c24bdd308f5..edff0877ed06f180ec16dfcc861334313637b395 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -10,6 +10,94 @@ This changelog is viewable on the web at https://docs.funkwhale.audio/changelog.
 
 .. towncrier
 
+0.14.1 (2018-06-06)
+-------------------
+
+Upgrade instructions are available at https://docs.funkwhale.audio/upgrading.html
+
+Enhancements:
+
+- Display server version in the footer (#270)
+- fix_track_files will now update files with bad mimetype (and not only the one
+  with no mimetype) (#273)
+- Huge performance boost (~x5 to x7) during CLI import that queries MusicBrainz
+  (#288)
+- Removed alpha-state transcoding support (#271)
+
+Bugfixes:
+
+- Broken logging statement during import error (#274)
+- Broken search bar on library home (#278)
+- Do not crash when importing track with an artist that do not match the
+  release artist (#237)
+- Do not crash when tag contains multiple uuids with a / separator (#267)
+- Ensure we do not store bad mimetypes (such as application/x-empty) (#266)
+- Fix broken "play all" button that played only 25 tracks (#281)
+- Fixed broken track download modal (overflow and wrong URL) (#239)
+- Removed hardcoded size limit in file upload widget (#275)
+
+
+Documentation:
+
+- Added warning about _protected/music location in nginx configuration (#247)
+
+
+Removed alpha-state transcoding (#271)
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+A few months ago, a basic transcoding feature was implemented. Due to the way
+this feature was designed, it was slow, CPU intensive on the server side,
+and very tightly coupled to the reverse-proxy configuration, preventing
+it to work Apache2, for instance. It was also not compatible with Subsonic clients.
+
+Based on that, we're currently removing support for transcoding
+**in its current state**. The work on a better designed transcoding feature
+can be tracked in https://code.eliotberriot.com/funkwhale/funkwhale/issues/272.
+
+You don't have to do anything on your side, but you may want to remove
+the now obsolete configuration from your reverse proxy file (nginx only)::
+
+    # Remove those blocks:
+
+    # transcode cache
+    proxy_cache_path /tmp/funkwhale-transcode levels=1:2 keys_zone=transcode:10m max_size=1g inactive=7d;
+
+    # Transcoding logic and caching
+    location = /transcode-auth {
+        include /etc/nginx/funkwhale_proxy.conf;
+        # needed so we can authenticate transcode requests, but still
+        # cache the result
+        internal;
+        set $query '';
+        # ensure we actually pass the jwt to the underlytin auth url
+        if ($request_uri ~* "[^\?]+\?(.*)$") {
+            set $query $1;
+        }
+        proxy_pass http://funkwhale-api/api/v1/trackfiles/viewable/?$query;
+        proxy_pass_request_body off;
+        proxy_set_header        Content-Length "";
+    }
+
+    location /api/v1/trackfiles/transcode/ {
+        include /etc/nginx/funkwhale_proxy.conf;
+        # this block deals with authenticating and caching transcoding
+        # requests. Caching is heavily recommended as transcoding
+        # is a CPU intensive process.
+        auth_request /transcode-auth;
+        if ($args ~ (.*)jwt=[^&]*(.*)) {
+            set $cleaned_args $1$2;
+        }
+        proxy_cache_key "$scheme$request_method$host$uri$is_args$cleaned_args";
+        proxy_cache transcode;
+        proxy_cache_valid 200 7d;
+        proxy_ignore_headers "Set-Cookie";
+        proxy_hide_header "Set-Cookie";
+        add_header X-Cache-Status $upstream_cache_status;
+        proxy_pass   http://funkwhale-api;
+    }
+    # end of transcoding logic
+
+
 0.14 (2018-06-02)
 -----------------
 
diff --git a/api/funkwhale_api/__init__.py b/api/funkwhale_api/__init__.py
index 0896aba8a73279d59aca4b8af0013dfab1ec2943..8b5b81ad4b070cafc4de189a439c46a8195efebd 100644
--- a/api/funkwhale_api/__init__.py
+++ b/api/funkwhale_api/__init__.py
@@ -1,3 +1,3 @@
 # -*- coding: utf-8 -*-
-__version__ = '0.14'
+__version__ = '0.14.1'
 __version_info__ = tuple([int(num) if num.isdigit() else num for num in __version__.replace('-', '.', 1).split('.')])
diff --git a/api/funkwhale_api/music/forms.py b/api/funkwhale_api/music/forms.py
deleted file mode 100644
index e68ab73cc2b95030d6dfb284a10b8becbefc9a9e..0000000000000000000000000000000000000000
--- a/api/funkwhale_api/music/forms.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from django import forms
-
-from . import models
-
-
-class TranscodeForm(forms.Form):
-    FORMAT_CHOICES = [
-        ('ogg', 'ogg'),
-        ('mp3', 'mp3'),
-    ]
-
-    to = forms.ChoiceField(choices=FORMAT_CHOICES)
-    BITRATE_CHOICES = [
-        (64, '64'),
-        (128, '128'),
-        (256, '256'),
-    ]
-    bitrate = forms.ChoiceField(
-        choices=BITRATE_CHOICES, required=False)
-
-    track_file = forms.ModelChoiceField(
-        queryset=models.TrackFile.objects.exclude(audio_file__isnull=True)
-    )
diff --git a/api/funkwhale_api/music/management/commands/fix_track_files.py b/api/funkwhale_api/music/management/commands/fix_track_files.py
index 9adc1b9bf1a9d24f3cb26b6da4dce68dc6078213..c18e2b255a6842d4dc998551b0b1b55acbbac5cf 100644
--- a/api/funkwhale_api/music/management/commands/fix_track_files.py
+++ b/api/funkwhale_api/music/management/commands/fix_track_files.py
@@ -33,9 +33,9 @@ class Command(BaseCommand):
     def fix_mimetypes(self, dry_run, **kwargs):
         self.stdout.write('Fixing missing mimetypes...')
         matching = models.TrackFile.objects.filter(
-            source__startswith='file://', mimetype=None)
+            source__startswith='file://').exclude(mimetype__startswith='audio/')
         self.stdout.write(
-            '[mimetypes] {} entries found with no mimetype'.format(
+            '[mimetypes] {} entries found with bad or no mimetype'.format(
                 matching.count()))
         for extension, mimetype in utils.EXTENSION_TO_MIMETYPE.items():
             qs = matching.filter(source__endswith='.{}'.format(extension))
diff --git a/api/funkwhale_api/music/metadata.py b/api/funkwhale_api/music/metadata.py
index 3637b1c8c5d78311dd2e3975cd02bd48d6f9269e..4c17c42c0d51ac9c92501d43ecbf1aab3de0377f 100644
--- a/api/funkwhale_api/music/metadata.py
+++ b/api/funkwhale_api/music/metadata.py
@@ -91,10 +91,23 @@ def convert_track_number(v):
         pass
 
 
+
+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]
+        except (AttributeError, IndexError, TypeError):
+            pass
+
+        return super().to_python(value)
+
+
 VALIDATION = {
-    'musicbrainz_artistid': forms.UUIDField(),
-    'musicbrainz_albumid': forms.UUIDField(),
-    'musicbrainz_recordingid': forms.UUIDField(),
+    'musicbrainz_artistid': FirstUUIDField(),
+    'musicbrainz_albumid': FirstUUIDField(),
+    'musicbrainz_recordingid': FirstUUIDField(),
 }
 
 CONF = {
diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py
index 0ba4d22c339f3798945d069c5aff801657cee5dc..bf3f9e12c29ebbada7e158a57a37b755aafecb1d 100644
--- a/api/funkwhale_api/music/models.py
+++ b/api/funkwhale_api/music/models.py
@@ -334,6 +334,11 @@ class TrackQuerySet(models.QuerySet):
                     .prefetch_related('files'))
 
 
+def get_artist(release_list):
+    return Artist.get_or_create_from_api(
+        mbid=release_list[0]['artist-credits'][0]['artists']['id'])[0]
+
+
 class Track(APIModelMixin):
     title = models.CharField(max_length=255)
     artist = models.ForeignKey(
@@ -363,8 +368,9 @@ class Track(APIModelMixin):
             'musicbrainz_field_name': 'title'
         },
         'artist': {
-            'musicbrainz_field_name': 'artist-credit',
-            'converter': lambda v: Artist.get_or_create_from_api(mbid=v[0]['artist']['id'])[0],
+            # we use the artist from the release to avoid #237
+            'musicbrainz_field_name': 'release-list',
+            'converter': get_artist,
         },
         'album': {
             'musicbrainz_field_name': 'release-list',
@@ -431,7 +437,40 @@ class Track(APIModelMixin):
             title__iexact=title,
             defaults=kwargs)
 
-
+    @classmethod
+    def get_or_create_from_release(cls, release_mbid, mbid):
+        release_mbid = str(release_mbid)
+        mbid = str(mbid)
+        try:
+            return cls.objects.get(mbid=mbid), False
+        except cls.DoesNotExist:
+            pass
+
+        album = Album.get_or_create_from_api(release_mbid)[0]
+        data = musicbrainz.client.api.releases.get(
+            str(album.mbid), includes=Album.api_includes)
+        tracks = [
+            t
+            for m in data['release']['medium-list']
+            for t in m['track-list']
+        ]
+        track_data = None
+        for track in tracks:
+            if track['recording']['id'] == mbid:
+                track_data = track
+                break
+        if not track_data:
+            raise ValueError('No track found matching this ID')
+
+        return cls.objects.update_or_create(
+            mbid=mbid,
+            defaults={
+                'position': int(track['position']),
+                'title': track['recording']['title'],
+                'album': album,
+                'artist': album.artist,
+            }
+        )
 class TrackFile(models.Model):
     uuid = models.UUIDField(
         unique=True, db_index=True, default=uuid.uuid4)
diff --git a/api/funkwhale_api/music/tasks.py b/api/funkwhale_api/music/tasks.py
index 218e374e8239c91861f82dbf75245cd94bee7ead..7b1b4898111f71b6947724dd4119fd1e36e1dfb5 100644
--- a/api/funkwhale_api/music/tasks.py
+++ b/api/funkwhale_api/music/tasks.py
@@ -259,7 +259,9 @@ def get_cover_from_fs(dir_path):
     'import_job')
 def import_job_run(self, import_job, replace=False, use_acoustid=False):
     def mark_errored(exc):
-        logger.error('[Import Job %s] Error during import: %s', str(exc))
+        logger.error(
+            '[Import Job %s] Error during import: %s',
+            import_job.pk, str(exc))
         import_job.status = 'errored'
         import_job.save(update_fields=['status'])
 
diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py
index f11e4507a7a0bc7d1648e5dfda18f3deb4542286..3b9fbb21476d8b4e45fa8fc8356d0b0b635e1685 100644
--- a/api/funkwhale_api/music/utils.py
+++ b/api/funkwhale_api/music/utils.py
@@ -43,9 +43,9 @@ def get_query(query_string, search_fields):
 
 
 def guess_mimetype(f):
-    b = min(100000, f.size)
+    b = min(1000000, f.size)
     t = magic.from_buffer(f.read(b), mime=True)
-    if t == 'application/octet-stream':
+    if not t.startswith('audio/'):
         # failure, we try guessing by extension
         mt, _ = mimetypes.guess_type(f.path)
         if mt:
diff --git a/api/funkwhale_api/music/views.py b/api/funkwhale_api/music/views.py
index 2f5b75a97a51de248fc128835c50d1b3b926ffd0..2850c077051a7a25b210f3d1eebe728b981bdba4 100644
--- a/api/funkwhale_api/music/views.py
+++ b/api/funkwhale_api/music/views.py
@@ -35,7 +35,6 @@ from funkwhale_api.musicbrainz import api
 from funkwhale_api.requests.models import ImportRequest
 
 from . import filters
-from . import forms
 from . import importers
 from . import models
 from . import permissions as music_permissions
@@ -324,42 +323,6 @@ class TrackFileViewSet(viewsets.ReadOnlyModelViewSet):
         except models.TrackFile.DoesNotExist:
             return Response(status=404)
 
-    @list_route(methods=['get'])
-    def viewable(self, request, *args, **kwargs):
-        return Response({}, status=200)
-
-    @list_route(methods=['get'])
-    def transcode(self, request, *args, **kwargs):
-        form = forms.TranscodeForm(request.GET)
-        if not form.is_valid():
-            return Response(form.errors, status=400)
-
-        f = form.cleaned_data['track_file']
-        if not f.audio_file:
-            return Response(status=400)
-        output_kwargs = {
-            'format': form.cleaned_data['to']
-        }
-        args = (ffmpeg
-            .input(f.audio_file.path)
-            .output('pipe:', **output_kwargs)
-            .get_args()
-        )
-        # we use a generator here so the view return immediatly and send
-        # file chunk to the browser, instead of blocking a few seconds
-        def _transcode():
-            p = subprocess.Popen(
-                ['ffmpeg'] + args,
-                stdout=subprocess.PIPE)
-            for line in p.stdout:
-                yield line
-
-        response = StreamingHttpResponse(
-            _transcode(), status=200,
-            content_type=form.cleaned_data['to'])
-
-        return response
-
 
 class TagViewSet(viewsets.ReadOnlyModelViewSet):
     queryset = Tag.objects.all().order_by('name')
diff --git a/api/funkwhale_api/providers/audiofile/tasks.py b/api/funkwhale_api/providers/audiofile/tasks.py
index 40114c8774abfa2f18e520dd15b19be9f382cb58..fb630673555bbb0cff380a5b835d725506a287da 100644
--- a/api/funkwhale_api/providers/audiofile/tasks.py
+++ b/api/funkwhale_api/providers/audiofile/tasks.py
@@ -12,25 +12,43 @@ from funkwhale_api.music import models, metadata
 @transaction.atomic
 def import_track_data_from_path(path):
     data = metadata.Metadata(path)
-    artist = models.Artist.objects.get_or_create(
-        name__iexact=data.get('artist'),
-        defaults={
-            'name': data.get('artist'),
-            'mbid': data.get('musicbrainz_artistid', None),
-        },
-    )[0]
+    album = None
+    track_mbid = data.get('musicbrainz_recordingid', None)
+    album_mbid = data.get('musicbrainz_albumid', None)
 
-    release_date = data.get('date', default=None)
-    album = models.Album.objects.get_or_create(
-        title__iexact=data.get('album'),
-        artist=artist,
-        defaults={
-            'title': data.get('album'),
-            'release_date': release_date,
-            'mbid': data.get('musicbrainz_albumid', None),
-        },
-    )[0]
+    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
+    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,
+            },
+        )[0]
     position = data.get('track_number', default=None)
     track = models.Track.objects.get_or_create(
         title__iexact=data.get('title'),
@@ -38,7 +56,6 @@ def import_track_data_from_path(path):
         defaults={
             'title': data.get('title'),
             'position': position,
-            'mbid': data.get('musicbrainz_recordingid', None),
         },
     )[0]
     return track
diff --git a/api/tests/music/test_commands.py b/api/tests/music/test_commands.py
index ff3343aa53fd13be34c99316c2da38c188571e93..6f03f6b8ad33e3f6a974ff2da8aa91f053d7ad09 100644
--- a/api/tests/music/test_commands.py
+++ b/api/tests/music/test_commands.py
@@ -1,5 +1,9 @@
+import os
+
 from funkwhale_api.music.management.commands import fix_track_files
 
+DATA_DIR = os.path.dirname(os.path.abspath(__file__))
+
 
 def test_fix_track_files_bitrate_length(factories, mocker):
     tf1 = factories['music.TrackFile'](bitrate=1, duration=2)
@@ -43,3 +47,27 @@ def test_fix_track_files_size(factories, mocker):
 
     # updated
     assert tf2.size == 2
+
+
+def test_fix_track_files_mimetype(factories, mocker):
+    name = 'test.mp3'
+    mp3_path = os.path.join(DATA_DIR, 'test.mp3')
+    ogg_path = os.path.join(DATA_DIR, 'test.ogg')
+    tf1 = factories['music.TrackFile'](
+        audio_file__from_path=mp3_path,
+        source='file://{}'.format(mp3_path),
+        mimetype='application/x-empty')
+
+    # this one already has a mimetype set, to it should not be updated
+    tf2 = factories['music.TrackFile'](
+        audio_file__from_path=ogg_path,
+        source='file://{}'.format(ogg_path),
+        mimetype='audio/something')
+    c = fix_track_files.Command()
+    c.fix_mimetypes(dry_run=False)
+
+    tf1.refresh_from_db()
+    tf2.refresh_from_db()
+
+    assert tf1.mimetype == 'audio/mpeg'
+    assert tf2.mimetype == 'audio/something'
diff --git a/api/tests/music/test_metadata.py b/api/tests/music/test_metadata.py
index 326f18324bd4ba027d225f28369e42d40e57ee87..a4f15b3355f9e2299e682675fd1622d16ab19353 100644
--- a/api/tests/music/test_metadata.py
+++ b/api/tests/music/test_metadata.py
@@ -95,3 +95,17 @@ def test_can_get_metadata_from_flac_file_not_crash_if_empty():
 
     with pytest.raises(metadata.TagNotFound):
         data.get('test')
+
+
+@pytest.mark.parametrize('field_name', [
+    'musicbrainz_artistid',
+    'musicbrainz_albumid',
+    'musicbrainz_recordingid',
+])
+def test_mbid_clean_keeps_only_first(field_name):
+    u1 = str(uuid.uuid4())
+    u2 = str(uuid.uuid4())
+    field = metadata.VALIDATION[field_name]
+    result = field.to_python('/'.join([u1, u2]))
+
+    assert str(result) == u1
diff --git a/api/tests/music/test_models.py b/api/tests/music/test_models.py
index feb68ea33ad53f146b8e05d7a47e288af2285d21..0ef54eb668014462e57649129115b2719e56e2f8 100644
--- a/api/tests/music/test_models.py
+++ b/api/tests/music/test_models.py
@@ -43,6 +43,53 @@ def test_import_album_stores_release_group(factories):
     assert album.artist == artist
 
 
+def test_import_track_from_release(factories, mocker):
+    album = factories['music.Album'](
+        mbid='430347cb-0879-3113-9fde-c75b658c298e')
+    album_data = {
+        'release': {
+            'id': album.mbid,
+            'title': 'Daydream Nation',
+            'status': 'Official',
+            'medium-count': 1,
+            'medium-list': [
+                {
+                    'position': '1',
+                    'format': 'CD',
+                    'track-list': [
+                        {
+                            'id': '03baca8b-855a-3c05-8f3d-d3235287d84d',
+                            'position': '4',
+                            'number': '4',
+                            'length': '417973',
+                            'recording': {
+                                'id': '2109e376-132b-40ad-b993-2bb6812e19d4',
+                                'title': 'Teen Age Riot',
+                                'length': '417973'},
+                            'track_or_recording_length': '417973'
+                        }
+                    ],
+                    'track-count': 1
+                }
+            ],
+        }
+    }
+    mocked_get = mocker.patch(
+        'funkwhale_api.musicbrainz.api.releases.get',
+        return_value=album_data)
+    track_data = album_data['release']['medium-list'][0]['track-list'][0]
+    track = models.Track.get_or_create_from_release(
+        '430347cb-0879-3113-9fde-c75b658c298e',
+        track_data['recording']['id'],
+    )[0]
+    mocked_get.assert_called_once_with(
+        album.mbid, includes=models.Album.api_includes)
+    assert track.title == track_data['recording']['title']
+    assert track.mbid == track_data['recording']['id']
+    assert track.album == album
+    assert track.artist == album.artist
+    assert track.position == int(track_data['position'])
+
 def test_import_job_is_bound_to_track_file(factories, mocker):
     track = factories['music.Track']()
     job = factories['music.ImportJob'](mbid=track.mbid)
diff --git a/api/tests/music/test_utils.py b/api/tests/music/test_utils.py
index 12b381a997c59ea85ebf1239024b1d3f288e2cdf..7b967dbbcceac9d6aade23d2dfb97d8f2a7d696b 100644
--- a/api/tests/music/test_utils.py
+++ b/api/tests/music/test_utils.py
@@ -15,9 +15,13 @@ def test_guess_mimetype_try_using_extension(factories, mocker):
     assert utils.guess_mimetype(f.audio_file) == 'audio/mpeg'
 
 
-def test_guess_mimetype_try_using_extension_if_fail(factories, mocker):
+@pytest.mark.parametrize('wrong', [
+    'application/octet-stream',
+    'application/x-empty',
+])
+def test_guess_mimetype_try_using_extension_if_fail(wrong, factories, mocker):
     mocker.patch(
-        'magic.from_buffer', return_value='application/octet-stream')
+        'magic.from_buffer', return_value=wrong)
     f = factories['music.TrackFile'].build(
         audio_file__filename='test.mp3')
 
diff --git a/api/tests/test_import_audio_file.py b/api/tests/test_import_audio_file.py
index de81860754a7da6c667ef05302b08ff7310ee6f8..da3d1959cbb076ccadf9ad5cff81ca2affb6dcb0 100644
--- a/api/tests/test_import_audio_file.py
+++ b/api/tests/test_import_audio_file.py
@@ -15,16 +15,13 @@ DATA_DIR = os.path.join(
 )
 
 
-def test_can_create_track_from_file_metadata(db, mocker):
+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'],
-        'musicbrainz_albumid': ['a766da8b-8336-47aa-a3ee-371cc41ccc75'],
-        'musicbrainz_trackid': ['bd21ac48-46d8-4e78-925f-d9cc2a294656'],
-        'musicbrainz_artistid': ['013c8e5b-d72a-4cd3-8dee-6c64d6125823'],
     }
     m1 = mocker.patch('mutagen.File', return_value=metadata)
     m2 = mocker.patch(
@@ -35,13 +32,64 @@ def test_can_create_track_from_file_metadata(db, mocker):
         os.path.join(DATA_DIR, 'dummy_file.ogg'))
 
     assert track.title == metadata['title'][0]
-    assert track.mbid == uuid.UUID(metadata['musicbrainz_trackid'][0])
+    assert track.mbid is None
     assert track.position == 4
     assert track.album.title == metadata['album'][0]
-    assert track.album.mbid == uuid.UUID(metadata['musicbrainz_albumid'][0])
+    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.mbid == uuid.UUID(metadata['musicbrainz_artistid'][0])
+    assert track.artist.mbid is None
+
+
+def test_can_create_track_from_file_metadata_mbid(factories, mocker):
+    album = factories['music.Album']()
+    mocker.patch(
+        'funkwhale_api.music.models.Album.get_or_create_from_api',
+        return_value=(album, True),
+    )
+
+    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',
+                            },
+                        }
+                    ],
+                    'track-count': 1
+                }
+            ],
+        }
+    }
+    mocker.patch(
+        'funkwhale_api.musicbrainz.api.releases.get',
+        return_value=album_data)
+    track_data = album_data['release']['medium-list'][0]['track-list'][0]
+    metadata = {
+        'musicbrainz_albumid': [album.mbid],
+        'musicbrainz_trackid': [track_data['recording']['id']],
+    }
+    m1 = mocker.patch('mutagen.File', return_value=metadata)
+    m2 = mocker.patch(
+        'funkwhale_api.music.metadata.Metadata.get_file_type',
+        return_value='OggVorbis',
+    )
+    track = tasks.import_track_data_from_path(
+        os.path.join(DATA_DIR, 'dummy_file.ogg'))
+
+    assert track.title == track_data['recording']['title']
+    assert track.mbid == track_data['recording']['id']
+    assert track.position == 4
+    assert track.album == album
+    assert track.artist == album.artist
 
 
 def test_management_command_requires_a_valid_username(factories, mocker):
diff --git a/changes/template.rst b/changes/template.rst
index 24f0e87ebc16c474c7b44841114816ab0f6fcb2f..9ffcdc08e6317223ad2f3cf39aedf85da826ecbb 100644
--- a/changes/template.rst
+++ b/changes/template.rst
@@ -1,5 +1,6 @@
 
-Upgrade instructions are available at https://docs.funkwhale.audio/upgrading.html
+Upgrade instructions are available at
+https://docs.funkwhale.audio/upgrading.html
 
 {% for section, _ in sections.items() %}
 {% if sections[section] %}
diff --git a/deploy/nginx.conf b/deploy/nginx.conf
index 5314d90175f981d7e6b809d45866a2753d3028be..66851321fb43017af8b893319940562309b5b604 100644
--- a/deploy/nginx.conf
+++ b/deploy/nginx.conf
@@ -1,8 +1,5 @@
 # Ensure you update at least the server_name variables to match your own
 
-# transcode cache
-proxy_cache_path /tmp/funkwhale-transcode levels=1:2 keys_zone=transcode:10m max_size=1g inactive=7d;
-
 # domain
 upstream funkwhale-api {
     # depending on your setup, you may want to udpate this
@@ -98,41 +95,6 @@ server {
         alias   /srv/funkwhale/data/music;
     }
 
-    # Transcoding logic and caching
-    location = /transcode-auth {
-        include /etc/nginx/funkwhale_proxy.conf;
-        # needed so we can authenticate transcode requests, but still
-        # cache the result
-        internal;
-        set $query '';
-        # ensure we actually pass the jwt to the underlytin auth url
-        if ($request_uri ~* "[^\?]+\?(.*)$") {
-            set $query $1;
-        }
-        proxy_pass http://funkwhale-api/api/v1/trackfiles/viewable/?$query;
-        proxy_pass_request_body off;
-        proxy_set_header        Content-Length "";
-    }
-
-    location /api/v1/trackfiles/transcode/ {
-        include /etc/nginx/funkwhale_proxy.conf;
-        # this block deals with authenticating and caching transcoding
-        # requests. Caching is heavily recommended as transcoding
-        # is a CPU intensive process.
-        auth_request /transcode-auth;
-        if ($args ~ (.*)jwt=[^&]*(.*)) {
-            set $cleaned_args $1$2;
-        }
-        proxy_cache_key "$scheme$request_method$host$uri$is_args$cleaned_args";
-        proxy_cache transcode;
-        proxy_cache_valid 200 7d;
-        proxy_ignore_headers "Set-Cookie";
-        proxy_hide_header "Set-Cookie";
-        add_header X-Cache-Status $upstream_cache_status;
-        proxy_pass   http://funkwhale-api;
-    }
-    # end of transcoding logic
-
     location /staticfiles/ {
         # django static files
         alias /srv/funkwhale/data/static/;
diff --git a/docker/nginx/conf.dev b/docker/nginx/conf.dev
index 673edd1a4b4422f94e12cd4886dc5296e8760ed7..2ed1a97d540823ab6021df46db677718885c4529 100644
--- a/docker/nginx/conf.dev
+++ b/docker/nginx/conf.dev
@@ -26,7 +26,6 @@ http {
     keepalive_timeout  65;
 
     #gzip  on;
-    proxy_cache_path /tmp/funkwhale-transcode levels=1:2 keys_zone=transcode:10m max_size=1g inactive=24h use_temp_path=off;
 
     map $http_upgrade $connection_upgrade {
         default upgrade;
@@ -46,38 +45,6 @@ http {
             internal;
             alias   /music;
         }
-        location = /transcode-auth {
-            # needed so we can authenticate transcode requests, but still
-            # cache the result
-            internal;
-            set $query '';
-            # ensure we actually pass the jwt to the underlytin auth url
-            if ($request_uri ~* "[^\?]+\?(.*)$") {
-                set $query $1;
-            }
-            include /etc/nginx/funkwhale_proxy.conf;
-            proxy_pass http://api:12081/api/v1/trackfiles/viewable/?$query;
-            proxy_pass_request_body off;
-            proxy_set_header        Content-Length "";
-        }
-
-        location /api/v1/trackfiles/transcode/ {
-            # this block deals with authenticating and caching transcoding
-            # requests. Caching is heavily recommended as transcoding
-            # is a CPU intensive process.
-            auth_request /transcode-auth;
-            if ($args ~ (.*)jwt=[^&]*(.*)) {
-                set $cleaned_args $1$2;
-            }
-            include /etc/nginx/funkwhale_proxy.conf;
-            proxy_cache_key "$scheme$request_method$host$uri$is_args$cleaned_args";
-            proxy_cache transcode;
-            proxy_cache_valid 200 7d;
-            proxy_ignore_headers "Set-Cookie";
-            proxy_hide_header "Set-Cookie";
-            add_header X-Cache-Status $upstream_cache_status;
-            proxy_pass http://api:12081;
-        }
         location / {
             include /etc/nginx/funkwhale_proxy.conf;
             proxy_pass   http://api:12081/;
diff --git a/docs/installation/index.rst b/docs/installation/index.rst
index ae5794b6cfd41ea2074be012d38748c2801bb06a..0628fe17f5c6eac184427dab7d5d8ddb00a3883f 100644
--- a/docs/installation/index.rst
+++ b/docs/installation/index.rst
@@ -16,10 +16,8 @@ The project relies on the following components and services to work:
 Hardware requirements
 ---------------------
 
-Funkwhale is not especially CPU hungry, unless you're relying heavily
-on the transcoding feature (which is basic and unoptimized at the moment).
-
-On a dockerized instance with 2 CPUs and a few active users, the memory footprint is around ~500Mb::
+Funkwhale is not especially CPU hungry. On a dockerized instance with 2 CPUs
+and a few active users, the memory footprint is around ~500Mb::
 
    CONTAINER                   MEM USAGE
    funkwhale_api_1             202.1 MiB
@@ -116,6 +114,13 @@ Then, download our sample virtualhost file and proxy conf:
 Ensure static assets and proxy pass match your configuration, and check the configuration is valid with ``nginx -t``.
 If everything is fine, you can restart your nginx server with ``service nginx restart``.
 
+.. warning::
+
+    If you plan to use to in-place import, ensure the alias value
+    in the ``_protected/music`` location matches your MUSIC_DIRECTORY_SERVE_PATH
+    env var.
+
+
 Apache2
 ^^^^^^^
 
@@ -125,10 +130,8 @@ Apache2
     are not working yet:
 
     - Websocket (used for real-time updates on Instance timeline)
-    - Transcoding of audio files
 
-    Those features are not necessary to use your Funkwhale instance, and
-    transcoding in particular is still in alpha-state anyway.
+    Those features are not necessary to use your Funkwhale instance.
 
 Ensure you have a recent version of apache2 installed on your server.
 You'll also need the following dependencies::
diff --git a/front/src/App.vue b/front/src/App.vue
index a213374284fd072b22513fa63f15b52b56458259..673f8386460ecba32737c129e3421adc06881f04 100644
--- a/front/src/App.vue
+++ b/front/src/App.vue
@@ -12,10 +12,13 @@
               <router-link class="item" to="/about">
                 <i18next path="About this instance" />
               </router-link>
-              <i18next tag="a" href="https://funkwhale.audio" class="item" target="_blank" path="Official website" />
-              <i18next tag="a" href="https://docs.funkwhale.audio" class="item" target="_blank" path="Documentation" />
-              <i18next tag="a" href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank" path="Source code" />
-              <i18next tag="a" href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank" path="Issue tracker" />
+              <a href="https://funkwhale.audio" class="item" target="_blank">{{ $t('Official website') }}</a>
+              <a href="https://docs.funkwhale.audio" class="item" target="_blank">{{ $t('Documentation') }}</a>
+              <a href="https://code.eliotberriot.com/funkwhale/funkwhale" class="item" target="_blank">
+                <template v-if="version">{{ $t('Source code ({% version %})', {version: version}) }}</template>
+                <template v-else>{{ $t('Source code') }}</template>
+              </a>
+              <a href="https://code.eliotberriot.com/funkwhale/funkwhale/issues" class="item" target="_blank">{{ $t('Issue tracker') }}</a>
             </div>
           </div>
           <div class="ten wide column">
@@ -39,6 +42,9 @@
 </template>
 
 <script>
+import axios from 'axios'
+import _ from 'lodash'
+
 import Sidebar from '@/components/Sidebar'
 import Raven from '@/components/Raven'
 
@@ -51,6 +57,11 @@ export default {
     Raven,
     PlaylistModal
   },
+  data () {
+    return {
+      nodeinfo: null
+    }
+  },
   created () {
     this.$store.dispatch('instance/fetchSettings')
     let self = this
@@ -58,6 +69,23 @@ export default {
       // used to redraw ago dates every minute
       self.$store.commit('ui/computeLastDate')
     }, 1000 * 60)
+    this.fetchNodeInfo()
+  },
+  methods: {
+    fetchNodeInfo () {
+      let self = this
+      axios.get('instance/nodeinfo/2.0/').then(response => {
+        self.nodeinfo = response.data
+      })
+    }
+  },
+  computed: {
+    version () {
+      if (!this.nodeinfo) {
+        return null
+      }
+      return _.get(this.nodeinfo, 'software.version')
+    }
   }
 }
 </script>
diff --git a/front/src/audio/backend.js b/front/src/audio/backend.js
index 5b4c707067698822f23f831284efcdabb82c2dc3..619f3cefdbd7b08f9879be343ce246e41e86de0d 100644
--- a/front/src/audio/backend.js
+++ b/front/src/audio/backend.js
@@ -26,7 +26,11 @@ export default {
       return url
     }
     if (url.startsWith('/')) {
-      return config.BACKEND_URL + url.substr(1)
+      let rootUrl = (
+        window.location.protocol + '//' + window.location.hostname +
+        (window.location.port ? ':' + window.location.port : '')
+      )
+      return rootUrl + url
     } else {
       return config.BACKEND_URL + url
     }
diff --git a/front/src/components/audio/PlayButton.vue b/front/src/components/audio/PlayButton.vue
index 6fc7699690f53ab66e998d320655327f51d21cc9..28a8900841afc29fdefe844b3b8c473420f7809c 100644
--- a/front/src/components/audio/PlayButton.vue
+++ b/front/src/components/audio/PlayButton.vue
@@ -67,8 +67,31 @@ export default {
     }
   },
   methods: {
+    getTracksPage (page, params, resolve, tracks) {
+      if (page > 10) {
+        // it's 10 * 100 tracks already, let's stop here
+        resolve(tracks)
+      }
+      // when fetching artists/or album tracks, sometimes, we may have to fetch
+      // multiple pages
+      let self = this
+      params['page_size'] = 100
+      params['page'] = page
+      tracks = tracks || []
+      axios.get('tracks/', {params: params}).then((response) => {
+        response.data.results.forEach(t => {
+          tracks.push(t)
+        })
+        if (response.data.next) {
+          self.getTracksPage(page + 1, params, resolve, tracks)
+        } else {
+          resolve(tracks)
+        }
+      })
+    },
     getPlayableTracks () {
       let self = this
+      this.isLoading = true
       let getTracks = new Promise((resolve, reject) => {
         if (self.track) {
           resolve([self.track])
@@ -82,44 +105,30 @@ export default {
             }))
           })
         } else if (self.artist) {
-          let params = {
-            params: {'artist': self.artist, 'ordering': 'album__release_date,position'}
-          }
-          axios.get('tracks', params).then((response) => {
-            resolve(response.data.results)
-          })
+          let params = {'artist': self.artist, 'ordering': 'album__release_date,position'}
+          self.getTracksPage(1, params, resolve)
         } else if (self.album) {
-          let params = {
-            params: {'album': self.album, 'ordering': 'position'}
-          }
-          axios.get('tracks', params).then((response) => {
-            resolve(response.data.results)
-          })
+          let params = {'album': self.album, 'ordering': 'position'}
+          self.getTracksPage(1, params, resolve)
         }
       })
       return getTracks.then((tracks) => {
+        setTimeout(e => {
+          self.isLoading = false
+        }, 250)
         return tracks.filter(e => {
           return e.files.length > 0
         })
       })
     },
-    triggerLoad () {
-      let self = this
-      this.isLoading = true
-      setTimeout(() => {
-        self.isLoading = false
-      }, 500)
-    },
     add () {
       let self = this
-      this.triggerLoad()
       this.getPlayableTracks().then((tracks) => {
         self.$store.dispatch('queue/appendMany', {tracks: tracks})
       })
     },
     addNext (next) {
       let self = this
-      this.triggerLoad()
       let wasEmpty = this.$store.state.queue.tracks.length === 0
       this.getPlayableTracks().then((tracks) => {
         self.$store.dispatch('queue/appendMany', {tracks: tracks, index: self.$store.state.queue.currentIndex + 1})
diff --git a/front/src/components/audio/Search.vue b/front/src/components/audio/Search.vue
index 890c83f5bfc2d6562eedfda26b9a63a4fe631bf1..9cfea3bc0cae5da768bbf1dbefe7e12b405f508c 100644
--- a/front/src/components/audio/Search.vue
+++ b/front/src/components/audio/Search.vue
@@ -29,9 +29,9 @@
 </template>
 
 <script>
+import _ from 'lodash'
 import axios from 'axios'
 import logger from '@/logging'
-import backend from '@/audio/backend'
 import AlbumCard from '@/components/audio/album/Card'
 import ArtistCard from '@/components/audio/artist/Card'
 
@@ -50,7 +50,6 @@ export default {
         albums: [],
         artists: []
       },
-      backend: backend,
       isLoading: false
     }
   },
@@ -61,7 +60,7 @@ export default {
     this.search()
   },
   methods: {
-    search () {
+    search: _.debounce(function () {
       if (this.query.length < 1) {
         return
       }
@@ -77,15 +76,11 @@ export default {
         self.results = self.castResults(response.data)
         self.isLoading = false
       })
-    },
+    }, 500),
     castResults (results) {
       return {
-        albums: results.albums.map((album) => {
-          return backend.Album.clean(album)
-        }),
-        artists: results.artists.map((artist) => {
-          return backend.Artist.clean(artist)
-        })
+        albums: results.albums,
+        artists: results.artists
       }
     }
   },
diff --git a/front/src/components/audio/Track.vue b/front/src/components/audio/Track.vue
index 08a055f5ca97186d51130e419176338473f9390e..366f104f1fc021fbe5478346439e94034b345e2a 100644
--- a/front/src/components/audio/Track.vue
+++ b/front/src/components/audio/Track.vue
@@ -18,7 +18,6 @@
 <script>
 import {mapState} from 'vuex'
 import url from '@/utils/url'
-import formats from '@/audio/formats'
 import _ from 'lodash'
 // import logger from '@/logging'
 
@@ -52,13 +51,6 @@ export default {
       let sources = [
         {type: file.mimetype, url: file.path}
       ]
-      formats.formats.forEach(f => {
-        if (f !== file.mimetype) {
-          let format = formats.formatsMap[f]
-          let url = `/api/v1/trackfiles/transcode/?track_file=${file.id}&to=${format}`
-          sources.push({type: f, url: url})
-        }
-      })
       if (this.$store.state.auth.authenticated) {
         // we need to send the token directly in url
         // so authentication can be checked by the backend
diff --git a/front/src/components/audio/track/Table.vue b/front/src/components/audio/track/Table.vue
index 7045cf9bd222085bbb7fbf1056a24f4c8205cb86..4559b3c41b59ccf798eec071b97c1ab55a81dceb 100644
--- a/front/src/components/audio/track/Table.vue
+++ b/front/src/components/audio/track/Table.vue
@@ -84,4 +84,7 @@ export default {
 tr:not(:hover) .favorite-icon:not(.favorited) {
   display: none;
 }
+pre {
+  overflow-x: scroll;
+}
 </style>
diff --git a/front/src/components/library/import/FileUpload.vue b/front/src/components/library/import/FileUpload.vue
index 7aa8adac0f00f72c9724603095977b6797aa4ea9..48ca0ad84adf41744303ed363d070e34daaed2bd 100644
--- a/front/src/components/library/import/FileUpload.vue
+++ b/front/src/components/library/import/FileUpload.vue
@@ -9,7 +9,6 @@
         :class="['ui', 'icon', 'left', 'floated', 'button']"
         :post-action="uploadUrl"
         :multiple="true"
-        :size="1024 * 1024 * 30"
         :data="uploadData"
         :drop="true"
         extensions="ogg,mp3,flac"