将PIL图像导入FFMPY/FFMPEG以保存为GIF/视频。

huangapple go评论57阅读模式
英文:

Importing PIL images into FFMPY/FFMPEG to save as GIF/video

问题

我想了解如何将PIL图像转换为FFMPY以将其保存为视频或gif,因为在某些情况下,PIL库的量化方法会造成严重的质量损失。我首先对PIL进行一些修改,然后想要导出和保存结果。

我在网上没有找到有关此主题的任何信息,除了一个关于从PIL到FFMPEG的帖子:
https://stackoverflow.com/questions/43650860/pipe-pil-images-to-ffmpeg-stdin-python
我应该如何在FFMPY中实现类似的功能?

如果我首先有这样的设置:

import ffmpy
import PIL
from PIL import Image as Img

images = [Img.open('frame 1.png'), Img.open('frame 2.png')]#我如何将它们转换为FFMPEG?

#这里我使用PIL修改图像

#使用FFMPEG保存:
ff = ffmpy.FFmpeg(
    inputs={images ?: None},#我如何在这里插入PIL图像?
    outputs={'output.gif': None},
    executable='ffmpeg\\bin\\ffmpeg.exe')
ff.run()

我应该如何继续转换并使用FFMPY保存图像为视频?
是否可以通过添加一些步骤来实现?我不想必须先将所有PIL图像保存为图像,然后再导入它们并使用FFMPY再次保存它们,因为对于较大的文件来说,这将非常耗时。

英文:

I would like to know how I can transfer PIL images to FFMPY to save it as video, or gif, since the PIL library's quantization method has strong quality losses in certain cases. I first do some modifications with PIL, and then want to export and save the result.

I did not find any information on the topic online, beside one post with PIL to FFMPEG:
https://stackoverflow.com/questions/43650860/pipe-pil-images-to-ffmpeg-stdin-python
How could I implement something similar in FFMPY?

If I have for example this setup to begin with:

import ffmpy
import PIL
from PIL import Image as Img

images = [Img.open('frame 1.png'),Img.open('frame 2.png')]#How do I convert them to FFMPEG?

#Here I modify the images using PIL

#Save with FFMPEG:
ff = ffmpy.FFmpeg(
    inputs={images ?: None},#How do I insert PIL images here?
    outputs={'output.gif': None},
    executable='ffmpeg\\bin\\ffmpeg.exe')
ff.run()

How would I proceed to convert and save the images as a video using FFMPY?
Is it possible by adding some steps inbetween? I wouldn't want to have to save all PIL images first as images, and then import them and save them with FFMPY a second time, since that would be very time consuming with larger files.

答案1

得分: 1

根据ffmpy文档,似乎最相关的选项是使用使用管道协议

  • 不使用PIL来读取图片,我们可以将PNG图片作为二进制数据读入BytesIO(将所有图片读入内存中的文件对象):

      # 输入图片文件列表(假设所有图片的分辨率相同,像素格式也相同)。
      images = ['frame 1.png', 'frame 2.png', 'frame 3.png', 'frame 4.png', 'frame 5.png', 'frame 6.png', 'frame 7.png', 'frame 8.png']
    
      # 从文件中读取PNG图片,并将其写入内存中的BytesIO对象中(以二进制数据形式读取图片,不进行解码)。
      images_in_memory = io.BytesIO()
      for png_file_name in images:
          with open(png_file_name, 'rb') as f:
              images_in_memory.write(f.read())
    
  • 使用管道协议运行ffmpy.FFmpeg
    images_in_memory.getbuffer()作为input_data参数传递给ff.run

      ff = ffmpy.FFmpeg(
          inputs={'pipe:0': '-y -f image2pipe -r 1'},
          outputs={'output.gif': None},
          executable='\\ffmpeg\\bin\\ffmpeg.exe')
    
      # 将所有编码后的PNG图片的完整缓冲区写入“管道”中。
      ff.run(input_data=images_in_memory.getbuffer(), stdout=subprocess.PIPE)
    
英文:

