See #272: linting and changelog
This commit is contained in:
parent
ac4bba816d
commit
bcd22eb38c
|
@ -3,7 +3,7 @@ import re
|
||||||
from django.db.models import Q
|
from django.db.models import Q
|
||||||
|
|
||||||
|
|
||||||
QUERY_REGEX = re.compile('(((?P<key>\w+):)?(?P<value>"[^"]+"|[\S]+))')
|
QUERY_REGEX = re.compile(r'(((?P<key>\w+):)?(?P<value>"[^"]+"|[\S]+))')
|
||||||
|
|
||||||
|
|
||||||
def parse_query(query):
|
def parse_query(query):
|
||||||
|
|
|
@ -209,12 +209,7 @@ class AlbumQuerySet(models.QuerySet):
|
||||||
|
|
||||||
def with_prefetched_tracks_and_playable_uploads(self, actor):
|
def with_prefetched_tracks_and_playable_uploads(self, actor):
|
||||||
tracks = Track.objects.with_playable_uploads(actor)
|
tracks = Track.objects.with_playable_uploads(actor)
|
||||||
return self.prefetch_related(
|
return self.prefetch_related(models.Prefetch("tracks", queryset=tracks))
|
||||||
models.Prefetch(
|
|
||||||
'tracks',
|
|
||||||
queryset=tracks,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Album(APIModelMixin):
|
class Album(APIModelMixin):
|
||||||
|
@ -413,13 +408,9 @@ class TrackQuerySet(models.QuerySet):
|
||||||
return self.exclude(uploads__in=files).distinct()
|
return self.exclude(uploads__in=files).distinct()
|
||||||
|
|
||||||
def with_playable_uploads(self, actor):
|
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(
|
return self.prefetch_related(
|
||||||
models.Prefetch(
|
models.Prefetch("uploads", queryset=uploads, to_attr="playable_uploads")
|
||||||
'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 create the version with an empty file, then
|
||||||
# we'll write to it
|
# we'll write to it
|
||||||
f = ContentFile(b"")
|
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
|
# we keep the same name, but we update the extension
|
||||||
new_name = os.path.splitext(
|
new_name = os.path.splitext(os.path.basename(self.audio_file.name))[
|
||||||
os.path.basename(self.audio_file.name)
|
0
|
||||||
)[0] + '.{}'.format(format)
|
] + ".{}".format(format)
|
||||||
version.audio_file.save(new_name, f)
|
version.audio_file.save(new_name, f)
|
||||||
utils.transcode_file(
|
utils.transcode_file(
|
||||||
input=self.audio_file,
|
input=self.audio_file,
|
||||||
|
@ -776,18 +769,18 @@ class Upload(models.Model):
|
||||||
output_format=utils.MIMETYPE_TO_EXTENSION[mimetype],
|
output_format=utils.MIMETYPE_TO_EXTENSION[mimetype],
|
||||||
)
|
)
|
||||||
version.size = version.audio_file.size
|
version.size = version.audio_file.size
|
||||||
version.save(update_fields=['size'])
|
version.save(update_fields=["size"])
|
||||||
|
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
|
||||||
MIMETYPE_CHOICES = [
|
MIMETYPE_CHOICES = [(mt, ext) for ext, mt in utils.AUDIO_EXTENSIONS_AND_MIMETYPE]
|
||||||
(mt, ext) for ext, mt in utils.AUDIO_EXTENSIONS_AND_MIMETYPE
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
class UploadVersion(models.Model):
|
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)
|
mimetype = models.CharField(max_length=50, choices=MIMETYPE_CHOICES)
|
||||||
creation_date = models.DateTimeField(default=timezone.now)
|
creation_date = models.DateTimeField(default=timezone.now)
|
||||||
accessed_date = models.DateTimeField(null=True, blank=True)
|
accessed_date = models.DateTimeField(null=True, blank=True)
|
||||||
|
@ -796,7 +789,7 @@ class UploadVersion(models.Model):
|
||||||
size = models.IntegerField()
|
size = models.IntegerField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
unique_together = ('upload', 'mimetype', 'bitrate')
|
unique_together = ("upload", "mimetype", "bitrate")
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def filename(self):
|
def filename(self):
|
||||||
|
|
|
@ -200,8 +200,8 @@ class SubsonicViewSet(viewsets.GenericViewSet):
|
||||||
if not upload:
|
if not upload:
|
||||||
return response.Response(status=404)
|
return response.Response(status=404)
|
||||||
|
|
||||||
format = data.get('format', 'raw')
|
format = data.get("format", "raw")
|
||||||
if format == 'raw':
|
if format == "raw":
|
||||||
format = None
|
format = None
|
||||||
return music_views.handle_serve(upload=upload, user=request.user, format=format)
|
return music_views.handle_serve(upload=upload, user=request.user, format=format)
|
||||||
|
|
||||||
|
|
|
@ -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)
|
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):
|
def test_stream_format(format, expected, logged_in_api_client, factories, mocker):
|
||||||
url = reverse("api:subsonic-stream")
|
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)
|
upload = factories["music.Upload"](playable=True)
|
||||||
response = logged_in_api_client.get(url, {"id": upload.track.pk, "format": format})
|
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
|
assert response.status_code == 200
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -134,7 +134,7 @@ def test_import_files_skip_if_path_already_imported(factories, mocker):
|
||||||
)
|
)
|
||||||
|
|
||||||
call_command(
|
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
|
assert library.uploads.count() == 1
|
||||||
|
|
||||||
|
|
|
@ -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.
|
Loading…
Reference in New Issue