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 f68bcf135..9adc1b9bf 100644 --- a/api/funkwhale_api/music/management/commands/fix_track_files.py +++ b/api/funkwhale_api/music/management/commands/fix_track_files.py @@ -2,6 +2,7 @@ import cacheops import os from django.db import transaction +from django.db.models import Q from django.conf import settings from django.core.management.base import BaseCommand, CommandError @@ -24,6 +25,8 @@ class Command(BaseCommand): if options['dry_run']: self.stdout.write('Dry-run on, will not commit anything') self.fix_mimetypes(**options) + self.fix_file_data(**options) + self.fix_file_size(**options) cacheops.invalidate_model(models.TrackFile) @transaction.atomic @@ -43,3 +46,60 @@ class Command(BaseCommand): if not dry_run: self.stdout.write('[mimetypes] commiting...') qs.update(mimetype=mimetype) + + def fix_file_data(self, dry_run, **kwargs): + self.stdout.write('Fixing missing bitrate or length...') + matching = models.TrackFile.objects.filter( + Q(bitrate__isnull=True) | Q(duration__isnull=True)) + total = matching.count() + self.stdout.write( + '[bitrate/length] {} entries found with missing values'.format( + total)) + if dry_run: + return + for i, tf in enumerate(matching.only('audio_file')): + self.stdout.write( + '[bitrate/length] {}/{} fixing file #{}'.format( + i+1, total, tf.pk + )) + + try: + audio_file = tf.get_audio_file() + if audio_file: + with audio_file as f: + data = utils.get_audio_file_data(audio_file) + tf.bitrate = data['bitrate'] + tf.duration = data['length'] + tf.save(update_fields=['duration', 'bitrate']) + else: + self.stderr.write('[bitrate/length] no file found') + except Exception as e: + self.stderr.write( + '[bitrate/length] error with file #{}: {}'.format( + tf.pk, str(e) + ) + ) + + def fix_file_size(self, dry_run, **kwargs): + self.stdout.write('Fixing missing size...') + matching = models.TrackFile.objects.filter(size__isnull=True) + total = matching.count() + self.stdout.write( + '[size] {} entries found with missing values'.format(total)) + if dry_run: + return + for i, tf in enumerate(matching.only('size')): + self.stdout.write( + '[size] {}/{} fixing file #{}'.format( + i+1, total, tf.pk + )) + + try: + tf.size = tf.get_file_size() + tf.save(update_fields=['size']) + except Exception as e: + self.stderr.write( + '[size] error with file #{}: {}'.format( + tf.pk, str(e) + ) + ) diff --git a/api/tests/music/test_commands.py b/api/tests/music/test_commands.py new file mode 100644 index 000000000..ff3343aa5 --- /dev/null +++ b/api/tests/music/test_commands.py @@ -0,0 +1,45 @@ +from funkwhale_api.music.management.commands import fix_track_files + + +def test_fix_track_files_bitrate_length(factories, mocker): + tf1 = factories['music.TrackFile'](bitrate=1, duration=2) + tf2 = factories['music.TrackFile'](bitrate=None, duration=None) + c = fix_track_files.Command() + + mocker.patch( + 'funkwhale_api.music.utils.get_audio_file_data', + return_value={'bitrate': 42, 'length': 43}) + + c.fix_file_data(dry_run=False) + + tf1.refresh_from_db() + tf2.refresh_from_db() + + # not updated + assert tf1.bitrate == 1 + assert tf1.duration == 2 + + # updated + assert tf2.bitrate == 42 + assert tf2.duration == 43 + + +def test_fix_track_files_size(factories, mocker): + tf1 = factories['music.TrackFile'](size=1) + tf2 = factories['music.TrackFile'](size=None) + c = fix_track_files.Command() + + mocker.patch( + 'funkwhale_api.music.models.TrackFile.get_file_size', + return_value=2) + + c.fix_file_size(dry_run=False) + + tf1.refresh_from_db() + tf2.refresh_from_db() + + # not updated + assert tf1.size == 1 + + # updated + assert tf2.size == 2