m3u8 视频在线提取工具

momo707577045

界面

工具在线地址,推荐使用 chrome 浏览器。

研发背景

  • m3u8视频格式简介

    • m3u8视频格式原理:将完整的视频拆分成多个 .ts 视频碎片,.m3u8 文件详细记录每个视频片段的地址。
    • 视频播放时,会先读取 .m3u8 文件,再逐个下载播放 .ts 视频片段。
    • 常用于直播业务,也常用该方法规避视频窃取的风险。加大视频窃取难度。
  • 鉴于 m3u8 以上特点,无法简单通过视频链接下载,需使用特定下载软件。

    • 但软件下载过程繁琐,试错成本高。
    • 使用软件的下载情况不稳定,常出现浏览器正常播放,但软件下载速度慢,甚至无法正常下载的情况。
    • 软件被编译打包,无法了解内部运行机制,不清楚里面到底发生了什么。
  • 基于以上原因,开发了本工具。

工具特点

  • 无需安装,打开网页即可用。
  • 强制下载现有片段,无需等待完整视频下载完成。
  • 操作直观,精确到视频碎片的操作。

功能说明


【解析下载】输入 m3u8 链接,点击下载视频。
【跨域复制代码】当资源出现跨域限制时,点击复制页面代码,在视频页面的控制台输入。将工具注入到视频页面中,解决跨域问题。
【重新下载错误片段】当部分视频片段下载失败时,点击该按钮,重新下载错误片段。
【强制下载现有片段】将已经下载好的视频片段强制整合下载。可以提前观看已经下载的片段。该操作不影响当前下载进程。
【片段Icon】对应每一个 .ts 视频片段的下载情况。「灰色」:待下载,「绿色」:下载成功,「红色」:下载失败。点击红色 Icon 可重新下载对应错误片段。

使用说明

  • 打开视频目标网页,鼠标右键「检查」,或者「开发者工具」,或者按下键盘的「F12」键
  • 找到 network,输入 m3u8,过滤 m3u8 文件。
  • 刷新页面,监听 m3u8 文件。

  • 找到目标m3u8文件,查看文件内容,是否符合格式。

    • 如下为索引文件,不是真正的视频 m3u8 文件

    • 一般内容有许多 ts 字眼的文件才是我们需要的视频 m3u8 文件。

  • 拷贝这个 m3u8 文件的链接。

  • 打开工具页面,输入链接,点击「解析下载」。
  • 出现片段 Icon,则证明操作成功,耐心等待视频下载。
  • 片段全部下载成功,将触发浏览器自动下载,下载整合后的完整视频。
  • 如果有片段下载失败,则点击对应片段,或点击「重新下载错误片段」按钮。重新下载错误片段。

异常情况

【无法下载,没有显示片段Icon】

  • 一般由于跨域造成。
  • 点击「跨域复制代码」按钮。
  • 打开视频目标网页的「开发者工具界面」,找到 console 栏。

  • 粘贴刚刚复制的内容,回车。
  • 滚动页面到底部,发现工具显示在底部。然后在注入的工具中正常使用。

【下载后的视频资源不可看】

  • 网站对视频源进行了加密操作。不同的视频网站有不同的算法操作。无法通用处理。
  • 一般网站不会有这种情况。爱奇艺,腾讯等大视频网站才会有该安全措施。

实现思路

【下载并解析 m3u8 文件】

  • 直接通过 ajax 的 get 请求 m3u8 文件。得到 m3u8 文件的内容字符串。读取字符串进行解析。
  • 需要注意的是,m3u8 文件不是 json 格式,不能将 dataType 设置为 json。
    【队列下载 ts 视频片段】
  • 同样使用 ajax 的 get 请求视频碎片,一个 ajax 请求一个 ts 视频碎片,但关键点在于,下载的是视频文件,属于二进制数据,需要将 responseType 请求头设置为 arraybuffer。xhr.responseType = 'arraybuffer'
  • 使用队列下载,是因为视频碎片太多,不可能一次性请求全部。需要分批下载。
  • 同时由于浏览器同源并发限制,视频同时请求数不能过多。本工具设置为并发下载数为 10。
    【组合 ts 视频片段】
  • 看似很难,但其实使用 Blob 对象即可将多个 ts 文件整合成一个文件。new Blob(),传入 ts 文件数组。
  • 这里有个小细节需要注意,需要在 new Blob 的第二个参数中设置文件的 MIME 类型,否则将默认为 txt 文件。 const fileBlob = new Blob(fileDataList, { type: 'video/MP2T' })
    【自动下载】
  • 下载,当然先要获得文件链接,即刚生成的 Blob 文件链接。
  • 使用 URL.createObjectURL,即可得到浏览器内存中,Blob 的文件链接。URL.createObjectURL(fileBlob)
  • 最后,使用 a 标签的 a.download 属性,将 a 标签设置为下载功能。主动调用 click 事件a.click()。完成文件自动下载。

