打造m3u8视频(流视频)下载解密合并器(kotlin)
本⽂是对我原创⼯具关键代码解析及软件实现的思路的讲解,想要⼯具的请跳转链接
1.思路说明
思路挺简单,具体步骤如下:
下载m3u8⽂件
解析m3u8⽂件获得ts⽂件列表
根据⽂件列表批量下载ts⽂件
进⾏ts的解密操作(如果没有加密则跳过此步骤)
将解密后的⽂件或未加密的ts⽂件按照m3u8中的列表顺序进⾏合并,得到mp4⽂件
可以把Kotlin看作为Java语⾔的增强版,Java中的知识Kotlin也是通⽤的
本⽂涉及到知识如下:
String字符串的处理
IO流,读⽂件进⾏读写
⽹络编程
AES解密(其实我也不是很懂)
2.m3u8格式介绍
可能这个格式⼤家不是很了解,其实现在⼤家看的⼤多数在线视频,都是使⽤了m3u8⽂件来实现在线视频播放的。
M3U8 是 Unicode 版本的 M3U,⽤ UTF-8 编码。"M3U" 和 "M3U8" ⽂件都是苹果公司使⽤的 HTTP Live Streaming(HLS)协议格式的基础,这种协议格式可以在 iPhone 和 Macbook 等设备播放。
简单地来说,m3u8就是⼀个播放列表,⾥⾯保存这多个短视频的地址,之后服务器从此⽂件中按照顺序依次下载ts⽂件并进⾏播放。
ts⽂件也可以看做为mp4⽂件,可以直接拿QQ影⾳等软件打开,但这只限于未加密的ts⽂件
可能有些⼩伙伴会发现,有些ts⽂件直接打开软件会提⽰不⽀持解析此⽂件,这其实就是因为ts⽂件已经被加密了。
我们可以以⽂本的⽅式打开m3u8的⽂件,内容如下:
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXTINF:9.009,
ample/first.ts
#EXTINF:9.009,
ample/second.ts
#EXTINF:3.003,
ample/third.ts
...
上⾯的是未加密的m3u8⽂件内容,我们来看看加密的m3u8⽂件:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="key.key"
#EXTINF:10.000000,
00000.ts
#EXTINF:10.000000,
00001.ts
#EXTINF:10.000000,
00002.tskotlin多线程
#EXTINF:10.000000
...
PS:想要了解m3u8格式更多的资料,请查看我底下的参考链接
这⾥提⼀下获取m3u8⽂件的⽅式,可以通过浏览器F12进⼊调试模式,之后到m3u8的⽹址资源,或者是通过猫抓(Chrome插件)获取链接,猫抓插件安装请⾃⾏百度
3.解析m3u8⽂件获取信息
由上⾯我们⼤概了解到了m3u8⽂件⾥⾯的内容,我们将m3u8⽂件下载到本地之后,可以得到两个信息,key⽂件地址(如果采⽤了加密的话)和全部的ts⽂件地址
#EXTM3U
#EXT-X-TARGETDURATION:10
#EXTINF:9.009,
ample/first.ts
#EXTINF:9.009,
ample/second.ts
#EXTINF:3.003,
ample/third.ts
...
上⾯的这个是没有采⽤加密的,⽽且,ts⽂件都是给出了具体的⽹址,这是极为理想的情况,但是市⾯上⼤部分不会采⽤这样的,⼀般都是像下⾯的这种格式:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x12345(可能有)
#EXTINF:10.000000,
00000.ts
#EXTINF:10.000000,
00001.ts
#EXTINF:10.000000,
00002.ts
#EXTINF:10.000000
...
上⾯的m3u8⽂件采⽤了加密,⽽且ts⽂件都是只有编号,没有⽹址,⽽且key⽂件也是⾮常的简短,根本就不是⼀个地址,这种情况我们就得进⾏字符串的拼接处理。
⼀般的⽹站,会将m3u8⽂件、key⽂件(有加密的话)、ts⽂件都是放在同⼀路径
⽐如说现在有个m3u8的地址为/2020/1/14/m3u8.m3u8,使⽤了加密,所以它的key⽂件
为/2020/1/14/key.key,ts⽂件为/2020/1/14/0000.ts
上⾯只是个简单的例⼦,具体的⽹站还得具体分析,可以使⽤抓包进⾏分析。
现在来对上⾯的m3u8⽂件进⾏简单地分析吧:
采⽤了AES-128进⾏了加密,key的地址为key.key,偏移量IV为12345,有些是没有使⽤偏移量,则可以使⽤0来代替
我们通过解析m3u8⽂件,⾸先是获得key⽂件和所有ts⽂件的地址,然后进⾏下载即可
通⽤的下载代码(下载m3u8⽂件、key⽂件、ts⽂件):
/**
* 下载⽂件到本地
* @param url ⽹址
* @param file ⽂件
*/
private fun downloadFile(url: String, file: File) {
val conn = URL(url).openConnection()
conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)")
val bytes = InputStream().readBytes()
file.writeBytes(bytes)
}
4.ts⽂件下载优化
ts⽂件过多,如果只开启⼀个单线程进⾏下载,下载太慢了,所以,可以采⽤多线程进⾏下载
这⾥的话,由于之前解析可以获得⼀个ts⽂件地址的列表,把此列表分为⼏份列表,每份列表开启⼀个⼦线程来进⾏下载,这样便可以保证任务的并发性,提⾼了下载速度。
这⾥,稍微有点复杂,因为要把列表划分成⼏份列表,我⼤概是这样分的:
⾸先,计算出列表可以平均分为⼏份,每份列表的数⽬,之后再将剩下的列表分为⼀份,但是,使⽤循环的话不是很好写,所以就先把第⼀个列表和最后⼀个列表分好,之后来个循环,将中间的平分完。
/**
* 下载ts⽂件
* @param threadCount 线程数(默认开启5个线程下载,速度较快,100M宽带测试速度有17M/s)
*/
fun downloadTsFile(threadCount: Int = 5) {
val countDownLatch = CountDownLatch(threadCount)
//每份列表的数⽬
val step = tsUrls.size / threadCount
//最后列表的数⽬(剩下的)
val yu = tsUrls.size % threadCount
//第⼀份列表
thread {
val firstList = tsUrls.take(step)
downloadTsList(firstList)
}
//最后⼀份列表
thread {
val lastList = tsUrls.takeLast(step + yu)
downloadTsList(lastList)
}
//中间的平分
for (i hreadCount - 2) {
val list = tsUrls.subList(i * step, (i + 1) * step + 1)
thread {
downloadTsList(list)
}
}
countDownLatch.await()
println("所有ts⽂件下载完毕")
}
上⾯的使⽤了CountDownLatch类的对象进⾏线程的控制,只有当所有线程完成之后,此⽅法才算结束
5.ts⽂件解密
先上代码,之后再细讲:
//1.获得key和iv的字符串
val keyString = "2e9515db8fe8358bc8fcf6ae601a00be"
val ivString = "d0817f83115d911241fe8ba17673f120"
//2.获得key和iv的bytes数组
val keyBytes = decodeHex(keyString)
val ivBytes = decodeHex(ivString)
/
/3.key数组转为SecretKeySpec对象,iv数组转为IvParameterSpec
val algorithm = "AES"
val skey = SecretKeySpec(keyBytes, algorithm)
val iv = IvParameterSpec(ivBytes)
//4. 初始化cipher
val transformation = "AES/CBC/PKCS5Padding"
val cipher = Instance(transformation)
cipher.init(Cipher.DECRYPT_MODE,skey,iv)
//5. 解密,
val tsFile = File("Q:\\m3u8破解\\2273\\440.ts")
val result = cipher.adBytes())
val newFile = File("Q:\\m3u8破解\\2273\\440_s.ts")
//6.写⼊⽂件
BufferedOutputStream(FileOutputStream(newFile)).write(result)
key⽂件本质是⼀个16字节⽂件,我们可以通过winhex等软件查看⾥⾯的内容
不过,查看出来之后的内容,我们还得进⾏转换,因为是字符串,所以得调⽤decodeHex⽅法,将字符串转为bytes数组
所以,直接使⽤代码查看更为⽅便,Kotlin中可以直接读取bytes(如果使⽤Java的话,推荐使⽤common-io的第三⽅jar包),如:
val keyFile = File("Q:\\test\key.key")
//获得bytes数组
val bytes = adBytes()
PS:对了,如果m3u8⽂件中没有使⽤到IV偏移量,直接使⽤0即可(要保证bytes数组的长度为16),如
果使⽤了IV的话,要使⽤decodeHex⽅法转为bytes数组
val ivBytes = if (ivString.isBlank()) byteArrayOf(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0) else decodeHex(ivString)
/**
* 将字符串转为16进制并返回字节数组
*/
private fun decodeHex(input: String): ByteArray {
val data = CharArray()
val len = data.size
if (len and 0x01 != 0) {
try {
throw Exception("Odd number of characters.")
} catch (e: Exception) {
e.printStackTrace()
}
}
val out = ByteArray(len shr 1)
try {
var i = 0
var j = 0
while (j < len) {
var f = toDigit(data[j], j) shl 4
j++
f = f or toDigit(data[j], j)
j++
out[i] = (f and 0xFF).toByte()
i++
}
} catch (e: Exception) {
e.printStackTrace()
}
return out
}
@Throws(Exception::class)
private fun toDigit(ch: Char, index: Int): Int {
val digit = Character.digit(ch, 16)
if (digit == -1) {
throw Exception("Illegal hexadecimal character $ch at index $index")
}
return digit
}
有了key⽂件和IV偏移量的bytes,我们就可以往下⾛了,下⾯的代码其实都没有什么好说明的,明眼⼈估计⼀看就懂了,这⾥就不多说了
需要注意的是,因为解密之后,我们还需要把所有已经解密好的ts⽂件按照顺序合并成⼀个mp4⽂件,所以,注意解密后数据的名字。
建议在保存原来编号的基础上,加上写简短的字母,之后,就可以通过contains⽅法进⾏判断是否⽂
件名是否符合条件
6.ts⽂件合并
合并的话,使⽤IO流,按照顺序依次把流追加到末尾即可
参考

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。