Search code examples
pythonffmpegpipe

Getting bytes output with ffmpeg and subprocess


I'm downloading some images from a website and making a video out of it. I don't want to save the video to a file and then read it so I can send it through a POST request. I'm looking to unify this process and Download > Make video > Get output bytes > POST.

def convert_images_to_video():
    #output_video = "output.mp4"

    data = get_radar_images("a")
    if not data:
        return False
    
    ffmpeg_cmd = [
        "ffmpeg",
        "-framerate",
        "2",  # Input frame rate (adjust as needed)
        "-f",
        "image2pipe",
        "-vcodec",
        "png",
        "-i",
        "-",  # Input from pipe
        "-vf",  # Video filter
        "fps=30",  # Output frame rate (adjust as needed)
        "-c:v",
        "libx264",  # Video codec (h.264)
        "-vf",  # Additional video filters
        "pad=ceil(iw/2)*2:ceil(ih/2)*2",  # Padding and resolution adjustment
        "-profile:v",
        "high",  # H.264 profile
        "-level",
        "3.0",  # H.264 level
        "-pix_fmt",
        "yuv420p",  # Pixel format
        "-brand",
        "mp42",
        "-movflags",
        "frag_keyframe",  # Branding
        "-f",
        "mp4",
        "-",
    ]


    ffmpeg_process = subprocess.Popen(
        ffmpeg_cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    radar_images = data.get("list")[::-1]

    for image in radar_images:
        img_url = f"https://mycoolweb.com/coolimages/{image}"
        content = requests.get(img_url).content
        ffmpeg_process.stdin.write(content)

    ffmpeg_process.stdin.close()
    ffmpeg_process.terminate()

    ffmpeg_process.wait()
    video_bytes = ffmpeg_process.stdout.read()

    print(video_bytes)

By doing this and executing python app.py makes the process hang in there without any output at all. I tried adding universal_newlines=True to the subprocess.Popen but that return a TypeError: write() argument must be str, not bytes exception.

Since I'm working with images it does not work to image.decode("utf-8"). I been trying for a while and reading ffmpeg docs on pipes but cant get it to work.


Solution

  • This can be solved by using Pillow. The library will handle the image processing details for you.

    from PIL import Image
    import io
    from subprocess import PIPE
    import subprocess
    
    ...
    
    def convert_images_to_video():
    
    ...
    
        ffmpeg_pipe = subprocess.Popen(ffmpeg_cmd, stdin=PIPE, stdout=PIPE)
    
        for img_url in urls:
            content = requests.get(img_url).content
            b = io.BytesIO(content)
            img = Image.open(b)
            img.save(ffmpeg_pipe.stdin, 'PNG')
            b.close()
    
        ffmpeg_pipe.stdin.close()
        video_bytes = ffmpeg_pipe.stdout.read()
        ffmpeg_pipe.terminate()
        ffmpeg_pipe.wait()
    
        # Save the video locally to verify
        with open("output.mp4", 'wb') as f:
            f.write(video_bytes)
    
        print(video_bytes)
    

    Also note that you need to read the bytes from the subprocess pipe after closing stdin but before calling terminate on the pipe.