核心代码

【整合及自动下载】

    // 下载整合后的TS文件
    downloadFile(fileDataList, fileName, fileType) {
      this.tips = 'ts 碎片整合中,请留意浏览器下载'
      const fileBlob = new Blob(fileDataList, { type: 'video/MP2T' }) // 创建一个Blob对象,并设置文件的 MIME 类型
      const a = document.createElement('a')
      a.download = fileName + '.' + fileType
      a.href = URL.createObjectURL(fileBlob)
      a.style.display = 'none'
      document.body.appendChild(a)
      a.click()
      a.remove()
    },

是的,涉及新知识点的部分只有上面一小段,其他的都是 JS 的基础应用。

除了 vue.js 文件,本工具代码均包含在 index.html 文件里面。包括换行,一共 540 行代码,其中 css 样式 190 行,html 标签 30 行。JS 逻辑代码 300 行。

罗列这些代码量只是想表明,本工具运用到的都只是 JS 的常见知识,并不复杂。鼓励大家多尝试阅读源码,其实看源码并没有想象中的那么困难。

源码链接

AES 常规解密功能

  • 借助「aes-decryptor.js」,该文件来至 hls.js

    MP4 转码功能

  • 借助「mux-mp4.js」,源码来至 mux.js
  • 但 mux.js 存在一个无法计算视频长度的 bug
  • 本人已 fork 该项目,并修复该 bug,修复后的项目链接在这里

    第三方接入

  • 在 url 中通过 source 参数拼接下载地址即可,如:http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=http://1257120875.vod2.myqcloud.com/0ef121cdvodtransgzp1257120875/3055695e5285890780828799271/v.f230.m3u8
  • 系统将自动解析该参数

    油猴插件

  • 「跳转下载」即新开页面,打开本工具页面,自动携带并解析目标地址
  • 「注入下载」为解决跨域而生,直接将代码注入到当前视频网站,进行视频下载
  • 插件地址点这里
  • 不一定能用,但可以试试。不行就用回老方法试试。

    完结撒花,感谢阅读。

油猴插件源码

// ==UserScript==
// @name         m3u8-downloader
// @namespace    https://github.com/Momo707577045/m3u8-downloader
// @version      0.3
// @description  https://github.com/Momo707577045/m3u8-downloader 配套插件
// @author       Momo707577045
// @include      *
// @exclude      http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html
// @grant        none
// @run-at document-start
// ==/UserScript==
 
