如何使用 Python和 FFmpeg 批量截图视频到各自文件夹中

 更新时间:2024年08月26日 10:00:59   作者:winfredzhang  
wxPython 提供了一个简单易用的界面,而 FFmpeg 则负责处理视频帧的提取,这个工具不仅对视频编辑工作有帮助,也为批量处理视频文件提供了极大的便利,这篇文章主要介绍了使用 Python和 FFmpeg 批量截图视频到各自文件夹中,需要的朋友可以参考下

在这篇博客中,我们将创建一个简单的图形用户界面 (GUI) 工具,利用 wxPythonFFmpeg 来从视频文件中批量生成截图。这个工具能够让用户选择一个文件夹,遍历其中的所有视频文件,按照视频长度将其分为四等分,然后为每个视频生成四张截图。所有生成的截图将保存在一个以视频名称命名的文件夹中,并在截图完成后自动打开该文件夹。
C:\pythoncode\new\multivideofilescreenshot.py

工具介绍

  • wxPython:用于创建桌面应用程序的图形界面。
  • FFmpeg:一个强大的多媒体处理工具,用于提取视频帧。

所有代码

import wx
import os
import subprocess
import threading
import datetime
import sys
class VideoScreenshotApp(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="视频截图工具", size=(600, 400))
        # 创建面板
        panel = wx.Panel(self)
        # 创建路径选择控件
        self.path_label = wx.StaticText(panel, label="请选择文件夹:")
        self.path_textctrl = wx.TextCtrl(panel, style=wx.TE_READONLY)
        self.path_button = wx.Button(panel, label="选择路径")
        self.path_button.Bind(wx.EVT_BUTTON, self.on_select_path)
        # 创建文件列表控件,使用 CheckListBox 以支持多选
        self.file_list_ctrl = wx.CheckListBox(panel)
        # 创建截图按钮
        self.capture_button = wx.Button(panel, label="截图")
        self.capture_button.Bind(wx.EVT_BUTTON, self.on_capture)
        # 布局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.path_label, 0, wx.ALL, 5)
        sizer.Add(self.path_textctrl, 0, wx.EXPAND | wx.ALL, 5)
        sizer.Add(self.path_button, 0, wx.ALL, 5)
        sizer.Add(self.file_list_ctrl, 1, wx.EXPAND | wx.ALL, 5)
        sizer.Add(self.capture_button, 0, wx.ALL | wx.ALIGN_CENTER, 5)
        panel.SetSizer(sizer)
        self.current_path = ""
    def on_select_path(self, event):
        dlg = wx.DirDialog(self, "选择文件夹", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.current_path = dlg.GetPath()
            self.path_textctrl.SetValue(self.current_path)
            self.update_file_list()
        dlg.Destroy()
    def update_file_list(self):
        self.file_list_ctrl.Clear()
        if not self.current_path:
            return
        file_list = self.search_video_files(self.current_path)
        for filename, file_path, duration in file_list:
            self.file_list_ctrl.Append(f"{filename} - {str(datetime.timedelta(seconds=int(duration)))}", file_path)
    def search_video_files(self, directory):
        video_extensions = ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm']
        file_list = []
        for root, dirs, files in os.walk(directory):
            for file in files:
                if os.path.splitext(file)[1].lower() in video_extensions:
                    file_path = os.path.join(root, file)
                    duration = self.get_video_duration(file_path)
                    file_list.append((file, file_path, duration))
        return file_list
    def get_video_duration(self, file_path):
        cmd = [
            'ffprobe',
            '-v', 'error',
            '-select_streams', 'v:0',
            '-show_entries', 'stream=duration',
            '-of', 'default=noprint_wrappers=1:nokey=1',
            file_path
        ]
        try:
            result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
            duration_str = result.stdout.strip()
            if not duration_str:
                raise ValueError("ffprobe output is empty")
            return float(duration_str)
        except subprocess.CalledProcessError as e:
            wx.LogError(f"ffprobe error: {e.stderr}")
            raise
        except ValueError as e:
            wx.LogError(f"Value error: {e}")
            raise
    def on_capture(self, event):
        selected_indices = self.file_list_ctrl.GetCheckedItems()
        if selected_indices:
            for index in selected_indices:
                file_path = self.file_list_ctrl.GetClientData(index)
                file_name = os.path.basename(file_path)
                duration = self.get_video_duration(file_path)
                thread = threading.Thread(target=self.capture_screenshots, args=(file_path, duration))
                thread.start()
        else:
            wx.MessageBox("请先选择一个或多个视频文件", "提示", wx.OK | wx.ICON_INFORMATION)
    def capture_screenshots(self, file_path, duration):
        output_dir = os.path.join(self.current_path, os.path.splitext(os.path.basename(file_path))[0])
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        # 计算每张截图的时间点
        intervals = [duration * i / 4 for i in range(1, 5)]
        # 生成截图
        for i, timestamp in enumerate(intervals):
            cmd = [
                'ffmpeg',
                '-ss', str(datetime.timedelta(seconds=int(timestamp))),
                '-i', file_path,
                '-vframes', '1',
                os.path.join(output_dir, f'screenshot_{i+1}.jpg')
            ]
            subprocess.run(cmd, check=True)
        # 截图完成后,自动打开文件夹
        if sys.platform.startswith('win'):
            subprocess.Popen(['explorer', output_dir])
        elif sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_dir])
        elif sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_dir])
