中秋节放假,本想好好休息一下,女朋友说手机里下载了的腾讯课堂的课程能不能够传到电脑上面去,因为手机的空间不够了,我心想这不是很简单吗。。
果然,事情没我想的那么简单,找到了腾讯课堂的视频缓存目录

/Android/data/com.tencent.edu/files/tencentedu/video/txdownload/

发现里面的文件格式全部都是 xxx.m3u8.sqlite,并不是我们常见的可播放文件
于是转而百度搜索了一下如何把.m3u8.sqlite 转为mp4,在这片文章上面找到了可行的方案
m3u8.sqlite转mp4

根据文章上面的描述,先读取sqlite数据库文件,然后根据第一行获取文件的元数据信息,根据第二行获取文件的解密秘钥。然后其余的行则是真实的视频内容。

这里的第一行第二行在不同的排序规则下数据其实是不一样的,所以判断规则可以这样来判断:
1、如果url中没有start参数那么说明这不是视频内容数据,是属于1,2行内容
2、如果url没有start并且没有sign,那么说明是秘钥,如果有sign,那么说明是文件元数据

再根据上面的逻辑处理好之后对真是的文件内容进行解密和拼接

本以为万事大吉,可没想到,仅仅只有一个文件能够解密成功,其他的文件竟然都无法解密。查看了一下报错原因,发现竟然是数据库文件无法解析,于是使用编辑器打开对比了一下不能解析的sqlite文件

上图中左边是正常的能解析的文件,右边是不能接下的,可以看到不能解析的sqlite头文件被加密了,但是具体的加密方式却不得而知。
于是换了一个思路,通过对比发现这些文件都只是头部被加密了,内容部分并没有进行加密,于是冒出了一个想法,既然头部被加密了,那我把被加密的头部换成一个正常的头部是不是就可以了呢?试试证明,确实没错。

最终效果:

具体的代码如下:
代码很粗略,能解决女朋友就行,哈哈哈

