1. 背景
通过在 chrome
或其他浏览器的 F12-Network
中发现, 当前网页播放的视频是通过一段一段的 ts
文件加载解析播放的;
如果需要下载该视频, 就需要将所有的 ts
文件下载下来并合并起来;
2. 方法一: cmd
的 copy /b
指令
在 ts
文件夹下, 打开 cmd
命令行窗口, 执行 copy /b *.ts target.mp4
命令, 将所有 ts
后缀的文件拷贝合并到目标文件 target.mp4
中即可;
需要注意两点:
2.1. 问题1, 序号列位数不一致
如果 ts
文件的序列号, 不是相同的位数, 即 0-9
是一位序列号, 10-99
是两位序列号, 依次类推, 而不是像上图所示一样, 前面会补零, 保证序列号位数一致;
这种情况下用该命令会导致合并文件时的顺序产生偏差, 最后合并的结果文件无法播放;
解决方法
可以手动将所有 ts
文件进行前置补零操作, 保证序列号的位数一致, 然后再使用该命令;
或者使用一下的 bat
脚本处理(原知乎作者提供, 本人未验证, 😃 因为直接跳过用方法二来着)
@echo off
set /p n=起始数字:
set /p end=结束数字:
copy %n%.ts out.ts
set num=%n%-%end%
:home
set /a n+=1
echo Y | copy /b out.ts+%n%.ts temp.ts && move /y temp.ts out.ts
if not %n%==%end% goto home
move /y out.ts out_%num%.mp4
pause
2.2. 问题2, 只适用于未加密
该方法只适用于合并后的 ts
文件(可能手动转成其他格式的视频文件) 可以直接播放的, 未经过加密的情况;
视频是否加密的判断, 可以在浏览器的 F12-Network
下面观察, 在第一个 *.ts
文件加载之前, 会提前加载一个 *.m3u8
的文件, 观察该文件的前面几行是否有一行的内容是表示该视频流的加密 key
的, 以下图为例:
3. 方法二: 自动化下载 ts 文件以及合并
方法一中, 需要手动下载所有的 ts
文件, 然后进行合并处理, 是一件很繁琐的事情;
该方法则通过 python
编写脚本来自动化处理该过程;
由上面的 2.2
中知道, 一个 ts
视频流加载的最初, 会优先加载一个 m3u8
后缀的文件, 该文件里标注了所有 ts
文件的名称, 以及起始时间位置等其他信息;
所以可以编写脚本, 通过该文件依次下载 ts
文件并进行合并操作;
3.1. 从 m3u8
文件中提取 ts
列表
def get_ts_list(file_path):
'''
加载 .m3u8 文件, 返回 ts 列表
'''
f = open(file=file_path, mode='r', encoding='UTF-8')
lines = f.readlines()
pattern_0 = r'#EXT-X-KEY:METHOD=(.*?),URI="(.*?)"'
pattern_1 = r'(.*?\.ts)'
method = ''
uri = ''
ts_list = []
for line in lines:
matchObj = re.match(pattern_0, line, re.I)
if matchObj:
method = matchObj.group(1)
uri = matchObj.group(2)
continue
matchObj = re.match(pattern_1, line, re.I)
if matchObj:
ts_list.append(matchObj.group(1))
continue
return uri, ts_list
其中
pattern_0
正则表达式是用来匹配获取加密key
的uri
地址的;
3.2. 根据源 m3u8
文件生成本地 m3u8
文件
m3u8
中由两部分需要替换成本地对应文件路径的:
- 加密
key
文件(#EXT-X-KEY:METHOD=AES-128,URI="(.*?)"
URI 后面那一部分), 替换成下载下来的key
文件; - 每个
ts
文件名称, 替换成本地对应的ts
文件路径;
注意路径的分隔符;
def generate_local_m3u8(src_file, target_file, local_key_file, base_path):
'''
根据下载下来的 src_file 文件生成对应的本地 target_file 文件
其中 key 替换成已下载下来的本地 key 文件路径
ts 名称替换为本地的 ts 文件路径
'''
local_key_file = local_key_file.replace('\\', '/')
base_path = base_path.replace('\\', '/')
with open(file=src_file, mode='r', encoding='UTF-8') as f:
lines = f.readlines()
pattern_0 = r'#EXT-X-KEY:METHOD=AES-128,URI="(.*?)"'
pattern_1 = r'(.*?\.ts)'
new_lines = []
for line in lines:
matchObj = re.match(pattern_0, line, re.I)
if matchObj:
new_lines.append('#EXT-X-KEY:METHOD=AES-128,URI="' +
local_key_file + '"\n')
continue
matchObj = re.match(pattern_1, line, re.I)
if matchObj:
new_lines.append(base_path + "/" + matchObj.group(1) + "\n")
continue
new_lines.append(line)
with open(target_file, mode='w', encoding='UTF-8') as f:
f.writelines(new_lines)
3.3. 调用 FFmpeg
合并
使用 python
的 ffmpeg3
包, 需要准备两步:
- 执行
pip install ffmpy3
命令安装ffmpy3
库; -
ffmpeg
官网 下载 windows 版本的压缩包;
下载解压缩后, 配置 环境变量路径
里的 path
, 添加解压缩后的文件夹里的 bin
目录到 path
中;
然后即可在 python
中使用 FFmpeg
(如果没有下载解压缩配置环境变量这一步, 会提示 No Module...
异常)
ff = FFmpeg(
inputs={ '上面生成的本地 m3u8 文件路径': '-allowed_extensions ALL'},
outputs={ "输出的视频文件.mp4" : '-c copy'})
print(ff.cmd)
ff.run()
注意
这里需要注意一下, 本地的m3u8
文件里的涉及到的文件路径问题, 包括加密key
文件路径以及各个ts
文件的路径, 这些路径可以使用绝对路径(注意分隔符用/
), 也可以使用相对路径, 但是相对路径是相对m3u8
文件来说的, 而不是相对于你编写的python
脚本来说的, 切记, 否则执行FFmpeg
合并文件的时候, 会找不到对应的文件的;
4. 方法三: 插件 猫抓
使用 chrome 扩展插件 猫抓
, 源码下载地址
一般可以通过猫抓
获取 m3u8
文件, 然后放入自己写的 python 脚本跑下载就可以了~
5. 资源
本文参考来源: https://zhuanlan.zhihu.com/p/94852550