Greasy Fork

Greasy Fork is available in English.

下载知乎视频

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

目前为 2018-03-05 提交的版本。查看 最新版本

// ==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 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);
        }
    });
})();