Greasy Fork is available in English.

阿里云盘字幕

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

// ==UserScript==
// @name         阿里云盘字幕
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  让你的视频文件和字幕文件梦幻联动!
// @author       polygon
// @match        https://www.aliyundrive.com/drive*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @runat        document-end
// ==/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, '').replaceAll('\\N', '<br/>').replaceAll('\\h', " ")
                }
            },
        },
        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, '').replaceAll('\\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/>').replaceAll(/{[\s\S]*?}/g, '')
                }
            },
        },
    }
    let subtitleType
    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
    }
    // read
    function read(blob) {
        return new Promise(function(resolve, reject) {
            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))
                }
                resolve(reader)
            }
            reader.onerror = reject
            reader.readAsText(blob, 'UTF-8')
        })
    }
    // parse subtitle
    async function parseTextToArray(fileInfo) {
        if (Object.keys(fileInfo).includes("text")) return
        console.log('fetch ' + fileInfo.name)
        let reader = await fetch(fileInfo.url, {headers: {Referer: 'https://www.aliyundrive.com/'}})
            .then(e => e.blob())
            .then(blob => {
                console.log(`UTF-8 ${fileInfo.name}`)
                return read(blob)
            })
        let text = reader.result 
        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}`)
            }
        })
        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;
                bottom: 0vh;
                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 searchIndex = function (target, arr) {
                let indexes = []
                for (let index=0;index<arr.length;index++) {
                    let e = arr[index]
                    if (target >= e.from && target <= e.to) {
                        if (!existIndex(index)) {
                            indexes.push(index)
                        }
                    }
                }
                return indexes
            }
            searchIndex(timeSec, subtitles).forEach((index) => {
                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 = parseFloat(bottom.replace('px', '')) + parseFloat(getComputedStyle(subtitleNode.childNodes[i]).height.replace('px', '')) + 'px'
                                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 fileInfoList = null
        let params = JSON.parse(window.localStorage.token)
        let drive_id = params.default_drive_id
        let access_token = params.access_token
        let parent_file_id = window.location.pathname.split("/").slice(-1)[0]
        console.log(drive_id, access_token, parent_file_id)
        GM_xmlhttpRequest({
            method: "POST",
            url: "https://api.aliyundrive.com/adrive/v3/file/list?jsonmask=next_marker,items(name,file_id,drive_id,url,file_extension)",
            data: JSON.stringify({
                drive_id: drive_id,
                parent_file_id: parent_file_id
            }),
            headers: {
                "authorization": `Bearer ${access_token}`,
            },
            responseType: "json",
            onload: function(res) {
                console.log(res.response)
                fileInfoList = res.response["items"]
            }
        })
        let id = setInterval(
            async function() {
                let modal = Node
                // find title name
                if (modal.querySelectorAll('[class^=filename] span').length == 0 || fileInfoList == null) return
                // 检查text是否加载完毕
                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 = await parseTextToArray(fileInfo)
                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})
})();