Python的音视频文件处理

news/2024/7/10 20:57:49 标签: python, 音视频, ffmpeg

文章目录

    • ffmpeg-python
      • 开始之前
      • 技术背景
      • 快速开始
        • 简单的例子,水平翻转视频
        • 复杂的例子,执行多滤波器
      • 更多尝试
        • 生成视频缩略图
        • 将视频转换为numpy数组
        • 通过管道读取单个视频帧为jpeg
        • 将声音转换为原始PCM音频
        • 从帧序列组装视频
        • 音视频管线
        • 单声道到立体声带偏移和视频
        • Jupyter Frame Viewer
        • Tensorflow Streaming
        • FaceTime webcam input (OS X)
        • Stream from a local video to HTTP server
        • Stream from RTSP server to TCP socket
        • 自定义过滤器
          • 多个输入
          • 多个输出

ffmpegpython_5">ffmpeg-python

ffmpeg-pythonffmpeg 的一个包装,通过 python 调用ffmpegAPI,实现高效的音视频文件处理
在这里插入图片描述

开始之前

安装 ffmpeg

# Linux
sudo apt install ffmpeg

# macOS
brew install ffmpeg

安装 ffmpeg-python

# 方式一
pip install ffmpeg-python

# 方式二
git clone git@github.com:kkroening/ffmpeg-python.git
pip install -e ./ffmpeg-python

参考

技术背景

音视频文件处理流程

输入文件 ⟹ 解封装 已编码的数据包 ⟹ 解码 被编码的帧 ( 可进行信号处理操作 ) ⟹ 编码 已编码的数据包 ⟹ 封装 输出文件 输入文件 \stackrel{解封装}{\Longrightarrow} 已编码的数据包 \stackrel{解码}{\Longrightarrow} 被编码的帧(可进行信号处理操作) \stackrel{编码}{\Longrightarrow} 已编码的数据包 \stackrel{封装}{\Longrightarrow} 输出文件 输入文件解封装已编码的数据包解码被编码的帧(可进行信号处理操作)编码已编码的数据包封装输出文件

在这里插入图片描述

ffmpeg-python 中通过 ffmpeg.input音视频文件解封装,通过 ffmpeg.filter 对被解码的帧进行信号处理操作,通过 ffmpeg.output 将数据包封装成音视频文件,整个过程会构建成一个结点图,最后通过调用 ffmpeg.run 执行节点图上的操作

python">import ffmpeg

"""
输入函数
ffmpeg.input(filename, **kwargs)

其中,参数
filename - 输入文件url
kwargs - 关键字参数逐字传递给ffmpeg

目的,创建读取文件结点

另外,如果存在音频那么注册为audio属性方法,可通过.video或["v"]获取,如果存在视频那么注册为video属性方法,可通过.audio或["a"]获取
"""
kwargs = {"t":20, "f":"mp4", "acodec":"pcm"}
ffmpeg.input('in.mkv', **kwargs)
input('in.mkv', **kwargs)
stream_video = ffmpeg.input('in.mkv', **kwargs).video
stream_video = ffmpeg.input('in.mkv', **kwargs)["v"]
stream_audio = ffmpeg.input('in.mkv', **kwargs).audio
stream_audio = ffmpeg.input('in.mkv', **kwargs)["a"]


"""
过滤器函数 
ffmpeg.filter(stream_spec, filter_name, *args, **kwargs)

其中,参数
stream_spec – 一个Stream, Streams列表,或者字典映射给Stream的标签
filter_name – ffmpeg中filter的名字, 例如colorchannelmixer、crop、hflip、volume、concat等过滤器
*args – 要逐字传递给ffmpeg的参数列表
**kwargs – 要逐字传递给ffmpeg的关键字参数列表

目的,创建处理数据结点

另外,作为函数时最好用filter_避免与python的filter内建函数混淆,其中filter_会直接return在ffmpeg中的filter,带有多个输出时使用ffmpeg.filter_multi_output(stream_spec, filter_name, *args, **kwargs)
"""
ffmpeg.input('in.mp4').filter('hflip')
ffmpeg.filter(input('in.mp4'), "hflip") 


