您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
extract stream file and convert to mp4
// ==UserScript== // @name GDC Vault downloader // @namespace http://tampermonkey.net/ // @version 0.0.5 // @description extract stream file and convert to mp4 // @author Askhento // @match https://*gdcvault.blazestreaming.com/* // @match https://*gdcvault.com/play/* // @match https://*.gdcvault.com/play/* // @icon data:image/gif;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAQAQAABMLAAATCwAAAAAAAAAAAAAAAABIAAAA5wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAOcAAABIAAAA5AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA5AAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP9gYGD/7+/v////////////YGBg/2BgYP///////////7+/v/8QEBD/gICA////////////39/f/wAAAP8AAAD/7+/v/8/Pz/8QEBD/z8/P/5+fn///////cHBw/yAgIP/Pz8//v7+///////+fn5//ICAg/1BQUP8AAAD/AAAA//////9gYGD/YGBg/7+/v/+fn5///////wAAAP8AAAD/gICA////////////QEBA/wAAAP8AAAD/AAAA/wAAAP/v7+//r6+v/xAQEP8gICD/UFBQ//////9AQED/QEBA/9/f3/+vr6///////5+fn/8QEBD/QEBA/wAAAP8AAAD/QEBA/+/v7////////////4CAgP////////////////+/v7//EBAQ/2BgYP/v7+////////////8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAOcAAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAOcAAAA/AAAAyQAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAP8AAAD/AAAA/wAAAMkAAAA/AAAJVgAAAAAAAB/nAABhcwAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAo8AAAACAABnIAAAAAAAAFQgAABydg== // @grant none // @license MIT // ==/UserScript== (function() { 'use strict'; //console.log("loc = " + window.self.location.href); // check if we are inside iframe if (window.top === window.self) { /* userscript should match main page and iframe with video element some years have different player???? */ function M3U8() { var _this = this; // access root scope this.ie = navigator.appVersion.toString().indexOf(".NET") > 0; this.ios = navigator.platform && /iPad|iPhone|iPod/.test(navigator.platform); this.start = function(m3u8, options) { if (!options) options = {}; var callbacks = { progress: null, finished: null, error: null, aborted: null } var recur; // the recursive download instance to later be initialized. Scoped here so callbakcs can access and manage it. function handleCb(key, payload) { if (key && callbacks[key]) callbacks[key](payload); } if (_this.ios) return handleCb("error", "Downloading on IOS is not supported."); var startObj = { on: function(str, cb) { switch (str) { case "progress": { callbacks.progress = cb; break; } case "finished": { callbacks.finished = cb; break; } case "error": { callbacks.error = cb; break; } case "aborted": { callbacks.aborted = cb; break; } } return startObj; }, abort: function() { ; recur && (recur.aborted = function() { handleCb("aborted"); }); } } var download = new Promise(function(resolve, reject) { var url = new URL(m3u8); var req = fetch(m3u8) .then(function(d) { return d.text(); }) .then(function(d) { const segmentReg = /^(?!#)(.+)\.(.+)$/gm; const segments = d.match(segmentReg); var mapped = map(segments, function(v, i) { let temp_url = new URL(v, url) // absolute url return temp_url.href }); if (!mapped.length) { reject("Invalid m3u8 playlist"); return handleCb("error", "Invalid m3u8 playlist"); } recur = new RecurseDownload(mapped, function(data) { var blob = new Blob(data, { type: "octet/stream" }); handleCb("progress", { status: "Processing..." }); if (!options.returnBlob) { if (_this.ios) { // handle ios? } else if (_this.ie) { handleCb("progress", { status: "Sending video to Internet Explorer... this may take a while depending on your device's performance." }); window.navigator.msSaveBlob(blob, (options && options.filename) || "video.mp4"); } else { handleCb("progress", { status: "Sending video to browser..." }); var a = document.createElementNS("http://www.w3.org/1999/xhtml", "a"); a.href = URL.createObjectURL(blob); a.download = (options && options.filename) || "video.mp4"; a.style.display = "none"; document.body.appendChild(a); // Firefox fix a.click(); handleCb("finished", { status: "Successfully downloaded video", data: blob }); resolve(blob); } } else { handleCb("finished", { status: "Successfully downloaded video", data: blob }); resolve(blob) } }, 0, []); recur.onprogress = function(obj) { handleCb("progress", obj); } }) .catch(function(err) { handleCb("error", "Something went wrong when downloading m3u8 playlist: " + err); }); }); return startObj; } function RecurseDownload(arr, cb, i, data) { // recursively download asynchronously 2 at the time var _this = this; this.aborted = false; recurseDownload(arr, cb, i, data); function recurseDownload(arr, cb, i, data) { var req = Promise.all([fetch(arr[i]), arr[i + 1] ? fetch(arr[i + 1]) : Promise.resolve()]) // HTTP protocol dictates only TWO requests can be simultaneously performed .then(function(d) { return map(filter(d, function(v) { return v && v.blob; }), function(v) { return v.blob(); }); }) .then(function(d) { return Promise.all(d); }) .then(function(d) { var blobs = map(d, function(v, j) { return new Promise(function(resolve, reject) { var reader = new FileReader(); var read = reader.readAsArrayBuffer(new Blob([v], { type: "octet/stream" })); // IE can't read Blob.arrayBuffer :( reader.addEventListener("loadend", function(event) { // event listener, my old friend we meet again... I cenrtainly haven't missed you in place of promise resolve(reader.result);; (_this.onprogress && _this.onprogress({ segment: i + j + 1, total: arr.length, percentage: Math.floor((i + j + 1) / arr.length * 100), downloaded: formatNumber(+reduce(map(data, function(v) { return v.byteLength; }), function(t, c) { return t + c; }, 0)), status: "Downloading..." })); }); }); }); Promise.all(blobs).then(function(d) { for (var n = 0; n < d.length; n++) { // polymorphism data.push(d[n]); } var increment = arr[i + 2] ? 2 : 1; // look ahead to see if we can perform 2 requests at the same time again if (_this.aborted) { data = null; // purge data... client side calling of garbage collector isn't possible. I know about opera and ie's garbage collectors but they're not ideal. _this.aborted(); return; // exit promise } else if (arr[i + increment]) { setTimeout(function() { recurseDownload(arr, cb, i + increment, data); }, _this.ie ? 500 : 0); } else { cb(data); } }); }) .catch(function(err) { ; _this.onerror && _this.onerror("Something went wrong when downloading ts file, nr. " + i + ": " + err); }); } } function filter(arr, condition) { var result = []; for (var i = 0; i < arr.length; i++) { if (condition(arr[i], i)) { result.push(arr[i]); } } return result; } function map(arr, condition) { var result = arr.slice(0); for (var i = 0; i < arr.length; i++) { result[i] = condition(arr[i], i); } return result; } function reduce(arr, condition, start) { var result = start; arr.forEach(function(v, i) { var res = +condition(result, v, i); result = res; }); return result; } function formatNumber(n) { var ranges = [{ divider: 1e18, suffix: "EB" }, { divider: 1e15, suffix: "PB" }, { divider: 1e12, suffix: "TB" }, { divider: 1e9, suffix: "GB" }, { divider: 1e6, suffix: "MB" }, { divider: 1e3, suffix: "kB" } ] for (var i = 0; i < ranges.length; i++) { if (n >= ranges[i].divider) { var res = (n / ranges[i].divider).toString() return res.toString().split(".")[0] + ranges[i].suffix; } } return n.toString(); } } let url; console.log("GDC Vault downloader injected!"); // Here we are at the top window and we setup our message event listener window.addEventListener("message", async function(event) { const text = await (await fetch(event.data.videoURL)).text(); const streamPath = text.split("\n").at(-2); url = event.data.videoURL.replace("index.m3u8", streamPath); }, false); function createBtn(name) { const btn = document.createElement("button") btn.innerText = name; btn.style.zIndex = "1000" btn.style.width = "100px"; btn.style.height = "50px"; btn.style.overflow = "visible" btn.style.top = "20px"; btn.style.left = "20px"; btn.style.position = "absolute" playerContainer.appendChild(btn) return btn; } const videoElem = document.getElementById("my-video_html5_api"); const playerContainer = document.getElementById("container"); const downBtn = createBtn(""); let loading = false; const m3u8 = new M3U8(); function setDown() { downBtn.innerText = "Download"; downBtn.onclick = () => { console.log("start! with url : " + url); loading = true; const download = m3u8.start(url); download.on("progress", progress => { //console.log(progress); // See Classes > M3U8.events.progress downBtn.innerText = "Loading : " + progress.percentage + "%"; }).on("finished", finished => { downBtn.innerText = "Finished!"; console.log(finished); // See Classes > M3U8.events.finished setResetClick(); }).on("error", message => { downBtn.innerText = "Error"; console.error(message); // See Classes > M3U8.events.error setResetClick(); }).on("aborted", () => { downBtn.innerText = "Aborted"; console.log("Download aborted"); setResetClick(); }); function setResetClick() { downBtn.onclick = () => { setDown(); } } downBtn.onclick = () => { download.abort(); } } } setDown(); } else { console.log("GDC Vault downloader iframe injected!"); // Here we get inside the iframes. // We can address and check each iframe url with document.domain window.top.postMessage({ videoURL : PLAYBACK_URL //.replace("index", "index_2") }, "*"); } })();