python ffmpeg 使用 pyav 转换 一组图像 到 视频

2022/8/4 更新
支持加入水印
水印必须包含透明图像,并且水印图像大小要等于原图像的大小

python convert_image_to_video.py -f 30 -m watermark.png im_dir out.mkv

2022/6/21 更新
让命令行参数更加易用
新的命令行使用方法

python convert_image_to_video.py -f 30 im_dir out.mkv

FFMPEG 命令行转换 一组JPG图像 到视频时,是将这组图像视为 MJPG 流。
我需要转换一组 PNG 图像到视频,FFMPEG 就不认了。

pyav内置了ffmpeg库,不需要系统带有ffmpeg工具

因此我使用 ffmpeg 的python包装 pyav写一个命令行工具,可以转换一组任意格式图像到视频,来解决这个问题。
视频尺寸为第一张图像的大小
编码方式为 crf 18
编码时间参考:i7-8750H 5分钟1080P 30FPS 视频,编码时间约为 30 分钟

requirement.txt

opencv-python
tqdm
av
click

使用方法
将以下代码,复制粘贴到一个文件内,命名为 convert_image_to_video.py
使用命令
python convert_image_to_video.py im_dir --out_file out.mkv

python convert_image_to_video.py -f 30 im_dir out.mkv

可以看到进度条,可以看到输出文件默认名字为 out.mkv
即使没有完成,也可以立刻使用播放器播放 out.mkv 文件观看成品质量。

import av
import numpy as np
import cv2
from glob import glob
from tqdm import tqdm
import click


support_im_exts = ('.png', '.bmp', '.webp', '.jpg', '.jp2', '.jpeg')


def blend(im, wm_color, wm_alpha):
    im = im * (1-wm_alpha) + wm_color * wm_alpha
    im = np.uint8(np.clip(np.round(im), 0, 255))
    return im


@click.command()
@click.argument('in-dir',   type=str)
@click.argument('out-file', type=str)
@click.option('-f', '--fps', type=click.IntRange(1), default=30, show_default=True, help='Video FPS.')
@click.option('-m', '--watermark', type=str, default=None, show_default=True, help='watermark file.')
def main(in_dir, out_file, fps=30, watermark=None):
    vcodec = 'libx264'

    if '*' not in in_dir:
        in_dir = f'{in_dir}/*.*'

    in_files = glob(in_dir, recursive=True)

    in_files = [f for f in in_files if f.endswith(support_im_exts)]
    in_files = sorted(in_files)

    if watermark is not None:
        buf = open(watermark, 'rb').read()
        buf = np.frombuffer(buf, dtype=np.uint8)
        wm = cv2.imdecode(buf, -1)
        assert wm is not None
        assert wm.ndim == 3 and wm.shape[-1] == 4
        wm = cv2.cvtColor(wm, cv2.COLOR_BGRA2RGBA)
    else:
        wm = None

    container = av.open(out_file, mode="w")

    stream = container.add_stream(vcodec, rate=fps)

    stream.pix_fmt = "yuv420p"
    # stream.bit_rate = 10000*1000
    stream.options["crf"] = "18"
    stream.options["profile"] = "high"
    stream.options["tune"] = "ssim"
    stream.options["preset"] = "7"
    stream.options["high-tier"] = "1"
    stream.options["level"] = "5"
    stream.options["tier"] = "high"

    wm_color = None
    wm_alpha = None

    for f_i, file in enumerate(tqdm(in_files)):

        im = cv2.imread(file, 1)
        assert im is not None
        im = cv2.cvtColor(im, cv2.COLOR_BGR2RGB)

        if f_i == 0:
            stream.width = im.shape[1]
            stream.height = im.shape[0]
            if wm is not None:
                wm = cv2.resize(wm, tuple(im.shape[:2][::-1]), interpolation=cv2.INTER_LINEAR)
                wm_color = wm[..., :3]
                wm_alpha = np.float32(wm[..., 3:4]) / 255.


        if wm is not None:
            im = blend(im, wm_color, wm_alpha)

        frame = av.VideoFrame.from_ndarray(im, format="rgb24")
        for packet in stream.encode(frame):
            container.mux(packet)

    # Flush stream
    for packet in stream.encode():
        container.mux(packet)

    # Close the file
    container.close()


if __name__ == '__main__':
    main()

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值