package mainimport ("bytes""crypto/aes""crypto/cipher""database/sql""fmt"_ "github.com/mattn/go-sqlite3""io""io/ioutil""net/url""os""strings"
)func main() {//当前文件所在的路径rootPath := "./"//rootPath := "D:\\IdeaWorkspace\\go-study\\src\\test\\"//获取一个可用的sqlite文件头部//head := readHead(rootPath)//使用固定头部var head = []byte{  0x53 ,0x21 ,0x4C ,0x69 ,0x74 ,0x65 ,0x20 ,0x66 ,0x6F ,0x72 ,0x6D ,0x71 ,0x74 ,0x20 ,0x33 ,0x00 ,0x10 ,0x00 ,0x01 ,0x01 ,0x00 ,0x40 ,0x20 ,0x20 ,0x00 ,0x00 ,0x00 ,0x73 ,0x00 ,0x00 ,0xEE ,0xBD,0x00 ,0x00 ,0x00 ,0x06 ,0x00 ,0x00 ,0x00 ,0x07 ,0x00 ,0x00 ,0x70 ,0x02 ,0x10 ,0x00 ,0x40 ,0x04,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x08 ,0x01 ,0x00 ,0x00 ,0x00 ,0x00,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x30 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x00 ,0x64,0x00 ,0x2E ,0x28 ,0x6B ,0x0D ,0x0F ,0xF8 ,0x00 ,0x44 ,0x0E ,0xF4 ,0x00 ,0x0F ,0x71 ,0x0F ,0xC7,0x0E ,0xF4 ,0x0F ,0x44 ,0x00 ,0x00 ,0x00 ,0x00 ,0x80 ,0x00 ,0x00 ,0x00 ,0x00 ,0x05 ,0x00 ,0x00}//输出的mp文件路径outPath := rootPath + "out/"pathExists, _ := PathExists(outPath)if !pathExists {os.Mkdir(outPath, 0666)}infos, _ := ioutil.ReadDir(rootPath)for _, file := range infos {suffix := strings.HasSuffix(file.Name(), "m3u8.sqlite")if suffix && !file.IsDir() {fmt.Println("开始处理文件:", file.Name(), "头部", head)rewriteHead(head, rootPath, file.Name())fmt.Println("校验头部:", getHead(rootPath+file.Name()))fmt.Println("开始处转换文件:", file.Name())GetMp4(rootPath, file.Name(), outPath)}}}func rewriteHead(head []byte, rootpath string, name string) {//head[95] = byte(100)nfile, err := os.OpenFile(rootpath+name, os.O_RDWR|os.O_CREATE, 0666)fmt.Println("文件", name, "打开并重写头部", err)n, err := nfile.WriteAt(head, 0)fmt.Println("重写是否发生异常", err, n)nfile.Close()
}func readHead(rootpath string) []byte {var filename stringinfos, _ := ioutil.ReadDir(rootpath)for _, file := range infos {suffix := strings.HasSuffix(file.Name(), "m3u8.sqlite")if suffix && !file.IsDir() {fmt.Println("开始获取文件头:", file.Name())gdb, e := sql.Open("sqlite3", rootpath+file.Name())if e == nil {rows, e1 := gdb.Query("SELECT * FROM caches")if e1 == nil && rows.Next() {filename = file.Name()break} else {fmt.Println("文件", file.Name(), "头部有问题")}} else {fmt.Println("文件", file.Name(), "头部有问题")}gdb.Close()}}fmt.Println("正确头部文件", filename, "文件打开")return getHead(rootpath + filename)}func getHead(path string) []byte {head := make([]byte, 128)file, _ := os.Open(path)io.ReadAtLeast(file, head[:], 128)file.Close()return head
}func GetMp4(path string, name string, outpath string) []byte {var gdb *sql.DBfmt.Println("打开数据库:", path+name)gdb, e := sql.Open("sqlite3", path+name)if e != nil {fmt.Println(e)return nil}defer gdb.Close()rows, _ := gdb.Query("SELECT * FROM caches")var temp = make(map[string][]byte, 10000)var aeskey []bytevar content stringfor rows.Next() {var key stringvar value []bytee = rows.Scan(&key, &value)//fmt.Println("urlkey:",key,e)u := url.URL{}parse, _ := u.Parse(key)start := parse.Query().Get("start")end := parse.Query().Get("end")sign := parse.Query().Get("sign")if strings.Contains(key, "?") {k := "start:" + start + "end:" + endfmt.Println("缓存key:", k)temp[k] = value} else {fmt.Println("没有缓存", key)}if start == "" {if sign != "" {s := string(value)content = s} else {aeskey = valuefmt.Println("获取秘钥的key:", key)}}}//fmt.Println("秘钥",aeskey)//fmt.Println("解析信息",content)var urlList []stringfor _, e := range strings.Split(content, "\n") {if e != "" && strings.Contains(e, "start") && strings.Contains(e, "end") {s := "http://www.baidu.com" + eu := url.URL{}parse, _ := u.Parse(s)start := parse.Query().Get("start")end := parse.Query().Get("end")k := "start:" + start + "end:" + endurlList = append(urlList, k)}}fmt.Println("开始解密")file, _ := os.Create(outpath + name + ".mp4")for _, str := range urlList {fmt.Println("解密:", str)video := temp[str]if len(video) == 0 {fmt.Println("待解密内容为空", str)continue}res, _ := AesDecrypt(video, aeskey)//fmt.Println("解密位置:",str, "  文件大小:",len(temp[str])," 解密之后 :",len(res))// 查找文件末尾的偏移量n, _ := file.Seek(0, io.SeekEnd)// 从末尾的偏移量开始写入内容_, _ = file.WriteAt(res, n)}file.Close()fmt.Println(name, "解密完成")gdb.Close()return aeskey
}func PKCS5Padding(ciphertext []byte, blockSize int) []byte {padding := blockSize - len(ciphertext)%blockSizepadtext := bytes.Repeat([]byte{byte(padding)}, padding)return append(ciphertext, padtext...)
}func PKCS5UnPadding(origData []byte) []byte {length := len(origData)unpadding := int(origData[length-1])return origData[:(length - unpadding)]
}//加密
func AesEncrypt(origData, key []byte) ([]byte, error) {block, err := aes.NewCipher(key)if err != nil {return nil, err}blockSize := block.BlockSize()origData = PKCS5Padding(origData, blockSize)blockMode := cipher.NewCBCEncrypter(block, key[:blockSize])crypted := make([]byte, len(origData))blockMode.CryptBlocks(crypted, origData)return crypted, nil
}
//解密
func AesDecrypt(crypted, key []byte) ([]byte, error) {block, err := aes.NewCipher(key)if err != nil {return nil, err}blockSize := block.BlockSize()blockMode := cipher.NewCBCDecrypter(block, key[:blockSize])origData := make([]byte, len(crypted))blockMode.CryptBlocks(origData, crypted)origData = PKCS5UnPadding(origData)return origData, nil
}//判断路径是否存在
func PathExists(path string) (bool, error) {_, err := os.Stat(path)if err == nil {return true, nil}if os.IsNotExist(err) {return false, nil}return false, err
}

最近修改了程序,使用固定的头部区替换文件的头部,避免有些人的文件列表里面找不到一个可用的头部的情况。

可执行程序下载地址:convert.exe

运行的时候直接双击安装就可以使用了

m3u8.sqlite转mp4(txkt,文末附程序下载地址)相关推荐

  1. 数据仓库指北(文末附PDF下载)

    文章开头介绍下,这篇文章的第一部分Q&A环节,主要来源于日常工作沉淀,于是决定抽空写篇原创博文来做技术分享,有技术问题均可在大数据阶梯之路技术交流群互相讨论,加我微信拉你进群.公众号持续加成输 ...

  2. 80行代码自己动手写一个表格拆分与合并小工具(文末附工具下载)

    点击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 瑶池阿母绮窗开,黄竹歌声动地哀. ...

  3. 大数据治理平台建设方案(文末附PDF下载)

    这份材料我给满分!分享一份非常好的大数据治理平台解决方案材料,这份PPT将理论与实践相结合,值得仔细阅读,建议收藏. 文档目录主要包含了以下几点: 数据治理概述 某行数据现状及问题 数据治理阶段目标 ...

  4. hfss matlab联合仿真_一文搞定matlab 与 STK 联合调试仿真环境配置(文末附软件下载链接)...

    最近在做导师给的课题:卫星星座的快速优化设计. 需要用到matlab 和 STK 来进行联合调试仿真,但是这第一步的环境配置就让我头疼了几天.在好几次重装,失败和查找资料之后,我终于成功实现了matl ...

  5. 陆奇最新分享:数字化进程加速,创新者如何把握机会?(文末附PPT下载链接)...

    4月8日,腾讯产业加速器学员社群"毕加所"举行线上发布会,邀请到陆奇博士一同见证发布时刻.从硅谷顶级科技公司高管转型企业孵化领域的创业者,奇绩创坛(原YC中国)创始人兼CEO陆奇为 ...

  6. 16篇最新推荐系统论文送你(文末附打包下载链接)

    A Survey on Knowledge Graph-Based Recommender  Systems 链接:https://arxiv.org/pdf/2003.00911 简介:该文是一篇利 ...

  7. 【推荐实践】微博在线机器学习和深度学习实践(文末附PPT下载链接)

    更多细节请关注公众号并回复"微博",获取下载链接. 「 更多干货,更多收获 」 推荐系统系列教程之十二:Facebook是怎么为十亿人互相推荐好友的? [干货]史上最全个性化推荐技 ...

  8. 数据化建设知识图谱(文末附PDF下载)

    一.前言导读 如今,国家大力倡导数字化,随之而来的各种数据概念也铺天盖地,数字化转型.数据中台.智慧XXX...... 面对这些高举的概念,身为IT工程师和数据建设者该如何着手,想必都有不少困惑和苦水 ...

  9. 提升机器学习数学,理论基础的7本著作(文末附资源下载!)

    来源:AI算法与图像处理 本文约1700字,建议阅读8分钟 本文从数学基础的角度入手,推荐了数据科学和机器学习方面的七本参考书以及两本补充读物. [ 导读 ]机器学习和数据科学离不开数学,本文从数学基 ...

最新文章

  1. linux缓存文件地址,如何遍历linux内核中的文件地址空间的页面缓存树(基数树)
  2. java rop_Java命令行界面(第23部分):Rop
  3. LeetCode 551. Student Attendance Record I
  4. qml 不刷新 放大还原_【显示器选择详解】你的电脑能否带动高分辨率,高刷新率显示器?...
  5. python优点是代码库支持、灵活_C++和Python混合编程的利器
  6. 简单的Jquery轮播
  7. CCF201709-3 JSON查询(100分)【文本处理】
  8. 点击选中框 批量删除
  9. group by having where order by
  10. 解决问题,别扩展问题
  11. 软件测试-搭建测试环境
  12. 用插值法求国债收益率
  13. android输入法剪贴板,QQ输入法手安卓V5.4剪贴板 任性粘贴
  14. 关于Palantir -第五部分:浏览器应用
  15. SAP中的录屏BDC最贱实践
  16. 蓝鲸作业流程编排--参数使用
  17. 一招惊艳所有人,HTML制作网页成绩表
  18. 【国内SEO大牛】网站统计显示被违禁词搜索进来原因
  19. 百度BAE搭建微信公众平台-git的使用
  20. Qt LINK : fatal error LNK1104: 无法打开文件“xxx.lib”

热门文章

  1. Datatables表格插件学习
  2. Webstorm里面创建XMl文件
  3. FC金手指使用方法+大全
  4. Chrome浏览器对统一资源发出多个请求时,导致最多停止20s问题
  5. Python金融系列第五篇:多元线性回归和残差分析
  6. fw325r服务器无响应,迅捷fw325r显示已连接不可上网怎么办?
  7. 查询某一天内所有数据(SQL)
  8. 二维码门禁(基于微信小程序)
  9. 查看linux镜像版本的命令,Linux镜像列表中 怎样决定自己下载哪个版本
  10. 飞书的聊天信息服务器,飞书服务端SDK java