Python使用ffmpeg合成视频音频

Posted 宿者朽命

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Python使用ffmpeg合成视频音频相关的知识,希望对你有一定的参考价值。

Python使用ffmpeg合成视频、音频

最近有在使用屏幕录制软件录制桌面,在用的过程中突发奇想,使用python能不能做屏幕录制工具,也锻炼下自己的动手能力。
接下准备写使用python如何做屏幕录制工具的系列文章:

大概上述四个部分,希望自己能够尽快完善,前两篇文章分享了利用opencv制作了屏幕录制部分,利用PyAudio录制音频,本篇文章分享如何使用ffmpeg将同时录制的屏幕录像和音频合成为有声音的屏幕录像。

应用平台

  • windows 10

  • python 3.7

  • ffmpeg

音视频合成

在python合成音视频有很多第三方包,操作方法各有不同,有简易的也有稍微复杂的,

起初也有想过使用moviepy中文文档,其在使用门槛上确实比ffmpeg要小很多,在翻查相关资料后,目前要考虑的事是合并音视频为一个有声音的视频,而且ffmpeg具有录制视频的功能,效果比使用cv2+ImageGrab方式要好,所以采用ffmpeg作为合成工具,ffmpeg.exe下载路径使用手册

python下使用ffmpeg,可以直接调用命令行工具,也可以使用封装的第三包。

pip install ffmpeg-python

使用参数与ffmpeg一致,不同处在于,在使用完后需要键入终止条件以结束ffmpeg的运行。

  • 将cv2的屏幕录制改成ffmpeg录制
import ffmpeg

# 屏幕录制画面大小
width = 1920
height = 1080
# 录制帧率,在cv2录制中,发现帧率比较固定且偏小,主要原因为ImageGrab间隔时间稍长
# 这里可以调整的稍微大一点,当然越大对固件性能越好,推荐在15~60之间(含)
fps = 30
# 录制画面是否包含鼠标,0:不包含,1:包含
# 录制方式为gdigrab模式,包含鼠标在录制过程会看到鼠标频闪的现象,可自行搜索模块插件解决
draw_mouse = 0
# 屏幕画面录制偏移距离
offset_x = 0
offset_y = 0
# 文件名称
filename = 'test.mp4'

# 录制桌面
process = (
            ffmpeg.output(
                ffmpeg.input(
                    filename='desktop', format='gdigrab', framerate=fps, offset_x=offset_x, offset_y=offset_y,
                    draw_mouse=draw_mouse, s=f'widthxheight'),
                filename=filename, pix_fmt='yuv420p'
            ).overwrite_output()
        )

# cmd: ffmpeg路径,如不设置,会搜寻环境变量下的ffmpeg
# 可直接下载ffmpeg.exe到工程文件目录下
ffmpeg_path = 'ffmpeg.exe'
process.run_async(cmd=ffmpeg_path, pipe_stdin=True, pipe_stdout=False, pipe_stderr=False)

# 自定义延时函数
delay()

# 传入中断参数,在调用之前,尽量在之前有足够的延时
process.communicate(str.encode("q"))
process.terminate()
  • 合成音视频
# 传入的视频路径
video_path = 'mp4_test.mp4'
# 传入的音频路径
audio_path = 'mp3_test.mp3'
# 生成的视频名称,不要和上述的路径一致
output_path = 'mixer.mp4'

