首页 > 编程语言 >Python 调用 FFMPEG

Python 调用 FFMPEG

时间:2024-10-18 18:10:48浏览次数:8  
标签:调用 FFMPEG Python self video str path out

Python 调用 FFMPEG

从 Python 中调用 FFMPEG 不是很方便,只有通过 subprocess 或 os.system 等执行 FFMPEG 命令。也有 ffmpeg-python 这种已经封装好的三方库,但本着学习的目的,还是自己写了一个简单的 Demo 来实现获取视频信息和视频转码。

Demo

需要先下载 FFMPEG 的可执行文件放入同级的 ffmpeg 目录下。

"""
视频操作

ffmpeg 命令
查看 FFMPEG 支持的编解码器:ffmpeg -codecs
查看 FFMPEG 支持的封装格式:ffmpeg -formats
"""
import dataclasses
import os
import platform
import queue
import re
import subprocess
import threading
from pathlib import Path
from typing import Union, Optional

# FFMPEG 路径
FFMPEG_DIR = os.path.join(os.path.dirname(__file__), 'ffmpeg')
if platform.system() == 'Linux':
    FFMPEG_EXE = os.path.join(FFMPEG_DIR, 'ffmpeg')
    ENCODING = 'UTF-8'
else:
    FFMPEG_EXE = os.path.join(FFMPEG_DIR, 'ffmpeg.exe')
    ENCODING = 'GBK'


@dataclasses.dataclass
class VideoInfo:
    """
    视频信息
    """
    video_name: str
    duration_str: str  # ffmpeg 输出的原始时间
    duration_seconds: float  # 转换为秒数
    bitrate: str
    encoding: str
    width: str
    height: str
    fps: int

    def __str__(self) -> str:
        return (f'时长:{self.duration_str} '
                f'编码:{self.encoding} '
                f'分辨率:{self.width}x{self.height} '
                f'帧率:{self.fps}')


def parse_ffmpeg_progress(line: str):
    """
    解析 ffmpeg 输出中的进度信息,并转换为秒数
    """
    match = re.match(r'frame.*time=(\d+:\d+:\d+\.\d+)',
                     line, flags=re.DOTALL)
    if match:
        # 将 "HH:MM:SS.ms" 格式转换为秒数
        time_str = match.group(1)
        hours, minutes, seconds = map(float, time_str.split(':'))
        return hours * 3600 + minutes * 60 + seconds
    return None


def stream_reader(popen: subprocess.Popen, total_duration: int, progress_q: queue.Queue):
    """
    读取 stderr 输出并计算进度百分比

    :param popen: 输入流对象
    :param total_duration: 总时长(秒)
    :param progress_q: 进度队列
    """
    buffer = ''
    while True:
        chunk = popen.stderr.read(256)
        if not chunk:
            break
        buffer += chunk.decode()
        # 检查是否有错误输出,停止子进程
        if 'Error' in buffer:
            print(buffer)
            if popen.poll() is None:
                popen.kill()
            progress_q.put(-1)
        # 查找 '\r' 代表的一行结束
        elif '\r' in buffer:
            # 按 '\r' 分割并获取最新的进度行
            lines = buffer.split('\r')
            buffer = lines[-1]  # 保留缓冲区中最后一部分(不完整的行)
            progress_output = lines[-2]  # 获取最后完整的一行
            # 解析进度并计算百分比
            current_time = parse_ffmpeg_progress(progress_output)
            if current_time:
                percent = (current_time / total_duration) * 100
                # print(f'Progress: {percent:.2f}%')
                progress_q.put(percent)
    progress_q.put(100)


class VideoOperator:
    """
    视频转换器

    :param video_path: 视频路径
    """
    # 解析 FFMPEG 输出的视频信息正则表达式
    VideoInfoReStr = (r'.+Duration: (?P<duration_str>\d+:\d+:\d+.\d+), start.+'
                      r'bitrate: (?P<bitrate>\d+) kb/s.+'
                      r'Video: (?P<encoding>.+?),.+, (?P<width>\d+)x(?P<height>\d+)'
                      r'.+, (?P<fps>\d+.?\d*) fps,.+')

    def __init__(self, video_path: Union[str, Path]):
        if not os.path.exists(FFMPEG_EXE):
            raise FileNotFoundError(f"FFmpeg executable not found at {FFMPEG_EXE}")
        if not os.path.exists(video_path):
            raise FileNotFoundError(f"Source video not found: {video_path}")
        self.source_video_path = video_path
        self.video_info = self.get_video_info()
        self.progress_q = queue.Queue()  # 创建一个队列接收进度信息

    def get_video_info(self) -> Optional[VideoInfo]:
        """
        获取视频信息
        """
        cmd = [FFMPEG_EXE, '-i', self.source_video_path, '-hide_banner']
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        _, stderr_data = p.communicate()  # FFMPEG 的所有输出信息都在 err 中
        video_info_str = stderr_data.decode()
        # print(video_info_str)
        match_res = re.match(self.VideoInfoReStr,
                             video_info_str, flags=re.DOTALL)
        if match_res:
            # 计算视频时长
            hours, minutes, seconds = map(float, match_res.groupdict()['duration_str'].split(':'))
            duration_time = hours * 3600 + minutes * 60 + seconds
            video_info = VideoInfo(
                video_name=os.path.basename(self.source_video_path),
                duration_seconds=duration_time,
                **match_res.groupdict())
            return video_info
        return None

    def convert_video(self, out_video_path: Union[str, Path],
                      video_decoder: str = None,
                      out_video_encoder: str = None,
                      out_video_format: str = None,
                      out_video_bitrate: int = None,
                      out_video_fps: str = None,
                      out_video_res: str = None):
        """
        视频转换

        :param out_video_path: 输出视频路径
        :param video_decoder: 输入视频解码器
        :param out_video_encoder: 输出视频编码器
        :param out_video_format: 输出视频格式
        :param out_video_bitrate: 输出视频码率
        :param out_video_fps: 输出视频帧率
        :param out_video_res: 输出视频分辨率
        :return: 
        """
        cmd = [FFMPEG_EXE]
        if video_decoder:
            cmd.extend(['-c:v', video_decoder])  # h264_qsv
        cmd.extend(['-i', self.source_video_path, '-hide_banner', '-y'])
        if out_video_encoder:
            cmd.extend(['-c:v', out_video_encoder])
        if out_video_format:
            cmd.extend(['-f', out_video_format])
        if out_video_bitrate:
            cmd.extend(['-b:v', f'{out_video_bitrate}k'])
        if out_video_fps:
            cmd.extend(['-r', out_video_fps])
        if out_video_res:
            cmd.extend(['-s', out_video_res])
        cmd.append(out_video_path)
        # print(cmd)
        # print(' '.join(cmd))
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        stderr_thread = threading.Thread(
            target=stream_reader,
            args=(p, self.video_info.duration_seconds, self.progress_q)
        )
        stderr_thread.start()


