Python使用ffmpeg合成视频、音频

Python使用ffmpeg合成视频、音频

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

  • 录制屏幕制作视频
  • 录制音频
  • 合成视频,音频
  • 基于pyqt5制作可视化窗口
  • 大概上述四个部分,希望自己能够尽快完善,前两篇文章分享了利用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'{width}x{height}'),
                    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.width}x{self.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
    
    

    来源:宿者朽命

    物联沃分享整理
    物联沃-IOTWORD物联网 » Python使用ffmpeg合成视频、音频

    发表评论