From 799684ac11484cf3d999c90285e18aff17202bf5 Mon Sep 17 00:00:00 2001 From: Peter Siemens Date: Wed, 22 Aug 2018 15:46:19 -0700 Subject: [PATCH] Process and play audio files on frontend --- dispatch/api/serializers.py | 7 +- dispatch/api/views.py | 13 -- dispatch/modules/podcasts/models.py | 26 --- .../EpisodeEditor/PodcastEpisodeForm.js | 184 ++++++++++-------- .../PodcastEditor/EpisodeEditor/index.js | 5 + .../manager/src/styles/components/inputs.scss | 9 +- 6 files changed, 126 insertions(+), 118 deletions(-) diff --git a/dispatch/api/serializers.py b/dispatch/api/serializers.py index c3c7e3a7c..ef5f8258c 100644 --- a/dispatch/api/serializers.py +++ b/dispatch/api/serializers.py @@ -1050,6 +1050,7 @@ class PodcastEpisodeSerializer(DispatchModelSerializer): file = serializers.FileField(write_only=True, validators=[FilenameValidator]) file_str = serializers.FileField(source='file', read_only=True, use_url=False) + file_url = serializers.FileField(source='file', read_only=True, use_url=True) class Meta: model = PodcastEpisode @@ -1061,9 +1062,11 @@ class Meta: 'author', 'image', 'image_id', - 'duration', 'published_at', 'explicit', 'file', - 'file_str' + 'file_str', + 'file_url', + 'duration', + 'type', ) diff --git a/dispatch/api/views.py b/dispatch/api/views.py index 6806e11ba..1b9d9b6be 100644 --- a/dispatch/api/views.py +++ b/dispatch/api/views.py @@ -639,16 +639,3 @@ def get_queryset(self): queryset = queryset.filter(podcast_id=podcast) return queryset - - def perform_create(self, serializer): - try: - super(PodcastEpisodeViewSet, self).perform_create(serializer) - except PodcastEpisode.InvalidAudioFile: - raise ValidationError({ 'file': 'File must be a valid audio file.' }) - - - def perform_update(self, serializer): - try: - super(PodcastEpisodeViewSet, self).perform_create(serializer) - except PodcastEpisode.InvalidAudioFile: - raise ValidationError({ 'file': 'File must be a valid audio file.' }) diff --git a/dispatch/modules/podcasts/models.py b/dispatch/modules/podcasts/models.py index 903e1811c..fa9db1952 100644 --- a/dispatch/modules/podcasts/models.py +++ b/dispatch/modules/podcasts/models.py @@ -1,8 +1,6 @@ import uuid import StringIO -import mutagen - from django.conf import settings from django.db.models import ( @@ -73,29 +71,5 @@ class PodcastEpisode(Model): file = FileField(upload_to='podcasts/') - class InvalidAudioFile(Exception): - pass - def get_absolute_url(self): return self.file.url - - # Override - def save(self, **kwargs): - """Custom save method to extract information from audio file.""" - - audio = mutagen.File(fileobj=self.file.file) - - if audio is None: - raise PodcastEpisode.InvalidAudioFile() - - mimes = audio.mime - - type = mimes[0] - duration = int(audio.info.length) - - self.duration = duration - self.type = type - - self.file.file.close() - - super(PodcastEpisode, self).save(**kwargs) diff --git a/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/PodcastEpisodeForm.js b/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/PodcastEpisodeForm.js index 8d4d073b4..c15eb2a6b 100644 --- a/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/PodcastEpisodeForm.js +++ b/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/PodcastEpisodeForm.js @@ -16,80 +16,112 @@ const EXPLICIT_OPTIONS = [ ['clean', 'Clean'], ] -export default function PodcastEpisodeForm(props) { - return ( -
- - props.update('title', e.target.value)} /> - - - - props.update('description', e.target.value)} /> - - - - props.update('author', e.target.value)} /> - - - - props.update('published_at', dt)} /> - - - - props.update('image', imageId)} /> - - - - props.update('explicit', e.target.value)} /> - - - - props.update('file', file)} /> - - -
- ) +export default class PodcastEpisodeForm extends React.Component { + + constructor(props) { + super(props) + + this.state = { + audioFile: this.props.listItem.file_url + } + } + + componentDidMount() { + this.refs.audioPlayer.ondurationchange = () => { + const duration = Math.round(this.refs.audioPlayer.duration) + this.props.update('duration', duration) + } + } + + updateFile(file) { + const fileUrl = URL.createObjectURL(file) + + this.setState({ audioFile: fileUrl }) + + const type = file.type + + this.props.bulkUpdate({ + file: file, + type: type + }) + } + + render() { + return ( +
+ + this.props.update('title', e.target.value)} /> + + + + this.props.update('description', e.target.value)} /> + + + + this.props.update('author', e.target.value)} /> + + + + this.props.update('published_at', dt)} /> + + + + this.props.update('image', imageId)} /> + + + + this.props.update('explicit', e.target.value)} /> + + + + this.updateFile(file)} /> + + +
+ ) + } } diff --git a/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/index.js b/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/index.js index fe658f45e..fd1448b94 100644 --- a/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/index.js +++ b/dispatch/static/manager/src/js/components/PodcastEditor/EpisodeEditor/index.js @@ -117,6 +117,10 @@ class PodcastEpisodeEditorComponent extends React.Component { this.props.setListItem(R.assoc(field, value, this.getListItem())) } + handleBulkUpdate(data) { + this.props.setListItem(R.merge(this.getListItem(), data)) + } + render() { const podcast = R.path(['podcasts', this.props.podcastId], this.props.entities) const listItem = this.getListItem() @@ -158,6 +162,7 @@ class PodcastEpisodeEditorComponent extends React.Component { listItem={listItem} errors={this.props.listItem ? this.props.listItem.errors : {}} update={(field, value) => this.handleUpdate(field, value)} + bulkUpdate={(data) => this.handleBulkUpdate(data)} settings={this.props.settings ? this.props.settings : {}} /> diff --git a/dispatch/static/manager/src/styles/components/inputs.scss b/dispatch/static/manager/src/styles/components/inputs.scss index f64c3386b..c6b8600a4 100644 --- a/dispatch/static/manager/src/styles/components/inputs.scss +++ b/dispatch/static/manager/src/styles/components/inputs.scss @@ -7,10 +7,17 @@ .c-form-input label.bp3-label > .bp3-button, .c-form-input label.bp3-label > .bp3-switch, .c-form-input label.bp3-label > .c-input--item-select, -.c-form-input label.bp3-label > .c-input--file { +.c-form-input label.bp3-label > .c-input--file, +.c-form-input label.bp3-label > audio { + // Structure margin-top: 10px; } +.c-form-input label.bp3-label > audio { + // Structure + width: 100%; +} + // Component: MultiSelectInput .c-input--item-select { // Text