0

I am trying to record audio via webbrowser for 6 seconds and send it to flask. The audio file then gets converted into a Spectogramm. Afterwards it should be transformed into a Numpy Array to be processed for a model.

I do not know if the problem is my upload function or something with librosa

I tried several different versions and options but cannot manage.

This is JS code:

function lDUp(){
    var formData = new FormData();

    const startButton = document.getElementById("startLungBtn");
    startButton.addEventListener("click", function() {
      if (!navigator.mediaDevices) {
        console.error("getUserMedia not supported.")
        return;
      }
  
    const constraints = { audio: true };
    navigator.mediaDevices.getUserMedia(constraints)
    .then(function(stream) {
        var chunks = [];
        const recorder = new MediaRecorder(stream);
        recorder.ondataavailable = event => {
            // Collect all the chunks of the recording in an array.
            chunks.push(event.data);
        };

        const audioContext = new AudioContext();
        const audioStreamSource = audioContext.createMediaStreamSource(stream);
        


        recorder.onstop = event => {
            console.log("Recording stopped.")
            // Create a blob with all the chunks of the recording.
            let blob = new Blob(chunks, { type: "audio/wav" }); 

            // let blob = new Blob(chunks, { type: recorder.mimeType }); 
            chunks = [];
            startButton.disabled = false;
  
            // Create form data that contain the recording.
            formData.append("audio_file", blob, "audio_file.wav");

            $.ajax({
                
                type: "POST",
                url: "http://127.0.0.1:5000/LungUploader",
                processData: false,
                contentType: false,
                cache: false,
                data: formData,
                success: function(response) {
                console.log(response);
                console.log("This is class: ", response)
                },
                error: function(err) {
                    console.log(err);
                    }
            });   
            

          };
        recorder.onstart = event => {
            console.log("Recording started.");
            startButton.disabled = true;
            // Stop recording when the time is up.
            setTimeout(function() { recorder.stop(); }, 6000);
            
                        
        };
        recorder.start();
    })
    .catch(function(err) {
          console.error(err);
      });
    });
        
    
}

This is code for flask:

@app.route('/LUploader', methods=["GET", 'POST'])
def lUploader():

    if request.method == "POST":

        f = request.files["audio_file.wav"]
        with open('audio.wav', 'wb') as audio:
            f.save(audio)


        print("Checkpoint1")
        y, sr = lb.load(f)

        print("Checkpoint")
        #Plot signal in
        plt.figure(figsize=(10,3))
        src_ft = lb.stft(y)
        src_db = lb.amplitude_to_db(abs(src_ft))
        specshow(src_db, sr=sr, x_axis='time', y_axis='hz')  
        plt.ylim(0, 5000)        
        plt.close()
}

Thanks for help in advance :)

Schaedel420
  • 175
  • 11

1 Answers1

2

You're trying to get the file by its filename. In order to receive the file within the form, the name of the input field is required. In your case this is "audio_file".

f = request.files['audio_file']

You will then get a file-like object of type werkzeug.datastructures.FileStorage, which can be saved with the save function.

f.save(f.filename)

You should distinguish between the name of the form field and the filename of the file sent. The name of the input field is required to request the file from the form (example: "audio_file"). The file name is the name under which the file is saved and loaded on the server (example: "audio_file.wav").

The code should look something like this.

from flask import (
    Flask, 
    request, 
    # ...
)
from werkzeug.utils import secure_filename
import os

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

# ...

@app.post('/upload')
def upload():
    # Get the FileStorage object with the name of the form's input field.
    file = request.files.get('audio_file')
    if file and file.filename != '': 
        dest = os.path.join(
            app.config['UPLOAD_FOLDER'], 
            secure_filename(file.filename)
        )
        # Save the file on the server.
        f.save(dest)
        
        # Load the file by filename using librosa.
        y,sr = librosa.load(dest)
        
        # ...

Detailed instructions for uploading files can be found here.


I wrote an example application for uploading, playing audio files and displaying the spectogram here. Please note that the audio format used depends on the platform and browser.

