Python实现FLV视频拼接功能


Posted in Python onJanuary 21, 2020

文章摘要

本文简单说明了FLV文件的格式,以此为出发点,使用 Python 实现FLV视频的拼接。

一.FLV文件格式

关于FLV文件格式的解析网上有诸多文章,在这里就简单介绍一下需要了解的部分,以便读者更好地明白各段代码的功能。

FLV文件是由文件头(Header)和文件体(Body)按顺序拼接而成。审查FLV内容时,以二进制方式读取内容。

Header:文件头表明了文件的封装格式为FLV,存储对象为音频、视频或两者。
以下为FLV文件的Header,共 9 个字节:

b'FLV\x01\x05\x00\x00\x00\t'
前 3 个字节(FLV)说明这是一个FLV文件
第 4 个字节(\x01)为版本号,固定为 1
第 5 个字节(\x05)表明存储对象,需将其转化成二进制(00000101)查看,左、右边的 1 分别表示文件含有音频和视频
后 4 个字节(\x00\x00\x00\t)表示文件头的长度,其值固定为 9

Body:文件体由若干个 Tag 组成,除了第一个,每个 Tag 是由头部( 11 字节)、主体(不定长)和尾部( 4 字节)组成。第一个 Tag 只有尾部。

Tag 又分为 3 类,脚本(scripts)、音频(audio)和视频(video)。通常第 2 个 Tag 为脚本类型,且只有一个,后续的都是音视频类型。

以下为脚本 Tag 的部分,作为示例介绍一下:

头部:b'\x12\x00\tb\x00\x00\x00\x00\x00\x00\x00'
第 1 个字节(\x12)表示 Tag 类型,脚本类型的对应值为 18 ,音频为 8 ,视频为 9
第 2-4 个字节(\x00\tb)表示 Tag 主体的长度,此处为 2402
第 5-7 个字节(\x00\x00\x00)为时间戳,脚本类型的时间戳通常为 0
第 8 个字节(\x00)是时间戳的扩展,当前 3 个字节不够用时会用这个字节当作大端
后 3 个字节(\x00\x00\x00)是 Stream id,固定为 0

主体:脚本 Tag 的主体包含FLV视频的基本信息,如时长、大小、分辨率等,比较复杂,在此不作介绍

尾部:b'\x00\x00\tm'

固定 4 字节,表示 Tag 头部加主体的长度,即 11 + 2402 = 2413

二.FLV视频拼接

将多个FLV视频合成一个可以正常播放的视频,便足够满足大部分的需求。因此,在接下来的拼接过程中,不会对FLV进行细致入微的调整,达到基本要求即可。

设置阅读器

阅读器可以使我们很方便地读取文件内容。

class Reader():
  def __init__(self, content): # content (bytes):FLV文件的二进制内容
    self.content = content
    self.start = 0
    self.eof = False # 判断是否已读完全部内容
    self.length = len(self.content)
    
  def read(self, n=1):
    # 设置 if 语句防止过度读取内容
    if self.length > (self.start + n):
      out = self.content[self.start:self.start + n]
      self.start += n
    else:
      out = self.content[self.start:]
      self.eof = True
    return out

向新建FLV文件写入 Header 和 Tag

在这里假设要拼接的视频基本信息相似,即都含有音视频,分辨率、码率等相同或相近。

为了生成一个可以正常播放的FLV视频,Header 和 Tag 是必不可少的。我们可以选取第一个FLV的文件头写入新建FLV中,然后依次将修改过时间戳的 Tag 写入其中,便可达到拼接目的。

def add_flv(flv, target, videoTimeStamp, audioTimeStamp): # 修改并添加 Tag 的函数
  with open(flv, 'rb') as f:
    content = f.read()
  reader = Reader(content)
  header = reader.read(13)
  with open(target, 'ab') as f:
    while not reader.eof: # 一直读取直到读完,此时 reader.eof = True
      dataType = reader.read(1)
      dataSize = reader.read(3)
      timeStamp = int.from_bytes(reader.read(3), 'big') # 将 3 字节转换成整数
      headerRemained = reader.read(4)
      if dataType == b'\t': # 视频
        timeStamp += videoTimeStamp
        videoTS = timeStamp
      if dataType == b'\x08': # 音频
        timeStamp += audioTimeStamp
        audioTS = timeStamp
      timeStamp = timeStamp.to_bytes(3, 'big') # 将整数转换成 3 字节
      tagHeader = dataType + dataSize + timeStamp + headerRemained
      tagData_andSize = reader.read(int.from_bytes(dataSize, 'big') + 4)
      f.write(tagHeader)
      f.write(tagData_andSize)
  return videoTS, audioTS
def merge_flv(flvs, target): # 主函数
  videoTS = 0
  audioTS = 0
  for i, flv in enumerate(flvs):
    with open(flv, 'rb') as f:
      content = f.read()
    reader = Reader(content)
    
    header = reader.read(13) # flvHeader + tagSize0
    if i == 0: # 写入第 1 个FLV视频的文件头
      with open(target, 'wb') as f:
        f.write(header)
        
    videoTS, audioTS = add_flv(flv, target, videoTS, audioTS)

拼接

