本文是对我原创工具m3u8视频下载合并器关键代码解析及软件实现的思路的讲解,想要工具的请跳转链接

1.思路说明

思路挺简单,具体步骤如下:

  1. 下载m3u8文件
  2. 解析m3u8文件获得ts文件列表
  3. 根据文件列表批量下载ts文件
  4. 进行ts的解密操作(如果没有加密则跳过此步骤)
  5. 将解密后的文件或未加密的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的文件,内容如下:

  1. #EXTM3U
  2. #EXT-X-TARGETDURATION:10
  3. #EXTINF:9.009,
  4. http://media.example.com/first.ts
  5. #EXTINF:9.009,
  6. http://media.example.com/second.ts
  7. #EXTINF:3.003,
  8. http://media.example.com/third.ts
  9. ...

上面的是未加密的m3u8文件内容,我们来看看加密的m3u8文件:

  1. #EXTM3U
  2. #EXT-X-VERSION:3
  3. #EXT-X-TARGETDURATION:10
  4. #EXT-X-MEDIA-SEQUENCE:0
  5. #EXT-X-KEY:METHOD=AES-128,URI="key.key"
  6. #EXTINF:10.000000,
  7. 00000.ts
  8. #EXTINF:10.000000,
  9. 00001.ts
  10. #EXTINF:10.000000,
  11. 00002.ts
  12. #EXTINF:10.000000
  13. ...

PS:想要了解m3u8格式更多的资料,请查看我底下的参考链接

这里提一下获取m3u8文件的方式,可以通过浏览器F12进入调试模式,之后找到m3u8的网址资源,或者是通过猫抓(Chrome插件)获取链接,猫抓插件安装请自行百度

3.解析m3u8文件获取信息

由上面我们大概了解到了m3u8文件里面的内容,我们将m3u8文件下载到本地之后,可以得到两个信息,key文件地址(如果采用了加密的话)和全部的ts文件地址

  1. #EXTM3U
  2. #EXT-X-TARGETDURATION:10
  3. #EXTINF:9.009,
  4. http://media.example.com/first.ts
  5. #EXTINF:9.009,
  6. http://media.example.com/second.ts
  7. #EXTINF:3.003,
  8. http://media.example.com/third.ts
  9. ...

上面的这个是没有采用加密的,而且,ts文件都是给出了具体的网址,这是极为理想的情况,但是市面上大部分不会采用这样的,一般都是像下面的这种格式:

  1. #EXTM3U
  2. #EXT-X-VERSION:3
  3. #EXT-X-TARGETDURATION:10
  4. #EXT-X-MEDIA-SEQUENCE:0
  5. #EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x12345(可能有)
  6. #EXTINF:10.000000,
  7. 00000.ts
  8. #EXTINF:10.000000,
  9. 00001.ts
  10. #EXTINF:10.000000,
  11. 00002.ts
  12. #EXTINF:10.000000
  13. ...

上面的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文件):

  1. /**
  2. * 下载文件到本地
  3. * @param url 网址
  4. * @param file 文件
  5. */
  6. private fun downloadFile(url: String, file: File) {
  7. val conn = URL(url).openConnection()
  8. conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)")
  9. val bytes = conn.getInputStream().readBytes()
  10. file.writeBytes(bytes)
  11. }

4.ts文件下载优化

ts文件过多,如果只开启一个单线程进行下载,下载太慢了,所以,可以采用多线程进行下载

这里的话,由于之前解析可以获得一个ts文件地址的列表,把此列表分为几份列表,每份列表开启一个子线程来进行下载,这样便可以保证任务的并发性,提高了下载速度。

这里,稍微有点复杂,因为要把列表划分成几份列表,我大概是这样分的:

首先,计算出列表可以平均分为几份,每份列表的数目,之后再将剩下的列表分为一份,但是,使用循环的话不是很好写,所以就先把第一个列表和最后一个列表分好,之后来个循环,将中间的平分完。

  1. /**
  2. * 下载ts文件
  3. * @param threadCount 线程数(默认开启5个线程下载,速度较快,100M宽带测试速度有17M/s)
  4. */
  5. fun downloadTsFile(threadCount: Int = 5) {
  6. val countDownLatch = CountDownLatch(threadCount)
  7. //每份列表的数目
  8. val step = tsUrls.size / threadCount
  9. //最后列表的数目(剩下的)
  10. val yu = tsUrls.size % threadCount
  11. //第一份列表
  12. thread {
  13. val firstList = tsUrls.take(step)
  14. downloadTsList(firstList)
  15. countDownLatch.countDown()
  16. }
  17. //最后一份列表
  18. thread {
  19. val lastList = tsUrls.takeLast(step + yu)
  20. downloadTsList(lastList)
  21. countDownLatch.countDown()
  22. }
  23. //中间的平分
  24. for (i in 1..threadCount - 2) {
  25. val list = tsUrls.subList(i * step, (i + 1) * step + 1)
  26. thread {
  27. downloadTsList(list)
  28. countDownLatch.countDown()
  29. }
  30. }
  31. countDownLatch.await()
  32. println("所有ts文件下载完毕")
  33. }

