m3u8视频下载和转码mp4

看到一部电影很喜欢,想下载保存收藏,但是很多影视网站出于版权和成本(节省带宽)原因,都不提供下载功能了;

既然能通过网页播放,肯定可以想方法缓存起来;F12看下控制台,每隔几秒就会下载一个二进制文件。请求一开始会下载一个m3u8的文件,可以看作是一个索引目录(包含了所有数据分片的地址);

维基:
M3U8 是 Unicode 版本的 M3U,用 UTF-8 编码。“M3U” 和 “M3U8” 文件都是苹果公司使用的 HTTP Live Streaming(HLS) 协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。
HLS 的工作原理是把整个流分成一个个小的基于 HTTP 的文件来下载,每次只下载一些。当媒体流正在播放时,客户端可以选择从许多不同的备用源中以不同的速率下载同样的资源,允许流媒体会话适> 应不同的数据速率。在开始一个流媒体会话时,客户端会下载一个包含元数据的 extended M3U (m3u8) playlist文件,用于寻找可用的媒体流。
HLS 只请求基本的 HTTP 报文,与实时传输协议(RTP)不同,HLS 可以穿过任何允许 HTTP 数据通>过的防火墙或者代理服务器。它也很容易使用内容分发网络来传输媒体流。

比如这个:《肖申克的救赎》

#EXTM3U
#EXT-X-VERSION:3
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-ALLOW-CACHE:YES
#EXT-X-TARGETDURATION:11
#EXTINF:10.427089,
https://ali6.a.yximgs.com/udata/music/music_bb8c3155635a46f19a0d6d953b86fdf60.jpg
#EXTINF:10.427078,
https://ali6.a.yximgs.com/udata/music/music_6fcca30eeade448aab28e23847cb62370.jpg
#EXTINF:10.427089,
https://ali6.a.yximgs.com/udata/music/music_7cfb3cc7aed241328edebb7b15e2a72a0.jpg
#EXTINF:10.427078,
https://ali6.a.yximgs.com/udata/music/music_353347189b484cf28e5bf72960a556b20.jpg
#EXTINF:10.427089,
https://ali6.a.yximgs.com/udata/music/music_d8dc644f4b784c079b77634aa86f02560.jpg

...省略若干个分片

#EXTINF:10.427078,
https://ali6.a.yximgs.com/udata/music/music_1def6db042a14f068ee95a5c8f06e5d10.jpg
#EXTINF:3.336667,
https://ali6.a.yximgs.com/udata/music/music_a3f184eb15404d93b1ebb1f90ab37b6e0.jpg
#EXT-X-ENDLIST

参考:https://developer.apple.com/streaming/

一部完整电影就是依靠这一个个的分片数据累计而成;
https://ali6.a.yximgs.com/udata/music/music_d8dc644f4b784c079b77634aa86f02560.jpg

ios 实现touchid iOS 实现下载m3u8_服务器


下载其中一个片段,修改直接修改后缀为mp4(现代化的播放器一般都支持直接播放m3u8和ts的),发现无法播放,正常m3u8视频串流文件为ts文件,显然该文件做了”处理“,看来这个jpg不是简单改个后缀名。

ios 实现touchid iOS 实现下载m3u8_HTTP_02


参考:https://zhuanlan.zhihu.com/p/523606957

ts都是以 0x47 开始的,使用Hex Editor Neo观察下文件头,果不其然,伪造了文件头。

ios 实现touchid iOS 实现下载m3u8_服务器_03


手动删掉前面字节,然后保存,再用播放器试试可以播放几秒钟视频,证明是可行的;

综合上面的实践;

下载思路如下:

  1. 下载分片文件(curl)
  2. 裁剪掉分片文件的伪造前缀(dd)
  3. 合并分片为mp4(ffmpeg)

考虑文件很多,决定写一个脚本来操作,由于文件是放置在远程服务器,而且文件经过”处理“,我们先统一下载下来,然后再统一截掉文件的伪造头,最后用ffmpeg神器合并成mp4;

