diff --git a/api/funkwhale_api/music/utils.py b/api/funkwhale_api/music/utils.py
index 5031e69ba8030f3db1ec4bffa22bd01af1151c5f..571bc4ddd42509320a9ec4ec8a9f3e503d9708da 100644
--- a/api/funkwhale_api/music/utils.py
+++ b/api/funkwhale_api/music/utils.py
@@ -12,7 +12,7 @@ def guess_mimetype(f):
     t = magic.from_buffer(f.read(b), mime=True)
     if not t.startswith("audio/"):
         # failure, we try guessing by extension
-        mt, _ = mimetypes.guess_type(f.path)
+        mt, _ = mimetypes.guess_type(f.name)
         if mt:
             t = mt
     return t
diff --git a/api/tests/music/test_utils.py b/api/tests/music/test_utils.py
index 87eaddc435d9270e29a0750a60f32b2af7f6e74f..982422c34a07978d9438d44044cd59f550682f30 100644
--- a/api/tests/music/test_utils.py
+++ b/api/tests/music/test_utils.py
@@ -36,3 +36,12 @@ def test_get_audio_file_data(name, expected):
         result = utils.get_audio_file_data(f)
 
     assert result == expected
+
+
+def test_guess_mimetype_dont_crash_with_s3(factories, mocker, settings):
+    """See #857"""
+    settings.DEFAULT_FILE_STORAGE = "funkwhale_api.common.storage.ASCIIS3Boto3Storage"
+    mocker.patch("magic.from_buffer", return_value="none")
+    f = factories["music.Upload"].build(audio_file__filename="test.mp3")
+
+    assert utils.guess_mimetype(f.audio_file) == "audio/mpeg"
diff --git a/changes/changelog.d/814.enhancement b/changes/changelog.d/814.enhancement
new file mode 100644
index 0000000000000000000000000000000000000000..c93b1983c954bbcec69ee5f3b0f6912d00ed7b5f
--- /dev/null
+++ b/changes/changelog.d/814.enhancement
@@ -0,0 +1 @@
+Added copy-to-clipboard button with Subsonic password input (#814)
diff --git a/changes/changelog.d/833.bugfix b/changes/changelog.d/833.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..06ce086f56832952ddf685e46ade30ddecfbb936
--- /dev/null
+++ b/changes/changelog.d/833.bugfix
@@ -0,0 +1 @@
+Fixed broken translation on home and track detail page (#833)
diff --git a/changes/changelog.d/855.bugfix b/changes/changelog.d/855.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..171865ed7737266f59f5d9757b5457a0581e8276
--- /dev/null
+++ b/changes/changelog.d/855.bugfix
@@ -0,0 +1 @@
+Fixed secondary menus truncated on narrow screens (#855)
diff --git a/changes/changelog.d/857.bugfix b/changes/changelog.d/857.bugfix
new file mode 100644
index 0000000000000000000000000000000000000000..5d525e3d9fd16f0cd0fff579be8edefb181c4b66
--- /dev/null
+++ b/changes/changelog.d/857.bugfix
@@ -0,0 +1 @@
+Fix broken upload for specific files when using S3 storage (#857)
diff --git a/front/src/components/Home.vue b/front/src/components/Home.vue
index b88369d952a816c0a4b9acf7db0555b8290049e3..9393d9b7850cf4e40594bbd4fdc47774753a0f1e 100644
--- a/front/src/components/Home.vue
+++ b/front/src/components/Home.vue
@@ -68,12 +68,7 @@
         <div class="ui list">
           <div class="item">
             <i class="tag icon"></i>
-            <div
-              class="content"
-              v-translate="{url: musicbrainzUrl}"
-              translate-context="Content/Home/List item/Verb">
-                Get quality metadata about your music thanks to <a href="%{ url }" target="_blank">MusicBrainz</a>
-            </div>
+            <div class="content" v-html="musicbrainzItem"></div>
           </div>
           <div class="item">
             <i class="plus icon"></i>
@@ -147,6 +142,10 @@ export default {
       return {
         title: this.$pgettext('Head/Home/Title', "Welcome")
       }
+    },
+    musicbrainzItem () {
+      let msg = this.$pgettext('Content/Home/List item/Verb', 'Get quality metadata about your music thanks to <a href="%{ url }" target="_blank">MusicBrainz</a>')
+      return this.$gettextInterpolate(msg, {url: this.musicbrainzUrl})
     }
   }
 }
diff --git a/front/src/components/auth/SubsonicTokenForm.vue b/front/src/components/auth/SubsonicTokenForm.vue
index 0184074e294365a15e453db7184644d66d75796a..fdd9f5e107b2de27b28cb5099b56751b9be180a6 100644
--- a/front/src/components/auth/SubsonicTokenForm.vue
+++ b/front/src/components/auth/SubsonicTokenForm.vue
@@ -24,7 +24,12 @@
     </div>
     <template v-if="subsonicEnabled">
       <div v-if="token" class="field">
-        <password-input v-model="token" />
+        <password-input
+          ref="passwordInput"
+          v-model="token"
+          :key="token"
+          :copy-button="true"
+          :default-show="showToken"/>
       </div>
       <dangerous-button
         v-if="token"
@@ -69,7 +74,8 @@ export default {
       errors: [],
       success: false,
       isLoading: false,
-      successMessage: ''
+      successMessage: '',
+      showToken: false
     }
   },
   created () {
@@ -98,6 +104,7 @@ export default {
       let self = this
       let url = `users/users/${this.$store.state.auth.username}/subsonic-token/`
       return axios.post(url, {}).then(response => {
+        self.showToken = true
         self.token = response.data['subsonic_api_token']
         self.isLoading = false
         self.success = true
diff --git a/front/src/components/forms/PasswordInput.vue b/front/src/components/forms/PasswordInput.vue
index d57e3017f3010000e6e45932e25e2092982ef246..702be4f66a5642493012afd70ae3ea7d949dd134 100644
--- a/front/src/components/forms/PasswordInput.vue
+++ b/front/src/components/forms/PasswordInput.vue
@@ -10,20 +10,37 @@
     <span @click="showPassword = !showPassword" :title="labels.title" class="ui icon button">
       <i class="eye icon"></i>
     </span>
+    <button v-if="copyButton" @click.prevent="copy" class="ui icon button" :title="labels.copy">
+      <i class="copy icon"></i>
+    </button>
   </div>
 </template>
 <script>
+
+function copyStringToClipboard (str) {
+  // cf https://techoverflow.net/2018/03/30/copying-strings-to-the-clipboard-using-pure-javascript/
+  let el = document.createElement('textarea');
+  el.value = str;
+  el.setAttribute('readonly', '');
+  el.style = {position: 'absolute', left: '-9999px'};
+  document.body.appendChild(el);
+  el.select();
+  document.execCommand('copy');
+  document.body.removeChild(el);
+}
+
 export default {
-  props: ['value', 'index'],
+  props: ['value', 'index', 'defaultShow', 'copyButton'],
   data () {
     return {
-      showPassword: false
+      showPassword: this.defaultShow || false,
     }
   },
   computed: {
     labels () {
       return {
-        title: this.$pgettext('Content/Settings/Button.Tooltip/Verb', 'Show/hide password')
+        title: this.$pgettext('Content/Settings/Button.Tooltip/Verb', 'Show/hide password'),
+        copy: this.$pgettext('*/*/Button.Label/Short, Verb', 'Copy')
       }
     },
     passwordInputType () {
@@ -32,6 +49,11 @@ export default {
       }
       return 'password'
     }
+  },
+  methods: {
+    copy () {
+      copyStringToClipboard(this.value)
+    }
   }
 }
 </script>
diff --git a/front/src/components/library/TrackBase.vue b/front/src/components/library/TrackBase.vue
index a968f8b0cf626a37b596832184c6e9782f65c95c..4edd00c5de9ae92f32254636bec9bc19f4643751 100644
--- a/front/src/components/library/TrackBase.vue
+++ b/front/src/components/library/TrackBase.vue
@@ -14,11 +14,7 @@
             <i class="circular inverted music orange icon"></i>
             <div class="content">
               {{ track.title }}
-              <div class="sub header">
-                <div translate-context="Content/Track/Paragraph"
-                  v-translate="{album: track.album.title, artist: track.artist.name, albumUrl: albumUrl, artistUrl: artistUrl}"
-                >From album <a class="internal" href="%{ albumUrl }">%{ album }</a> by <a class="internal" href="%{ artistUrl }">%{ artist }</a></div>
-              </div>
+              <div class="sub header" v-html="subtitle"></div>
             </div>
           </h2>
           <div class="header-buttons">
@@ -230,6 +226,10 @@ export default {
         ")"
       )
     },
+    subtitle () {
+      let msg = this.$pgettext('Content/Track/Paragraph', 'From album <a class="internal" href="%{ albumUrl }">%{ album }</a> by <a class="internal" href="%{ artistUrl }">%{ artist }</a>')
+      return this.$gettextInterpolate(msg, {album: this.track.album.title, artist: this.track.artist.name, albumUrl: this.albumUrl, artistUrl: this.artistUrl})
+    }
   },
   watch: {
     id() {
diff --git a/front/src/style/_main.scss b/front/src/style/_main.scss
index bc527415c7658b82a418f0f678398f01f8627d3e..4c6c6d61e4893dbf2e400426afaa717af648ab54 100644
--- a/front/src/style/_main.scss
+++ b/front/src/style/_main.scss
@@ -131,6 +131,7 @@ body {
   margin-left: 0;
   margin-right: 0;
   border: none;
+  overflow-y: auto;
   .ui.item {
     border: none;
     border-bottom-style: none;