英文:
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.
Passimages_in_memory.getbuffer()
asinput_data
argument toff.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)
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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论