Bilibili上下弹幕变字幕

用于突出显示B站的顶部弹幕与底部弹幕,使其呈现Youtube字幕的效果。适用于一些有字幕弹幕的生肉视频。

当前为 2018-04-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         Bilibili上下弹幕变字幕
// @namespace    https://space.bilibili.com/68391#!/
// @version      1.1
// @description  用于突出显示B站的顶部弹幕与底部弹幕,使其呈现Youtube字幕的效果。适用于一些有字幕弹幕的生肉视频。
// @author       剧情帝
// @match        https://www.bilibili.com/video/av*
// @match        http://www.bilibili.com/video/av*
// @match        https://www.bilibili.com/watchlater*
// @match        http://www.bilibili.com/watchlater*
// @run-at       document-end
// @create       2017-11-16
// @lastmodified 2017-04-12
// @note         2017.11.24-V0.2 新增识别弹幕颜色,深色弹幕增加白色文字边框的功能
// @note         2017.11.27-V1.0 新增字幕样式设置,可更改字幕的大小、颜色,以及是否显示文字的黑色背景
// @note         2-18.-4.12-V1.1 修复插件
// ==/UserScript==

(function() {
    'use strict';

    var $style = $('<style id="subtitleStyle" type="text/css"></style>');
    $('head').append($style);
    var sheet = $style[0].sheet;

    var $style2 = $('<style id="colorPickerStyle" type="text/css"></style>');
    $('head').append($style2);
    $style2[0].sheet.insertRule('.bilibili-player-color-picker-container .bilibili-player-color-picker-panel .color-span:hover {z-index:10; border-color:#fff}'); //色盘格子的hover样式

    function rgb2hex(rgb) {
        rgb = rgb.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
        function hex(x) {
            return ("0" + parseInt(x).toString(16)).slice(-2);
        }
        return "" + hex(rgb[1]) + hex(rgb[2]) + hex(rgb[3]); //#号不要了
    }

    function setCookie (name, value) {
        var newCookie = name + '=' + value + '; path=/; domain=.bilibili.com; expires=';
        var date = new Date();
        if(value != null)
            date.setTime( date.getTime() + 90 * 24 * 3600 * 1000);
        else
            date.setTime( date.getTime() - 1 * 24 * 3600 * 1000);
        newCookie += date.toGMTString();
        document.cookie = newCookie;
    }

    function getCookie (cname) {
        var name = cname + "=";
        var ca = document.cookie.split(';');
        for(var i=0; i<ca.length; i++)
        {
            var c = ca[i].trim();
            if (c.indexOf(name)==0)
                return c.substring(name.length,c.length);
        }
        return null;
    }

    function calLight (rgb) {
        rgb = rgb.replace('rgb(', '');
        rgb = rgb.replace(')', '');
        rgb = rgb.split(', ');
        return ((parseInt(rgb[0]) * 0.3 + parseInt(rgb[1]) * 0.6 + parseInt(rgb[2]) * 0.1) / 255);
    }

    var $damunContainer, $currentDanmu;
    //监测弹幕变化
    var observer = new MutationObserver(function(records){
        records.map(function(record){
            if(record.addedNodes.length > 0){
                for (var i = record.addedNodes.length - 1; i >= 0; i--) {
                    $currentDanmu = $(record.addedNodes[i]);
                    if($currentDanmu.css('transform') == 'none'){ //不移动的弹幕,也就是顶端和底端弹幕
                        if(calLight($currentDanmu.css('color')) < 0.5)
                            $currentDanmu.css('text-shadow', 'rgb(255, 255, 255) 1px 0px 1px, rgb(255, 255, 255) 0px 1px 1px, rgb(255, 255, 255) 0px -1px 1px, rgb(255, 255, 255) -1px 0px 1px');
                    }
                }
            }
        });
    });

    //等待弹幕图层的加载
    var damunContainerWaiter = new MutationObserver(function(records){
        $damunContainer = $('#bofqi .bilibili-player-video-danmaku');
        if($damunContainer.length > 0){ //弹幕图层加载完毕
            damunContainerWaiter.disconnect();
            observer.observe($damunContainer[0], {'childList':true});
            createSettings();
        }
    });

    damunContainerWaiter.observe($("#bofqi")[0], {'childList':true, 'subtree':true});

    function createSettings(){
        var $player = $("#bofqi #bilibiliPlayer");
        var $playerSetting = $(".bilibili-player-setting-btn", $player);
        var $playerRight = $(".bilibili-player-auxiliary-area", $player);
        var $settingBtn = $('<div class="bilibili-player-setting-btn" name="subtitle_setting" style="right: 72px;"><svg class="icon" style="height: 28px;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1289"><path d="M188.7 604.6c-12.6-29.7-18.9-61.5-18.9-95.5 0-37.1 7.1-69.5 21.3-97.1 14.1-27.6 34.3-48.6 60.5-62.9 26.2-14.4 55-21.5 86.4-21.5 35.6 0 65.6 9.1 89.9 27.2 24.3 18.1 41.2 43.7 50.8 76.6l-46.4 10.9c-8.3-25.9-20.3-44.8-36-56.6-15.7-11.8-35.5-17.7-59.3-17.7-27.4 0-50.3 6.6-68.6 19.7-18.4 13.1-31.4 30.7-38.8 52.9-7.4 22.1-11.2 44.9-11.2 68.4 0 30.3 4.4 56.7 13.2 79.4 8.8 22.6 22.6 39.5 41.2 50.7 18.6 11.2 38.8 16.8 60.5 16.8 26.4 0 48.8-7.6 67.1-22.9 18.3-15.2 30.7-37.8 37.2-67.8l47.2 11.9c-9.9 38.7-27.7 68.3-53.4 88.6-25.7 20.3-57.1 30.5-94.2 30.5-38.4 0-69.7-7.8-93.7-23.5-23.9-15.8-42.2-38.4-54.8-68.1z m414.5 67.9c24 15.7 55.3 23.5 93.7 23.5 37.1 0 68.5-10.2 94.2-30.5 25.7-20.4 43.5-49.9 53.4-88.6L797.3 565c-6.5 30-18.9 52.6-37.2 67.8-18.3 15.2-40.7 22.9-67.1 22.9-21.7 0-41.9-5.6-60.5-16.8-18.6-11.2-32.4-28.1-41.2-50.7-8.8-22.7-13.2-49.1-13.2-79.4 0-23.5 3.7-46.3 11.2-68.4 7.4-22.2 20.4-39.8 38.8-52.9 18.4-13.1 41.3-19.7 68.6-19.7 23.8 0 43.6 5.9 59.3 17.7 15.7 11.8 27.7 30.7 36 56.6l46.4-10.9c-9.6-32.9-26.5-58.4-50.8-76.6-24.3-18.1-54.3-27.2-89.9-27.2-31.5 0-60.2 7.1-86.4 21.5-26.2 14.3-46.3 35.3-60.5 62.9-14.2 27.7-21.3 60-21.3 97.1 0 34 6.3 65.9 18.9 95.5 12.4 29.9 30.7 52.5 54.8 68.1z m357.2-462.3v597.4H64.2V210.2h896.2z m-49.8 49.7H114v497.9h796.6V259.9z" p-id="1290"></path></svg></div>');

        var settingPanelHtml = '';
        settingPanelHtml += '<div id="settingPanel" class="bilibili-player-setting mCustomScrollbar _mCS_9 mCS-autoHide mCS_no_scrollbar _mCS_6">\n';
        settingPanelHtml += '<div class="mCustomScrollBox mCS-light mCSB_vertical mCSB_inside" style="max-height: none;" tabindex="0">\n';
        settingPanelHtml += '<div class="mCSB_container mCS_y_hidden mCS_no_scrollbar_y" style="position: relative; top: 0px; left: 0px;" dir="ltr">\n';
        settingPanelHtml += '<div class="mCustomScrollBox mCS-light mCSB_vertical mCSB_inside" style="max-height: 556px;" tabindex="0">\n';
        settingPanelHtml += '<div class="mCSB_container mCS_y_hidden mCS_no_scrollbar_y" style="position: relative; top: 0px; left: 0px;" dir="ltr">\n';
        settingPanelHtml += '<div class="bilibili-player-panel-title">字幕设置\n';
        settingPanelHtml += '<small style="font-size:13px">(对所有顶端与底端弹幕生效)</small>\n';
        settingPanelHtml += '<i class="bilibili-player-iconfont bilibili-player-panel-back icon-close"></i>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '<div class="bilibili-player-panel-area" style="padding:23px">\n';
        settingPanelHtml += '<div class="size-setting">\n';
        settingPanelHtml += '<label>字体大小:</label>\n';
        settingPanelHtml += '<select name="size">\n';
        settingPanelHtml += '<option value="original">原大小</option>\n';
        settingPanelHtml += '<option value="big">大</option>\n';
        settingPanelHtml += '<option value="medium">中</option>\n';
        settingPanelHtml += '<option value="small">小</option>\n';
        settingPanelHtml += '</select>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '<br>\n';
        settingPanelHtml += '<div class="back-setting">\n';
        settingPanelHtml += '<label style="vertical-align: bottom;">底板:&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</label>\n';
        settingPanelHtml += '<input name="back" type="checkbox">\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '<br>\n';
        settingPanelHtml += '<div class="color-setting">\n';
        settingPanelHtml += '<label>文字颜色:</label>\n';
        settingPanelHtml += '<select name="color">\n';
        settingPanelHtml += '<option value="original">原颜色</option>\n';
        settingPanelHtml += '<option value="selected">自定义</option>\n';
        settingPanelHtml += '</select>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '</div>\n';
        settingPanelHtml += '</div>\n';
        var $settingPanel = $(settingPanelHtml);
        var $colorPicker = $('.bilibili-player-area .bilibili-player-color-picker-container', $player).clone();
        $('.bilibili-player-color-picker-mask', $colorPicker).remove();
        $colorPicker.css({
            'position': 'unset',
            'margin-top': '25px',
            'border-left': '1px solid #e2e2e2',
            'width': '263px'
        });
        $('.bilibili-player-color-picker-panel .color-spans .color-span.active', $colorPicker).removeClass('active').prop('data-active', null);

        $('.bilibili-player-panel-area .color-setting', $settingPanel).append( $colorPicker);
        $settingBtn.click(function(){
            $settingPanel.show();
        });
        $(".icon-close", $settingPanel).click(function(){
            $settingPanel.hide();
        });

        //设置字体大小选择框的功能
        var size = getCookie('subtitle-size');
        if(size == null){
            setCookie('subtitle-size', 'big');
            size = 'big';
        }
        $(".size-setting select", $settingPanel).val( size).change(function(){
            setCookie('subtitle-size', $(this).val());
            setCss();
        });

        //设置底板选项框的功能
        var bg = getCookie('subtitle-bg');
        if(bg == null){
            setCookie('subtitle-bg', true);
            bg = true;
        }
        else if(bg == 'true')
            bg = true;
        else if(bg == 'false')
            bg = false;
        $(".back-setting input", $settingPanel).prop('checked', bg).change(function(){
            setCookie('subtitle-bg', $(this).prop('checked'));
            setCss();
        });

        //设置颜色选项功能
        var color = getCookie('subtitle-color');
        if(color == null){
            setCookie('subtitle-color', 'original');
            color = 'original';
        }
        if(color != 'original'){
            $colorPicker.addClass('active');
            $(".bilibili-player-color-picker-color-current", $colorPicker).css('background-color', '#'+color);
            $(".bilibili-player-color-picker-color-code", $colorPicker).val( color);
        }
        $(".color-setting select", $settingPanel).val( (color == 'original') ? 'original' : 'selected').change(function(){
            if($(this).val() == 'original'){
                setCookie('subtitle-color', 'original');
                $colorPicker.removeClass('active');
            }
            else if($(this).val() == 'selected'){
                setCookie('subtitle-color', 'ffffff');
                $(".bilibili-player-color-picker-color-current", $colorPicker).css('background-color', '#ffffff');
                $(".bilibili-player-color-picker-color-code", $colorPicker).val( 'ffffff');
                $colorPicker.addClass('active');
            }
            setCss();
        });

        //设置色盘功能
        $(".bilibili-player-color-picker-panel", $colorPicker).click(function(e){
            color = rgb2hex( $(e.target).css('background-color'));
            $(".bilibili-player-color-picker-color-current", $colorPicker).css('background-color', '#'+color);
            $(".bilibili-player-color-picker-color-code", $colorPicker).val( color);
            setCookie('subtitle-color', color);
            setCss();
        });

        $settingBtn.insertBefore( $playerSetting);
        $settingPanel.appendTo($playerRight);

        setCss();
    }

    //读取Cookie,设置字幕的样式
    function setCss(){
        if(sheet.cssRules.length > 0)
            sheet.deleteRule(0);
        var cssRule = '#bofqi .bilibili-player-video-danmaku .bilibili-danmaku:not([style*="transition"]) {';
        if(getCookie('subtitle-bg') == 'true'){
            cssRule += 'background-color:rgba(0,0,0,0.75);';
            cssRule += ' padding:4px 10px;';
            cssRule += ' border-radius:3px;';
            cssRule += ' line-height:1 !important;';
        }
        switch( getCookie('subtitle-size')){
            case 'big':
                cssRule += ' font-size:36px !important;';
                break;
            case 'medium':
                cssRule += ' font-size:25px !important;';
                break;
            case 'small':
                cssRule += ' font-size:18px !important;';
        }
        if(getCookie('subtitle-color') != 'original')
            cssRule += ' color:#' + getCookie('subtitle-color') + ' !important;';
        cssRule += ' opacity:1 !important; }';
        sheet.insertRule( cssRule);
    }
})();