From 3a520f2159a6956cd7e3ebedc193a964b732cff9 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Sat, 16 May 2020 15:35:56 +0200 Subject: [PATCH] check and set sound file duration at upload time --- emissions/forms.py | 9 ++++++ .../management/commands/create-sound-files.py | 19 +++-------- emissions/models.py | 8 ++++- emissions/utils.py | 32 +++++++++++++++++++ emissions/views.py | 7 ++++ 5 files changed, 60 insertions(+), 15 deletions(-) diff --git a/emissions/forms.py b/emissions/forms.py index e5f90f6..6544e90 100644 --- a/emissions/forms.py +++ b/emissions/forms.py @@ -6,6 +6,7 @@ import uuid from django import forms from django.forms import fields +from django.forms import ValidationError from django.core.files.storage import DefaultStorage from django.core.urlresolvers import reverse @@ -19,6 +20,7 @@ from taggit.forms import TagWidget from .models import (Emission, Episode, Diffusion, Schedule, SoundFile, NewsItem, Absence, PlaylistElement) +from .utils import get_duration from .widgets import DateTimeWidget, DateWidget @@ -240,6 +242,13 @@ class SoundFileForm(forms.ModelForm): 'file': JqueryFileUploadInput(), } + def clean(self): + super().clean() + if self.cleaned_data.get('file'): + duration = get_duration(self.cleaned_data['file'].file.name) + if not duration: + raise ValidationError(_('Invalid file, could not get duration.')) + class SoundFileEditForm(forms.ModelForm): class Meta: diff --git a/emissions/management/commands/create-sound-files.py b/emissions/management/commands/create-sound-files.py index c489268..e68724e 100644 --- a/emissions/management/commands/create-sound-files.py +++ b/emissions/management/commands/create-sound-files.py @@ -82,12 +82,8 @@ class Command(BaseCommand): if not soundfile.podcastable: # get duration using initial file if not soundfile.duration: - cmd = ['soxi', '-D', soundfile.file.path] - try: - soundfile.duration = int(float(subprocess.check_output(cmd))) - except (ValueError, subprocess.CalledProcessError): - pass - else: + soundfile.compute_duration() + if soundfile.duration: soundfile.save() continue for format in formats.split(','): @@ -98,14 +94,9 @@ class Command(BaseCommand): if created or reset_metadata: self.set_metadata(soundfile, format) if (force or not soundfile.duration): - for extension in ('ogg', 'mp3'): - soundfile_name = soundfile.get_format_path(extension) - if os.path.exists(soundfile_name): - cmd = ['soxi', '-D', soundfile_name] - soundfile.duration = int(float(subprocess.check_output(cmd))) - soundfile.save() - break - + soundfile.compute_duration() + if soundfile.duration: + soundfile.save() def create(self, soundfile, format): file_path = soundfile.get_format_path(format) diff --git a/emissions/models.py b/emissions/models.py index cf4f5c4..627bf44 100644 --- a/emissions/models.py +++ b/emissions/models.py @@ -17,7 +17,7 @@ from django.dispatch.dispatcher import receiver from ckeditor.fields import RichTextField from taggit.managers import TaggableManager -from .utils import maybe_resize +from .utils import maybe_resize, get_duration LICENSES = ( @@ -496,6 +496,12 @@ class SoundFile(models.Model): creation_timestamp = models.DateTimeField(auto_now_add=True, null=True) last_update_timestamp = models.DateTimeField(auto_now=True, null=True) + def compute_duration(self): + for path in (self.get_format_path('ogg'), self.get_format_path('mp3'), self.file.path): + self.duration = get_duration(path) + if self.duration: + return + def get_format_filename(self, format): return '%s_%05d__%s.%s' % ( self.episode.slug, diff --git a/emissions/utils.py b/emissions/utils.py index 5a81f47..e6dc1d9 100644 --- a/emissions/utils.py +++ b/emissions/utils.py @@ -1,7 +1,39 @@ from datetime import datetime, timedelta, time import os +import subprocess from PIL import Image + +def get_duration(filename): + p = subprocess.Popen(['mediainfo', '--Inform=Audio;%Duration%', filename], + close_fds=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + try: + return int(stdout) / 1000 + except ValueError: + pass + + # fallback on soxi + p = subprocess.Popen(['soxi', filename], close_fds=True, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + for line in stdout.splitlines(): + line = force_text(line) + if not line.startswith('Duration'): + continue + try: + hours, minutes, seconds = re.findall(r'(\d\d):(\d\d):(\d\d)', line)[0] + except IndexError: + continue + return int(hours) * 3600 + int(minutes) * 60 + int(seconds) + return None + + def maybe_resize(image_path): if not os.path.exists(image_path): return diff --git a/emissions/views.py b/emissions/views.py index 36f5806..f21c486 100644 --- a/emissions/views.py +++ b/emissions/views.py @@ -370,6 +370,13 @@ class EpisodeAddSoundFileView(CreateView): raise PermissionDenied() return super(EpisodeAddSoundFileView, self).get_form(*args, **kwargs) + def form_valid(self, form): + response = super().form_valid(form) + if not form.instance.duration: + form.instance.compute_duration() + form.instance.save() + return response + def get_success_url(self): messages.success(self.request, SUCCESS_MESSAGE) return self.object.episode.get_absolute_url() -- 2.39.2