See #195: set bitrate, duration and size when importing file
This commit is contained in:
parent
7425a8ea4d
commit
1bc4ceab9e
|
@ -54,6 +54,10 @@ class TrackFileFactory(factory.django.DjangoModelFactory):
|
|||
audio_file = factory.django.FileField(
|
||||
from_path=os.path.join(SAMPLES_PATH, 'test.ogg'))
|
||||
|
||||
bitrate = None
|
||||
size = None
|
||||
duration = None
|
||||
|
||||
class Meta:
|
||||
model = 'music.TrackFile'
|
||||
|
||||
|
|
|
@ -479,6 +479,24 @@ class TrackFile(models.Model):
|
|||
return
|
||||
return os.path.splitext(self.audio_file.name)[-1].replace('.', '', 1)
|
||||
|
||||
def get_file_size(self):
|
||||
if self.audio_file:
|
||||
return self.audio_file.size
|
||||
|
||||
if self.source.startswith('file://'):
|
||||
return os.path.getsize(self.source.replace('file://', '', 1))
|
||||
|
||||
if self.library_track and self.library_track.audio_file:
|
||||
return self.library_track.audio_file.size
|
||||
|
||||
def get_audio_file(self):
|
||||
if self.audio_file:
|
||||
return self.audio_file.open()
|
||||
if self.source.startswith('file://'):
|
||||
return open(self.source.replace('file://', '', 1), 'rb')
|
||||
if self.library_track and self.library_track.audio_file:
|
||||
return self.library_track.audio_file.open()
|
||||
|
||||
def save(self, **kwargs):
|
||||
if not self.mimetype and self.audio_file:
|
||||
self.mimetype = utils.guess_mimetype(self.audio_file)
|
||||
|
|
|
@ -27,6 +27,7 @@ class SimpleArtistSerializer(serializers.ModelSerializer):
|
|||
|
||||
class ArtistSerializer(serializers.ModelSerializer):
|
||||
tags = TagSerializer(many=True, read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.Artist
|
||||
fields = ('id', 'mbid', 'name', 'tags', 'creation_date')
|
||||
|
@ -40,11 +41,21 @@ class TrackFileSerializer(serializers.ModelSerializer):
|
|||
fields = (
|
||||
'id',
|
||||
'path',
|
||||
'duration',
|
||||
'source',
|
||||
'filename',
|
||||
'mimetype',
|
||||
'track')
|
||||
'track',
|
||||
'duration',
|
||||
'mimetype',
|
||||
'bitrate',
|
||||
'size',
|
||||
)
|
||||
read_only_fields = [
|
||||
'duration',
|
||||
'mimetype',
|
||||
'bitrate',
|
||||
'size',
|
||||
]
|
||||
|
||||
def get_path(self, o):
|
||||
url = o.path
|
||||
|
|
|
@ -134,6 +134,19 @@ def _do_import(import_job, replace=False, use_acoustid=True):
|
|||
# in place import, we set mimetype from extension
|
||||
path, ext = os.path.splitext(import_job.source)
|
||||
track_file.mimetype = music_utils.get_type_from_ext(ext)
|
||||
audio_file = track_file.get_audio_file()
|
||||
if audio_file:
|
||||
with audio_file as f:
|
||||
audio_data = music_utils.get_audio_file_data(f)
|
||||
track_file.duration = int(audio_data['length'])
|
||||
track_file.bitrate = audio_data['bitrate']
|
||||
track_file.size = track_file.get_file_size()
|
||||
else:
|
||||
lt = track_file.library_track
|
||||
if lt:
|
||||
track_file.duration = lt.get_metadata('length')
|
||||
track_file.size = lt.get_metadata('size')
|
||||
track_file.bitrate = lt.get_metadata('bitrate')
|
||||
track_file.save()
|
||||
import_job.status = 'finished'
|
||||
import_job.track_file = track_file
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import magic
|
||||
import mimetypes
|
||||
import mutagen
|
||||
import re
|
||||
|
||||
from django.db.models import Q
|
||||
|
@ -82,3 +83,12 @@ def get_type_from_ext(extension):
|
|||
# we remove leading dot
|
||||
extension = extension[1:]
|
||||
return EXTENSION_TO_MIMETYPE.get(extension)
|
||||
|
||||
|
||||
def get_audio_file_data(f):
|
||||
data = mutagen.File(f)
|
||||
d = {}
|
||||
d['bitrate'] = data.info.bitrate
|
||||
d['length'] = data.info.length
|
||||
|
||||
return d
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import json
|
||||
import os
|
||||
import pytest
|
||||
|
||||
from django.urls import reverse
|
||||
|
@ -7,6 +8,8 @@ from funkwhale_api.federation import actors
|
|||
from funkwhale_api.federation import serializers as federation_serializers
|
||||
from funkwhale_api.music import tasks
|
||||
|
||||
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def test_create_import_can_bind_to_request(
|
||||
artists, albums, mocker, factories, superuser_api_client):
|
||||
|
@ -40,11 +43,20 @@ def test_create_import_can_bind_to_request(
|
|||
assert batch.import_request == request
|
||||
|
||||
|
||||
def test_import_job_from_federation_no_musicbrainz(factories):
|
||||
def test_import_job_from_federation_no_musicbrainz(factories, mocker):
|
||||
mocker.patch(
|
||||
'funkwhale_api.music.utils.get_audio_file_data',
|
||||
return_value={'bitrate': 24, 'length': 666})
|
||||
mocker.patch(
|
||||
'funkwhale_api.music.models.TrackFile.get_file_size',
|
||||
return_value=42)
|
||||
lt = factories['federation.LibraryTrack'](
|
||||
artist_name='Hello',
|
||||
album_title='World',
|
||||
title='Ping',
|
||||
metadata__length=42,
|
||||
metadata__bitrate=43,
|
||||
metadata__size=44,
|
||||
)
|
||||
job = factories['music.ImportJob'](
|
||||
federation=True,
|
||||
|
@ -56,6 +68,9 @@ def test_import_job_from_federation_no_musicbrainz(factories):
|
|||
|
||||
tf = job.track_file
|
||||
assert tf.mimetype == lt.audio_mimetype
|
||||
assert tf.duration == 42
|
||||
assert tf.bitrate == 43
|
||||
assert tf.size == 44
|
||||
assert tf.library_track == job.library_track
|
||||
assert tf.track.title == 'Ping'
|
||||
assert tf.track.artist.name == 'Hello'
|
||||
|
@ -234,13 +249,13 @@ def test_import_batch_notifies_followers(
|
|||
|
||||
|
||||
def test__do_import_in_place_mbid(factories, tmpfile):
|
||||
path = '/test.ogg'
|
||||
path = os.path.join(DATA_DIR, 'test.ogg')
|
||||
job = factories['music.ImportJob'](
|
||||
in_place=True, source='file:///test.ogg')
|
||||
in_place=True, source='file://{}'.format(path))
|
||||
|
||||
track = factories['music.Track'](mbid=job.mbid)
|
||||
tf = tasks._do_import(job, use_acoustid=False)
|
||||
|
||||
assert bool(tf.audio_file) is False
|
||||
assert tf.source == 'file:///test.ogg'
|
||||
assert tf.source == 'file://{}'.format(path)
|
||||
assert tf.mimetype == 'audio/ogg'
|
||||
|
|
|
@ -85,3 +85,28 @@ def test_track_file_file_name(factories):
|
|||
tf = factories['music.TrackFile'](audio_file__from_path=path)
|
||||
|
||||
assert tf.filename == tf.track.full_name + '.mp3'
|
||||
|
||||
|
||||
def test_track_get_file_size(factories):
|
||||
name = 'test.mp3'
|
||||
path = os.path.join(DATA_DIR, name)
|
||||
tf = factories['music.TrackFile'](audio_file__from_path=path)
|
||||
|
||||
assert tf.get_file_size() == 297745
|
||||
|
||||
|
||||
def test_track_get_file_size_federation(factories):
|
||||
tf = factories['music.TrackFile'](
|
||||
federation=True,
|
||||
library_track__with_audio_file=True)
|
||||
|
||||
assert tf.get_file_size() == tf.library_track.audio_file.size
|
||||
|
||||
|
||||
def test_track_get_file_size_in_place(factories):
|
||||
name = 'test.mp3'
|
||||
path = os.path.join(DATA_DIR, name)
|
||||
tf = factories['music.TrackFile'](
|
||||
in_place=True, source='file://{}'.format(path))
|
||||
|
||||
assert tf.get_file_size() == 297745
|
||||
|
|
|
@ -62,6 +62,9 @@ def test_import_job_can_run_with_file_and_acoustid(
|
|||
'score': 0.860825}],
|
||||
'status': 'ok'
|
||||
}
|
||||
mocker.patch(
|
||||
'funkwhale_api.music.utils.get_audio_file_data',
|
||||
return_value={'bitrate': 42, 'length': 43})
|
||||
mocker.patch(
|
||||
'funkwhale_api.musicbrainz.api.artists.get',
|
||||
return_value=artists['get']['adhesive_wombat'])
|
||||
|
@ -82,7 +85,9 @@ def test_import_job_can_run_with_file_and_acoustid(
|
|||
|
||||
with open(path, 'rb') as f:
|
||||
assert track_file.audio_file.read() == f.read()
|
||||
assert track_file.duration == 268
|
||||
assert track_file.bitrate == 42
|
||||
assert track_file.duration == 43
|
||||
assert track_file.size == os.path.getsize(path)
|
||||
# audio file is deleted from import job once persisted to audio file
|
||||
assert not job.audio_file
|
||||
assert job.status == 'finished'
|
||||
|
|
|
@ -1,5 +1,10 @@
|
|||
import os
|
||||
import pytest
|
||||
|
||||
from funkwhale_api.music import utils
|
||||
|
||||
DATA_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
|
||||
|
||||
def test_guess_mimetype_try_using_extension(factories, mocker):
|
||||
mocker.patch(
|
||||
|
@ -17,3 +22,16 @@ def test_guess_mimetype_try_using_extension_if_fail(factories, mocker):
|
|||
audio_file__filename='test.mp3')
|
||||
|
||||
assert utils.guess_mimetype(f.audio_file) == 'audio/mpeg'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('name, expected', [
|
||||
('sample.flac', {'bitrate': 1608000, 'length': 0.001}),
|
||||
('test.mp3', {'bitrate': 8000, 'length': 267.70285714285717}),
|
||||
('test.ogg', {'bitrate': 128000, 'length': 229.18304166666667}),
|
||||
])
|
||||
def test_get_audio_file_data(name, expected):
|
||||
path = os.path.join(DATA_DIR, name)
|
||||
with open(path, 'rb') as f:
|
||||
result = utils.get_audio_file_data(f)
|
||||
|
||||
assert result == expected
|
||||
|
|
Loading…
Reference in New Issue