下载芒果TV PC版视频并转换成 mp4 的简单实现

视频 2020-06-29 阅读 902 评论 0

需求分析

也许是因为版权越来越受重视,百度上已经很少出现各大视频平台的下载链接了,但是有时候下载却又很又必要,比如做课件。

这里简单说明下载芒果TV PC版视频的流程。芒果TV的视频播放,主要用了2种流媒体协议,HLS(比较普遍)和 MPEG DASH(一般用在vip视频中)。HLS 比较简单,就先介绍一下。

HLS协议

HLS (HTTP Live Streaming)是Apple的动态码率自适应技术。主要用于PC和Apple终端的音视频服务。包括一个m3u(8)的索引文件,TS媒体分片文件和key加密串文件。

使用 ffmpeg 这个工具,可以将一个视频切成多个 Segments,如

$ ffmpeg -i foo.mp4 -g 25 -hls_time 30 -hls_list_size 0 index.m3u8

这个命令把视频按 30 秒切成 Segments。命令执行成功后,会在当前目录下生成一个 m3u8 文件和一系列的 ts 文件。在 HLS 中,每个 Segments 都是可以独立播放的 MPEG-2 TS 文件,而 m3u8 的作用就是明确这些 ts 文件的顺序。m3u8 文件是纯文本格式,可以方便的阅读修改。命令行参数中,-g 用来指定按 frame 切视频,而 -hls_time 指定 Segments 的长度为 30s。这两个参数可以限定切出来的 Segments 基本符合 30s 一段的规则。所以在使用 -g 时需要先确定源视频文件的 fps 后再设定。不过,即便如此,也有一些 Segments 的长度会有 30s 以内的偏差,应该是无法避免的了。-hls_list_size 表示最后生成的 m3u8 中列出的ts文件的数目,默认是 5,此处写 0 表示把所有的 ts 文件都列上,实际使用中可以适当设置以减少 m3u8 文件大小。

$ ls
foo.mp4  index.m3u8  index0.ts	index1.ts  index2.ts  index3.ts  index4.ts  index5.ts  index6.ts  index7.ts  index8.ts
$ cat index.m3u8 
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:31
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:30.000000,
index0.ts
#EXTINF:30.680000,
index1.ts
#EXTINF:29.720000,
index2.ts
#EXTINF:29.800000,
index3.ts
#EXTINF:29.920000,
index4.ts
#EXTINF:30.480000,
index5.ts
#EXTINF:29.600000,
index6.ts
#EXTINF:30.200000,
index7.ts
#EXTINF:4.360000,
index8.ts
#EXT-X-ENDLIST

合并芒果TV视频

了解 HLS 之后,我们可以反过来,首先找到 m3u8 这个文件,再将文件中提到的 ts 文件按照顺序下载下来并合并在一起。以芒果TV的小伴龙学唐诗为例,网址是 https://www.mgtv.com/b/329118/5447195.html,打开 Google Chrome,按 F12 进入调试模式,再输入网址,搜索 m3u8?,如下图。

我们可以看到4个 url,代表标清、高清、超清、蓝光,右边可以看到内容如 EXT-MGTV-VIDEO-WIDTH:848EXT-MGTV-VIDEO-HEIGHT:480,表示视频的分辨率为 848x480。

bash 实现视频下载

这里以 Linux/Unix 的 bash 来实现视频的下载,主要是使用 curl 来下载 ts 视频文件,其他语言的实现也是同样道理。

mgtv.sh 代码如下:

#!/bin/bash

# m3u8 的路径
url="https://pcvideoyf.titan.mgtv.com/c1/2019/04/10_0/76C2189E9627236DF71693A88C3C7F2F_20190410_1_1_163_mp4/0BC0A6ED17C9A100D09141E6D91FE034.m3u8?arange=0&pm=DrSWt_7WjKMQOXAID843tmYD1WOMnNS28LoZT6TYAil54VSzeDTzIbt0RRnOdCjX8gLe0QBsTvCGB0zt1r0WZCjQvm3LA9Dn3KjVeoeZqCKKgYjdGOPJofUK8f04_N8IqCLODB~aGDG6siKPF7U~BFGUAoXhloCZHU7sjhk3S8swoloEfCFtgYQwZLLarMiDL4Rp3WvZTmVntN1H8NMU_RvDcKQj2f843NFMMnP98BrugmJLqlyHK4XCKvInSInTXH5nLt4MpgS2_ky8rC1TDxZ7jtdEJZAEUQnM8ZabgHT606ghT88d8kplHcdW5_K9_~xVLIkcluYj4EpNbwfA0l4HtyrvWgWnp8a~~i0sNJJANjVlvCWEH1J6vpL1hVhUXbx6MgVmwUOhnZCfAztYMcJKr6F6WM1X6ksu86xs4yD2rtV0fNSoIFQGW44_DCaQ1opl8dTOf161CTfUfXoVu2iOqjnlH1VM43c1nCaGxTAPZDvj&mr=2bDnvprpOyoifIKOJRM9uOXh~pG~l0k4dDa_FRHZ~KfaIPh6LOxrJpJ_M2VMwRpUuR99h1jUqFyoT6dPX1TdBR9OzTzLl6rr6oQGv5hM56fsePk0GxkPGzJ8zHvki5EvWmfwK4ZiPsf6CsZyvWFNEJP9YRG94Qs7gvKRD~CW10v9gUJwOvzJOBUMtxLMVN5KunfE2Q~t7wVI0lxUoSxvYQ0BQvi4BvhZ~dtLDQ--&uid=cfd677d9e6494dfba94a4c07df37bb62&vcdn=0&scid=25022&_t=1593431532949"
# 存放单个 ts 的临时路径
temp_ts=./temp.ts
# 多个 ts 视频合并的路径
temp_video=./video.ts