According to ffmpy documentation, it seems like the most relevant option is using using-pipe-protocol.

  • Instead of using PIL for reading the images, we may read the PNG images as binary data into BytesIO (reading all images to in-memory file-like object):

     # List of input image files (assume all images are in the same resolution, and the same "pixel format").
     images = ['frame 1.png', 'frame 2.png', 'frame 3.png', 'frame 4.png', 'frame 5.png', 'frame 6.png', 'frame 7.png', 'frame 8.png']
    
     # Read PNG images from files, and write to BytesIO object in memory (read images as binary data without decoding).
     images_in_memory = io.BytesIO()
     for png_file_name in images:
         with open(png_file_name, 'rb') as f:
             images_in_memory.write(f.read())
    
  • Run ffmpy.FFmpeg using pipe protocol.
    Pass images_in_memory.getbuffer() as input_data argument to ff.run:

     ff = ffmpy.FFmpeg(
         inputs={'pipe:0': '-y -f image2pipe -r 1'},
         outputs={'output.gif': None},
         executable='\\ffmpeg\\bin\\ffmpeg.exe')
    
     # Write the entire buffer of encoded PNG images to the "pipe".
     ff.run(input_data=images_in_memory.getbuffer(), stdout=subprocess.PIPE)
    

The above solution seems a bit awkward, but it's the best solution I could find using ffmpy.
There are other FFmpeg to Python binding like ffmpeg-python, that supports writing the images one by one in a loop.
Using ffmpy, we have to read all the images into memory from advance.

The above solution keeps the PNG images in their encoded (binary form).
Instead of decoding the images with PIL (for example), FFmpeg is going to decode the PNG images.
Letting FFmpeg decode the images is more efficient, and saves memory.
The limitation is that all the images must have the same resolution.
The images also must have the same "pixel format" (all RGB or all RGBA but not a mix).
In case images have different resolution or pixels format, we have to decode the images (and maybe resize the images) using Python, and write images as "raw video".


For testing we may create PNG images using FFmpeg CLI:

ffmpeg -f lavfi -i testsrc=size=192x108:rate=1:duration=8 "frame %d.png".


Complete code sample:

import ffmpy
import io
import subprocess

#Building sample images using FFmpeg CLI for testing: ffmpeg -f lavfi -i testsrc=size=192x108:rate=1:duration=8 "frame %d.png"

# List of input image files (assume all images are in the same resolution, and the same "pixel format").
images = ['frame 1.png', 'frame 2.png', 'frame 3.png', 'frame 4.png', 'frame 5.png', 'frame 6.png', 'frame 7.png', 'frame 8.png']

# Read PNG images from files, and write to BytesIO object in memory (read images as binary data without decoding).
images_in_memory = io.BytesIO()
for png_file_name in images:
    with open(png_file_name, 'rb') as f:
        images_in_memory.write(f.read())

# Use pipe protocol: https://ffmpy.readthedocs.io/en/latest/examples.html#using-pipe-protocol
ff = ffmpy.FFmpeg(
    inputs={'pipe:0': '-y -f image2pipe -r 1'},
    outputs={'output.gif': None},
    executable='\\ffmpeg\\bin\\ffmpeg.exe')  # Note: I have ffmpeg.exe is in C:\ffmpeg\bin folder

ff.run(input_data=images_in_memory.getbuffer(), stdout=subprocess.PIPE)

Sample output output.gif:
将PIL图像导入FFMPY/FFMPEG以保存为GIF/视频。


Update:

Same solution using images from Pillow:

The above solution also works if we save the images from Pillow to BytesIO in PNG format.

Example:

import ffmpy
import io
import subprocess
from PIL import Image as Img

#Building sample images using FFmpeg CLI for testing: ffmpeg -f lavfi -i testsrc=size=192x108:rate=1:duration=8 "frame %d.png"

# List of input image files (assume all images are in the same resolution, and the same "pixel format").
images = ['frame 1.png', 'frame 2.png', 'frame 3.png', 'frame 4.png', 'frame 5.png', 'frame 6.png', 'frame 7.png', 'frame 8.png']

# Read PNG images from files, and write to BytesIO object in memory (read images as binary data without decoding).
images_in_memory = io.BytesIO()
for png_file_name in images:
    img = Img.open(png_file_name)
    # Modify the images using PIL...
    img.save(images_in_memory, format="png")

# Use pipe protocol: https://ffmpy.readthedocs.io/en/latest/examples.html#using-pipe-protocol
ff = ffmpy.FFmpeg(
    inputs={'pipe:0': '-y -f image2pipe -r 1'},
    outputs={'output.gif': None},
    executable='\\ffmpeg\\bin\\ffmpeg.exe')

ff.run(input_data=images_in_memory.getbuffer(), stdout=subprocess.PIPE)

Encoding the images as PNG in memory is not most efficient in terms of execution time, but it saves memory space.

huangapple
  • 本文由 发表于 2023年5月22日 19:51:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/76305884.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定