Converting audio files with Django and Python

Intro

TLDR: Ready file snippet 💾

The use case is:

  • I want to upload audio file via JavaScript form using Django REST and using Python pydub package convert it to audio file of different format before sending it to AWS S3 bucket.

There are 2 ways we can get files in a web browser to upload:

  • Use browser to record audio from microphone. I use mic-recorder-to-mp3d with javascript to record audio mp3.
  • Upload file to server with javascript file uploader. I use vue file uploader.

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 this solution? 🤷‍♂️

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).

Process of uploading - the overview 🗺️

  • User uploads or records a file in a browser
  • API endpoint receives the file (this is a way to upload file without django form)
  • Django saves it on a local storage (your server) as TemporaryUploadedFile
  • pydub opens this file and exports it to a new format
  • we read the exported file and save it to models
  • Django sends the file to media files location (e.g. S3 bucket)
  • We request files in a browser and django delivers a media URL that points to where the file way store

Let’s start ! 🚗

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.

Async file saving to local disk

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

Sum up

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

Oh, what is that? No newsletter?

Freeing you from mailing list is my first step of helping the world to be a better place.