TLDR: Ready file snippet 💾
The use case is:
There are 2 ways we can get files in a web browser to upload:
In this case I will show you how to convert files to .mp3. We will use pydub which offers many different formats so you can use python to convert between wav, m4a, mp4, oog and more. You can also to batch converting if you extend my solution. It works pretty much the same on mac and linux. The only difference is installation of ffmpeg which is solution that pydub is based on.
On Mac we install it with:
brew install ffmpeg
on Linux
sugo apt-get install ffmpeg
(on Windows just download package from their web)
While we’re at it let’s install the pydub too:
pip3 install pydub
Why not to use something that already exist somewhere?
There is a django-elastic-transcoder which is a Django package that helps you leveraging AWS transcoder so you could manipulate files. I wanted to have a simpler solution and use my server to convert files. This way the converting is being done on the server and you could make mp4 to mp3 online converter to not store people’s files. This is a also good solution to make django audio player (in case you have for example a Raspberry Pi). I do not have much traffic for now so it’s not an obstacle.
There is also a project called django-audiofile but it does not use pydub nor django-storage (which is storing files in the cloud).
The first step is to send the files to our backend. Because we will use MultiPartParser from django REST framework, we need to construct the POST (or PUT, PATCH) request with FormData:
// Front-End
onAudioUpload(audio_file){
var data = new FormData();
data.append("audio_file", audio_file, audio_file.name);
axios.put(URL, data, headers)
.then((response) => {console.log(response)})
.catch((error) => {console.log(error)});
In order to request files with django we need to have a model to store them. We use file fied here to store mp3 audio (models.FileField).
We start by making a function to separate stored files in subfolders - more on this in official docs.
# models.py
import os
def content_file_name(instance):
return os.path.join('podcasts', "{}".format(instance.file.name)
The we add the model itself:
from django.db import models
class Podcast(models.Model):
audio_file = models.FileField(
upload_to=content_file_name, null=True, blank=True)
Now let’s make a serializer with some validation to reject the formats that you don’t want to support (for example big .flac or .mov files):
from django.core.validators import FileExtensionValidator
from rest_framework import serializers
class PodcastSerializer(serializers.Serializer):
# This does not validate the content of the data itself, just the extension!
audio_file = serializers.FileField(
validators=[FileExtensionValidator(allowed_extensions=['flac', 'mov'])])
class Meta:
model = Podcast
read_only_fields = ('id')
fields = '__all__'
Now let’s have a quick look into the core function of this post so we can start explaining it:
# convert_audio_file.py
from django.core.files import File
from pydub import AudioSegment
from pathlib import Path
import os
def convert_to_mp3(audio_file, target_filetype='mp3', content_type='audio/mpeg',bitrate="192k"):
file_path = audio_file.temporary_file_path()
original_extension = file_path.split('.')[-1]
mp3_converted_file = AudioSegment.from_file(file_path, original_extension)
new_path = file_path[:-3] + target_filetype
mp3_converted_file.export(new_path, format=target_filetype, bitrate="192k")
converted_audiofile = File(
file=open(new_path, 'rb'),
name=Path(new_path)
)
converted_audiofile.name = Path(new_path).name
converted_audiofile.content_type = content_type
converted_audiofile.size = os.path.getsize(new_path)
return converted_audiofile
The audio_file we pass as a parameter is a TemporaryUploadedFile that is why we can call a method .temporaryfilepath() on it. It gives us path like:
/var/folders/7d/x0glvhhj4mq3wtfm15qd_84w0000gn/T/our_audiofile.m4a
We use this path to get the extension for AudioSegment to open the file. The we open the file using AudioSegment and passing created data.
file_path = audio_file.temporary_file_path()
original_extension = file_path.split('.')[-1]
mp3_converted_file = AudioSegment.from_file(file_path, original_extension)
Now we create a new path for a file that we want to export using default extension (.mp3) and we export the file with default settings.
new_path = file_path[:-3] + target_filetype
mp3_converted_file.export(new_path, format=target_filetype, bitrate=bitrate)
File is being exported and saved to a new path. Now we have to use the django File instance to read the file that we have just exported:
converted_audiofile = File(
file=open(new_path, 'rb'),
name=Path(new_path)
)
Now we add extra information to the object. Not passing a valid object could cause errors like: The submitted data was not a file. Check the encoding type on the form. or The file was empty. Now we just need to return the File()
converted_audiofile.name = Path(new_path).name
converted_audiofile.content_type = content_type
converted_audiofile.size = os.path.getsize(new_path)
return converted_audiofile
Now knowing that we need a TemporaryUploadedFile to pass it to our custom function let’s dive in into API.
Let’s start with default class API view!
# views.py
from .converter import convert_audio_file
from rest_framework.generics import (UpdateAPIView)
from rest_framework.response import Response
from rest_framework import status
from tracks.models import Podcast
from rest_framework import permissions
class PodcastAPIView(UpdateAPIView):
serializer_class = PodcastSerializer
permission_classes = (permissions.IsAuthenticated,)
parser_classes = [MultiPartParser, ]
queryset = Podcast.objects.all()
lookup_field = 'id'
Now the function PUT. When we get the _tempaudiofile_ it is already written on our server as TemporaryUploadedFile. Now we know we need to pass it through our custom function. This is all PUT function with commented steps:
def put(self, request, *args, **kwargs):
file_obj = request.data
temp_audio_file = request.FILES.get('file')
# Using our custom convert_to_mp3 function to obtain converted file
converted_temp_audio_file = convert_to_mp3(temp_audio_file)
# Adding this file to the serializer
file_obj['file'] = converted_temp_audio_file
serializer = PodcastSerializer(data=file_obj)
if not serializer.is_valid():
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# Actual place where we save it to the MEDIA_ROOT (cloud or other)
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
Now we just need to create URL to call this function.
from django.urls import path,
from podcast.views import PodcastAPIView
urlpatterns = [
path("",
PodcastAPIView.as_view(), name="upload-podcast"),
]
Done! You can test it out with POSTMAN which is an API testing tool. In case you don’t know that well the awesomeness of this tool you can check my beginner or intermediate tutorials.
Now you can play with this solution, create html audio player or start your django audio streaming app. You could for example make a text reader that would create movies from text. Pass text to mp3 in python and then convert it to mp4 clip. The possibilities are endless and programing is like magic.
Saving to hard drive with django and converting are resource and time consuming operations. In order to provide your users with good User Experience you should implement it once you start getting visitors. You could use django celery
We created a file converted that is living on your server. We used pydub and Django classes to format, read, open and save the obtained file. In case you know a better solution comment or drop me PM!
Cheers ! 🙋♂️
PS: If you want to convert mp3 to text using python or Django then remember that there is a Speech recognition web API that browsers provide and you don’t need to use the server for that!
Tell me about your insights and leave a comment - you are most welcome to see more posts of this type just go to home page