# 编译方式安装ffmpeg
# 依赖包安装
yum install autoconf automake gcc gcc-c++ git libtool make nasm pkgconfig zlib-devel -y
# 下载安装源
wget http://www.ffmpeg.org/releases/ffmpeg-4.1.tar.gz
# 解压缩安装包
tar -zxvf ffmpeg-4.1.tar.gz
cd ffmpeg-4.1
./configure --prefix=/usr/local/ffmpeg
# 编译安装
make && make install
# 设置环境变量
echo "export PATH=$PATH:/usr/local/ffmpeg/bin" >> /etc/profile
# 环境变量生效
source /ect/profile
# 查看版本
ffmpeg -version

# 注意
# 若安装过程中出现以下错误:
yasm/nasm not found or too old. Use –disable-yasm for a crippled build.
If you think configure made a mistake, make sure you are using the latest
version from Git. If the latest version fails, report the problem to the
ffmpeg-user@ffmpeg.org mailing list or IRC #ffmpeg on irc.freenode.net.
Include the log file “config.log” produced by configure as this will help
solve the problem.

# 需要安装yasm
wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz
tar -zxvf yasm-1.3.0.tar.gz
cd yasm-1.3.0 # ./configure
make && make install

分片伪装成jpg,这是个相当有意思的事情,这样处理,我原以为是为了混淆加密,实际上应该不是,这”加密“太简单一眼识破。我在这个小视频网站下载了好几部电影,才发现了分片的url对应的服务器,居然都是快手之类的文件存储服务器;

这小站点把视频化整为零,白嫖大厂文件存储服务器,为了绕过厂家的限制,还将其视频伪装成图片文件(类似于图床);我又想起进站的公告提示,hahaha牛皮,还是他们会玩。

ios 实现touchid iOS 实现下载m3u8_HTTP_04

最后给出脚本:

#!/bin/bash
# 本脚本批量下载m3u8分片,再合并成mp4,依赖ffmpeg
# 使用方法:sh downm3u8.sh xxx.m3u8 119
# --参数1:m3u8文件名字,带后缀(和本脚本放在同级目录)
# --参数2:分片跳过的开头字节数,不用跳过请输入0 (处理伪装成jpg/png的情况)
# 最终合并输出路径:同级目录,target/xxx.m3u8/final/xxx.m3u8.mp4

# 输出文件夹相对脚本路径
origin_path=target/$1/origin
transfer_path=target/$1/transfer
# 合并后的mp4文件路径
final_path=target/$1/final
# 分片url匹配前缀
url_prifix="http"
# 新生成的链接本地分片的m3u8文件
merge_m3u8=$1.m3u8
# 跳过的分片头的字节数
skip_byte=$2
# 分片处理计数器
declare -i num=0;
start_time=$(date +%s)

# 下载片段和裁剪字节
dealDown(){
    # tr -d '\r' 去掉文本文件中行末的换行符,要进行删除
    url=$(echo "$1" | tr -d '\r')
    # 源文件名
    origin_name=$(echo ${url##*/})
    # 处理后文件名
    transfer_name=$(echo ${origin_name%\.*}).mp4

    # 下载文件
    curl -s -o $origin_path/$origin_name $url

    # 下载后,改后缀名,再删除前n个字节,jpg伪装
	origin_file=$origin_path/$origin_name
	transfer_file=$transfer_path/$transfer_name
	
    dd if=$origin_file of=$transfer_file bs=16M iflag=skip_bytes skip=$skip_byte >/dev/null 2>&1
    
    echo $transfer_file >> $merge_m3u8
    
    num=$((num+1))
    printf "已下载:\r%d 个,进行中..." $num 
}

# 创建保存目录
mkdir -p $origin_path $transfer_path $final_path

# 创建m3u8合并索引文件,并置空
cat /dev/null > $merge_m3u8

while read line 
do
    if [[ $line =~ http ]]
    then
        dealDown $line
    else
        echo $line >> $merge_m3u8
    fi
done < $1 

echo 下载完成

# 处理 ffmpeg合并m3u8串流
ffmpeg -i $merge_m3u8 -c copy $final_path/$1.mp4

echo ffmpeg合并视频完成

end_time=$(date +%s)
cost_time=$[ $end_time-$start_time ]

echo "开始时间: $start_time"
echo "结束时间: $end_time"
echo "累计耗时: $(($cost_time/60))min $(($cost_time%60))s"

ios 实现touchid iOS 实现下载m3u8_服务器_05