2
$\begingroup$

I want to frameserve rendered images to an external ffmpeg binary for x264rgb/lossless encoding (its much faster/smaller/efficient than PNG/FFV1). In Blender 2.7x there was OUTPUT->FRAMESERVER that could be used for that (via PPM format - like this) but that function seems to be removed in 2.8x. I also tried cmdline rendering but its not honoring the pipe (it uses the '-' char literally creating local files):

blender -b ~/Blender/test.blend -o - -x 1 -F BMP -a | ffmpeg -hide_banner -y -framerate 30 -i - -c:v libx264rgb -preset veryfast -tune animation -qp 0 -pix_fmt rgb24 blender.mkv

External renderfarm software (flamenco, crowdrender, etc) are not acceptable as they are either too bloated or not freely available (registration). Rendering all to BMP first and encoding afterwards with ffmpeg is not a goal either. Its wasting too much HDD space.

So the question is: How to frameserve to an external ffmpeg binary?

Blender 2.82 Arch Linux 5.6.15

$\endgroup$
1
  • $\begingroup$ Most of your render time isnt spend on creating a mpg, personally i prefer to work with 2 passes, one render pass, saving frames to png, next pass a vfx-file.blend to render it, (keeping me safe from blender crashes) then for quick render previews (on windows) i use virtualdub, Most often after rendering i have to do video editing so for the final product i prefer the builtin encoder as it does compress better with loosless settings. $\endgroup$
    – Peter
    Commented Jun 18, 2020 at 14:49

1 Answer 1

1
+50
$\begingroup$

https://lists.ffmpeg.org/pipermail/ffmpeg-user/2015-July/027532.html First Solution

My first solution involves taking the rendered image, saving it to a single tmp.png, then appending that to an ffmpeg mp4 at the end of every frame. First we need to bootstrap

ffmpeg -r 24 -f concat -safe 0 -i video-input-list.txt -safe 0  -pix_fmt yuv420p -crf 23 -r 24 -shortest -y video-from-frames.mp4

video-input-list.txt looks something like this:

file 'tmp.png'

next we need to copy the frame's boostrapped video into a temp video with do_ffmpeg_with_tmp.sh :

ffmpeg -i input1.mp4 -filter_complex "concat=n=2:v=1:a=0" -r 24 -f concat -safe 0 -i video-input-list.txt -safe 0  -pix_fmt yuv420p -crf 23 -r 24 -shortest -y video-from-frames.mp4```
This ends up copying the video to a temp as well since the input cannot be the same as the output in ffmpeg. 

Next we need to call all this in Blender : 
```import bpy
def render_image():
    print("after render", bpy.data.filepath)
    scene = bpy.context.scene
    scene.render.image_settings.file_format = 'PNG'
    scene.render.filepath = "tmp.png"
    bpy.ops.render.render(write_still = 1)
    os.system("do_ffmpeg_with_tmp.sh")
    # increment to next frame
    
while bpy.context.scene.frame_current <= bpy.context.scene.frame_end:
    render_image()
    bpy.context.scene.frame_set(bpy.context.scene.frame_current +1 ) 

This is all to say that doing it with a video and image buffer that ffmpeg concats at the end of every frame is doable, but image sequences are better. if you're worried about space, you could write a script similar to this one that changes the location of the storage after every 1000 frames or so, then compile the files together later through image sequences and video concatenations.

Second Solution - piping output

I looked into getting a pipe out from blender: The main issue is that blender is noisy and piping out If you want blender to pipe you need to have it return a png image and flush stdout. ffmpeg cannot read a piped in image without image2pipe https://superuser.com/questions/426193/multiple-png-images-over-a-single-pipe-to-ffmpeg-no-file-writes-to-disk.

We convert image to png data and send it accross the noisy wire.

import base64
import zlib
import struct
import sys
import os
def render_image():
    print("after render", bpy.data.filepath)
    scene = bpy.context.scene
    scene.render.image_settings.file_format = 'PNG'
    scene.render.filepath = "./tmp.png"
    bpy.ops.render.render(write_still = 0)

def png_to_base64(png_bytes):
    return 'data:image/png;base64,' + base64.b64encode(png_bytes).decode()

def img_to_png(blender_image):
    width = blender_image.size[0]
    height = blender_image.size[1]
    buf = bytearray([int(p * 255) for p in blender_image.pixels])

    # reverse the vertical line order and add null bytes at the start
    width_byte_4 = width * 4
    raw_data = b''.join(b'\x00' + buf[span:span + width_byte_4]
                        for span in range((height - 1) * width_byte_4, -1, - width_byte_4))

    def png_pack(png_tag, data):
        chunk_head = png_tag + data
        return (struct.pack("!I", len(data)) +
                chunk_head +
                struct.pack("!I", 0xFFFFFFFF & zlib.crc32(chunk_head)))

    png_bytes = b''.join([
        b'\x89PNG\r\n\x1a\n',
        png_pack(b'IHDR', struct.pack("!2I5B", width, height, 8, 6, 0, 0, 0)),
        png_pack(b'IDAT', zlib.compress(raw_data, 9)),
        png_pack(b'IEND', b'')])

    return png_bytes

render_image()
bpy.data.images['Render Result'].save_render("/tmp/tmp.png")
r2 = bpy.data.images.load("/tmp/tmp.png")
print("*#*#*#")
print(img_to_png(r2))
#file = open('/tmp/stufff.png','wb')
#file.write(img_to_png(r2))
#file.close()
sys.stdout.flush()
print("*#*#*#")

To compensate for the noise we use a runner:

import sys
from subprocess import check_output
render_progress = check_output(['blender','--background','--python','pipe_image.py','scripting_ffmpeg.blend'])
msg = render_progress.split('*#*#*#')[1]

print(msg)
sys.stdout.flush()

This is what we use to pipe png files out. However the only remaining problem to be solved is to pass the frame in through the runner and find the amount left in the render, and to put the runner in a while loop to output piped data. This should provide piped image output to the ffmpeg. Its a very complex process.Maybe even needlessly so. I explored it though.

$\endgroup$
1
  • $\begingroup$ I made a script which allows direct rendering to external ffmpeg (via temp file) from Blender. Might be useful to someone. Here you go. $\endgroup$
    – Hitokage
    Commented Apr 11, 2022 at 17:08

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .