Skip to content

Commit

Permalink
Process and play audio files on frontend
Browse files Browse the repository at this point in the history
  • Loading branch information
psiemens committed Aug 22, 2018
1 parent 2bc2110 commit 799684a
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 118 deletions.
7 changes: 5 additions & 2 deletions dispatch/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -1061,9 +1062,11 @@ class Meta:
'author',
'image',
'image_id',
'duration',
'published_at',
'explicit',
'file',
'file_str'
'file_str',
'file_url',
'duration',
'type',
)
13 changes: 0 additions & 13 deletions dispatch/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.' })
26 changes: 0 additions & 26 deletions dispatch/modules/podcasts/models.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import uuid
import StringIO

import mutagen

from django.conf import settings

from django.db.models import (
Expand Down Expand Up @@ -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)
Original file line number Diff line number Diff line change
Expand Up @@ -16,80 +16,112 @@ const EXPLICIT_OPTIONS = [
['clean', 'Clean'],
]

export default function PodcastEpisodeForm(props) {
return (
<form>
<FormInput
label='Title'
padded={false}
error={props.errors.title}>
<TextInput
placeholder='Title'
value={props.listItem.title || ''}
fill={true}
onChange={e => props.update('title', e.target.value)} />
</FormInput>

<FormInput
label='Description'
padded={false}
error={props.errors.description}>
<TextAreaInput
placeholder='Description'
value={props.listItem.description || ''}
fill={true}
onChange={e => props.update('description', e.target.value)} />
</FormInput>

<FormInput
label='Author'
padded={false}
error={props.errors.author}>
<TextInput
placeholder='Author'
value={props.listItem.author || ''}
fill={true}
onChange={e => props.update('author', e.target.value)} />
</FormInput>

<FormInput
label='Published At'
padded={false}
error={props.errors.published_at}>
<DateTimeInput
value={props.listItem.published_at}
onChange={dt => props.update('published_at', dt)} />
</FormInput>

<FormInput
label='Image'
padded={false}
error={props.errors.image}>
<ImageInput
selected={props.listItem.image}
onChange={imageId => props.update('image', imageId)} />
</FormInput>

<FormInput
label='Explicit'
padded={false}>
<SelectInput
options={EXPLICIT_OPTIONS}
selected={props.listItem.explicit}
onChange={e => props.update('explicit', e.target.value)} />
</FormInput>

<FormInput
label='Audio File'
padded={false}
error={props.errors.file}>
<FileInput
placeholder={props.listItem.file_str || 'Choose a file...'}
value={props.listItem.file || ''}
fill={true}
onChange={file => props.update('file', file)} />
</FormInput>

</form>
)
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 (
<form>
<FormInput
label='Title'
padded={false}
error={this.props.errors.title}>
<TextInput
placeholder='Title'
value={this.props.listItem.title || ''}
fill={true}
onChange={e => this.props.update('title', e.target.value)} />
</FormInput>

<FormInput
label='Description'
padded={false}
error={this.props.errors.description}>
<TextAreaInput
placeholder='Description'
value={this.props.listItem.description || ''}
fill={true}
onChange={e => this.props.update('description', e.target.value)} />
</FormInput>

<FormInput
label='Author'
padded={false}
error={this.props.errors.author}>
<TextInput
placeholder='Author'
value={this.props.listItem.author || ''}
fill={true}
onChange={e => this.props.update('author', e.target.value)} />
</FormInput>

<FormInput
label='Published At'
padded={false}
error={this.props.errors.published_at}>
<DateTimeInput
value={this.props.listItem.published_at}
onChange={dt => this.props.update('published_at', dt)} />
</FormInput>

<FormInput
label='Image'
padded={false}
error={this.props.errors.image}>
<ImageInput
selected={this.props.listItem.image}
onChange={imageId => this.props.update('image', imageId)} />
</FormInput>

<FormInput
label='Explicit'
padded={false}>
<SelectInput
options={EXPLICIT_OPTIONS}
selected={this.props.listItem.explicit}
onChange={e => this.props.update('explicit', e.target.value)} />
</FormInput>

<FormInput
label='Audio File'
padded={false}
error={this.props.errors.file}>
<FileInput
placeholder={this.props.listItem.file_str || 'Choose a file...'}
value={this.props.listItem.file || ''}
fill={true}
onChange={file => this.updateFile(file)} />
<audio ref='audioPlayer' src={this.state.audioFile} controls />
</FormInput>

</form>
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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 : {}} />
</div>
</div>
Expand Down
9 changes: 8 additions & 1 deletion dispatch/static/manager/src/styles/components/inputs.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 799684a

Please sign in to comment.