使用 python 渲染和保存视频文件

Render and save a video file with python

我想使用 python 生成一些移动的 2d 几何对象(圆形、正方形...)的视频。

我怀疑解决方案可能是使用像 pygame 这样的库,piglet 进行渲染,然后使用其他库保存屏幕截图并附加到视频文件。

重要的是,我需要在不打开 screen/window 的情况下执行此操作;基本上 pyagme 或 piglet 应该在某个缓冲区而不是屏幕上写图像。

我使用 matplotlib 取得了一些成功,但我觉得它不是这个项目最合适的工具,特别是如果我想让图形更漂亮并且想要运行速度更快的东西。

编辑: 我最终使用了像 ffmpeg

这样的命令行工具

如果您在网上进行搜索并最终来到这里,希望有一个班轮来做这件事,恐怕您不会在这里找到这个。但是,这可能会为您指明正确的方向。

在 pyglet 和大多数 Python 的 GL 库中,您不会找到预先创建的 API 来生成视频流,但是它们确实提供了一种获取原始视频流的方法每个单独帧的像素数据。

我会坚持使用 Pyglet,因为它是迄今为止我尝试过的最快的库,而且它是我的信仰。

pyglet.image.get_buffer_manager().get_color_buffer().save('screenshot.png')

此代码不用于保存特定图像,而是用于抓取整个 window 并将其保存到名为 screenshot.png.

的文件中

使用它来创建一个索引系列的屏幕截图:

frame = 0
def on_draw():
    ...
    pyglet.image.get_buffer_manager().get_color_buffer().save(str(frame)+'.png')

完成后运行您的应用程序只需使用任何视频编码工具(mencoder、ffmpeg、windows movie maker 或 w/e)并将所有静止图像组合成视频文件。

ffmpeg -f image2 -framerate 25 -pattern_type sequence -start_number 0 -r 3 -i %04d.png -s 720x480 test.avi

瞧,你应该有一个 test.avi 你的剧照的视频文件。
现在有更好的选择,例如将视频传输到 ffmpeg 每个单独的帧以节省处理时间,但要做到这一点,您需要立即与 ffmpeg 交互,并且有用于此的库,例如 https://github.com/kanryu/pipeffmpeg

Zengl 和 ffmpeg 是一个强大的组合。此示例以 193fps 呈现并创建一个名为 test.mp4 的视频和一个名为 hello.png.

的图像
import math
from tqdm import tqdm
import zengl
from PIL import Image
import numpy as np
import ffmpeg

ctx = zengl.context(zengl.loader(headless=True))

size = (1280, 720)
image = ctx.image(size, 'rgba8unorm', samples=1)
depth = ctx.image(size, 'depth24plus', samples=1)
process2 = ffmpeg \
            .input('pipe:', format='rawvideo', pix_fmt='rgba', s='{}x{}'.format(size[0], size[1]), r=60) \
            .vflip() \
            .output("test.mp4", pix_fmt='rgb24', loglevel="quiet", r=60) \
            .overwrite_output() \
            .run_async(pipe_stdin=True)

def grass_mesh():
    a = np.linspace(0.0, 1.0, 8)
    b = np.square(a)
    c = np.sin(b * (np.pi - 1.0) + 1.0)
    verts = []
    for i in range(7):
        verts.append((-c[i] * 0.03, b[i] * 0.2, a[i]))
        verts.append((c[i] * 0.03, b[i] * 0.2, a[i]))
    verts.append((0.0, 0.2, 1.0))
    verts = ','.join('vec3(%.8f, %.8f, %.8f)' % x for x in verts)
    return 'vec3 grass[15] = vec3[](%s);' % verts

uniform_buffer = ctx.buffer(size=80)

N = 1000

instances = np.array([
    np.random.uniform(-1.0, 1.0, N),
    np.random.uniform(-1.0, 1.0, N),
    np.random.uniform(-np.pi, np.pi, N),
    np.random.uniform(0.0, 1.0, N),
]).T

instance_buffer = ctx.buffer(instances.astype('f4').tobytes())

ctx.includes['grass'] = grass_mesh()

triangle = ctx.pipeline(
    vertex_shader='''
        #version 330
        #include "grass"
        layout (std140) uniform Common {
            mat4 mvp;
        };
        layout (location = 0) in vec4 in_data;
        out vec2 v_data;
        void main() {
            vec3 v = grass[gl_VertexID];
            vec3 vert = vec3(
                in_data.x + cos(in_data.z) * v.x + sin(in_data.z) * v.y,
                in_data.y + cos(in_data.z) * v.y - sin(in_data.z) * v.x,
                v.z
            );
            gl_Position = mvp * vec4(vert, 1.0);
            v_data = vec2(in_data.w, v.z);
        }
    ''',
    fragment_shader='''
        #version 330
        in vec2 v_data;
        layout (location = 0) out vec4 out_color;
        void main() {
            vec3 yl = vec3(0.63, 1.0, 0.3);
            vec3 gn = vec3(0.15, 0.83, 0.3);
            out_color = vec4((yl + (gn - yl) * v_data.x) * v_data.y, 1.0);
        }
    ''',
    layout=[
        {
            'name': 'Common',
            'binding': 0,
        },
    ],
    resources=[
        {
            'type': 'uniform_buffer',
            'binding': 0,
            'buffer': uniform_buffer,
        },
    ],
    framebuffer=[image, depth],
    topology='triangle_strip',
    cull_face='back',
    vertex_buffers=zengl.bind(instance_buffer, '4f /i', 0),
    instance_count=N,
    vertex_count=15,
)

for i in tqdm(range(300)):
    x, y = math.sin(i * 0.01) * 3.0, math.cos(i * 0.01) * 3.0
    camera = zengl.camera((x, y, 2.0), (0.0, 0.0, 0.5), aspect=size[0] / size[1], fov=45.0)

    uniform_buffer.write(camera)
    uniform_buffer.write(zengl.pack(x, y, 2.0, 0.0), offset=64)

    image.clear_value = (1.0, 1.0, 1.0, 1.0)
    image.clear()
    depth.clear()
    triangle.render()
    image.blit()
    process2.stdin.write(
            image.read()
        )
process2.stdin.close()
process2.wait()

Image.frombuffer('RGBA', size, image.read(), 'raw', 'RGBA', 0, -1).save('hello.png')