阿里云盘字幕

让你的视频文件和字幕文件梦幻联动!

目前为 2022-04-15 提交的版本。查看 最新版本

// ==UserScript==
// @name         阿里云盘字幕
// @namespace    http://tampermonkey.net/
// @version      0.53
// @description  让你的视频文件和字幕文件梦幻联动!
// @author       polygon
// @match        https://www.aliyundrive.com/drive*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_addStyle
// @runat        document-start
// ==/UserScript==
!function(f){"object"==typeof exports&&"undefined"!=typeof module?module.exports=f():"function"==typeof define&&define.amd?define([],f):("undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:this).languageEncoding=f()}(function(){return function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var p="function"==typeof require&&require;if(!f&&p)return p(i,!0);if(u)return u(i,!0);throw(p=new Error("Cannot find module '"+i+"'")).code="MODULE_NOT_FOUND",p}p=n[i]={exports:{}},e[i][0].call(p.exports,function(r){return o(e[i][1][r]||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i<t.length;i++)o(t[i]);return o}({1:[function(require,module,exports){const byteOrderMarks=require("../config/byteOrderMarkObject.js");module.exports=uInt8Start=>{for(const element of byteOrderMarks)if(element.regex.test(uInt8Start))return element.encoding;return null}},{"../config/byteOrderMarkObject.js":6}],2:[function(require,module,exports){module.exports=content=>{for(let b=0;b<content.length;b++)if("�"===content[b])return!1;return!0}},{}],3:[function(require,module,exports){const countAllMatches=require("./processing-content/countAllMatches.js"),calculateConfidenceScore=require("./processing-content/calculateConfidenceScore.js");module.exports=(data,fileInfo)=>{data.languageArr=countAllMatches(data,fileInfo.encoding),fileInfo.language=data.languageArr.reduce((acc,val)=>acc.count>val.count?acc:val).name,data.pos=data.languageArr.findIndex(elem=>elem.name===fileInfo.language),fileInfo.encoding||(fileInfo.encoding=data.languageArr[data.pos].encoding);var calculations=calculateConfidenceScore(data,fileInfo);return data.testFile?calculations:(fileInfo.confidence.encoding||(fileInfo.confidence.encoding=calculations),fileInfo.confidence.language=calculations,data.languageArr[data.pos].count||(fileInfo.language=null,fileInfo.confidence.language=null),fileInfo)}},{"./processing-content/calculateConfidenceScore.js":4,"./processing-content/countAllMatches.js":5}],4:[function(require,module,exports){module.exports=(surplus,fileInfo)=>{var confidenceRaise=new RegExp(/\d|\n|\s|\-|\.|\,|\:|\;|\?|\!|\<|\>|\[|\]|\{|\}|\&|\=|\|/,"g"),totalCharacters=surplus.content.replace(confidenceRaise,"").length;const langArr=surplus.languageArr;var pos=surplus.pos;const testFile=surplus.testFile;var secondLanguage=langArr.reduce((acc,val)=>acc.name!==fileInfo.language&&(val.name===fileInfo.language||acc.count>=val.count)?acc:val);const languageRatio=langArr[pos].count/(secondLanguage.count+langArr[pos].count),characterWordRatio=langArr[pos].count/totalCharacters;let lowerLimit=null,upperLimit=null;upperLimit="UTF-8"===fileInfo.encoding||"UTF-16LE"===fileInfo.encoding?(lowerLimit=langArr[pos].utfFrequency?.8*langArr[pos].utfFrequency.low:null,langArr[pos].utfFrequency?(langArr[pos].utfFrequency.low+langArr[pos].utfFrequency.high)/2:null):(lowerLimit=langArr[pos].isoFrequency?.8*langArr[pos].isoFrequency.low:null,langArr[pos].isoFrequency?(langArr[pos].isoFrequency.low+langArr[pos].isoFrequency.high)/2:null);let confidenceScore;return confidenceScore=lowerLimit&&upperLimit?characterWordRatio>=upperLimit?1:characterWordRatio>lowerLimit?(confidenceRaise=upperLimit-lowerLimit,surplus=characterWordRatio-lowerLimit,confidenceRaise=(1-languageRatio)*(surplus/confidenceRaise),Number((languageRatio+confidenceRaise).toFixed(2))):Number((languageRatio*(characterWordRatio/lowerLimit)).toFixed(2)):null,testFile?{name:testFile.substr(testFile.lastIndexOf("/")+1),path:testFile,encoding:fileInfo.encoding,language:fileInfo.language,languageConfidence:confidenceScore,ratio:Number(languageRatio.toFixed(2)),count:langArr[pos].count,totalCharacters:totalCharacters,characterWordRatio:characterWordRatio.toFixed(6),secondLanguage:{name:secondLanguage.name,count:secondLanguage.count}}:confidenceScore}},{}],5:[function(require,module,exports){const languageArr=require("../../config/languageObject.js");module.exports=(data,encoding)=>{const newLanguageArr=[];languageArr.forEach(obj=>{const updatedLangObj={};Object.keys(obj).forEach(key=>{"count"!==key?updatedLangObj[key]=obj[key]:updatedLangObj.count=0}),newLanguageArr.push(updatedLangObj)});const regex=encoding?"utfRegex":"isoRegex";return newLanguageArr.forEach(lang=>{var matches;!lang[regex]||(matches=data.content.match(lang[regex]))&&(lang.count=matches.length)}),newLanguageArr}},{"../../config/languageObject.js":7}],6:[function(require,module,exports){module.exports=[{encoding:"UTF-EBCDIC",regex:new RegExp("221 115 102 115")},{encoding:"GB-18030",regex:new RegExp("132 49 149 51")},{encoding:"UTF-32LE",regex:new RegExp("255 254 0 0")},{encoding:"UTF-32BE",regex:new RegExp("0 0 254 255")},{encoding:"UTF-8",regex:new RegExp("239 187 191")},{encoding:"UTF-7",regex:new RegExp("43 47 118")},{encoding:"UTF-1",regex:new RegExp("247 100 76")},{encoding:"SCSU",regex:new RegExp("14 254 255")},{encoding:"BOCU-1",regex:new RegExp("251 238 40")},{encoding:"UTF-16BE",regex:new RegExp("254 255")},{encoding:"UTF-16LE",regex:new RegExp("255 254")}]},{}],7:[function(require,module,exports){var sharedRegex={czech:new RegExp(/jsem|jsi/,"gi"),hungarian:new RegExp(/\snem\s/,"gi"),slovak:new RegExp(/poriadku|myslím|\ssme\s/,"gi"),slovenian:new RegExp(/\skaj\s|lahko|zdaj/,"gi"),albanian:new RegExp(/nuk/,"gi"),english:new RegExp(/ the /,"gi"),french:new RegExp(/c'est/,"gi"),portuguese:new RegExp(/ não /,"gi"),spanish:new RegExp(/estaba|\smuy\s|siempre|ahora/,"gi"),german:new RegExp(/\sdas\s/,"gi"),italian:new RegExp(/\sche\s/,"gi"),danish:new RegExp(/hvad|noget/,"gi"),norwegian:new RegExp(/deg/,"gi"),swedish:new RegExp(/ jag /,"gi"),dutch:new RegExp(/ het /,"gi"),finnish:new RegExp(/hän/,"gi"),"serbo-croatian":new RegExp(/ sam | kako /,"gi"),estonian:new RegExp(/\sseda\s|\spole\s|midagi/,"gi"),icelandic:new RegExp(/Það/,"gi"),"malay-indonesian":new RegExp(/tidak/,"gi"),turkish:new RegExp(/ bir /,"gi"),lithuanian:new RegExp(/taip|\stai\s/,"gi"),bengali:new RegExp(/এটা/,"gi"),hindi:new RegExp(/हैं/,"gi"),urdu:new RegExp(/ایک/,"gi"),vietnamese:new RegExp(/ không /,"gi")},sharedFrequency={polish:{low:.004355,high:.005102},czech:{low:.004433,high:.007324},hungarian:{low:.004994,high:.005183},romanian:{low:.003319,high:.00419},slovak:{low:.001736,high:.002557},slovenian:{low:.004111,high:.004959},albanian:{low:.003773,high:.007313},ukrainian:{low:.002933,high:.005389},english:{low:.004679,high:.00758},french:{low:.003016,high:.004825},portuguese:{low:.003406,high:.005032},spanish:{low:.002348,high:.002881},german:{low:.004044,high:.004391},italian:{low:.003889,high:.005175},danish:{low:.00363,high:.004189},norwegian:{low:.00241,high:.003918},swedish:{low:.004916,high:.007221},dutch:{low:.003501,high:.00415},finnish:{low:.003308,high:.005135},"serbo-croatian":{low:.002568,high:.005182},estonian:{low:.002892,high:.003963},icelandic:{low:.004366,high:.004366},"malay-indonesian":{low:.002825,high:.003932},greek:{low:.00344,high:.004862},turkish:{low:.002915,high:.004588},hebrew:{low:.003663,high:.004666},lithuanian:{low:.003277,high:.003768},bengali:{low:.003155,high:.005236},hindi:{low:.004159,high:.006478},urdu:{low:.004118,high:.005851},vietnamese:{low:.003387,high:.005191}};module.exports=[{name:"polish",count:0,utfRegex:new RegExp(/się/,"gi"),isoRegex:new RegExp(/siê/,"gi"),encoding:"CP1250",utfFrequency:sharedFrequency.polish,isoFrequency:sharedFrequency.polish},{name:"czech",count:0,utfRegex:sharedRegex.czech,isoRegex:sharedRegex.czech,encoding:"CP1250",utfFrequency:sharedFrequency.czech,isoFrequency:sharedFrequency.czech},{name:"hungarian",count:0,utfRegex:sharedRegex.hungarian,isoRegex:sharedRegex.hungarian,encoding:"CP1250",utfFrequency:sharedFrequency.hungarian,isoFrequency:sharedFrequency.hungarian},{name:"romanian",count:0,utfRegex:new RegExp(/sunt|eşti/,"gi"),isoRegex:new RegExp(/sunt|eºti/,"gi"),encoding:"CP1250",utfFrequency:sharedFrequency.romanian,isoFrequency:sharedFrequency.romanian},{name:"slovak",count:0,utfRegex:sharedRegex.slovak,isoRegex:sharedRegex.slovak,encoding:"CP1250",utfFrequency:sharedFrequency.slovak,isoFrequency:sharedFrequency.slovak},{name:"slovenian",count:0,utfRegex:sharedRegex.slovenian,isoRegex:sharedRegex.slovenian,encoding:"CP1250",utfFrequency:sharedFrequency.slovenian,isoFrequency:sharedFrequency.slovenian},{name:"albanian",count:0,utfRegex:sharedRegex.albanian,isoRegex:sharedRegex.albanian,encoding:"CP1250",utfFrequency:sharedFrequency.albanian,isoFrequency:sharedFrequency.albanian},{name:"russian",count:0,utfRegex:new RegExp(/что/,"gi"),isoRegex:new RegExp(/÷òî/,"gi"),encoding:"CP1251",utfFrequency:{low:.004965,high:.005341},isoFrequency:{low:.003884,high:.003986}},{name:"ukrainian",count:0,utfRegex:new RegExp(/він|але/,"gi"),isoRegex:new RegExp(/â³í|àëå/,"gi"),encoding:"CP1251",utfFrequency:sharedFrequency.ukrainian,isoFrequency:sharedFrequency.ukrainian},{name:"bulgarian",count:0,utfRegex:new RegExp(/това|какво/,"gi"),isoRegex:new RegExp(/òîâà|äîáðå|êaêâo/,"gi"),encoding:"CP1251",utfFrequency:{low:.005225,high:.005628},isoFrequency:{low:.002767,high:.004951}},{name:"english",count:0,utfRegex:sharedRegex.english,isoRegex:sharedRegex.english,encoding:"CP1252",utfFrequency:sharedFrequency.english,isoFrequency:sharedFrequency.english},{name:"french",count:0,utfRegex:sharedRegex.french,isoRegex:sharedRegex.french,encoding:"CP1252",utfFrequency:sharedFrequency.french,isoFrequency:sharedFrequency.french},{name:"portuguese",count:0,utfRegex:sharedRegex.portuguese,isoRegex:sharedRegex.portuguese,encoding:"CP1252",utfFrequency:sharedFrequency.portuguese,isoFrequency:sharedFrequency.portuguese},{name:"spanish",count:0,utfRegex:sharedRegex.spanish,isoRegex:sharedRegex.spanish,encoding:"CP1252",utfFrequency:sharedFrequency.spanish,isoFrequency:sharedFrequency.spanish},{name:"german",count:0,utfRegex:sharedRegex.german,isoRegex:sharedRegex.german,encoding:"CP1252",utfFrequency:sharedFrequency.german,isoFrequency:sharedFrequency.german},{name:"italian",count:0,utfRegex:sharedRegex.italian,isoRegex:sharedRegex.italian,encoding:"CP1252",utfFrequency:sharedFrequency.italian,isoFrequency:sharedFrequency.italian},{name:"danish",count:0,utfRegex:sharedRegex.danish,isoRegex:sharedRegex.danish,encoding:"CP1252",utfFrequency:sharedFrequency.danish,isoFrequency:sharedFrequency.danish},{name:"norwegian",count:0,utfRegex:sharedRegex.norwegian,isoRegex:sharedRegex.norwegian,encoding:"CP1252",utfFrequency:sharedFrequency.norwegian,isoFrequency:sharedFrequency.norwegian},{name:"swedish",count:0,utfRegex:sharedRegex.swedish,isoRegex:sharedRegex.swedish,encoding:"CP1252",utfFrequency:sharedFrequency.swedish,isoFrequency:sharedFrequency.swedish},{name:"dutch",count:0,utfRegex:sharedRegex.dutch,isoRegex:sharedRegex.dutch,encoding:"CP1252",utfFrequency:sharedFrequency.dutch,isoFrequency:sharedFrequency.dutch},{name:"finnish",count:0,utfRegex:sharedRegex.finnish,isoRegex:sharedRegex.finnish,encoding:"CP1252",utfFrequency:sharedFrequency.finnish,isoFrequency:sharedFrequency.finnish},{name:"serbo-croatian",count:0,utfRegex:sharedRegex["serbo-croatian"],isoRegex:sharedRegex["serbo-croatian"],encoding:"CP1252",utfFrequency:sharedFrequency["serbo-croatian"],isoFrequency:sharedFrequency["serbo-croatian"]},{name:"estonian",count:0,utfRegex:sharedRegex.estonian,isoRegex:sharedRegex.estonian,encoding:"CP1252",utfFrequency:sharedFrequency.estonian,isoFrequency:sharedFrequency.estonian},{name:"icelandic",count:0,utfRegex:sharedRegex.icelandic,isoRegex:sharedRegex.icelandic,encoding:"CP1252",utfFrequency:sharedFrequency.icelandic,isoFrequency:sharedFrequency.icelandic},{name:"malay-indonesian",count:0,utfRegex:sharedRegex["malay-indonesian"],isoRegex:sharedRegex["malay-indonesian"],encoding:"CP1252",utfFrequency:sharedFrequency["malay-indonesian"],isoFrequency:sharedFrequency["malay-indonesian"]},{name:"greek",count:0,utfRegex:new RegExp(/είναι/,"gi"),isoRegex:new RegExp(/åßíáé/,"gi"),encoding:"CP1253",utfFrequency:sharedFrequency.greek,isoFrequency:sharedFrequency.greek},{name:"turkish",count:0,utfRegex:sharedRegex.turkish,isoRegex:sharedRegex.turkish,encoding:"CP1254",utfFrequency:sharedFrequency.turkish,isoFrequency:sharedFrequency.turkish},{name:"hebrew",count:0,utfRegex:new RegExp(/אתה/,"gi"),isoRegex:new RegExp(/àúä/,"gi"),encoding:"CP1255",utfFrequency:sharedFrequency.hebrew,isoFrequency:sharedFrequency.hebrew},{name:"arabic",count:0,utfRegex:new RegExp(/هذا/,"gi"),isoRegex:new RegExp(/åðç/,"gi"),encoding:"CP1256",utfFrequency:{low:.003522,high:.004348},isoFrequency:{low:.003773,high:.005559}},{name:"farsi-persian",count:0,utfRegex:new RegExp(/اون/,"gi"),isoRegex:new RegExp(/çíä/,"gi"),encoding:"CP1256",utfFrequency:{low:.002761,high:.004856},isoFrequency:{low:.00301,high:.006646}},{name:"lithuanian",count:0,utfRegex:sharedRegex.lithuanian,isoRegex:sharedRegex.lithuanian,encoding:"CP1257",utfFrequency:sharedFrequency.lithuanian,isoFrequency:sharedFrequency.lithuanian},{name:"chinese-simplified",count:0,utfRegex:new RegExp(/么/,"gi"),isoRegex:new RegExp(/´ó|¶¯|Å®/,"gi"),encoding:"GB18030",utfFrequency:{low:.009567,high:.011502},isoFrequency:{low:.003137,high:.005009}},{name:"chinese-traditional",count:0,utfRegex:new RegExp(/們/,"gi"),isoRegex:new RegExp(/¦b/,"gi"),encoding:"BIG5",utfFrequency:{low:.012484,high:.014964},isoFrequency:{low:.005063,high:.005822}},{name:"japanese",count:0,utfRegex:new RegExp(/ど/,"gi"),isoRegex:new RegExp(/‚»/,"gi"),encoding:"Shift-JIS",utfFrequency:{low:.004257,high:.006585},isoFrequency:{low:.004286,high:.004653}},{name:"korean",count:0,utfRegex:new RegExp(/도/,"gi"),isoRegex:new RegExp(/àö¾î|å¾ß|¡¼­/,"gi"),encoding:"EUC-KR",utfFrequency:{low:.01091,high:.01367},isoFrequency:{low:.004118,high:.004961}},{name:"thai",count:0,utfRegex:new RegExp(/แฮร์รี่|พอตเตอร์/,"gi"),isoRegex:new RegExp(/áîãìãõè|¾íµàµíãì­/,"gi"),encoding:"TIS-620",utfFrequency:{low:.003194,high:.003468},isoFrequency:{low:.002091,high:.002303}},{name:"bengali",count:0,utfRegex:sharedRegex.bengali,isoRegex:sharedRegex.bengali,utfFrequency:sharedFrequency.bengali,isoFrequency:sharedFrequency.bengali},{name:"hindi",count:0,utfRegex:sharedRegex.hindi,isoRegex:sharedRegex.hindi,utfFrequency:sharedFrequency.hindi,isoFrequency:sharedFrequency.hindi},{name:"urdu",count:0,utfRegex:sharedRegex.urdu,isoRegex:sharedRegex.urdu,utfFrequency:sharedFrequency.urdu,isoFrequency:sharedFrequency.urdu},{name:"vietnamese",count:0,utfRegex:sharedRegex.vietnamese,isoRegex:sharedRegex.vietnamese,utfFrequency:sharedFrequency.vietnamese,isoFrequency:sharedFrequency.vietnamese}]},{}],8:[function(require,module,exports){const checkUTF=require("./components/checkUTF.js"),processContent=require("./components/processContent.js"),checkByteOrderMark=require("./components/checkByteOrderMark.js");module.exports=file=>new Promise((resolve,reject)=>{const fileInfo={encoding:null,language:null,confidence:{encoding:null,language:null}},data={},byteOrderMarkBuffer=new FileReader;byteOrderMarkBuffer.onload=()=>{var byteOrderMark=new Uint8Array(byteOrderMarkBuffer.result).slice(0,4).join(" "),byteOrderMark=checkByteOrderMark(byteOrderMark);if(byteOrderMark){fileInfo.encoding=byteOrderMark,fileInfo.confidence.encoding=1;const byteOrderMarkReader=new FileReader;byteOrderMarkReader.onload=()=>{data.content=byteOrderMarkReader.result,resolve(processContent(data,fileInfo))},byteOrderMarkReader.onerror=err=>{reject(err)},byteOrderMarkReader.readAsText(file,fileInfo.encoding)}else{const utfReader=new FileReader;utfReader.onload=()=>{var utfContent=utfReader.result,utf8=checkUTF(utfContent);if(utf8&&(fileInfo.encoding="UTF-8",fileInfo.confidence.encoding=1),utf8)data.content=utfContent,resolve(processContent(data,fileInfo));else{const isoReader=new FileReader;isoReader.onload=()=>{data.content=isoReader.result,resolve(processContent(data,fileInfo))},isoReader.readAsText(file,"ISO-8859-1")}},utfReader.onerror=err=>{reject(err)},utfReader.readAsText(file,"UTF-8")}},byteOrderMarkBuffer.onerror=err=>{reject(err)},byteOrderMarkBuffer.readAsArrayBuffer(file)})},{"./components/checkByteOrderMark.js":1,"./components/checkUTF.js":2,"./components/processContent.js":3}]},{},[8])(8)});

const notification = (function() {
    'use strict';
    GM_addStyle(`
        #notification {
            box-sizing: border-box;
            position: fixed;
            left: calc(50% - 365.65px / 2);
            display: flex;
            flex-direction: row;
            align-items: center;
            justify-content: center;
            height: 50px;
            background-color: #ff7675;
            border-radius: 50px;
            padding: 0 0px 0px 20px;
            top: -50px;
            transition: top .5s ease-out;
            z-index: 9999999999;
        }
        #notification .content {
            display: flex;
            align-items: center;
            justify-content: center;
            color: white;
            font-size: 25px;
        }
        #notification .closeBox {
            margin: 0 10px;
            transform: rotate(90deg);
            cursor: pointer;
        }
        #notification .closeBox .progress {
            margin: 0 10px;
            cursor: pointer;
        }
        #notification .closeBox .progress .circle {
            stroke-dasharray: 100;
            animation: progressOffset 0s linear;
        }
        @keyframes progressOffset {
            from {
                stroke-dashoffset: 100;
            }
            to {
                stroke-dashoffset: 0;
            }
        }
    `)
    return {
        open(info, timeout, autoClose=true) {
            let eles = document.querySelectorAll('#notification')
            for (let i=0;i<eles.length;i++) {
                document.body.removeChild(eles[i])
            }
            this.box = document.createElement('div')
            this.box.setAttribute('id', 'notification')
            this.box.innerHTML = `
                <div class="content"></div>
                <svg class="closeBox" width="40" height="40">
                    <g class="close" style="stroke: white; stroke-width: 2; stroke-linecap: round;">
                        <line x1="13" y1="13" x2="27" y2="27"/>
                        <line x1="13" y1="27" x2="27" y2="13"/>
                    </g>
                    <g class="progress" fill="transparent" stroke-width="3">
                        <circle class="background" cx="20" cy="20" r="16" stroke="rgba(255,255,255,0.15)"/>
                        <circle class="circle" cx="20" cy="20" r="16" stroke="rgba(255,255,255,1)"/>
                    </g>
                </svg>
                `
            document.body.appendChild(this.box)
            this.box.querySelector('.content').innerHTML = info
            let width = getComputedStyle(this.box).width
            this.box.style.left = `clac(50%-${width}/2)`
            this.box.querySelector('.closeBox .progress .circle').style['animation-duration'] = `${timeout}s`
            this.box.style.top = '100px'
            this.box.querySelector('.closeBox .progress').addEventListener('click', () => {
                console.log('you close...')
                this.close()
                console.log('you clear...')
            })
            if (autoClose) {
                setTimeout(() => {
                    console.log('timeout close...')
                    this.close()
                    console.log('timeout clear ...')
                }, timeout * 1000)
            }
        },
        close() {
            this.box.style['transition-duration'] = '.23s'
            this.box.style['transition-timing-function'] = 'eaer-out'
            this.box.style.top = '-50px'
            setTimeout(() => {
                try {
                    document.body.removeChild(this.box)
                } catch {
                    console.log('clear')
                }
            }, 1000)
        }
    }
})();

(function() {
    'use strict'
    // create new XMLHttpRequest
    const subtitleParser = {
        ass: {
            getItems(text) { return text.match(/Dialogue:.+/g) },
            getInfo(item) {
                let [from, to, content] = /Dialogue: 0,(.+?),(.+?),.*?,.*?,.*?,.*?,.*?,.*?,([^\n]+)/.exec(item).slice(1)
                return {
                    from: toSeconds(from),
                    to: toSeconds(to),
                    content: content.replace(/{[\s\S]*?}/g, '').replace('\\N', '<br/>')
                }
            },
        },
        ssa: {
            getItems(text) { return text.match(/Dialogue:.+/g) },
            getInfo(item) {
                let [from, to, content] = /Dialogue: Marked=\d*?,(.+?),(.+?),.*?,.*?,.*?,.*?,.*?,.*?,([^\n]+)/.exec(item).slice(1)
                return {
                    from: toSeconds(from),
                    to: toSeconds(to),
                    content: content.replace(/{[\s\S]*?}/g, '').replace('\\N', '<br/>')
                }
            },
        },
        srt: {
            getItems(text) { return text.split(/(\r\n|\n)\1+/) },
            getInfo(item) {
                let lineArray = item.match(/(.+)/g)
                let [from, to] = lineArray[1].split(' --> ')
                return {
                    from: toSeconds(from),
                    to: toSeconds(to),
                    content: lineArray.slice(2).join('<br/>').replace(/{[\s\S]*?}/g, '')
                }
            },
        },
    }
    let subtitleType
    let fileInfoList = null
    const nativeSend = window.XMLHttpRequest.prototype.send
    XMLHttpRequest.prototype.send = function() {
        if (this.openParams[1].includes('file/list')) {
            this.addEventListener("load", function(event) {
                let target = event.currentTarget
                if (target.readyState == 4 && target.status == 200) {
                    fileInfoList = JSON.parse(target.response).items
                    console.log('saving all subtitle text...')
                    fileInfoList.forEach(fileInfo => {
                        if (Object.keys(subtitleParser).includes(fileInfo.file_extension)) {
                            // download file
                            console.log('caching ' + fileInfo.name)
                            fetch(fileInfo.url, {headers: {Referer: 'https://www.aliyundrive.com/'}})
                            .then(e => e.blob())
                            .then(blob => {
                                console.log(blob)
                                let reader = new FileReader()
                                reader.onload = function(e) {
                                    let text = reader.result
                                    // 可能错误编码方式
                                    if (text.indexOf("�") !== -1) {
                                        console.log('ERROR in UTF-8')
                                        console.log(`GBK ${fileInfo.name}`)
                                        return reader.readAsText(blob, 'GBK')
                                    }
                                    // 可能错误编码方式
                                    if (text.indexOf("") !== -1) {
                                        console.log('ERROR in UTF-8')
                                        console.log(`ISO-8859-1 ${fileInfo.name}`)
                                        languageEncoding(blob)
                                            .then(info => reader.readAsText(blob, info.encoding))
                                    }

                                    fileInfo.text = text
                                }
                                console.log(`UTF-8 ${fileInfo.name}`)
                                reader.readAsText(blob, 'UTF-8')
                            })
                        }
                    })
                }
            })
        }
        nativeSend.apply(this, arguments)
    }
    let toSeconds = (timeStr) => {
        let timeArr = timeStr.replace(',', '.').split(':')
        let timeSec = 0
        for (let i = 0; i < timeArr.length; i++) {
            timeSec += 60 ** (timeArr.length - i - 1) * parseFloat(timeArr[i])
        }
        return timeSec
    }
    // parse subtitle
    let parseTextToArray = (text) => {
        let itemArray = subtitleParser[subtitleType].getItems(text)
        console.log(itemArray)
        let InfoArray = []
        itemArray.forEach((item) => {
            try {
                let info = subtitleParser[subtitleType].getInfo(item)
                InfoArray.push(info)
            }
            catch {
                console.log(`[ERROR] ${item}`)
            }
        })
        console.log(InfoArray)
        return InfoArray
    }

    // add subtitle to video
    let addSubtitle = (subtitles) => {
        console.log('add subtitle...')
        window.startTime = 0
        window.endTime = 0
        const fontsize = 4.23
        // 00:00
        let percentNode = document.querySelector("[class^=modal] [class^=progress-bar] [class^=current]")
        let totalTimeNode = document.querySelector("[class^=modal] [class^=progress-bar] span:last-child")
        // create a subtitle div
        const videoStageNode = document.querySelector("[class^=video-stage]")
        subtitleNode && subtitleNode.parentNode && subtitleNode.parentNode.removeChild(subtitleNode)
        subtitleNode = document.createElement('div')
        subtitleNode.setAttribute('id', 'subtitle')
        GM_addStyle(`
            #subtitle {
                position: absolute;
                display: flex;
                flex-direction: column-reverse;
                align-items: flex-end;
                color: white;
                width: 100%;
                height: 100%;
                bottom: 4vh;
                transition: bottom .2s linear;
                z-index: 9;
            }
            #subtitle .subtitleText {
                position: absolute;
                display: flex;
                align-items: center;
                justify-content: center;
                text-align: center;
                width: 100%;
                color: white;
                text-shadow: 0 0 1px var(--basic_black);
                font-size: ${fontsize}vh;
                visibility: hidden;
            }
            @keyframes subtitle {
                from {
                    visibility: visible;
                }
                to {
                    visibility: visible;
                }
            }
        `)
        videoStageNode.appendChild(subtitleNode)
        console.log('add subtitleNode')
        // 观察变化
        let video = document.querySelector('video')
        const totalSec = video.duration
        console.log(`total time is ${totalSec}s`)
        let insertSubtitle = function (mutationsList, observer) {
            // 00:00:00 => 秒
            let timeSec = video.currentTime
            // 保护时间,防止重复
            if (timeSec > window.endTime || timeSec < window.startTime) {
                // 此时用户可能在拖动进度条,反之拖动后重叠,清空subtitleNode
                subtitleNode.innerHTML = ""
            } else {
                let pTags = subtitleNode.querySelectorAll('[animationend]')
                for (let i=0;i<pTags.length;i++) {
                    subtitleNode.removeChild(pTags[i])
                }
            }
            let existIndex = (index) => {
                if (subtitleNode.childNodes.length) {
                    for (let i=0; i<subtitleNode.childNodes.length; i++) {
                        if (subtitleNode.childNodes[i].getAttribute('index') == String(index)) {
                            return true
                        }
                    }
                }
                return false
            }
            let continueSearch = (index, target, arr, direction, flag=false) => {
                // flag=true,为反向查找开一次路
                if (existIndex(index) || flag) {
                    // 存在,继续向下查找
                    direction ? index ++ : index --
                    if (arr[index] && target >= arr[index].from && target <= arr[index].to) {
                        return continueSearch(index, target, arr)
                    } else {
                        // 没有包含,而且已存在当前,返回无
                        return ''
                    }
                } else {
                    // 不存在index直接返回
                    // 返回string,因为0索引会被误认为false
                    return String(index)
                }
            }
            let binarySearch = function (target, arr) {
                var from = 0;
                var to = arr.length - 1;
                while (from <= to) {
                    let mid = parseInt(from + (to - from) / 2)
                    if (target >= arr[mid].from && target <= arr[mid].to) {
                        // 先向上查找,略过mid本身,在向下查找,包括mid
                        let index = continueSearch(mid, target, arr, false, true) || continueSearch(mid, target, arr, true)
                        return index ? Number(index) : -1
                    } else if (target > arr[mid].to) {
                        from = mid + 1;
                    } else {
                        to = mid - 1;
                    }
                }
                return -1;
            }
            var index = binarySearch(timeSec, subtitles)
            if (index == -1) { return false}
            let oneSubtitle = subtitles[index]
            let subtitleText = document.createElement('p')
            subtitleText.setAttribute('class', 'subtitleText')
            subtitleText.setAttribute('index', String(index))
            subtitleText.innerHTML = oneSubtitle.content
            let duration = oneSubtitle.to - oneSubtitle.from - (timeSec - oneSubtitle.from)
            subtitleText.addEventListener('animationend', function() {
                subtitleText.setAttribute('animationend', '')
            })
            // 合适位置插入
            if (subtitleNode.childNodes.length) {
                // debugger
                let bottom = '0px'
                let i = 0
                while (true) {
                    if (subtitleNode.childNodes[i]) {
                        if (parseFloat(bottom.replace('px', '')) < parseFloat(subtitleNode.childNodes[i].style.bottom.replace('px', ''))) {
                            subtitleText.style.bottom = bottom
                            subtitleNode.insertBefore(subtitleText, subtitleNode.childNodes[i])
                            break
                        } else {
                            bottom = getComputedStyle(subtitleNode.childNodes[i]).height
                            i ++
                            continue
                        }
                    } else {
                        // px -> vh 相对高度,调整窗口自适应
                        subtitleText.style.bottom = parseFloat(bottom.replace('px', '')) / parseFloat(window.innerHeight) * 100 + 'vh'
                        subtitleNode.appendChild(subtitleText)
                        break
                    }
                }
            } else {
                subtitleNode.appendChild(subtitleText)
            }

            subtitleText.style.animation = `subtitle ${duration}s linear`
            // 记录结束时间
            window.startTime = oneSubtitle.from
            window.endTime = oneSubtitle.to
            return true
        }
        var config = { attributes: true, childList: true, subtree: true }
        var observer = new MutationObserver(insertSubtitle)
        observer.observe(percentNode, config)
        // 暂停播放事件
        let playBtnEvent = () => {
            subtitleNode.innerHTML = ""
            while (true) {
                if (!insertSubtitle(null, null)) {
                    break
                }
            }
            subtitleNode.childNodes.forEach((p) => {
                p.style.visibility = 'visible'
            })
        }
        window.addEventListener('keydown', () => {
            if (window.event.which == 32 | window.event.which == 39 | window.event.which == 37) {
                playBtnEvent()
            }
        })
        document.querySelector('[class^=video-player]').addEventListener('click', () => {
            playBtnEvent()
        }, false)
        return observer
    }
    // observer root
    const rootNode = document.querySelector('#root')
    // no root, exist
    if (!rootNode) { return }
    let obsArray = [], subtitleNode
    const callback = function (mutationList, observer) {
        // add subtitle
        subtitleNode = document.querySelector('#subtitle')
        if (subtitleNode) {subtitleNode.parentNode.removeChild(subtitleNode)}
        let Node = mutationList[0].addedNodes[0]
        if (!Node || !Node.getAttribute('class').includes('modal')) { return }
        // clear observer
        obsArray.forEach(obs => {
            console.log(obs)
            console.log('disconnect')
            obs.disconnect()
        })
        obsArray = []
        console.log('add a video modal')
        let id = setInterval(
            () => {
                let modal = Node
                // find title name
                if (modal.querySelectorAll('[class^=filename] span').length == 0) return
                clearInterval(id)
                let filename = modal.querySelector('[class^=filename] span').innerText
                let title = filename.split('.').slice(0, -1).join('.')
                console.log(title)
                console.log(fileInfoList)
                // search the corresponding ass url
                let isInclude = (c1, c2) => {
                    return c1.match(new RegExp((c2.split('.').slice(0, -1).join('.') || c2).replace('(', '\\(').replace(')', '\\)'), 'i'))
                }
                // 先过滤出适配的字幕格式文件
                fileInfoList = fileInfoList.filter((fileInfo) => {
                    if (Object.keys(subtitleParser).includes(fileInfo.name.split('.').slice(-1)[0])) {
                        return true
                    }
                })
                let fileInfo = fileInfoList.filter((fileInfo) => {
                    if (!fileInfo.name.includes('.')) return false
                    if (fileInfo.name == filename) return false
                    // 你中有我,或我中有你
                    let flag = isInclude(fileInfo.name, title) || isInclude(title, fileInfo.name)
                    // S01E01样式匹配
                    const reg = /s\d+e\d+/i
                    let subtitleMatch = fileInfo.name.match(reg)
                    let videoMatch = title.match(reg)
                    if (!flag) {
                        flag =  subtitleMatch && videoMatch && subtitleMatch[0].toUpperCase() == videoMatch[0].toUpperCase()
                    }
                    // 单音频单字幕文件直接匹配,因为没得选
                    if (fileInfoList.length == 1) {
                        flag = true
                    }
                    return flag
                })
                console.log(fileInfo)
                // no file, exit
                if (!fileInfo.length) {console.log('subtitle exit...'); return}
                fileInfo = fileInfo[0]
                console.log(fileInfo)
                subtitleType = fileInfo.name.split('.').slice(-1)
                console.log(`[subtitleType] ${subtitleType}`)
                let subtitles = parseTextToArray(fileInfo.text)
                obsArray.push(addSubtitle(subtitles))
                console.log(`${subtitles.length}条字幕添加成功`)
                notification.open(`${subtitles.length}条字幕添加成功`, 3)
                // 是否变更视频
                let obs = new MutationObserver((mutationList, obs) => {
                    let filenameNode = modal.querySelector('[class^=header] [class^=text]')
                    if (filenameNode && filenameNode.innerText !== filename) {
                        setTimeout(() => {
                            callback([{addedNodes: [modal]}], null)
                        }, 0)
                    }
                })
                obs.observe(modal, {subtree: true, childList: true})
                obsArray.push(obs)
                // 是否触发控制条
                let playerTool = document.querySelector('[class^=video-player]')
                let offsetSubtitle = (mutationList, obs) => {
                    // let subtitleNode = document.querySelector('#subtitle')
                    if (subtitleNode && mutationList[0].attributeName == 'class') {
                        if (mutationList[0].target.classList.length == 2 && document.fullscreenElement) {
                            subtitleNode.style['bottom'] = '13vh'
                        } else {
                            subtitleNode.style['bottom'] = '4vh'
                        }
                    }
                }
                obs = new MutationObserver(offsetSubtitle)
                obs.observe(playerTool, {attributes: true, childList: true})
                offsetSubtitle([{attributeName: 'class', target: playerTool}])
                obsArray.push(obs)
                document.onfullscreenchange = () => {
                    offsetSubtitle([{attributeName: 'class', target: playerTool}], obs)
                }
            }, 10)
    }
    const observer = new MutationObserver(callback)
    observer.observe(rootNode, {childList: true})
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址