# 下载 ts
downloadTs() {
    downloadTsUrl=$1
    path=$2
    shortUrl=${downloadTsUrl%%\?*}
    shortUrl=${shortUrl##*\/}
    http_code=$(curl -o "$temp_ts" -H "referer: https://www.mgtv.com" -s -w "%{http_code}\n" "$downloadTsUrl")
    if [[ "$http_code" == 200 ]]; then
        echo "$shortUrl" ok
        cat $temp_ts >> "$path"
    else
        echo "$shortUrl" error "$http_code"
    fi
}

# 删除文件
removeFile() {
    if [[ -e $1 ]]; then
        rm "$1"
    fi
}

> $temp_video
host="${url%\/*}/"

echo "------------- 开始下载 -------------"
savePath=$temp_video
# 获取 ts 数组
while IFS= read -r line
do
    if [[ "$line" == "#"* ]]; then
        continue
    fi
    url="$host$line"
    downloadTs "$url" "$savePath"
done < <(curl -s -H "referer: https://www.mgtv.com" "$url")
# 删除临时文件
removeFile "$temp_ts"

运行示例

$ ./mgtv.sh
------------- 开始下载 -------------
C23171BCF9E1AA2727B4CA0229D7DB19_0_5080_273_v02_mp4.ts ok
F07C7DA8E6971EEB90388BBE4A9D30A4_5080_10080_341_v02_mp4.ts ok
0B60091110888F9595690A1F3FD40EB1_10080_20080_200_v02_mp4.ts ok
2E8BA43F5DAD737C946D027ED4762D13_20080_30080_235_v02_mp4.ts ok
5236A1AB13DDA680C60D69688FEA4CEE_30080_40080_236_v02_mp4.ts ok
DB39019F28170DF2FD218831DBD7CF14_40080_50080_193_v02_mp4.ts ok
67A38E8C222BB6F4A3B746E77E759CEF_50080_60080_181_v02_mp4.ts ok
ACB0FE63853A2F70850907F7E4719D98_60080_70080_181_v02_mp4.ts ok
0DD173CE5205788C57D62E09A2D0FD92_70080_80080_218_v02_mp4.ts ok
968CC0B1FE91973711B9904F22D8FEF6_80080_90080_148_v02_mp4.ts ok
8CCCC868D6097CA7821C2B7D3F26E4E9_90080_100080_141_v02_mp4.ts ok
7FB380048D8088B60732C681ADC3AD7E_100080_110080_134_v02_mp4.ts ok
2E34E098AE669D497AB4B3FB7F962BA5_110080_120080_151_v02_mp4.ts ok
A72582FBAF8741A63456AD4575DFD954_120080_130080_137_v02_mp4.ts ok
55E4AAC0D13605B2D6A1E1F60271F9EF_130080_140080_148_v02_mp4.ts ok
976009868673C1F7FB6E3FFD58D99E75_140080_150080_146_v02_mp4.ts ok
4EE72AC6D7333B33A19B79FD41F1E787_150080_160080_205_v02_mp4.ts ok
F48DC2BDE25B7A68DF6401A78E929ABB_160080_170080_205_v02_mp4.ts ok
2489CA42882752F4918040D0B9742DD1_170080_178160_178_v02_mp4.ts ok

ts 转换成 mp4

ts 转换成 mp4,可以使用 ffmpeg 工具,运行:

ffmpeg -i video.ts -c copy video.mp4
最后更新 2020-06-29
MIP.watch('startSearch', function (newVal, oldVal) { if(newVal) { var keyword = MIP.getData('keyword'); console.log(keyword); // 替换当前历史记录,新增 MIP.viewer.open('/s/' + keyword, {replace: true}); setTimeout(function () { MIP.setData({startSearch: false}) }, 1000); } }); MIP.watch('goHome', function (newVal, oldVal) { MIP.viewer.open('/', {replace: false}); });