在线视频外挂字幕

目前支持bilibili,优酷,爱奇艺和西瓜视频,按Q键+100ms,按W键-100ms,按E键显示/隐藏字幕,console可作为transcript使用

目前為 2020-03-10 提交的版本,檢視 最新版本

// ==UserScript==
// @name         在线视频外挂字幕
// @namespace    https://truework.top
// @version      0.26
// @description  目前支持bilibili,优酷,爱奇艺和西瓜视频,按Q键+100ms,按W键-100ms,按E键显示/隐藏字幕,console可作为transcript使用
// @author       cyj98
// @match        https://www.bilibili.com/bangumi/*
// @match        https://www.iqiyi.com/*
// @match        https://www.ixigua.com/cinema/album/*
// @grant        none
// @require https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @require https://greasyfork.org/scripts/373379-subtitle-utils-module/code/subtitle%20utils%20module.js?version=637875
// ==/UserScript==

(function () {
    'use strict';
    $("body").prepend('<button id="open-file">字幕</button>')
    $("#open-file").click(() => {
        let hostname = $(location).attr('hostname')
        let subtitleSel, curTimeSel
        if (hostname === 'www.bilibili.com') {
            // '.bilibili-player-video-subtitle'
            subtitleSel = '.subtitle-position'
            curTimeSel = 'bilibili-player-video-time-now'
        } else if (hostname === 'www.iqiyi.com') {
            subtitleSel = '.iqp-subtitle'
            curTimeSel = 'iqp-time-cur'
            $(".iqp-subtitle").css({ "display": "block", "height": "50px" });
        } else if (hostname === 'www.ixigua.com') {
            subtitleSel = '.teleplay__playerContainer'
            curTimeSel = 'xgplayer-time'
        }

        $('head').append('<style>' + `
        #snackbar {
            font-size: 16px;
            visibility: hidden;
            position: absolute;
            text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;
        }
        #snackbar.show {
            visibility: visible;
        } 
        ` + '</style>');

        $(subtitleSel).prepend('<div id="custom-subtitle" style="text-align:center;  font-size: 32px; font-weight: bold;  text-shadow: -1px 0 black, 0 1px black, 1px 0 black, 0 -1px black;"> </div>')
        $(subtitleSel).prepend('<div id="snackbar"></div>')

        if (hostname === 'www.ixigua.com') {
            $('#snackbar').css({ "position": "absolute", "z-index": '1', "color": "white", "bottom": "56px" })
            $("#custom-subtitle").css({ "position": "absolute", "z-index": '1', "color": "white", "bottom": "0", "left": "50%", "-webkit-transform": "translateX(-50%)", "transform": "translateX(-50%)" });
        }

        let fileInput = document.createElement("input")
        fileInput.type = 'file'
        fileInput.style.display = 'none'
        fileInput.onchange = (e) => {
            let file = e.target.files[0];
            if (!file) {
                return;
            }

            let isShowSubtitle = false
            let isShowDelay = false

            let reader = new FileReader();
            reader.onload = (e) => {
                let delayTime = 0
                $(window).on('keypress', (e) => {
                    let key = e.key.toUpperCase()
                    if (key === 'Q' || key === 'W') {
                        if (key === 'Q') delayTime += 100
                        if (key === 'W') delayTime -= 100
                        // console.log("delayTime: ", delayTime)

                        var delaySnackbar = document.getElementById("snackbar");
                        delaySnackbar.innerHTML = 'delaytime: ' + delayTime
                        if (isShowDelay) {
                            return
                        }
                        delaySnackbar.className = "show";
                        isShowDelay = true
                        setTimeout(() => {
                            delaySnackbar.className = delaySnackbar.className.replace("show", "");
                            isShowDelay = false
                        }, 2000);
                    } else if (key === 'E') {
                        if (!isShowSubtitle) {
                            $('#custom-subtitle').css({ "display": "none" })
                            console.log("hide subtitle")
                        } else {
                            $('#custom-subtitle').css({ "display": "block" })
                            console.log("show subtitle")
                        }
                        isShowSubtitle = !isShowSubtitle
                    }
                });

                let contents = e.target.result;
                document.body.removeChild(fileInput)
                // console.log(contents)

                let targetNode = document.getElementsByClassName(curTimeSel)[0]
                let config = {
                    attributes: true,
                    childList: true,
                    subtree: true
                };

                let prevPos = -2

                let binarySearch = (target, arr) => {
                    let start = 0;
                    let end = arr.length - 1;

                    while (start <= end) {
                        let mid = parseInt(start + (end - start) / 2);
                        if (target >= arr[mid].start && target <= arr[mid].end) {
                            return mid;
                        } else if (target > arr[mid].end) {
                            start = mid + 1;
                        } else {
                            end = mid - 1;
                        }
                    }
                    return -1;
                }

                let callback = (_, observer) => {
                    // console.log("observer.subtitles", observer.subtitles)
                    let strTime
                    if (hostname === 'www.ixigua.com') {
                        strTime = targetNode.firstElementChild.firstElementChild.innerHTML;
                    } else {
                        strTime = targetNode.innerHTML
                    }
                    if (strTime.length <= 5) {
                        strTime = "00:" + strTime + ",000"
                    } else {
                        strTime = strTime + ",000"
                    }
                    let time = window.Subtitle.toMS(strTime);

                    let subtitles = observer.subtitles;
                    let pos = binarySearch(time + delayTime, subtitles)
                    if (pos === -1) {
                        $('#custom-subtitle').css({ "display": "none" })
                        return;
                    }
                    if (pos === prevPos) {
                        prevPos = pos
                        return
                    }
                    console.log(subtitles[pos].text);
                    if (isShowSubtitle === false) {
                        $('#custom-subtitle').css({ "display": "block" })
                    }
                    $('#custom-subtitle').html(subtitles[pos].text)
                    window.subtitleCount += 1
                    prevPos = pos
                };

                let observer = new MutationObserver(callback);
                // 将字幕转换为对象数组
                try {
                    observer.subtitles = window.Subtitle.parse(contents)
                } catch (e) {
                    alert("字幕解析出现问题");
                }
                observer.observe(targetNode, config);
            }
            reader.readAsText(file)
        }

        document.body.appendChild(fileInput)
        let eventMouse = document.createEvent("MouseEvents")
        eventMouse.initMouseEvent("click", true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
        fileInput.dispatchEvent(eventMouse)
    })
})();