Greasy Fork

Greasy Fork is available in English.

下载知乎视频

在知乎的视频播放器里显示下载项

当前为 2018-03-05 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         下载知乎视频
// @version      0.2
// @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    http://greasyfork.icu/users/38953
// ==/UserScript==

(function () {
    if (window.location.host == 'www.zhihu.com') return;

    var $download,
        svgDownload = '<path d="M9.5,4 H14.5 V10 H17.8 L12,15.8 L6.2,10 H9.5 Z M6.2,18 H17.8 V20 H6.2 Z"></path>',
        svgCircle = '<circle cx="12" cy="12" r="8" fill="none" stroke-width="2" stroke="#555" />' +
            '<text x="50%" y="50%" dy=".4em" text-anchor="middle" fill="#fff" font-size="9">0</text>' +
            '<path fill="none" r="8" transform="translate(12,12)" stroke-width="2" stroke="#fff" />',
        $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);
        }
    });
})();