Python, Docker, and FFmpeg to convert H.264 to MP4

Posted on 2018-04-09

H264 is a codec. MP4 is a file container, which can contain H264. If you want to display H264 video on a web browser, it needs muxing into MP4 or another file container format.

FFmpeg is the leading tool available when muxing is required. The only downside is that it requires a C toolchain, which in my experience, are troublesome to build cross-platform. Luckily, you can grab a docker container with it in.

Below is a small python webapp. It serves HTML, which contains a video tag requesting video.mp4. Once requested, a python generator is used to stream the results of a very crude subprocess which cats a video file into the ffmpeg docker container for muxing and returns the results in a pipe. Each ffmpeg argument is commented to describe why it is required.

from flask import Flask, Response, render_template
import subprocess

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')

cmd = 'docker run -i --rm jrottenberg/ffmpeg:3.4-alpine' \
        ' -c:v h264' \ # input is h264
        ' -i -' \ # input from stdin
        ' -movflags frag_keyframe+empty_moov' \ # allows mp4 to be streamable
        ' -vf scale=640:-1' \ # scales output to 640px in height, and a width to keep the current ratio.
        ' -f mp4 -' # output in mp4 to stdout
p = subprocess.Popen('cat video.h264 | ' + cmd, shell=True, stdin=subprocess.PIPE, stdout=subprocess.PIPE)

@app.route('/video.mp4')
def video_stream():
    def generate():
        for data in iter(p.stdout.readline, b''):
            yield data
    return Response(generate(), mimetype='video/mp4')

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

The HTML video tag is this:

<video width="640" height="480" autoplay>
  <source src="/video.mp4" type="video/mp4">
</video>

One of the key observations here is that MP4 is not naturally suited to streaming media. By default, an MP4 file contains a moov atom, which details information how to play the file. This is normally present at the end of the file. For streaming, this has to moved, so you don’t have to download the entire stream before playing it.

MP4 is generally not used by companies noted for streaming either. MPEG-DASH is the technology used by YouTube and Netflix, but unfortunately, it is not natively supported by browsers.

My motivation for writing this code was to stream video from my Raspberry Pi, and display in a web browser. Having finished this little investigation, and learnt a lot about video muxing, the main conclusion is that a Raspberry Pi Zero doesn’t have the processing power to mux at an acceptable frame-rate. On a laptop, it is super-fast.

I’ll probably revisit this in the future.