上面的使用了CountDownLatch类的对象进行线程的控制,只有当所有线程完成之后,此方法才算结束

5.ts文件解密

先上代码,之后再细讲:

  1. //1.获得key和iv的字符串
  2. val keyString = "2e9515db8fe8358bc8fcf6ae601a00be"
  3. val ivString = "d0817f83115d911241fe8ba17673f120"
  4. //2.获得key和iv的bytes数组
  5. val keyBytes = decodeHex(keyString)
  6. val ivBytes = decodeHex(ivString)
  7. //3.key数组转为SecretKeySpec对象,iv数组转为IvParameterSpec
  8. val algorithm = "AES"
  9. val skey = SecretKeySpec(keyBytes, algorithm)
  10. val iv = IvParameterSpec(ivBytes)
  11. //4. 初始化cipher
  12. val transformation = "AES/CBC/PKCS5Padding"
  13. val cipher = Cipher.getInstance(transformation)
  14. cipher.init(Cipher.DECRYPT_MODE,skey,iv)
  15. //5. 解密,
  16. val tsFile = File("Q:\\m3u8破解\\2273\\440.ts")
  17. val result = cipher.doFinal(tsFile.readBytes())
  18. val newFile = File("Q:\\m3u8破解\\2273\\440_s.ts")
  19. //6.写入文件
  20. BufferedOutputStream(FileOutputStream(newFile)).write(result)

key文件本质是一个16字节文件,我们可以通过winhex等软件查看里面的内容

不过,查看出来之后的内容,我们还得进行转换,因为是字符串,所以得调用decodeHex方法,将字符串转为bytes数组

所以,直接使用代码查看更为方便,Kotlin中可以直接读取bytes(如果使用Java的话,推荐使用common-io的第三方jar包),如:

  1. val keyFile = File("Q:\\test\key.key")
  2. //获得bytes数组
  3. val bytes = keyFile.readBytes()

PS:对了,如果m3u8文件中没有使用到IV偏移量,直接使用0即可(要保证bytes数组的长度为16),如果使用了IV的话,要使用decodeHex方法转为bytes数组

  1. 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)
  1. /**
  2. * 将字符串转为16进制并返回字节数组
  3. */
  4. private fun decodeHex(input: String): ByteArray {
  5. val data = input.toCharArray()
  6. val len = data.size
  7. if (len and 0x01 != 0) {
  8. try {
  9. throw Exception("Odd number of characters.")
  10. } catch (e: Exception) {
  11. e.printStackTrace()
  12. }
  13. }
  14. val out = ByteArray(len shr 1)
  15. try {
  16. var i = 0
  17. var j = 0
  18. while (j < len) {
  19. var f = toDigit(data[j], j) shl 4
  20. j++
  21. f = f or toDigit(data[j], j)
  22. j++
  23. out[i] = (f and 0xFF).toByte()
  24. i++
  25. }
  26. } catch (e: Exception) {
  27. e.printStackTrace()
  28. }
  29. return out
  30. }
  31. @Throws(Exception::class)
  32. private fun toDigit(ch: Char, index: Int): Int {
  33. val digit = Character.digit(ch, 16)
  34. if (digit == -1) {
  35. throw Exception("Illegal hexadecimal character $ch at index $index")
  36. }
  37. return digit
  38. }

有了key文件和IV偏移量的bytes,我们就可以往下走了,下面的代码其实都没有什么好说明的,明眼人估计一看就懂了,这里就不多说了

需要注意的是,因为解密之后,我们还需要把所有已经解密好的ts文件按照顺序合并成一个mp4文件,所以,注意解密后数据的名字。

建议在保存原来编号的基础上,加上写简短的字母,之后,就可以通过contains方法进行判断是否文件名是否符合条件

6.ts文件合并

合并的话,使用IO流,按照顺序依次把流追加到末尾即可

参考

m3u8 文件格式详解

关于m3u8格式的视频文件ts转mp4下载和key加密问题

aes 256 32位key和32位iv

加密ts解密

