本文是對我原創工具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解密