if __name__ == "__main__":
    app = wx.App(False)
    frame = VideoScreenshotApp()
    frame.Show()
    app.MainLoop()

代码实现

下面是我们的工具实现代码:

import wx
import os
import subprocess
import threading
import datetime
import sys
class VideoScreenshotApp(wx.Frame):
    def __init__(self):
        wx.Frame.__init__(self, None, title="视频截图工具", size=(600, 400))
        # 创建面板
        panel = wx.Panel(self)
        # 创建路径选择控件
        self.path_label = wx.StaticText(panel, label="请选择文件夹:")
        self.path_textctrl = wx.TextCtrl(panel, style=wx.TE_READONLY)
        self.path_button = wx.Button(panel, label="选择路径")
        self.path_button.Bind(wx.EVT_BUTTON, self.on_select_path)
        # 创建文件列表控件,使用 CheckListBox 以支持多选
        self.file_list_ctrl = wx.CheckListBox(panel)
        # 创建截图按钮
        self.capture_button = wx.Button(panel, label="截图")
        self.capture_button.Bind(wx.EVT_BUTTON, self.on_capture)
        # 布局
        sizer = wx.BoxSizer(wx.VERTICAL)
        sizer.Add(self.path_label, 0, wx.ALL, 5)
        sizer.Add(self.path_textctrl, 0, wx.EXPAND | wx.ALL, 5)
        sizer.Add(self.path_button, 0, wx.ALL, 5)
        sizer.Add(self.file_list_ctrl, 1, wx.EXPAND | wx.ALL, 5)
        sizer.Add(self.capture_button, 0, wx.ALL | wx.ALIGN_CENTER, 5)
        panel.SetSizer(sizer)
        self.current_path = ""
    def on_select_path(self, event):
        dlg = wx.DirDialog(self, "选择文件夹", style=wx.DD_DEFAULT_STYLE)
        if dlg.ShowModal() == wx.ID_OK:
            self.current_path = dlg.GetPath()
            self.path_textctrl.SetValue(self.current_path)
            self.update_file_list()
        dlg.Destroy()
    def update_file_list(self):
        self.file_list_ctrl.Clear()
        if not self.current_path:
            return
        file_list = self.search_video_files(self.current_path)
        for filename, file_path, duration in file_list:
            self.file_list_ctrl.Append(f"{filename} - {str(datetime.timedelta(seconds=int(duration)))}", file_path)
    def search_video_files(self, directory):
        video_extensions = ['.mp4', '.avi', '.mkv', '.mov', '.wmv', '.flv', '.webm']
        file_list = []
        for root, dirs, files in os.walk(directory):
            for file in files:
                if os.path.splitext(file)[1].lower() in video_extensions:
                    file_path = os.path.join(root, file)
                    duration = self.get_video_duration(file_path)
                    file_list.append((file, file_path, duration))
        return file_list
    def get_video_duration(self, file_path):
        cmd = [
            'ffprobe',
            '-v', 'error',
            '-select_streams', 'v:0',
            '-show_entries', 'stream=duration',
            '-of', 'default=noprint_wrappers=1:nokey=1',
            file_path
        ]
        try:
            result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True)
            duration_str = result.stdout.strip()
            if not duration_str:
                raise ValueError("ffprobe output is empty")
            return float(duration_str)
        except subprocess.CalledProcessError as e:
            wx.LogError(f"ffprobe error: {e.stderr}")
            raise
        except ValueError as e:
            wx.LogError(f"Value error: {e}")
            raise
    def on_capture(self, event):
        selected_indices = self.file_list_ctrl.GetCheckedItems()
        if selected_indices:
            for index in selected_indices:
                file_path = self.file_list_ctrl.GetClientData(index)
                file_name = os.path.basename(file_path)
                duration = self.get_video_duration(file_path)
                thread = threading.Thread(target=self.capture_screenshots, args=(file_path, duration))
                thread.start()
        else:
            wx.MessageBox("请先选择一个或多个视频文件", "提示", wx.OK | wx.ICON_INFORMATION)
    def capture_screenshots(self, file_path, duration):
        output_dir = os.path.join(self.current_path, os.path.splitext(os.path.basename(file_path))[0])
        if not os.path.exists(output_dir):
            os.makedirs(output_dir)
        # 计算每张截图的时间点
        intervals = [duration * i / 4 for i in range(1, 5)]
        # 生成截图
        for i, timestamp in enumerate(intervals):
            cmd = [
                'ffmpeg',
                '-ss', str(datetime.timedelta(seconds=int(timestamp))),
                '-i', file_path,
                '-vframes', '1',
                os.path.join(output_dir, f'screenshot_{i+1}.jpg')
            ]
            subprocess.run(cmd, check=True)
        # 截图完成后,自动打开文件夹
        if sys.platform.startswith('win'):
            subprocess.Popen(['explorer', output_dir])
        elif sys.platform.startswith('darwin'):
            subprocess.Popen(['open', output_dir])
        elif sys.platform.startswith('linux'):
            subprocess.Popen(['xdg-open', output_dir])
