Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When streaming response in Flask file unplayable

I currently have a function that runs ffmpeg enconder on a flv stream from youtube.

def console(cmd, add_newlines=False):
    p = Popen(cmd, shell=True, stdout=PIPE)
    while True:
        data = p.stdout.readline()
        if add_newlines:
            data += str('\n')
        yield data

        p.poll()
        if isinstance(p.returncode, int):
            if p.returncode > 0:
                # return code was non zero, an error?
                print 'error:', p.returncode
            break

This works fine when I run the ffmpeg command and have it output to a file. The file is playable.

mp3 = console('ffmpeg -i "%s" -acodec libmp3lame -ar 44100 -f mp3 test.mp3' % video_url, add_newlines=True)

But when I have ffmpeg output to stdout via - instead of test.mp3, and stream that response. The file streams fine, is the correct size. But does not play correctly. Sounds chopy, and when I check the properties of the file it doesn't show the data of it as it does with test.mp3

@app.route('/test.mp3')
def generate_large_mp3(path):
    mp3 = console('ffmpeg -i "%s" -acodec libmp3lame -ar 44100 -f mp3 -' % video_url, add_newlines=True)
    return Response(stream_with_context(mp3), mimetype="audio/mpeg3",
                   headers={"Content-Disposition":
                                "attachment;filename=test.mp3"})

Is there something I am missing?

like image 766
nadermx Avatar asked Jan 21 '26 09:01

nadermx


2 Answers

You have a couple of issues. It seems you copied this console function from somewhere. The main problem is that this function is designed to work on text output. The command that you are running with ffmpeg will dump the binary raw mp3 data to stdout, so reading that output as if it was a text file with readlines() will not work. The concept of lines does not exist at all in the mp3 binary stream.

I have adapted your console() function to work on binary streams. Here is my version:

def console(cmd):
    p = Popen(cmd, shell=True, stdout=PIPE)
    while True:
        data = p.stdout.read()
        yield data

        p.poll()
        if isinstance(p.returncode, int):
            if p.returncode > 0:
                # return code was non zero, an error?
                print 'error:', p.returncode
            break

Note that I have removed all the references to newline handling, and also replaced readline() with read().

With this version I get the streaming to work. I hope this helps!

Edit: You could force the response to go out in chunks of a specified size by calling read(N) with N being the chunk size. For a given audio bitrate, you can calculate the size in bytes for a chunk of audio with a duration of say, 5 seconds, and then stream 5 second chunks.

like image 101
Miguel Avatar answered Jan 23 '26 23:01

Miguel


To stream mp3 content generated by a subprocess using flask:

#!/usr/bin/env python
import os
from functools import partial
from subprocess import Popen, PIPE

from flask import Flask, Response  # $ pip install flask

mp3file = 'test.mp3'
app = Flask(__name__)


@app.route('/')
def index():
    return """<!doctype html>
<title>Play {mp3file}</title>
<audio controls autoplay >
    <source src="{mp3file}" type="audio/mp3" >
    Your browser does not support this audio format.
</audio>""".format(mp3file=mp3file)


@app.route('/' + mp3file)
def stream():
    process = Popen(['cat', mp3file], stdout=PIPE, bufsize=-1)
    read_chunk = partial(os.read, process.stdout.fileno(), 1024)
    return Response(iter(read_chunk, b''), mimetype='audio/mp3')

if __name__ == "__main__":
    app.run()

Replace ['cat', mp3file] with your ffmpeg command that writes mp3 content to its stdout.

like image 33
jfs Avatar answered Jan 23 '26 22:01

jfs



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!