Joseph Na
Joseph Na

Reputation: 11

How do I convert my blob audio into a wav file for my Python Flask application to use?

I'm writing a Flask application and am using Javascript MediaRecorder to record the user's audio. I managed to get the Blob populated and sent back to my Flask endpoint, but I can't seem to convert it into a working audio file.

Javascript

const blob = new Blob(chunks, { type: "audio/wav" });
        chunks = [];
        const audioURL = window.URL.createObjectURL(blob);
        audio.src = audioURL;
        console.log("recorder stopped");

        const formData = new FormData();
        formData.append("audioData", blob);
        console.log(blob);

        fetch("/upload-audio", {
          method: "POST",
          body: formData,
        })
          .then((response) => {
            // Handle the response from the server
          })
          .catch((error) => {
            // Handle any errors that occurred
          });

Python - Flask

@app.route('/upload-audio', methods=['POST'])
def upload_audio():
    audio_data = request.files['audioData'].read()
    print("audio data", audio_data)
    audio_data_save_attempt = request.files['audioData']
    audio_data_save_attempt.save("filenametest.wav")

    return "Trying to get audio data saved to disk"

Python returns the following:

print("audio data", audio_data) returns: audio data b'\x1aE\xdf\xa3\x9fB\x86\x81\x01B\xf7\x81\x01B\xf2\x81\x04B\xf3\x81\x08B\x82\x84webmB\x87\x81\x04B\x85\x81\x02\x18S\x80g\x01\xff\xff\xff\xff\xff\xff\xff\x15I\xa9f\x99*\xd7\xb1\x83\x0fB@M\x80\x86ChromeWA\x86Chrome\x1` etc..

audio_data_save_attempt creates a file but the file is not openable.


My gut feeling is that audio data does have the frames information but I can't get it to convert properly. I tried using io.BytesIO and the python wave module and neither seem to have worked for me, although I am very new to working with audio and could have made a mistake.

    # Create a wave file from the audio data
    with io.BytesIO(audio_data) as audio_stream:
        with wave.open(audio_stream, mode="rb") as audio_file:
            # Create a new wave file in write mode
            with wave.open("audio.wav", mode="wb") as output_file:
                output_file.setparams(audio_file.getparams())
                output_file.writeframes(audio_file.readframes(audio_file.getnframes()))

Upvotes: 1

Views: 1238

Answers (1)

Detlef
Detlef

Reputation: 8592

It could have several causes why the audio file cannot be played.

On the one hand, the pointer, when you read a file completely, points to the end of the file. Before you can read the data a second time to save it, you have to put the pointer back to the beginning of the file using file.seek(0).

On the other hand, it seems to me that your data is not in the wave format. Depending on the platform and browser, the audio data are exported in different formats. The Chrome browser tends to the 'WebA' format.

Below is an example of recording audio with the browser. The data is sent to the server and then saved. The user receives an overview of the files recorded and can play them.
In this example, several, but possibly not all file formats are supported.

from flask import (
    Flask,
    Response,
    abort,
    current_app,
    render_template,
    redirect,
    request,
    stream_with_context,
    url_for

)
from collections import namedtuple
from glob import glob
from mimetypes import add_type, guess_extension, guess_type
from werkzeug.utils import secure_filename
import os

add_type('audio/aac', '.m4a', strict=True)

Record = namedtuple('Record', ('filename', 'created_at'))

app = Flask(__name__)
app.config['UPLOAD_FOLDER'] = 'uploads'
try:
    os.makedirs(os.path.join(
        app.instance_path,
        app.config.get('UPLOAD_FOLDER', 'uploads')
    ), exist_ok=True)
except:
    pass

@app.route('/')
def audio_index():
    patterns = [
        '*.m4a', # MacOS & Safari
        '*.wav', # Windows & ?
        '*.weba' # MacOS & Chrome
    ]
    path = os.path.join(
        current_app.instance_path,
        current_app.config.get('UPLOAD_FOLDER', 'uploads')
    )
    records = [
        Record(fn[len(path)+1:], os.path.getctime(fn)) \
         for ptrn in patterns for fn in glob(os.path.join(path, ptrn))
    ]
    return render_template('index.html', records=records)

@app.route('/audio-upload', methods=['POST'])
def audio_upload():
    if 'audio_file' in request.files:
        file = request.files['audio_file']
        extname = guess_extension(file.mimetype)
        if not extname:
            abort(400)

        # Check for allowed file extensions.

        i = 1
        while True:
            dst = os.path.join(
                current_app.instance_path,
                current_app.config.get('UPLOAD_FOLDER', 'uploads'),
                secure_filename(f'audio_record_{i}{extname}'))
            if not os.path.exists(dst): break
            i += 1

        file.save(dst)

    return redirect(url_for('audio_index'))

@app.route('/audio/stream/<path:fn>', methods=['GET'])
def audio_download(fn):
    @stream_with_context
    def generator(src):
        CHUNK_SIZE = 1024
        with open(src, 'rb') as fp:
            while True:
                data = fp.read(CHUNK_SIZE)
                if not data: break
                yield data

    src = os.path.join(
        current_app.instance_path,
        current_app.config.get('UPLOAD_FOLDER', 'uploads'),
        fn
    )

    if not os.path.exists(src):
        return abort(404)

    mime,_ = guess_type(src)
    return Response(generator(src), mimetype=mime)
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Audio Index</title>
  </head>
  <body>
      <div class="rec-container">
        <div class="rec-column rec-column-1">
          <button class="rec-btn" id="toggle-rec-btn">Record</button>
        </div>
        <div class="rec-column rec-column-2">
          {% for record in records|sort(attribute='created_at', reverse=True) -%}
          <div class="rec-item">
            <div class="content">
              <span class="rec-title">{{ record.filename }}</span>
              <audio
                controls
                src="{{ url_for('audio_download', fn=record.filename) }}"
              ></audio>
            </div>
          </div>
          {% endfor -%}
        </div>
    </div>

    <script type="text/javascript">
    (function() {
      const uploadURL = "{{ url_for('audio_upload') }}";
      const startButton = document.getElementById("toggle-rec-btn");
      startButton.addEventListener("click", function() {
        if (!navigator.mediaDevices) {
          console.error("getUserMedia not supported.")
          return;
        }

        const constraints = { audio: true };
        navigator.mediaDevices.getUserMedia(constraints)
        .then(function(stream) {
            let chunks = []
            let recorder = new MediaRecorder(stream);
            recorder.ondataavailable = event => {
                chunks.push(event.data);
            };
            recorder.onstop = event => {
              console.log("Recording stopped.")
              let blob = new Blob(chunks, { type: recorder.mimeType }); 
              chunks = [];
              startButton.disabled = false;

              let formData = new FormData();
              formData.append('audio_file', blob);

              fetch(uploadURL, {
                method: "POST",
                cache: "no-cache",
                body: formData
              }).then(resp => {
                if (resp.status === 200) {
                  window.location.reload(true);
                } else {
                  console.error("Error:", resp)
                }
              }).catch(err => {
                console.error(err);
              });
            };
            recorder.onstart = event => {
              console.log("Recording started.");
              startButton.disabled = true;
              setTimeout(function() { recorder.stop(); }, 10000);
            };
            recorder.start();
        })
        .catch(function(err) {
            console.error(err);
        });
      });
    })();
    </script>
  </body>
</html>

Upvotes: 0

Related Questions