if __name__ == "__main__":
    app = wx.App(False)
    frame = VideoScreenshotApp()
    frame.Show()
    app.MainLoop()

代码解释

创建主窗口

  • 使用 wx.Frame 创建主窗口,添加路径选择控件、文件列表控件以及截图按钮。

路径选择

  • on_select_path 方法允许用户选择一个文件夹,并更新文件列表。

文件列表更新

  • update_file_list 方法遍历所选文件夹中的视频文件,获取每个视频的时长,并将信息显示在 CheckListBox 中。

视频时长获取

  • get_video_duration 方法使用 ffprobe 命令来获取视频时长。

截图生成

  • on_capture 方法处理截图请求,使用多线程来生成截图,以避免阻塞主线程。
  • capture_screenshots 方法使用 ffmpeg 命令生成四张截图,并将截图保存在以视频名称命名的文件夹中。

自动打开文件夹

  • 截图完成后,自动在文件浏览器中打开保存截图的文件夹。

效果如下

在这里插入图片描述

总结

通过这个工具,你可以轻松地从多个视频文件中生成截图,而无需手动操作。wxPython 提供了一个简单易用的界面,而 FFmpeg 则负责处理视频帧的提取。这个工具不仅对视频编辑工作有帮助,也为批量处理视频文件提供了极大的便利。

以上就是使用 Python和 FFmpeg 批量截图视频到各自文件夹中的详细内容,更多关于Python FFmpeg 批量截图视频的资料请关注脚本之家其它相关文章!

相关文章

  • OpenCV-Python实现图像平滑处理操作

    OpenCV-Python实现图像平滑处理操作

    图像平滑处理的噪声取值主要有6种方法,本文主要介绍了这6种方法的具体使用并配置实例方法,具有一定的参考价值,感兴趣的可以了解一下
    2021-06-06
  • 使用 Python ssh 远程登陆服务器的最佳方案

    使用 Python ssh 远程登陆服务器的最佳方案

    这篇文章主要介绍了使用 Python ssh 远程登陆服务器的最佳方案,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-03-03
  • Python3.7 新特性之dataclass装饰器

    Python3.7 新特性之dataclass装饰器

    Python 3.7中一个令人兴奋的新特性是 data classes 。这篇文章主要介绍了Python3.7 新特性之dataclass装饰器,需要的朋友可以参考下
    2019-05-05
  • python列表删除元素的三种实现方法

    python列表删除元素的三种实现方法

    本文主要介绍了python列表删除元素的三种实现方法,主要包括pop方法,remove方法,del方法这三种,具有一定的参考价值,感兴趣的可以了解一下
    2024-01-01
  • 使用flask如何获取post请求参数

    使用flask如何获取post请求参数

    近日在使用flask框架获取前端的请求时获取参数时,遇到了几个问题,所以下面这篇文章主要给大家介绍了关于使用flask如何获取post请求参数的相关资料,需要的朋友可以参考下
    2022-08-08
  • Python实现制作销售数据可视化看板详解

    Python实现制作销售数据可视化看板详解

    在数据时代,销售数据分析的重要性已无需赘言。只有对销售数据的准确分析我们才有可能找准数据变动的原因。本文将介绍用Python制作销售数据大屏的方法。感兴趣的可以关注一下
    2021-11-11
  • 使用python求解迷宫问题的三种实现方法

    使用python求解迷宫问题的三种实现方法

    关于迷宫问题,常见会问能不能到达某点,以及打印到达的最短路径,下面这篇文章主要给大家介绍了关于如何使用python求解迷宫问题的三种实现方法,需要的朋友可以参考下
    2022-03-03
  • Python使用Scrapy爬取妹子图

    Python使用Scrapy爬取妹子图

    前面我们给大家介绍了使用nodejs来爬取妹纸图片的方法,下面我们来看下使用Python是如何实现的呢,有需要的小伙伴参考下吧。
    2015-05-05
  • python使用递归解决全排列数字示例

    python使用递归解决全排列数字示例

    有1,2,3,4这4个数字,能组成多少个互不相同且无重复数字的三位数,下面是二种解决示例,需要的朋友可以参考下
    2014-02-02
  • PyQt5+QtChart绘制散点图

    PyQt5+QtChart绘制散点图

    QChart是一个QGraphicScene中可以显示的QGraphicsWidget。本文将利用QtChart实现绘制散点图,文中的示例代码讲解详细,感兴趣的小伙伴可以了解一下
    2022-12-12

最新评论