diff --git a/api/funkwhale_api/common/search.py b/api/funkwhale_api/common/search.py index 5fc6f6804..70aecd632 100644 --- a/api/funkwhale_api/common/search.py +++ b/api/funkwhale_api/common/search.py @@ -3,7 +3,7 @@ import re from django.db.models import Q -QUERY_REGEX = re.compile('(((?P\w+):)?(?P"[^"]+"|[\S]+))') +QUERY_REGEX = re.compile(r'(((?P\w+):)?(?P"[^"]+"|[\S]+))') def parse_query(query): diff --git a/api/funkwhale_api/music/models.py b/api/funkwhale_api/music/models.py index 43a95047f..a49b55236 100644 --- a/api/funkwhale_api/music/models.py +++ b/api/funkwhale_api/music/models.py @@ -209,12 +209,7 @@ class AlbumQuerySet(models.QuerySet): def with_prefetched_tracks_and_playable_uploads(self, actor): tracks = Track.objects.with_playable_uploads(actor) - return self.prefetch_related( - models.Prefetch( - 'tracks', - queryset=tracks, - ) - ) + return self.prefetch_related(models.Prefetch("tracks", queryset=tracks)) class Album(APIModelMixin): @@ -413,13 +408,9 @@ class TrackQuerySet(models.QuerySet): return self.exclude(uploads__in=files).distinct() def with_playable_uploads(self, actor): - uploads = Upload.objects.playable_by(actor).select_related('track') + uploads = Upload.objects.playable_by(actor).select_related("track") return self.prefetch_related( - models.Prefetch( - 'uploads', - queryset=uploads, - to_attr='playable_uploads' - ) + models.Prefetch("uploads", queryset=uploads, to_attr="playable_uploads") ) @@ -763,11 +754,13 @@ class Upload(models.Model): # we create the version with an empty file, then # we'll write to it f = ContentFile(b"") - version = self.versions.create(mimetype=mimetype, bitrate=self.bitrate or 128000, size=0) + version = self.versions.create( + mimetype=mimetype, bitrate=self.bitrate or 128000, size=0 + ) # we keep the same name, but we update the extension - new_name = os.path.splitext( - os.path.basename(self.audio_file.name) - )[0] + '.{}'.format(format) + new_name = os.path.splitext(os.path.basename(self.audio_file.name))[ + 0 + ] + ".{}".format(format) version.audio_file.save(new_name, f) utils.transcode_file( input=self.audio_file, @@ -776,18 +769,18 @@ class Upload(models.Model): output_format=utils.MIMETYPE_TO_EXTENSION[mimetype], ) version.size = version.audio_file.size - version.save(update_fields=['size']) + version.save(update_fields=["size"]) return version -MIMETYPE_CHOICES = [ - (mt, ext) for ext, mt in utils.AUDIO_EXTENSIONS_AND_MIMETYPE -] +MIMETYPE_CHOICES = [(mt, ext) for ext, mt in utils.AUDIO_EXTENSIONS_AND_MIMETYPE] class UploadVersion(models.Model): - upload = models.ForeignKey(Upload, related_name='versions', on_delete=models.CASCADE) + upload = models.ForeignKey( + Upload, related_name="versions", on_delete=models.CASCADE + ) mimetype = models.CharField(max_length=50, choices=MIMETYPE_CHOICES) creation_date = models.DateTimeField(default=timezone.now) accessed_date = models.DateTimeField(null=True, blank=True) @@ -796,7 +789,7 @@ class UploadVersion(models.Model): size = models.IntegerField() class Meta: - unique_together = ('upload', 'mimetype', 'bitrate') + unique_together = ("upload", "mimetype", "bitrate") @property def filename(self): diff --git a/api/funkwhale_api/subsonic/views.py b/api/funkwhale_api/subsonic/views.py index 343d87881..8aa9c9dbe 100644 --- a/api/funkwhale_api/subsonic/views.py +++ b/api/funkwhale_api/subsonic/views.py @@ -200,8 +200,8 @@ class SubsonicViewSet(viewsets.GenericViewSet): if not upload: return response.Response(status=404) - format = data.get('format', 'raw') - if format == 'raw': + format = data.get("format", "raw") + if format == "raw": format = None return music_views.handle_serve(upload=upload, user=request.user, format=format) diff --git a/api/tests/subsonic/test_views.py b/api/tests/subsonic/test_views.py index d2269ba5c..c0ae84073 100644 --- a/api/tests/subsonic/test_views.py +++ b/api/tests/subsonic/test_views.py @@ -206,14 +206,18 @@ def test_stream(f, db, logged_in_api_client, factories, mocker, queryset_equal_q playable_by.assert_called_once_with(music_models.Track.objects.all(), None) -@pytest.mark.parametrize("format,expected", [("mp3", 'mp3'), ("raw", None)]) +@pytest.mark.parametrize("format,expected", [("mp3", "mp3"), ("raw", None)]) def test_stream_format(format, expected, logged_in_api_client, factories, mocker): url = reverse("api:subsonic-stream") - mocked_serve = mocker.patch.object(music_views, "handle_serve", return_value=Response()) + mocked_serve = mocker.patch.object( + music_views, "handle_serve", return_value=Response() + ) upload = factories["music.Upload"](playable=True) response = logged_in_api_client.get(url, {"id": upload.track.pk, "format": format}) - mocked_serve.assert_called_once_with(upload=upload, user=logged_in_api_client.user, format=expected) + mocked_serve.assert_called_once_with( + upload=upload, user=logged_in_api_client.user, format=expected + ) assert response.status_code == 200 diff --git a/api/tests/test_import_audio_file.py b/api/tests/test_import_audio_file.py index ce6aebbc3..c6b8aea60 100644 --- a/api/tests/test_import_audio_file.py +++ b/api/tests/test_import_audio_file.py @@ -134,7 +134,7 @@ def test_import_files_skip_if_path_already_imported(factories, mocker): ) call_command( - "import_files", str(library.uuid), path, async=False, interactive=False + "import_files", str(library.uuid), path, async_=False, interactive=False ) assert library.uploads.count() == 1 diff --git a/changes/changelog.d/272.feature b/changes/changelog.d/272.feature new file mode 100644 index 000000000..8a1419370 --- /dev/null +++ b/changes/changelog.d/272.feature @@ -0,0 +1,13 @@ +Audio transcoding is back! (#272) + + +Audio transcoding is back! +-------------------------- + +After removal of our first, buggy transcoding implementation, we're proud to announce +that this feature is back. It is enabled by default, and can be configured/disabled +in your instance settings! + +This feature works in the browser, with federated/non-federated tracks and using Subsonic clients. +Transcoded tracks are generated on the fly, and cached for a configurable amount of time, +to reduce the load on the server.