// ==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})
})();