Flask
'''
pip install flask librosa numpy scikit-image
'''

from flask import (
    Flask,
    Response,
    abort,
    current_app,
    render_template,
    redirect,
    request,
    send_from_directory,
    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 librosa
import numpy
import os
import skimage.io


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

Record = namedtuple('Record', ('filename', 'specimage', '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')
    ))
except:
    pass

@app.route('/')
def audio_index():
    patterns = [
        '*.m4a',
        '*.wav',
        '*.weba'
    ]
    path = os.path.join(
        current_app.instance_path,
        current_app.config.get('UPLOAD_FOLDER', 'uploads')
    )
    records = [
        Record(fn[len(path)+1:], f'{fn[len(path)+1:]}.png', 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)

        # WARNING: 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)

        img = os.path.join(
            current_app.instance_path,
            current_app.config.get('UPLOAD_FOLDER', 'uploads'),
            secure_filename(f'{os.path.basename(dst)}.png')
        )
        hop_length = 512
        n_mels = 128
        time_steps = 384
        y, sr = librosa.load(dst)
        spectrogram_image(y, sr=sr, out=img, hop_length=hop_length, n_mels=n_mels)

    return redirect(url_for('audio_index'))

def spectrogram_image(y, sr, out, hop_length, n_mels):
    mels = librosa.feature.melspectrogram(
        y=y, sr=sr, n_mels=n_mels,
        n_fft=hop_length*2, hop_length=hop_length
    )
    mels = numpy.log(mels + 1e-9)
    img = scale_minmax(mels, 0, 255).astype(numpy.uint8)
    img = numpy.flip(img, axis=0)
    img = 255-img
    skimage.io.imsave(out, img)

def scale_minmax(X, min=0.0, max=1.0):
    X_std = (X - X.min()) / (X.max() - X.min())
    X_scaled = X_std * (max - min) + min
    return X_scaled

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

@app.route('/audio/spec/<path:fn>')
def audio_spec(fn):
    path = os.path.join(
        current_app.instance_path,
        current_app.config.get('UPLOAD_FOLDER', 'uploads')
    )
    return send_from_directory(path, fn)
HTML (templates/index.html)
<!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 class="spec">
              <img src="{{ url_for('audio_spec', fn=record.specimage) }}" />
            </div>
          </div>
          {% endfor -%}
        </div>
    </div>

    <script type="text/javascript">
    (function() {
      const uploadURL = {{ url_for('audio_upload') | tojson }};
      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>
Detlef
  • 6,137
  • 2
  • 6
  • 24
  • thanks for your answer, i inserted f.save(f.filename) and f.seek(0) sadly i get the same error message do you maybe have a working solution for me? :) – Schaedel420 Jul 15 '22 at 13:45
  • The error that has now occurred results from the fact that librosa expects a file name when loading and not a file-like object. So if you pass the filename to load you don't need to seek and the error you're getting now shouldn't happen again. I don't know how to create the spectrogram. Maybe [this](https://stackoverflow.com/questions/56719138/how-can-i-save-a-librosa-spectrogram-plot-as-a-specific-sized-image/57204349#57204349) post will help you. – Detlef Jul 15 '22 at 15:08
  • Thanks for your reply! When I put in the Filename „audio_file“ I get a key error :/ – Schaedel420 Jul 15 '22 at 16:01
  • In my answer I tried to explain again what the KeyError results from and what is required to save and load the file sent. Additionally, I've added an example to the answer. I wish you every success in implementing your project and I assume that you will find my answer helpful and accept it. – Detlef Jul 16 '22 at 12:54
  • Thank you so much for your answer it really helped! You are a legend! – Schaedel420 Jul 19 '22 at 19:26
  • Is there any way to just load file into librosa format without saving it? – Mr Patience Jan 29 '23 at 18:56
  • 1
    @MrPatience I think [this](https://stackoverflow.com/a/74899880/13708022) answer should help you. – Detlef Jan 29 '23 at 19:35