(function() {
  'use strict';
  var m3u8Target = ''
  var originXHR = window.XMLHttpRequest
 
  function ajax(options) {
    options = options || {};
    let xhr = new originXHR();
    if (options.type === 'file') {
      xhr.responseType = 'arraybuffer';
    }
 
    xhr.onreadystatechange = function() {
      if (xhr.readyState === 4) {
        let status = xhr.status;
        if (status >= 200 && status < 300) {
          options.success && options.success(xhr.response);
        } else {
          options.fail && options.fail(status);
        }
      }
    };
 
    xhr.open("GET", options.url, true);
    xhr.send(null);
  }
 
  // 检测 m3u8 链接的有效性
  function checkM3u8Url(url) {
    ajax({
      url,
      success: (fileStr) => {
        if (fileStr.indexOf('.ts') > -1) {
          appendDom()
          m3u8Target = url
          console.log('【m3u8】----------------------------------------')
          console.log(url)
          console.log('http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=' + url)
        }
      }
    })
  }
 
  function resetAjax() {
    if (window._hadResetAjax) { // 如果已经重置过,则不再进入。解决开发时局部刷新导致重新加载问题
      return
    }
    window._hadResetAjax = true
 
    var originOpen = originXHR.prototype.open
    window.XMLHttpRequest = function() {
      var realXHR = new originXHR()
      realXHR.open = function(method, url, asyn) {
        url.indexOf('m3u8') > 0 && checkM3u8Url(url)
        originOpen.call(realXHR, method, url, asyn)
      }
      return realXHR
    }
  }
 
  function appendDom() {
    if (document.getElementById('m3u8-download-dom')) {
      return
    }
    var domStr = `
  <div style="
    margin-top: 6px;
    padding: 6px 10px ;
    font-size: 18px;
    color: white;
    cursor: pointer;
    border-radius: 4px;
    border: 1px solid #eeeeee;
    background-color: #3D8AC7;
  " id="m3u8-jump">跳转下载</div>
  <div style="
    margin-top: 6px;
    padding: 6px 10px ;
    font-size: 18px;
    color: white;
    cursor: pointer;
    border-radius: 4px;
    border: 1px solid #eeeeee;
    background-color: #3D8AC7;
  " id="m3u8-append">注入下载</div>
  <div style="
    margin-top: 4px;
    height: 34px;
    width: 34px;
    line-height: 34px;
    display: inline-block;
    border-radius: 50px;
    background-color: rgba(0, 0, 0, 0.5);
  " id="m3u8-close">
    <img style="
      padding-top: 4px;
      width: 24px;
      cursor: pointer;
    " src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAMAAABg3Am1AAAAk1BMVEUAAAD////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////ROyVeAAAAMHRSTlMA1Sq7gPribxkJx6Ey8onMsq+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">
  </div>
    `
    var $section = document.createElement('section')
    $section.id = 'm3u8-download-dom'
    $section.style.position = 'fixed'
    $section.style.zIndex = '9999'
    $section.style.bottom = '20px'
    $section.style.right = '20px'
    $section.style.textAlign = 'center'
    $section.innerHTML = domStr
    document.body.appendChild($section);
 
    var m3u8Jump = document.getElementById('m3u8-jump')
    var m3u8Close = document.getElementById('m3u8-close')
    var m3u8Append = document.getElementById('m3u8-append')
 
    m3u8Close.addEventListener('click', function() {
      $section.remove()
    })
 
    m3u8Jump.addEventListener('click', function() {
      window.open('http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html?source=' + m3u8Target)
    })
 
    m3u8Append.addEventListener('click', function() {
      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);
      })();
      ajax({
        url: 'http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/index.html',
        success: (fileStr) => {
          let fileList = fileStr.split(`<!--vue 前端框架--\>`);
          let dom = fileList[0];
          let script = fileList[1] + fileList[2];
          script = script.split('// script注入');
          script = script[1] + script[2];
 
          if (m3u8Target) {
            script = script.replace(`url: '', // 在线链接`, `url: '${m3u8Target}',`);
          }
 
          // 注入html
          let $section = document.createElement('section')
          $section.innerHTML = `${dom}`
          $section.style.width = '100%'
          $section.style.height = '800px'
          $section.style.top = '0'
          $section.style.left = '0'
          $section.style.position = 'relative'
          $section.style.zIndex = '9999'
          $section.style.backgroundColor = 'white'
          document.body.appendChild($section);
 
          // 加载 ASE 解密
          let $ase = document.createElement('script')
          $ase.src = 'http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/aes-decryptor.js'
 
          // 加载 mp4 转码
          let $mp4 = document.createElement('script')
          $mp4.src = 'http://blog.luckly-mjw.cn/tool-show/m3u8-downloader/mux-mp4.js'
 
          // 加载 vue
          let $vue = document.createElement('script')
          $vue.src = 'https://cdn.bootcss.com/vue/2.6.10/vue.min.js'
 
          // 监听 vue 加载完成,执行业务代码
          $vue.addEventListener('load', function() {eval(script)})
          document.body.appendChild($vue);
          document.body.appendChild($mp4);
          document.body.appendChild($ase);
          alert('注入成功,请滚动到页面底部')
        },
      })
    })
 
  }
 
  resetAjax()
})();
阅读 54.5k

原创小文章
专注中小团队开发。前端技术,效率工具分享与探讨。
0 条评论
你知道吗?

宣传栏