import time
since = time.time()
flvs = ['m1.flv', 'm2.flv', 'm3.flv', 'm4.flv'] # 视频大小:45MB,20MB,59MB,54MB
target = 't.flv'
merge_flv(flvs, target)
end = time.time()
print('Merging flvs takes {:.2f} s'.format(end - since))
# Merging flvs takes 0.88 s

可以看到,拼接 4 个共 178MB视频用时 0.88 秒。

总结

FLV文件格式还是比较简明的,对数据的要求也是比较宽松的,即便没有对 Scripts 里的参数作调整,拼接后的视频依然能够正常播放。

不过,拼接的视频是有不少隐形问题,如到视频末尾可能会出现音画不同步( 0.5 秒左右)的现象,以及不能够方便地分离出完整的视频和音频。

以上所述是小编给大家介绍的Python实现FLV视频拼接功能,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!
如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!

Python 相关文章推荐
Python中3种内建数据结构:列表、元组和字典
Nov 30 Python
python的类方法和静态方法
Dec 13 Python
Python数据结构与算法之图的最短路径(Dijkstra算法)完整实例
Dec 12 Python
python决策树之C4.5算法详解
Dec 20 Python
python机器学习之KNN分类算法
Aug 29 Python
深入理解Django-Signals信号量
Feb 19 Python
set在python里的含义和用法
Jun 24 Python
pyQt5实时刷新界面的示例
Jun 25 Python
Django如何使用第三方服务发送电子邮件
Aug 14 Python
Python 变量的创建过程详解
Sep 02 Python
Python sklearn库实现PCA教程(以鸢尾花分类为例)
Feb 24 Python
keras 多gpu并行运行案例
Jun 10 Python
TFRecord格式存储数据与队列读取实例
Jan 21 #Python
TensorFlow dataset.shuffle、batch、repeat的使用详解
Jan 21 #Python
使用 tf.nn.dynamic_rnn 展开时间维度方式
Jan 21 #Python
python爬取本站电子书信息并入库的实现代码
Jan 20 #Python
浅谈Tensorflow 动态双向RNN的输出问题
Jan 20 #Python
关于tf.nn.dynamic_rnn返回值详解
Jan 20 #Python
双向RNN:bidirectional_dynamic_rnn()函数的使用详解
Jan 20 #Python
You might like
收藏的一个php小偷的核心程序
2007/04/09 PHP
简单示例AJAX结合PHP代码实现登录效果代码
2008/07/25 PHP
php 搜索框提示(自动完成)实例代码
2012/02/05 PHP
全新Mac配置PHP开发环境教程
2016/02/03 PHP
php求今天、昨天、明天时间戳的简单实现方法
2016/07/28 PHP
PHP面向对象五大原则之单一职责原则(SRP)详解
2018/04/04 PHP
PHP页面静态化――纯静态与伪静态用法详解
2020/06/05 PHP
jQuery源码解读之removeClass()方法分析
2015/02/20 Javascript
Jquery网页内滑动缓冲导航的实现代码
2015/04/05 Javascript
javascript实现验证IP地址等相关信息代码
2015/05/10 Javascript
javascript每日必学之封装
2016/02/23 Javascript
js鼠标单击和双击事件冲突问题的快速解决方法
2016/07/11 Javascript
js 奇葩技巧之隐藏代码
2017/08/11 Javascript
JS实现简单的选择题测评系统代码思路详解(demo)
2017/09/03 Javascript
Angular2学习笔记之数据绑定的示例代码
2018/01/03 Javascript
JS解析后台返回的JSON格式数据实例
2018/08/06 Javascript
解决vue-router在同一个路由下切换,取不到变化的路由参数问题
2018/09/01 Javascript
vue中的router-view组件的使用教程
2018/10/23 Javascript
vue 实现模糊检索并根据其他字符的首字母顺序排列
2019/09/19 Javascript
[38:23]完美世界DOTA2联赛循环赛 FTD vs PXG BO2第二场 11.01
2020/11/02 DOTA
Python实现类继承实例
2014/07/04 Python
在Django的form中使用CSS进行设计的方法
2015/07/18 Python
对python append 与浅拷贝的实例讲解
2018/05/04 Python
python实现中文文本分句的例子
2019/07/15 Python
Pytorch 神经网络—自定义数据集上实现教程
2020/01/07 Python
Python 通过监听端口实现唯一脚本运行方式
2020/05/05 Python
Python中关于logging模块的学习笔记
2020/06/03 Python
CSS3 对过渡(transition)进行调速以及延时
2020/10/21 HTML / CSS
澳大利亚拥有最佳跳伞降落点和最好服务的跳伞项目运营商:Skydive Australia
2018/03/05 全球购物
馥蕾诗美国官网:Fresh美国
2019/10/09 全球购物
机械系毕业生求职信
2014/05/28 职场文书
工作总结与自我评价
2014/09/18 职场文书
2016年综治和平安建设宣传月活动总结
2016/04/01 职场文书
python 经纬度求两点距离、三点面积操作
2021/06/03 Python
React 并发功能体验(前端的并发模式)
2021/07/01 Javascript
MySQL 计算连续登录天数
2022/05/11 MySQL