if __name__ == '__main__':
    video_path = ‘'  # 视频路径
    vo = VideoOperator(video_path)
    print(vo.video_info)

标签:调用,FFMPEG,Python,self,video,str,path,out
From: https://www.cnblogs.com/shouwangrenjian/p/18470342

相关文章

  • python - 分享绕过验证码登录的方法
    一、通过webdriver启动浏览器:二、添加cookie:三、切换到目标地址: #ThisisasamplePythonscript.fromseleniumimportwebdriverimporttime#PressShift+F10toexecuteitorreplaceitwithyourcode.#PressDoubleShifttosearcheverywhereforclas......
  • 使用Python和BeautifulSoup进行网页爬虫与数据采集
    目录一、什么是网页爬虫?1.1网页爬虫的应用场景二、爬虫的基本流程三、准备工作四、实战:抓取豆瓣电影Top2504.1发送请求4.2解析页面4.3数据存储五、应对反爬虫技术5.1使用代理5.2模拟浏览器行为六、爬虫的扩展与优化6.1处理分页6.2多线程爬取6.3动态......
  • 超简单的婴儿哭声检测实现方案--python版
    超简单的婴儿哭声检测实现方案--python版1.构建项目项目结构└─audio_data##音频文件├─mp3├─test└─wav##训练音频源文件每个目录代表不同的标签,自己定义和随意增加,cry目录存放的是婴儿的哭声├─cry├─non_cry└─other└─get-model.py└─main.py└......
  • gTTS: 强大的Python文本转语音库
    gTTSgTTS简介gTTS(GoogleText-to-Speech)是一个Python库和命令行工具,用于与GoogleTranslate的文本转语音API进行交互。它允许用户将文本转换为语音,并将结果保存为MP3文件或进行进一步的音频处理。gTTS支持多种语言,并提供了丰富的自定义选项,使其成为一个强大而灵活的文本转......
  • Python中的索引和切片
    Python中的索引和切片是比较重要的内容,在数组处理中至关重要。这里对于2种情况做一个甄别。索引单元素索引单元素索引的工作原理与其他标准Python序列完全相同。它是从0开始的,并接受负索引以从数组的末尾进行索引。x=np.arange(10)x[2]2x[-2]8无需将每个维度的索引......
  • 最新毕设-SpringBoot-校园学习交流和资源共享平台-78210(免费领项目)可做计算机毕业设计
    目录1绪论1.1选题背景与意义1.2国内外研究现状1.3论文结构与章节安排2系统分析2.1可行性分析2.2系统流程分析2.2.1 数据流程2.2.2 用户登录流程2.3 系统功能分析2.3.1功能性分析2.3.2非功能性分析2.4 系统用例分析2.5本章小结3 系统......
  • 基于卷积神经网络的乳腺癌细胞识别系统,resnet50,mobilenet模型【pytorch框架+python源
     更多目标检测和图像分类识别项目可看我主页其他文章功能演示:卷积神经网络,乳腺癌细胞识别系统,resnet50,mobilenet【pytorch框架,python】_哔哩哔哩_bilibili(一)简介基于卷积神经网络的乳腺癌细胞识别系统是在pytorch框架下实现的,这是一个完整的项目,包括代码,数据集,训练好的模......
  • 关于Python AI 编程助手Fitten Code的应用体验以及Python 修改删除 sys.path 路径以实
    一、关于PythonAI编程助手FittenCode的应用体验        AI现在无孔不入,现在都开始进入到编程中了,有一个能适用多种编译器环境的AI编程插件FittenCode。其适配了ViusalStudio,VSCode(本文使用),JetBrains系列(本文使用)以及Vim等多种编译器环境的插件FittenCo......
  • 调用淘宝 API 时如何处理错误信息?
    在调用淘宝API时,可以按照以下方式处理错误信息:一、了解淘宝API的错误码体系:仔细研读文档:淘宝API有一套自己的错误码定义,在开发前要认真阅读淘宝开放平台提供的API文档,熟悉各种错误码的含义及对应的解决方案。例如,“isv.invalid-parameter”可能表示参数无效,“isv.trade-no......
  • python+flask框架的购书推荐小程序(开题+程序+论文) 计算机毕业设计
    本系统(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。系统程序文件列表开题报告内容选题背景随着互联网技术的飞速发展,电子商务已成为人们日常生活的重要组成部分,其中在线购书因其便捷性和丰富的选择而备受欢迎。然而,面对海量的图书......