m3u8视频文件破解爬取教程(请尊重知识产权,切勿违法),该方法需要基础开发知识
// ==UserScript==
// @name media-source-extract
// @namespace https://github.com/Momo707577045/media-source-extract
// @version 0.8.2
// @description https://github.com/Momo707577045/media-source-extract 配套插件
// @author Momo707577045
// @include *
// @exclude http://blog.luckly-mjw.cn/tool-show/media-source-extract/player/player.html
// @downloadURL https://blog.luckly-mjw.cn/tool-show/media-source-extract/media-source-extract.user.js
// @updateURL https://blog.luckly-mjw.cn/tool-show/media-source-extract/media-source-extract.user.js
// @grant none
// @run-at document-start
// ==/UserScript==(function () {'use strict';(function () {if (document.getElementById('media-source-extract')) {return}// 轮询监听 iframe 的加载setInterval(() => {try {Array.prototype.forEach.call(document.getElementsByTagName('iframe'), (iframe) => {// 若 iframe 使用了 sandbox 进行操作约束,删除原有 iframe,拷贝其备份,删除 sandbox 属性,重新载入// 若 iframe 已载入,再修改 sandbox 属性,将修改无效。故通过新建 iframe 的方式绕过if(iframe.hasAttribute('sandbox')){const parentNode = iframe.parentNode;const tempIframe = iframe.cloneNode()tempIframe.removeAttribute("sandbox");iframe.remove()parentNode.appendChild(tempIframe);}})} catch (error) {console.log(error)}}, 1000)let sumFragment = 0 // 已经捕获的所有片段数let isClose = false // 是否关闭let isStreamDownload = false // 是否使用流式下载let _sourceBufferList = [] // 媒体轨道const $showBtn = document.createElement('div') // 展示按钮const $btnDownload = document.createElement('div') // 下载按钮const $btnStreamDownload = document.createElement('div') // 流式下载按钮const $downloadNum = document.createElement('div') // 已捕获视频片段数const $tenRate = document.createElement('div') // 十倍速播放const $closeBtn = document.createElement('div') // 关闭const $container = document.createElement('div') // 容器$closeBtn.innerHTML = `<img style="padding-top: 4px;width: 24px;display: inline-block;cursor: pointer;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAk1BMVEUAAADROyVeAAAAMHRSTlMA1Sq7gPribxkJx6Ey8onMsq+GTe10QF8kqJl5WEcvIBDc0sHAkkk1FgO2ZZ+dj1FHfPqwAAACNElEQVRIx6VW6ZqqMAwtFlEW2Rm3EXEfdZa+/9PdBEvbIVXu9835oW1yjiQlTWQE/iYPuTObOTzMNz4bQFRlY2FgnFXRC/o01mytiafP+BPvQZk56bcLSOXem1jpCy4QgXvRtlEVCARfUP65RM/hp29/+0R7eSbhoHlnffZ8h76e6x1tyw9mxXaJ3nfTVLd89hQr9NfGceJxfLIXmONh6eNNYftNSESRmgkHlEOjmhgBbYcEW08FFQN/ro6dvAczjhgXEdQP76xHEYxM+igQq259gLrCSlwbD3iDtTMy+A4Yuk0B6zV8c+BcO2OgFIp/UvJdG4o/Rp1JQYXeZFflPEFMfvugiFGFXN587YtgX7C8lRGFXPCGGYCCzlkoxJ4xqmi/jrIcdYYh5pwxiwI/gt7lDDFrcLiMKhBJ//W78ENsJgVUsV8wKpjZBXshM6cCW0jbRAilICFxIpgGMmmiWGHSIR6ViY+DPFaqSJCbQ5mbxoZLIlU0Al/cBj6N1uXfFI0okLppi69StmumSFQRP6oIKDedFi3vRDn3j6KozCZlu0DdJb3AupJXNLmqkk9+X9FEHLt1Jq8oi1H5n01AtRlvwQZQl9hmtPY4JEjMDs5ftWJN4Xr4lLrV2OHiUDHCPgvA/Tn/hP4zGUBfjZ3eLJ+NIOfHxi8CMoAQtYfmw93v01O0e7VlqqcCsXML3Vsu94cxnb4c7ML5chG8JIP9b38dENGaj3+x+TpiA/AL/fen8In7H8l3ZjdJQt2TAAAAAElFTkSuQmCC">`$showBtn.innerHTML = `<img style="padding-top: 4px;width: 24px;display: inline-block;cursor: pointer;" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMgAAADIBAMAAABfdrOtAAAAElBMVEUAAAD///8+Uq06AAAABXRSTlMA2kCAv5tF5NoAAAErSURBVHja7dzNasJAFIbhz8Tu7R0Eq/vQNHuxzL6YnPu/ldYpAUckxJ8zSnjfdTIPzHrOUawJdqmDJre1S/X7avigbM08kMgMSmt+iPWKbcwTsb3+KswXseOFLb2RnaTgjXTxtpwRq7XMgWz9kZ8cSKcwE6SX+SMGAgICAvJCyHdz2ud0pEx+/BpFaj2kEgQEBAQEBAQEBOT1kXWSkhbvk1vptOLs1LEWNrmVRgIBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBeTayTqpufogxduqM3q2AgICAgICAgICA3IOko4ZXkB/pqOHzhyZBQEBAQLIieVahtDNBDnrLgZT+yC4HUkmtN9JnWUiVZbVWliVhseCJdPqvCH5IV2tQNl4r6Bod+wWq9eeDik+xFQAAAABJRU5ErkJggg==">`// 十倍速播放function _tenRatePlay() {let playbackRate = 10if ($tenRate.innerHTML === '十倍速捕获') {$tenRate.innerHTML = '恢复正常播放'} else {playbackRate = 1$tenRate.innerHTML = '十倍速捕获'}let $domList = document.getElementsByTagName('video')for (let i = 0, length = $domList.length; i < length; i++) {const $dom = $domList[i]$dom.playbackRate = playbackRate}}// 获取顶部 window title,因可能存在跨域问题,故使用 try catch 进行保护function getDocumentTitle() {let title = document.title;try {title = window.top.document.title} catch (error) {console.log(error)}return title}// 流式下载function _streamDownload() {var _hmt = _hmt || [];(function () {var hm = document.createElement("script");hm.src = "https://hm.baidu.com/hm.js?1f12b0865d866ae1b93514870d93ce89";var s = document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})();// 对应状态未下载结束的媒体轨道const remainSourceBufferList = []_sourceBufferList.forEach((target) => {// 对应的 MSE 状态为已下载完成状态if (target.MSEInstance.readyState === 'ended') {target.streamWriter.close()} else {remainSourceBufferList.push(target)}})// 流式下载,释放已下载完成的媒体轨道,回收内存_sourceBufferList = remainSourceBufferList}// 普通下载function _download() {var _hmt = _hmt || [];(function () {var hm = document.createElement("script");hm.src = "https://hm.baidu.com/hm.js?1f12b0865d866ae1b93514870d93ce89";var s = document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})();_sourceBufferList.forEach((target) => {const mime = target.mime.split(';')[0]const type = mime.split('/')[1]const fileBlob = new Blob(target.bufferList, { type: mime }) // 创建一个Blob对象,并设置文件的 MIME 类型const a = document.createElement('a')a.download = `${getDocumentTitle()}.${type}`a.href = URL.createObjectURL(fileBlob)a.style.display = 'none'document.body.appendChild(a)// 禁止 click 事件冒泡,避免全局拦截a.onclick = function (e) {e.stopPropagation();}a.click()a.remove()})}// 监听资源全部录取成功let _endOfStream = window.MediaSource.prototype.endOfStreamwindow.MediaSource.prototype.endOfStream = function () {if (isStreamDownload) {alert('资源全部捕获成功,即将下载!')setTimeout(_streamDownload) // 等待 MediaSource 状态变更_endOfStream.call(this)return}if (confirm('资源全部捕获成功,即将下载!') == true) {_download()} else {// 不下载资源}_endOfStream.call(this)}// 录取资源let _addSourceBuffer = window.MediaSource.prototype.addSourceBufferwindow.MediaSource.prototype.addSourceBuffer = function (mime) {_appendDom()let sourceBuffer = _addSourceBuffer.call(this, mime)let _append = sourceBuffer.appendBufferlet bufferList = []const _sourceBuffer = {mime,bufferList,MSEInstance: this,}// 如果 streamSaver 已提前加载完成,则初始化对应的 streamWritertry {if (window.streamSaver) {const type = mime.split(';')[0].split('/')[1]_sourceBuffer.streamWriter = streamSaver.createWriteStream(`${getDocumentTitle()}.${type}`).getWriter()}} catch (error) {console.error(error)}_sourceBufferList.push(_sourceBuffer)sourceBuffer.appendBuffer = function (buffer) {sumFragment++$downloadNum.innerHTML = `已捕获 ${sumFragment} 个片段`if (isStreamDownload && _sourceBuffer.streamWriter) { // 流式下载_sourceBuffer.streamWriter.write(new Uint8Array(buffer));} else { // 普通 blob 下载bufferList.push(buffer)}_append.call(this, buffer)}return sourceBuffer}window.MediaSource.prototype.addSourceBuffer.toString = function () {return 'function addSourceBuffer() { [native code] }'}// 添加操作的 domfunction _appendDom() {if (document.getElementById('media-source-extract')) {return}$container.style = `position: fixed;top: 50px;right: 50px;text-align: right;z-index: 9999;`const baseStyle = `float:right;clear:both;margin-top: 10px;padding: 0 20px;color: white;cursor: pointer;font-size: 16px;font-weight: bold;line-height: 40px;text-align: center;border-radius: 4px;background-color: #3498db;box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.3);`$tenRate.innerHTML = '十倍速捕获'$downloadNum.innerHTML = '已捕获 0 个片段'$btnStreamDownload.innerHTML = '特大视频下载,边下载边保存'$btnDownload.innerHTML = '下载已捕获片段'$btnDownload.id = 'media-source-extract'$tenRate.style = baseStyle$downloadNum.style = baseStyle$btnDownload.style = baseStyle$btnStreamDownload.style = baseStyle$btnStreamDownload.style.display = 'none'$showBtn.style = `float:right;clear:both;display: none;margin-top: 4px;height: 34px;width: 34px;line-height: 34px;text-align: center;border-radius: 4px;background-color: rgba(0, 0, 0, 0.5);`$closeBtn.style = `float:right;clear:both;margin-top: 10px;height: 34px;width: 34px;line-height: 34px;text-align: center;display: inline-block;border-radius: 50%;background-color: rgba(0, 0, 0, 0.5);`$btnDownload.addEventListener('click', _download)$tenRate.addEventListener('click', _tenRatePlay)// 关闭控制面板$closeBtn.addEventListener('click', function () {$downloadNum.style.display = 'none'$btnStreamDownload.style.display = 'none'$btnDownload.style.display = 'none'$closeBtn.style.display = 'none'$tenRate.style.display = 'none'$showBtn.style.display = 'inline-block'isClose = true})// 显示控制面板$showBtn.addEventListener('click', function () {if (!isStreamDownload) {$btnDownload.style.display = 'inline-block'$btnStreamDownload.style.display = 'inline-block'}$downloadNum.style.display = 'inline-block'$closeBtn.style.display = 'inline-block'$tenRate.style.display = 'inline-block'$showBtn.style.display = 'none'isClose = false})// 启动流式下载$btnStreamDownload.addEventListener('click', function () {(function () {var hm = document.createElement("script");hm.src = "https://hm.baidu.com/hm.js?1f12b0865d866ae1b93514870d93ce89";var s = document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm, s);})();isStreamDownload = true$btnDownload.style.display = 'none'$btnStreamDownload.style.display = 'none'_sourceBufferList.forEach(sourceBuffer => {if (!sourceBuffer.streamWriter) {const type = sourceBuffer.mime.split(';')[0].split('/')[1]sourceBuffer.streamWriter = streamSaver.createWriteStream(`${getDocumentTitle()}.${type}`).getWriter()sourceBuffer.bufferList.forEach(buffer => {sourceBuffer.streamWriter.write(new Uint8Array(buffer));})sourceBuffer.bufferList = []}})})document.getElementsByTagName('html')[0].insertBefore($container, document.getElementsByTagName('head')[0]);$container.appendChild($btnStreamDownload)$container.appendChild($downloadNum)$container.appendChild($btnDownload)$container.appendChild($tenRate)$container.appendChild($closeBtn)$container.appendChild($showBtn)// 加载 stream 流式下载器try {let $streamSaver = document.createElement('script')$streamSaver.src = 'https://upyun.luckly-mjw.cn/lib/stream-saver.js'document.body.appendChild($streamSaver);$streamSaver.addEventListener('load', () => {$btnStreamDownload.style.display = 'inline-block'})} catch (error) {console.error(error)}}})()
})();
油猴插件,点击自动安装
- 检测到符合条件的视频资源,自动注入代码。
- 之后再也不需要自己注入代码啦,降低使用成本。
- 可能有 Bug,但可以试试。如果使用中有问题,在 github 中提 issues 给我呗。 我们一起完善它。
- 插件源码: https://github.com/Momo707577...
- 如果没成功检测到,咱们就用回老方法试试。再给我提个 bug。
-
手动添加油猴插件步骤
-
点击 tamper-monkey「油猴」icon,点击「添加新脚本」
-
在当前位置,粘贴上述链接中的源码
-
点击「文本」,「保存」
-
得到如下结果,即为添加成功
-
背景
- 之前笔者实现了m3u8 视频在线提取工具,可对 m3u8 视频进行提取,合并,下载。实现整个视频下载流程。
- 后续还实现了非定制性的 ASE 解密功能(不提供定制性服务,定制性解密,属于破解,侵权行为,需尊重知识产权)
-
但上述工具仍存在一定的通用性问题。为彻底解决通用性,实现无差别视频提取,开发了这个工具。
特点
- 优点,通用性强,无差别提取,只要使用到 MES 主流媒体播放技术的视频,均可捕获。
- 优点,足够简单,在视频播放的最后一个步骤进行拦截,规避视频加载,加密,解密等复杂过程。
- 缺点,被动,无法主动干预视频加载,只可被动捕获视频资源。
-
缺点,有一定门槛,依赖 chrome 浏览器开发者模式,无法实现全自动化,有一定使用门槛。
使用示例链接
功能说明
-
【已捕获 0 个片段】
- 显示程序已捕获的视频片段数。
-
【下载已捕获片段】
- 可以强制下载已经捕获的片段,无需等待整个视频全部捕获完成。
-
【十倍速捕获】
- 由于视频捕获是依赖视频加载进度的。
- 点击该按钮,可以十倍速播放,加速视频加载,加速视频捕获。
-
当视频全部加载完成,将触发自动下载。
- 若无触发,可手动点击「下载已捕获片段」按钮,对捕获到的视频进行下载。
使用方式
示例实验链接
-
复制工具代码
- 可以直接复制本文中的核心源码
-
也可以点开示例实验链接,点击按钮,快速复制工具代码。
-
打开目前页面的控制台
-
ctrl + f ,输入 <iframe,判断是否存在 iframe 内嵌页面。若存在 iframe,请看完本说明,再继续查看下一节「iframe 解决方案」。若无,则下一步
-
打开代码调试面板
-
在调试面板中,找到当前页面的代码
- 注意文件的寻找方法,需根据 URL 中的路径层级寻找。
- 点击下方按钮,对代码进行排版。
-
搜索,找到第一个 <script 标签,并设置多个断点
-
搜索,如果第一个 <script 标签是一个链接。
-
则找到对应文件,设置断点。
-
-
刷新页面,出现如下状态,则证明断点设置成功
- 若页面白屏,为正常现象,按照步骤继续执行即可。
- 若页面白屏,为正常现象,按照步骤继续执行即可。
-
在 console 栏,粘贴工具代码,回车
-
回到 source 栏,点击按钮,恢复运行
-
若页面出现这几个按钮,则证明注入成功,工具运行成功
-
正常观看视频,等待视频捕获
- 可点击「十倍速捕获」,接口视频播放速度,加快视频捕获速度。
-
若页面出现如下弹窗,即捕获完成,视频自动下载(也可以点击「下载已捕获片段」,手动下载)
-
视频下载完成,得到「音频」文件,「视频」文件
- 可使用专属播放器,进行播放。
-
也可以使用其他工具,进行合并。
iframe 解决方案
示例实验链接
-
找到 iframe 标签,复制 src 中的 url,新建页面打开该 url。
-
如果该新建页面能正常播放视频,则在该新建页面,使用上述「使用说明」即可。
-
- 如果新建页面没有正常播放页面,则回到原页面,换一种方式实现。
-
回到原页面,找到 iframe 内嵌页面的源码。
-
同样搜索 <script,但这一次,要找带 src 的 script 标签
-
-
找到该 src 对应的文件,并打断点
-
刷新页面,并在源文件中,插入代码
- 注意,打断点和插入代码是在不同的栏,打断点的栏中,有「:format」标识。插入代码的栏,没有该标识。
-
粘贴代码,ctrl + s 进行保存
-
恢复执行(操作方式,查看上一节「使用说明」)
- 完成代码插入,捕获视频
特别说明
- 在代码操作过程中,页面白屏是正常的,按照步骤继续执行即可。
- 如果不行,安装使用说明,多试几遍就可以了。可能是视频广告导致。
- 注意 Chrome 的多文件下载询问,如果拒绝过,需要重新打开。
-
视频捕获,分为「视频」文件与「音频」文件,「视频」文件是纯视频,没声音的。需要搭配「音频」文件播放。点击这里,使用专属播放器。
专属播放器
- 由于采集工具是单独对「视频」和「音频」分开采集的。
- 使用普通播放器可能无法正常播放。
- 可利用本工具同时加载「视频」和「音频」同步播放。
-
本工具还附有倍速播放功能。
window 系统,音视频合成方法
- 可使用「小丸工具箱」完成
- 具体的方式,笔者没有实践过,有经验的朋友欢迎在评论区留言使用教程。万分感谢。
- 特别感谢㍿⃣ 介绍的「小丸工具箱」解决方案
mac 系统,音视频合成方法
- 安装 ffmpeg 视频编辑库
-
先把音频「audio_mp4」进行转码
- 命令行执行
ffmpeg -i "*-audio_mp4;codecs=*.mp4" -acodec copy "audio.aac"
- 命令行执行
-
原始视频「video_mp4」和上一步得到的 aac 「audio.aac」组装到一起
- 命令行执行
ffmpeg -i "*-video_mp4;codecs=*.mp4" -i "audio.aac" -c copy -shortest "result.mp4"
- 命令行执行
- 得到的「result.mp4」就是音视频合成成功的视频
-
特别感谢journey-ad 介绍的 ffmpeg 合成教程。
原理
- 主流视频媒体播放技术,均使用到 MES 技术
-
MES 技术播放流程一般如下:
- 创建 video 播放器标签。
- 拉取视频片段。
- 解密视频片段(如果对视频进行了加密操作)
- 解析视频片段,分为「视频轨」「音频轨」。
- 将每个片段的「视频轨」「音频轨」,"喂给" video 标签进行播放。
-
当已加载的视频片段快要播完时,重复第二个步骤,拉取新的视频片段,进行投喂。
-
本工具的核心逻辑
- 覆写视频片段的"投喂"操作。
- 插入自定义代码,收集"投喂"的「视频」「音频」资源,进行下载。
核心源码
(function () {let _sourceBufferList = []let $btnDownload = document.createElement('div')let $downloadNum = document.createElement('div')let $tenRate = document.createElement('div') // 十倍速播放// 十倍速播放function _tenRatePlay () {let $domList = document.getElementsByTagName('video')for (let i = 0, length = $domList.length; i < length; i++) {const $dom = $domList[i]$dom.playbackRate = 10}}// 下载捕获到的资源function _download () {_sourceBufferList.forEach((target) => {const mime = target.mime.split(';')[0]const type = mime.split('/')[1]const fileBlob = new Blob(target.bufferList, { type: mime }) // 创建一个Blob对象,并设置文件的 MIME 类型const a = document.createElement('a')a.download = `${document.title}.${type}`a.href = URL.createObjectURL(fileBlob)a.style.display = 'none'document.body.appendChild(a)a.click()a.remove()})}// 监听资源全部录取成功let _endOfStream = window.MediaSource.prototype.endOfStreamwindow.MediaSource.prototype.endOfStream = function () {alert('资源全部捕获成功,即将下载!')_download()_endOfStream.call(this)}// 捕获资源let _addSourceBuffer = window.MediaSource.prototype.addSourceBufferwindow.MediaSource.prototype.addSourceBuffer = function (mime) {console.log(mime)let sourceBuffer = _addSourceBuffer.call(this, mime)let _append = sourceBuffer.appendBufferlet bufferList = []_sourceBufferList.push({mime,bufferList,})sourceBuffer.appendBuffer = function (buffer) {$downloadNum.innerHTML = `已捕获 ${_sourceBufferList[0].bufferList.length} 个片段`bufferList.push(buffer)_append.call(this, buffer)}return sourceBuffer}// 添加操作的 domfunction _appendDom () {const baseStyle = `position: fixed;top: 50px;right: 50px;height: 40px;padding: 0 20px;z-index: 9999;color: white;cursor: pointer;font-size: 16px;font-weight: bold;line-height: 40px;text-align: center;border-radius: 4px;background-color: #3498db;box-shadow: 0 3px 6px 0 rgba(0, 0, 0, 0.3);`$tenRate.innerHTML = '十倍速捕获'$downloadNum.innerHTML = '已捕获 0 个片段'$btnDownload.innerHTML = '下载已捕获片段'$tenRate.style = baseStyle + `top: 150px;`$btnDownload.style = baseStyle + `top: 100px;`$downloadNum.style = baseStyle$btnDownload.addEventListener('click', _download)$tenRate.addEventListener('click', _tenRatePlay)document.getElementsByTagName('html')[0].insertBefore($tenRate, document.getElementsByTagName('head')[0]);document.getElementsByTagName('html')[0].insertBefore($downloadNum, document.getElementsByTagName('head')[0]);document.getElementsByTagName('html')[0].insertBefore($btnDownload, document.getElementsByTagName('head')[0]);}_appendDom()
})()
项目源码
声明
- 本项目仅用于学习,交流,切勿用于侵权行为。