process = (
            ffmpeg.output(
                ffmpeg.input(filename=video_path),
                ffmpeg.input(filename=audio_path),
                filename=output_path, vcodec='copy', acodec='aac', strict='experimental', pix_fmt='yuv420p'
            ).overwrite_output()

ffmpeg_path = 'ffmpeg.exe'
process.run_async(cmd=ffmpeg_path, pipe_stdin=True, pipe_stdout=False, pipe_stderr=False)

time.sleep(1)
process.communicate(str.encode("q"))
process.terminate()

ps: 上述方法也可以封装到类中,方便pyqt5窗口的实现。

看到这里可能会想到,有音频录制,视频录制,音视频合成,但是不好让音视频分开录制,导致音视频不同步,看起来也别扭,下面就来实现将两者同时录制同时结束。

可沿用录制屏幕制作视频(推荐用本篇下方的代码)录制音频 两篇里的代码,将关于键盘监听部分注释掉,避免冲突。

from threading import Thread
from pynput import keyboard
from Audio_record import AudioRecord
from Screenshot_record import Screenshot


def hotkey():
    """热键监听"""
    with keyboard.Listener(on_press=on_press) as listener:
        listener.join()


def on_press(key):
    try:
        video.terminate()
        if key.char == 't':  # t键,录制结束,保存音视频
            audio.stop_flag = True
        elif key.char == 'k':  # k键,录制中止,删除文件
            audio.stop_flag = True
            audio.kill = True
            video.unlink('test.mp4')
    except Exception as e:
        print(e)


key_thread = Thread(target=hotkey, daemon=True)
audio = AudioRecord()
video = Screenshot()
key_thread.start()
audio.run(filename='test.mp3')
video.record('test.mp4')

利用三组线程,当该代码运行时就会监听键盘按键,同时录制音频、视频,当按下t键结束录制,保存音视频。

总结

通过音视频分线程录制,保证两个文件的时长一致且同步的情况,在这过程中学习了如何在python中调用ffmpeg模块,对此进行音视频合并,完成视频合成。

远处的峰亦不能遮挡看到山后的风景。


于二零二二年四月十七日作

ffmpeg录屏源代码:

"""
Screenshot_record.py 使用ffmpeg录制屏幕
"""
from pathlib import Path
import ffmpeg


class Screenshot:

    def __init__(self, width=1920, height=1080, fps=15):
        self.width = width
        self.height = height
        self.fps = fps
        self.process = None
        self.ffmpeg_path = file_path('ffmpeg.exe')

    def __call__(self, width, height, fps=None):
        self.width = width
        self.height = height
        self.fps = fps if fps else self.fps

    @staticmethod
    def unlink(filename):
        Path(filename).unlink()

    def record(self, filename, offset_x=0, offset_y=0, draw_mouse=0):
        self.process = (
            ffmpeg.output(
                ffmpeg.input(
                    filename='desktop', format='gdigrab', framerate=self.fps, offset_x=offset_x, offset_y=offset_y,
                    draw_mouse=draw_mouse, s=f'self.widthxself.height'),
                filename=filename, pix_fmt='yuv420p'
            ).overwrite_output()
        )
        self.ffmpeg_async()

    def compose_audio(self, video_path, audio_path, output_path):
        self.process = (
            ffmpeg.output(
                ffmpeg.input(filename=video_path),
                ffmpeg.input(filename=audio_path),
                filename=output_path, vcodec='copy', acodec='aac', strict='experimental', pix_fmt='yuv420p'
            ).overwrite_output()
        )
        self.ffmpeg_async()

    def ffmpeg_async(self):
        self.process = self.process.run_async(cmd=self.ffmpeg_path, pipe_stdin=True, pipe_stdout=False,
                                              pipe_stderr=False)

    def terminate(self):
        if self.process is not None:
            self.process.communicate(str.encode("q"))
            self.process.terminate()
            self.process = None

图片转视频python/ffmpeg

# coding=utf-8
import os
import cv2
from PIL import Image
def makevideo(path, fps):
    """ 将图片合成视频. path: 视频路径,fps: 帧率 """
    fourcc = cv2.VideoWriter_fourcc(*'mp4v')
    path1 = r'F:\\code1\\auto_panorama\\res_img\\20'
    img_list = os.listdir(path1)
    im = Image.open(r'F:\\code1\\auto_panorama\\res_img\\20\\1.jpg')
    print(im.size)
    vw = cv2.VideoWriter(path, fourcc, fps, im.size)
    for i in range(len(img_list)):
        frame = cv2.imread(path1 + '/' + str(i)+ ".jpg" )
        vw.write(frame)

if __name__ == '__main__':
    video_path = './output2.mp4'
    makevideo(video_path, 10)  # 图片转视频

ffmpeg转图片为视频

ffmpeg -r 10 -f image2 -i output/img%d.png -s 448x256 -c:v libx264 -pix_fmt yuv420p output/demo.mp4 -q:v 0 -q:a 0

使用ffmpeg将图片转成gif

ffmpeg -r 10 -f image2 -i output/img%d.png -s 448x256 -vf "split[s0][s1];[s0]palettegen=stats_mode=single[p];[s1][p]paletteuse=new=1" output/demo.gif

提取每一帧为图像:

ffmpeg -i input_video.mp4 output_image_%04d.jpg

这将从input_video.mp4中提取每一帧,并将其保存为output_image_0001.jpg,output_image_0002.jpg等等。

提取特定时间范围内的每一帧为图像:

ffmpeg -i input_video.mp4 -ss 00:01:00 -t 00:00:10 -r 1 output_image_%04d.jpg

这将从01分钟处开始提取10秒钟的每一帧,以每秒1帧的速度,并将其保存为output_image_0001.jpg,output_image_0002.jpg等等。
提取单个帧为图像:

ffmpeg -i input_video.mp4 -vf "select=eq(n\\,100)" -vframes 1 output_image.jpg

这将提取第100帧,并将其保存为output_image.jpg。

ffmpeg无损将红外视频转成图像格式

ffmpeg -i input_video.mp4 -vf "format=gray, split [o1] [o2]; [o1] palettegen [p]; [o2] fifo [o3]; [o3] [p] paletteuse" output_image_%03d.png

-i input_video.mp4:指定要转换的视频文件名。

-vf “format=gray, split [o1] [o2]; [o1] palettegen [p]; [o2] fifo [o3]; [o3] [p] paletteuse”:设置视频过滤器,首先将视频转换为灰度格式,然后拆分成两个输出([o1]和[o2])。第一个输出[o1]通过palettegen过滤器生成一张调色板,第二个输出[o2]通过fifo过滤器使得输出缓存的帧以FIFO(先进先出)的顺序传递给下一个过滤器。最后将paletteuse过滤器应用于第二个输出,使用之前生成的调色板将帧转换为PNG格式。

output_image_%03d.png:指定输出的文件名格式,%03d表示使用3位数字作为文件名的占位符。
这个命令将视频转换为一系列PNG图片,文件名以output_image_001.png,output_image_002.png等等命名,依次对应视频中的每一帧

ffmpeg录制红外码流

ffmpeg -f <input_format> -i <input_address> -c:v copy -c:a copy <output_file>

ffmpeg -i rtsp://192.168.1.10:554/stream -c:v copy -c:a copy output.mp4

以上是关于Python使用ffmpeg合成视频音频的主要内容,如果未能解决你的问题,请参考以下文章

ffmpeg图片转视频,图片+音频合成视频每秒一张图

一次用ffmpeg实现图片+音频合成视频的开发

视频提取图片/图片合成视频ffmpeg(二十)

如何使用ffmpeg进行音视频裁剪命令和音视频合成命令

图片转视频python/ffmpeg

转载:ffmpeg 音视频合成分割