"""
输出函数 
ffmpeg.output(*streams_and_filename, **kwargs)

其中,参数
video_bitrate – 即ffmpeg中的视频码率参数 -b:v, e.g. video_bitrate=1000.
audio_bitrate – 即ffmpeg中的音频码率参数 -b:a, e.g. audio_bitrate=200.
format – 即ffmpeg中的格式参数 -f, e.g. format='mp4' (equivalent to f='mp4').

目的,创建写入文件结点
"""
kwargs = {"-b:v":1000, "-b:a":200, "f":"mp4", "acodec":"pcm", "vcodec":"rawvideo"}
streams = ffmpeg.input("in.mkv").filter('hflip')
ffmpeg.output(streams, "out.mp4", **kwargs)
streams.output(streams, "out.mp4", **kwargs)


"""
运行函数
ffmpeg.run(stream_spec, cmd='ffmpeg', capture_stdout=False, capture_stderr=False, input=None, quiet=False, overwrite_output=False)

其中,参数
capture_stdout – 是否捕捉标stdout
capture_stderr – 是否捕捉到stderr
quiet – 设置capture_stdout和capture_stderr的简写
input – 要发送到stdin的文本
overwrite_output - 是否覆盖已有文件
其中,函数返回包含捕获的stdout和stderr数据

目的,调用ffmpeg执行构建好的结点图

另外,ffmpeg.run_async(stream_spec, cmd='ffmpeg', pipe_stdin=False, pipe_stdout=False, pipe_stderr=False, quiet=False, overwrite_output=False),异步调用ffmpeg执行构建好的节点图
"""
streams = ffmpeg.input('in.mp4').filter('hflip').output('out.mp4').run(overwrite_output=True)
ffmpeg.run(streams, overwrite_output=True)

