// ==UserScript== // @name 阿里云盘字幕 // @namespace http://tampermonkey.net/ // @version 0.5.1 // @description 让你的视频文件和字幕文件梦幻联动! // @author polygon // @match https://www.aliyundrive.com/drive* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_addStyle // @runat document-start // @downloadURL none // ==/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{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{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 ` 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', '
') } }, }, 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('
').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) languageEncoding(blob).then((fileInfo) => console.log(fileInfo)); 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; -webkit-text-stroke: 0.04rem black; font-weight: bold; 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 { if (subtitleNode.childNodes.length) { for (let i=0; i { // 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}) })();