// ==UserScript== // @name 下载知乎视频 // @version 0.1 // @description 在知乎的视频播放器里显示下载项 // @author Chao // @include *://www.zhihu.com/* // @match *://www.zhihu.com/* // @include https://v.vzuu.com/video/* // @match https://v.vzuu.com/video/* // @connect zhihu.com // @connect vdn.vzuu.com // @grant GM_xmlhttpRequest // @require https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js // @namespace https://greasyfork.org/users/38953 // @downloadURL none // ==/UserScript== (function () { if (window.location.host == 'www.zhihu.com') return; var $download, svgDownload = '', svgCircle = '' + '0' + '', $menu, $menuItem, blobs = null, ratio = 0, refererBaseUrl = 'https://v.vzuu.com/video/', playlistBaseUrl = 'https://lens.zhihu.com/api/videos/', playlistId = window.location.pathname.split('/').pop(); // 重置下载图标 var resetDownloadIcon = function () { $download.find('svg:first').html(svgDownload); }; // 更新进度界面 var updateProgress = function (progress) { var r = 8, degrees = progress / 100 * 360, // 进度对应的角度值 rad = degrees * (Math.PI / 180), // 角度对应的弧度值 x = (Math.sin(rad) * r).toFixed(2), // 极坐标转换成直角坐标 y = -(Math.cos(rad) * r).toFixed(2); // 大于180度时画大角度弧,小于180度时画小角度弧,(deg > 180) ? 1 : 0 var lenghty = window.Number(degrees > 180); // path 属性 var paths = ['M', 0, -r, 'A', r, r, 0, lenghty, 1, x, y]; $download.find('svg:first > path').attr('d', paths.join(' ')); $download.find('svg:first > text').text(progress); }; // 保存视频文件 var store = function () { for (i in blobs) { if (blobs[i] == undefined) return; } var blob = new Blob(blobs, {type: 'video/h264'}), url = window.URL.createObjectURL(blob), a = document.createElement('a'), filename = (new Date()).valueOf() + '.mp4'; blobs = null; // 结束进度显示 resetDownloadIcon(); if (window.navigator && window.navigator.msSaveBlob) { window.navigator.msSaveBlob(blob, filename); } else { a.href = url; a.download = filename; a.click(); window.URL.revokeObjectURL(url); } }; // 下载 m3u8 文件中的单个 ts 文件 var downloadTs = function (url, order) { fetch(url).then(function (res) { return res.blob().then(function (blob) { ratio++; updateProgress(Math.round(100 * ratio / blobs.length)); blobs[order] = blob; store(); }); }); }; // 下载 m3u8 文件 var downloadM3u8 = function (url) { $.get(url, function (res) { //console.log(res.responseText); // 代码参考 http://nuttycase.com/vidio/ var i = 0; blobs = []; ratio = 0; // 初始化进度显示 $download.find('svg:first').html(svgCircle); res.split('\n').forEach(function (line) { if (line.match(/\.ts/)) { blobs[i] = undefined; downloadTs(url.replace(/\/[^\/]+?$/, '/' + line), i++); } }); }); }; // 读取 playlist $.getJSON({ url: playlistBaseUrl + playlistId, headers: { authorization: 'oauth c3cef7c66a1843f8b3a9e6a1e3160e20', // in zplayer.min.js of zhihu referer: refererBaseUrl + playlistId }, success: function (res) { var $player = $('#player'), $controlBar = $player.find('> div:first-child > div:eq(1) > div:last-child > div:first-child'), $fullScreen = $controlBar.find('> div:nth-last-of-type(1)'), $resolution = $controlBar.find('> div:nth-last-of-type(3)'), videos = []; // 添加下载项 $download = $resolution.clone(); // 不同分辨率视频的信息 $.each(res.playlist, function (key, value) { value.name = key; videos.push(value); }); // 按大小排序 videos = videos.sort(function (v1, v2) { return v1.width == v2.width ? 0 : (v1.width > v2.width ? 1 : -1); }).reverse(); // 下载按钮文字 $download.find('button:first').html($fullScreen.clone().find('button:first').html()).find('svg').html(svgDownload); // 各分辨率菜单 $menuItem = $download.find('button:eq(1)'); $menu = $menuItem.parent().empty(); $.each(videos, function (i, value) { $menu.append($menuItem.clone().text(value.width).css({width: '100%', textAlign: 'right'})); }); $download // 显示下载菜单 .on('pointerenter', function () { if (blobs == null) { $menu.parent().attr('style', 'opacity: 1 !important; visibility: visible !important'); } }) // 隐藏下载菜单 .on('pointerleave', function () { if (blobs == null) { $menu.parent().removeAttr('style'); } }) // 暂停下载 .on('pointerdown', function () { return; // if (blobs != null) { // resetDownloadIcon(); // } }); // 选择下载菜单 $menu.on('pointerup', 'button', function () { var video = videos[$(this).index()]; $menu.parent().removeAttr('style'); downloadM3u8(video.play_url); }); // 添加下载项 $controlBar.append($download); } }); })();