另外,音视频文件解封装后得到的数据包称为 stream(如视频、音频与字幕等)可以通过 ffmpeg.probe 查看,结点图可通过 ffmpeg.view 查看

  • 执行函数

    python">"""
    音视频文件摘要函数 ffmpeg.probe(filename, cmd='ffprobe', **kwargs)
    
    如果ffprobe返回非零退出码,则返回一个带有通用错误消息的Error,可以通过访问异常的stderr属性来检索stderr输出
    """
    
    ffmpeg.probe("in.mkv")
    
  • 打印输出

    {'streams': [{'index': 0,
       'codec_name': 'hevc',
       'codec_long_name': 'H.265 / HEVC (High Efficiency Video Coding)',
       'profile': 'Main 10',
       'codec_type': 'video',
       'codec_time_base': '1001/24000',
       'codec_tag_string': '[0][0][0][0]',
       'codec_tag': '0x0000',
       'width': 1920,
       'height': 1080,
       'coded_width': 1920,
       'coded_height': 1080,
       'has_b_frames': 2,
       'sample_aspect_ratio': '1:1',
       'display_aspect_ratio': '16:9',
       'pix_fmt': 'yuv420p10le',
       'level': 120,
       'color_range': 'tv',
       'color_space': 'bt2020nc',
       'color_transfer': 'smpte2084',
       'color_primaries': 'bt2020',
       'refs': 1,
       'r_frame_rate': '24000/1001',
       'avg_frame_rate': '24000/1001',
       'time_base': '1/1000',
       'start_pts': 0,
       'start_time': '0.000000',
       'disposition': {'default': 1,
        'dub': 0,
        'original': 0,
        'comment': 0,
        'lyrics': 0,
        'karaoke': 0,
        'forced': 0,
        'hearing_impaired': 0,
        'visual_impaired': 0,
        'clean_effects': 0,
        'attached_pic': 0,
        'timed_thumbnails': 0},
       'tags': {'language': 'jpn',
        'BPS': '3285534',
        'DURATION': '00:24:10.032000000',
        'NUMBER_OF_FRAMES': '34766',
        'NUMBER_OF_BYTES': '595516342',
        '_STATISTICS_WRITING_APP': "mkvmerge v70.0.0 ('Caught A Lite Sneeze') 64-bit",
        '_STATISTICS_TAGS': 'BPS DURATION NUMBER_OF_FRAMES NUMBER_OF_BYTES'}},
      {'index': 1,
       'codec_name': 'eac3',
       'codec_long_name': 'ATSC A/52B (AC-3, E-AC-3)',
       'codec_type': 'audio',
       'codec_time_base': '1/48000',
       ...
       }
      },
      {'index': 2,
       'codec_name': 'eac3',
       'codec_long_name': 'ATSC A/52B (AC-3, E-AC-3)',
       'codec_type': 'audio',
       'codec_time_base': '1/48000',
       ...
       }
      },
      {'index': 3,
       'codec_name': 'subrip',
       'codec_long_name': 'SubRip subtitle',
       'codec_type': 'subtitle',
       'codec_time_base': '0/1',
       ...
       }
      },
     },
      ......
    }
    
  • 执行函数

    python">"""
    结点图可视化函数 view(detail=False, filename=None, pipe=False, **kwargs)
    
    其中,参数
    detail - 是否打印结点和边的关键信息,如stream的映射关系等
    """
    streams = ffmpeg.input('in.mp4')
    video = streams.video.filter('hflip')
    audio = streams.audio
    ffmpeg.concat(video, audio, v=1, a=1).overlay(ffmpeg.input("overlay.png")).output("out.mp4").view(detail=True)
    
  • 打印输出

    img-BahO6xrs-1676388632665

快速开始

简单的例子,水平翻转视频

img-3niYiDjS-1676388632666

python">import ffmpeg

# 方式一、一般写法
stream = ffmpeg.input('input.mp4')
stream = ffmpeg.hflip(stream)
stream = ffmpeg.output(stream, 'output.mp4')
ffmpeg.run(stream)

# 方式二、数据流写法(更易读)
(
    ffmpeg
    .input('input.mp4')
    .hflip()
    .output('output.mp4')
    .run()
)

复杂的例子,执行多滤波器

ffmpeg 非常强大,但使用多个过滤器(filter)处理的信号时,命令代码显得非常粗糙。例如,将输入视频两次修剪后合并,再将水平翻转的图片覆盖在视频上,接着在上面画一个框输出视频

Screenshot > ***图中绿色代表输入文件,黄色代表过滤器,蓝色代表输出文件,箭头代表数据流***

在这里插入图片描述

ffmpeg 的命令很难记忆与理解琐碎

ffmpeg -i input.mp4 -i overlay.png -filter_complex "[0]trim=start_frame=10:end_frame=20[v0];\
    [0]trim=start_frame=30:end_frame=40[v1];[v0][v1]concat=n=2[v2];[1]hflip[v3];\
    [v2][v3]overlay=eof_action=repeat[v4];[v4]drawbox=50:50:120:120:red:t=5[v5]"\
    -map [v5] output.mp4

ffmpeg-python 的命令简单明了

python">import ffmpeg

in_file = ffmpeg.input('input.mp4')
overlay_file = ffmpeg.input('overlay.png')
(
    ffmpeg
    .concat(
        in_file.trim(start_frame=10, end_frame=20),
        in_file.trim(start_frame=30, end_frame=40),
    )
    .overlay(overlay_file.hflip())
    .drawbox(50, 50, 120, 120, color='red', thickness=5)
    .output('out.mp4')
    .run()
)

更多尝试

生成视频缩略图

在这里插入图片描述

(
    ffmpeg
    .input(in_filename, ss=time)
    .filter('scale', width, -1)
    .output(out_filename, vframes=1)
    .run()
)

将视频转换为numpy数组

在这里插入图片描述

out, _ = (
    ffmpeg
    .input('in.mp4')
    .output('pipe:', format='rawvideo', pix_fmt='rgb24')
    .run(capture_stdout=True)
)
video = (
    np
    .frombuffer(out, np.uint8)
    .reshape([-1, height, width, 3])
)

通过管道读取单个视频帧为jpeg

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CNIDaGpj-1676388632667)(./Markdown%E5%9B%BE%E5%BA%8A/Python%E7%9A%84%E9%9F%B3%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/read_frame_as_jpeg.png)]

out, _ = (
    ffmpeg
    .input(in_filename)
    .filter('select', 'gte(n,{})'.format(frame_num))
    .output('pipe:', vframes=1, format='image2', vcodec='mjpeg')
    .run(capture_stdout=True)
)

将声音转换为原始PCM音频

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bjgfw8aO-1676388632667)(./Markdown%E5%9B%BE%E5%BA%8A/Python%E7%9A%84%E9%9F%B3%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/transcribe.png)]

out, _ = (ffmpeg
    .input(in_filename, **input_kwargs)
    .output('-', format='s16le', acodec='pcm_s16le', ac=1, ar='16k')
    .overwrite_output()
    .run(capture_stdout=True)
)

从帧序列组装视频

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9o9Us6Fp-1676388632667)(./Markdown%E5%9B%BE%E5%BA%8A/Python%E7%9A%84%E9%9F%B3%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/glob.png)]

(
    ffmpeg
    .input('/path/to/jpegs/*.jpg', pattern_type='glob', framerate=25)
    .output('movie.mp4'
    .run()
)

添加额外过滤器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n0rJEid3-1676388632667)(./Markdown%E5%9B%BE%E5%BA%8A/Python%E7%9A%84%E9%9F%B3%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/glob-filter.png)]

(
    ffmpeg
    .input('/path/to/jpegs/*.jpg', pattern_type='glob', framerate=25)
    .filter('deflicker', mode='pm', size=10)
    .filter('scale', size='hd1080', force_original_aspect_ratio='increase')
    .output('movie.mp4', crf=20, preset='slower', movflags='faststart', pix_fmt='yuv420p')
    .view(filename='filter_graph')
    .run()
)

音视频管线

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rZW0V8J1-1676388632667)(./Markdown%E5%9B%BE%E5%BA%8A/Python%E7%9A%84%E9%9F%B3%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/av-pipeline.png)]

in1 = ffmpeg.input('in1.mp4')
in2 = ffmpeg.input('in2.mp4')
v1 = in1.video.hflip()
a1 = in1.audio
v2 = in2.video.filter('reverse').filter('hue', s=0)
a2 = in2.audio.filter('areverse').filter('aphaser')
joined = ffmpeg.concat(v1, a1, v2, a2, v=1, a=1).node
v3 = joined[0]
a3 = joined[1].filter('volume', 0.8)
out = ffmpeg.output(v3, a3, 'out.mp4')
out.run()

单声道到立体声带偏移和视频

audio_left = (
    ffmpeg
    .input('audio-left.wav')
    .filter('atrim', start=5)
    .filter('asetpts', 'PTS-STARTPTS')
)

audio_right = (
    ffmpeg
    .input('audio-right.wav')
    .filter('atrim', start=10)
    .filter('asetpts', 'PTS-STARTPTS')
)

input_video = ffmpeg.input('input-video.mp4')

(
    ffmpeg
    .filter((audio_left, audio_right), 'join', inputs=2, channel_layout='stereo')
    .output(input_video.video, 'output-video.mp4', shortest=None, vcodec='copy')
    .overwrite_output()
    .run()
)

Jupyter Frame Viewer

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7ajLbHN-1676388632668)(./Markdown%E5%9B%BE%E5%BA%8A/Python%E7%9A%84%E9%9F%B3%E8%A7%86%E9%A2%91%E5%A4%84%E7%90%86/jupyter-screenshot.png)]

Tensorflow Streaming

在这里插入图片描述

  • ffmpeg 解码输入视频
  • tensorflow 使用 “deep dream” 处理视频
  • ffmpeg 编码输出视频
process1 = (
    ffmpeg
    .input(in_filename)
    .output('pipe:', format='rawvideo', pix_fmt='rgb24', vframes=8)
    .run_async(pipe_stdout=True)
)

process2 = (
    ffmpeg
    .input('pipe:', format='rawvideo', pix_fmt='rgb24', s='{}x{}'.format(width, height))
    .output(out_filename, pix_fmt='yuv420p')
    .overwrite_output()
    .run_async(pipe_stdin=True)
)

while True:
    in_bytes = process1.stdout.read(width * height * 3)
    if not in_bytes:
        break
    in_frame = (
        np
        .frombuffer(in_bytes, np.uint8)
        .reshape([height, width, 3])
    )

    # See examples/tensorflow_stream.py:
    out_frame = deep_dream.process_frame(in_frame)

    process2.stdin.write(
        out_frame
        .astype(np.uint8)
        .tobytes()
    )

process2.stdin.close()
process1.wait()
process2.wait()

在这里插入图片描述

FaceTime webcam input (OS X)

(
    ffmpeg
    .input('FaceTime', format='avfoundation', pix_fmt='uyvy422', framerate=30)
    .output('out.mp4', pix_fmt='yuv420p', vframes=100)
    .run()
)

Stream from a local video to HTTP server

video_format = "flv"
server_url = "http://127.0.0.1:8080"

process = (
    ffmpeg
    .input("input.mp4")
    .output(
        server_url, 
        codec = "copy", # use same codecs of the original video
        listen=1, # enables HTTP server
        f=video_format)
    .global_args("-re") # argument to act as a live stream
    .run()
)

在终端中使用 ffplay 接收视频

$ ffplay -f flv http://localhost:8080

Stream from RTSP server to TCP socket

packet_size = 4096

process = (
    ffmpeg
    .input('rtsp://%s:8554/default')
    .output('-', format='h264')
    .run_async(pipe_stdout=True)
)

while process.poll() is None:
    packet = process.stdout.read(packet_size)
    try:
        tcp_socket.send(packet)
    except socket.error:
        process.stdout.close()
        process.wait()
        break

自定义过滤器

虽然 ffmpeg-python 只直接提供了部分ffmpeg 的过滤器 API,但仍可以通过 .filter 运算符见解引用全部的过滤器

python">(
    ffmpeg
    .input('dummy.mp4')
    .filter('fps', fps=25, round='up')
    .output('dummy2.mp4')
    .run()
)
多个输入

接收多个输入流的过滤器可以通过将输入流作为数组传递给 ffmpeg.filter 使用

python">main = ffmpeg.input('main.mp4')
logo = ffmpeg.input('logo.png')
(
    ffmpeg
    .filter([main, logo], 'overlay', 10, 10)
    .output('out.mp4')
    .run()
)
多个输出

产生多个输出的过滤器可以与 .filter_multi_output 一起使用

python">split = (
    ffmpeg
    .input('in.mp4')
    .filter_multi_output('split')
)
(
    ffmpeg
    .concat(split[0], split[1].reverse())
    .output('out.mp4')
    .run()
)

http://www.niftyadmin.cn/n/68054.html

相关文章

机器学习笔记之生成模型综述(五)重参数化技巧(随机反向传播)

机器学习笔记之生成模型综述——重参数化技巧[随机反向传播]引言回顾神经网络的执行过程变分推断——重参数化技巧重参数化技巧(随机反向传播)介绍示例描述——联合概率分布示例描述——条件概率分布总结引言 本节将系统介绍重参数化技巧。 回顾 神经网络的执行过程 上一节…

10 个很棒的 JavaScript 字符串技巧

字符串是几乎所有编程语言中的基本类型之一。以下10 个重要的JS技巧可能是你不知道的。 那么,我们现在就开始吧。 1.如何多次复制一个字符串 JS 字符串允许简单的重复,不同于纯手工复制字符串,我们可以使用字符串重复的方法。 const laughi…

太上感应篇

太上感应篇原文 太上曰。祸福无门。惟人自召。善恶之报。如影随形。 是以天地有司过之神。依人所犯轻重。以夺人算。算减则贫耗。多逢忧患。人皆恶之。刑祸随之。吉庆避之。恶星灾之。算尽则死。 又有三台北斗神君。在人头上。录人罪恶。夺其纪算。又有三尸神。在人身中。每…

java 程序员怎么做找工作

java 程序员怎么做找工作 在网络招聘网站上搜索职位。在中国,像智联招聘、前程无忧、猎聘网等招聘网站上,有许多公司在招聘JAVA程序员。通过这些网站可以快速找到自己合适的工作。 关注社交媒体和专业网站。 加入一些面向JAVA程序员的社交媒体和专业网…

LeetCode 刷题之动态规划【Python版】

1. 斐波那契数列 509. 斐波那契数 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:F(0) 0,F(1) 1 F(n) F(n - 1) F(n - 2)&a…

Dubbo的基本使用(二)

目录​​​​​​​ ​​​​​​​1、Dubbo的基本介绍 1.1、基本概念 1.2、RPC 1.2.1、序列化和反序列化 2、zookeeper(注册中心) 2.1、zookeeper 下载 2.2、zookeper安装使用(window版) 2.3、zookeper安装使用&#xff…

空间误差分析:统一的应用导向处理(Matlab代码实现)

💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…

共享—1658页《Java面试突击核心手册》几乎覆盖市面上所有面试考点

说快也快,说不快也不慢! 年前,陆陆续续,好多大厂都在裁员; 年后,又有一大批程序员失业,找不到避风港; 这时候,就有人说了,为什么找工作这么难?…