本文是对我原创工具m3u8视频下载合并器关键代码解析及软件实现的思路的讲解,想要工具的请跳转连接html
思路挺简单,具体步骤以下:git
能够把Kotlin看做为Java语言的加强版,Java中的知识Kotlin也是通用的编程
本文涉及到知识以下:数组
可能这个格式你们不是很了解,其实如今你们看的大多数在线视频,都是使用了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, http://media.example.com/first.ts #EXTINF:9.009, http://media.example.com/second.ts #EXTINF:3.003, http://media.example.com/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.ts #EXTINF:10.000000 ...
PS:想要了解m3u8格式更多的资料,请查看我底下的参考连接
这里提一下获取m3u8文件的方式,能够经过浏览器F12进入调试模式,以后找到m3u8的网址资源,或者是经过猫抓(Chrome插件)
获取连接,猫抓插件安装请自行百度
由上面咱们大概了解到了m3u8文件里面的内容,咱们将m3u8文件下载到本地以后,能够获得两个信息,key文件地址(若是采用了加密的话)和所有的ts文件地址
#EXTM3U #EXT-X-TARGETDURATION:10 #EXTINF:9.009, http://media.example.com/first.ts #EXTINF:9.009, http://media.example.com/second.ts #EXTINF:3.003, http://media.example.com/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的地址为www.xxx.com/2020/1/14/m3u8.m3u8
,使用了加密,因此它的key文件为www.xxx.com/2020/1/14/key.key
,ts文件为www.xxx.com/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 = conn.getInputStream().readBytes() file.writeBytes(bytes) }
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) countDownLatch.countDown() } //最后一份列表 thread { val lastList = tsUrls.takeLast(step + yu) downloadTsList(lastList) countDownLatch.countDown() } //中间的平分 for (i in 1..threadCount - 2) { val list = tsUrls.subList(i * step, (i + 1) * step + 1) thread { downloadTsList(list) countDownLatch.countDown() } } countDownLatch.await() println("全部ts文件下载完毕") }
上面的使用了CountDownLatch类的对象进行线程的控制,只有当全部线程完成以后,此方法才算结束
先上代码,以后再细讲:
//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 = Cipher.getInstance(transformation) cipher.init(Cipher.DECRYPT_MODE,skey,iv) //5. 解密, val tsFile = File("Q:\\m3u8破解\\2273\\440.ts") val result = cipher.doFinal(tsFile.readBytes()) 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 = keyFile.readBytes()
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 = input.toCharArray() 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方法进行判断是否文件名是否符合条件
合并的话,使用IO流,按照顺序依次把流追加到末尾便可
m3u8 文件格式详解
关于m3u8格式的视频文件ts转mp4下载和key加密问题
aes 256 32位key和32位iv
加密ts解密