MSE Dump Tools

Media Source Extensions API 数据 Dump 工具

当前为 2024-08-16 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name               MSE Dump Tools
// @name:zh-CN         MSE Dump Tools
// @namespace          CloudMoeMediaSourceExtensionsAPIDataDumper
// @version            1.5.0
// @description        Media Source Extensions API Data Dump Tool
// @description:zh-CN  Media Source Extensions API 数据 Dump 工具
// @author             TGSAN
// @include            /.*/
// @run-at             document-start
// @grant              GM_unregisterMenuCommand
// @grant              GM_registerMenuCommand
// @grant              unsafeWindow
// ==/UserScript==

(function () {
    'use strict';

    GM_registerMenuCommand(`视频 - 最快播放速度`, function () { document.getElementsByTagName("video")[0].playbackRate = 16 });
    GM_registerMenuCommand(`视频 - 恢复播放速度`, function () { document.getElementsByTagName("video")[0].playbackRate = 1 });
    GM_registerMenuCommand(`音频 - 最快播放速度`, function () { document.getElementsByTagName("audio")[0].playbackRate = 16 });
    GM_registerMenuCommand(`音频 - 恢复播放速度`, function () { document.getElementsByTagName("audio")[0].playbackRate = 1 });
    GM_registerMenuCommand(`尝试直接下载页面中的视频`, function () { DirectDownloadPlayingVideo("video") });
    GM_registerMenuCommand(`尝试直接下载页面中的音频`, function () { DirectDownloadPlayingVideo("audio") });
    GM_registerMenuCommand(`结束Dump`, EndAllDumpTasks);

    var dumpEndTasks = [];

    function dateFormat(dataObj, fmt) {
        var o = {
            "M+": dataObj.getMonth() + 1,               //月份
            "d+": dataObj.getDate(),                    //日
            "h+": dataObj.getHours(),                   //小时
            "m+": dataObj.getMinutes(),                 //分
            "s+": dataObj.getSeconds(),                 //秒
            "q+": Math.floor((dataObj.getMonth() + 3) / 3), //季度
            "S": dataObj.getMilliseconds()             //毫秒
        };
        if (/(y+)/.test(fmt)) {
            fmt = fmt.replace(RegExp.$1, (dataObj.getFullYear() + "").substr(4 - RegExp.$1.length));
        }
        for (var k in o) {
            if (new RegExp("(" + k + ")").test(fmt)) {
                fmt = fmt.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
            }
        }
        return fmt;
    }

    async function DirectDownloadPlayingVideo(tag) {
        let elements = document.getElementsByTagName(tag);
        let downloadCount = 0;
        let mseCount = 0;
        for(let i = 0; i < elements.length; i++) {
            let videoLink = document.getElementsByTagName("video")[i].currentSrc;
            if (videoLink.startsWith("blob:")) {
                mseCount++;
                continue;
            }
            let a = document.createElement('a');
            a.download = "direct_" + tag;
            var res = await fetch(videoLink);
            var videoBlob = await res.blob();
            var url = window.URL.createObjectURL(videoBlob);
            a.href = url;
            a.click();
            a.remove();
            window.URL.revokeObjectURL(url);
            downloadCount ++;
        }
        if (downloadCount == 0) {
            if (mseCount == 0) {
                alert("没有找到可以下载的项目。");
            } else {
                alert("没有找到可以直接下载的项目,但是找到了 " + mseCount + " 个使用 MSE 的项目,需要在结束播放后点击 “结束Dump” 按钮来保存。");
            }
        }
    }

    function EndAllDumpTasks() {
        while (dumpEndTasks.length > 0) {
            let endTask = dumpEndTasks.shift();
            endTask();
        }
    }

    unsafeWindow.SavedDataList = [];

    unsafeWindow.DownloadData = function (dataKey, fileName) {
        const link = document.createElement('a');
        link.href = URL.createObjectURL(new Blob([unsafeWindow.SavedDataList[dataKey]]));
        link.download = fileName;
        link.click();
        window.URL.revokeObjectURL(link.href);
    }

    function DownloadDataCmd(key, type, date) {
        var ext = "bin";
        if (type == "audio") {
            ext = "m4a";
        } else if (type == "video") {
            ext = "mp4";
        }
        DownloadData(key, "dumped_" + type + "_" + dateFormat(date, "yyyyMMddhhmmss") + "." + ext);
    }

    function Uint8ArrayConcat(a, b) {
        var c = new Uint8Array(a.length + b.length);
        c.set(a);
        c.set(b, a.length);
        return c;
    }

    function BytesToSize(bytes) {
        if (bytes === 0) return '0 B';
        var k = 1024;
        var sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
        var i = Math.floor(Math.log(bytes) / Math.log(k));
        return (bytes / Math.pow(k, i)).toPrecision(3) + ' ' + sizes[i];
    }

    var _addSourceBuffer = unsafeWindow.MediaSource.prototype.addSourceBuffer;
    unsafeWindow.MediaSource.prototype.addSourceBuffer = function (mime) {
        console.log("MediaSource addSourceBuffer Type: ", mime);
        var sourceBuffer = _addSourceBuffer.call(this, mime);
        var _append = sourceBuffer.appendBuffer;
        var endToSave = false;
        var sourceBufferData = new Uint8Array();
        var isVideo = (mime.startsWith("audio") ? false : true);
        var type = (isVideo ? "video" : "audio");
        var key = type + "_" + window.performance.now().toString();
        var startDate = new Date();
        dumpEndTasks.push(() => {
            endToSave = true;
            console.warn(`轨道: ${mime} 已结束保存。`);
            unsafeWindow.SavedDataList[key] = sourceBufferData;
            let downloadCaption = `下载${(isVideo ? "视频" : "音频")}数据 (${BytesToSize(sourceBufferData.length)}, at ${dateFormat(startDate, "yyyy/MM/dd hh:mm:ss")})`;
            GM_registerMenuCommand(downloadCaption, () => { DownloadDataCmd(key, type, startDate); });
        });
        sourceBuffer.appendBuffer = function (buffer) {
            if (!endToSave) {
                sourceBufferData = Uint8ArrayConcat(sourceBufferData, new Uint8Array(buffer));
            }
            _append.call(this, buffer);
        }
        return sourceBuffer;
    }

})();