打造m3u8视频(流视频)下载解密合并器(kotlin)的更多相关文章

  1. stars-one原创工具——m3u8视频下载合并器(kotlin)

    一款可以下载m3u8.解密ts文件及合并ts文件的视频下载工具 蓝奏云下载地址 github地址 软件对你有帮助的话,不妨赞赏一波!感谢! 程序说明 采用多线程下载,可有效的提高下载速度 内置解密程序 ...

  2. Python3 根据m3u8下载视频,批量下载ts文件并且合并

    Python3 根据m3u8下载视频,批量下载ts文件并且合并 m3u8是苹果公司推出一种视频播放标准,是一种文件检索格式,将视频切割成一小段一小段的ts格式的视频文件,然后存在服务器中(现在为了减少 ...

  3. 关于m3u8格式的视频文件ts转mp4下载和key加密问题

    一,利用网站浏览器F12键,利用谷歌浏览器插件找到视频的.m3u8文件,并打开. 二,打开m3u8文件后,里面有很多.ts的链接,和key的链接. 三,保存为html文件,下载ts文件,代码如下:可加 ...

  4. python+fiddler下载vip视频 && ts视频可合并

    如果你只想在线看视频可以去看这篇博客:python实现通过指定浏览器免费观看vip视频  先看一下我们程序运行的结果 我们要解析的接口就是(就是这个"接口+视频地址"可以解析出vi ...

  5. 怎么下载腾讯课堂M3U8格式的视频

    好好学习,天天向上 本文已收录至我的Github仓库DayDayUP:github.com/RobodLee/DayDayUP,欢迎Star,更多文章请前往:目录导航 前言 用过腾讯课堂的小伙伴们可能 ...

  6. Python爬虫入门教程 51-100 Python3爬虫通过m3u8文件下载ts视频-Python爬虫6操作

    什么是m3u8文件 M3U8文件是指UTF-8编码格式的M3U文件. M3U文件是记录了一个索引纯文本文件, 打开它时播放软件并不是播放它,而是根据它的索引找到对应的音视频文件的网络地址进行在线播放. ...

  7. m3u8解析、转码、下载、合并

    m3u8解析.转码.下载.合并 现在网也上大多数视频需要下载都很麻烦,极少数是MP4,大多都是m3u8, 先说视频下载, pc端: 打开网页,点击视频播放,打开开发者工具,找到网络那一栏, 等整个网页 ...

  8. 使用vlc播放器播放rtsp流视频

    可参考: 使用vlc播放器做rtsp服务器 web网页中使用vlc插件播放相机rtsp流视频 使用vlc进行二次开发做自己的播放器 首先需要安装vlc播放器,下载及安装步骤略 使用vlc播放器播放rt ...

  9. web网页中使用vlc插件播放相机rtsp流视频

    可参考: 使用vlc播放器做rtsp服务器 使用vlc播放器播放rtsp视频 使用vlc进行二次开发做自己的播放器 vlc功能还是很强大的,有很多的现成的二次开发接口,不需配置太多即可轻松做客户端播放 ...

随机推荐

  1. springboot2动态数据源的绑定

    由于springboot2更新了绑定参数的api,部分springboot1用于绑定的工具类如RelaxedPropertyResolver已经无法在新版本中使用.本文实现参考了https://blo ...

  2. Python--day24--多继承

    如果本生没有func方法的话就调用距离自己最近的基类的方法 钻石继承: 查找方法的顺序:如下例的找func方法(广度优先) 例1: 例2: 漏斗继承: 小乌龟继承问题:(最顶端的节点F是最后查找的) ...

  3. python的if判断

    if 判断条件的时候,如果是多个条件一起进行判断,那么就需要逻辑运算符   并且-----------and 或者-----------or 非(取反)----not   if 条件1 and 条件2 ...

  4. Ubuntu 19.04安装phpipam软件

    1ftp下载xampp2安装xampp chmod 777sudo ./xampp.run3,ftp phpipam.tar.gz 解压 ./opt/lampp/www/phpipam/cp conf ...

  5. Django入门10--admin增强

  6. springboot中使用spring-session实现共享会话到redis(二)

    上篇文章介绍了springboot中集成spring-session实现了将session分布式存到redis中.这篇在深入介绍一些spring-session的细节. 1.session超时: 在t ...

  7. POJ 2778 DNA Sequence (ac自动机+矩阵快速幂)

    DNA Sequence Description It's well known that DNA Sequence is a sequence only contains A, C, T and G ...

  8. JavaSE基础知识---常用对象API之String类

    一.String类 Java中用String类对字符串进行了对象的封装,这样的好处在于对象封装后可以定义N多属性和行为,就可以对字符串这种常见的数据进行方便的操作. 格式:(1)String s1 = ...

  9. jenkins+Git+Gitlab+Ansible实现持续集成自动化部署静态网站(二)

    引言:首先我们可以实现一键部署网站,但在实际生产环境网站部署完成之后,我们的开发隔三差五要修改下网站的内容,难道都要我们运维手动执行命令吗?没有一种方法使得开发人员修改完代码自己测试,部署上线呢,那这 ...

  10. apk混淆打包和反编译(转)

    前面有人写过了,我就直接引用了,大家就不乱找了.以后有问题再继续更新. 一.混淆打包.编译 1.Android 代码混淆.http://blog.csdn.net/zjclugger/article/ ...