// ==UserScript== // @name 阿里云盘字幕 // @namespace http://tampermonkey.net/ // @version 0.3 // @description aliyun subtitle // @author polygon // @match https://www.aliyundrive.com/drive* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant GM_addStyle // @runat document-start // @downloadURL none // ==/UserScript== (function() { 'use strict' // create new XMLHttpRequest const regex = { ass: { line: /Dialogue:.+/g, info: /Dialogue: 0,(.+?),(.+?),Default,,0000,0000,0000,,.*?{[\s\S]+?}([^\n]+)/, pureContent(content) { return content.replace('\\N', '
').replace('{\\r}', '').replace(/{[\s\S]*?}/g, '') } }, srt: { line: /.+? --> .+?\r\n[^\r\n]+\r\n[^\r\n]+/g, info: /(.+?) --> (.+?)\r\n([^\r\n]+\r\n[^\r\n]+)/, pureContent(content) { return content.replace('\r\n', '
').replace(/{[\s\S]*?}/g, '') } }, other: { line: null, info: null, pureContent() {} } } 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 } }) } 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 lineArray = text.match(regex[subtitleType].line) let InfoArray = [] lineArray.forEach((line) => { try { let [_, from, to, content] = regex[subtitleType].info.exec(line) InfoArray.push({ from: toSeconds(from), to: toSeconds(to), content: regex[subtitleType].pureContent(content) }) } catch { console.log(`[ERROR] ${line}`) } }) console.log(InfoArray) return InfoArray } // add subtitle to video let addSubtitle = (subtitles) => { console.log('add subtitle...') window.startTime = 0 window.endTime = 0 // 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]") let subtitleNode = document.createElement('div') subtitleNode.setAttribute('id', 'subtitle') GM_addStyle(` #subtitle { position: absolute; display: flex; flex-direction: row; align-items: flex-end; color: white; width: 100%; height: 100%; z-index: 9; } #subtitle .subtitleText { display: flex; align-items: center; justify-content: center; text-align: center; width: 100%; height: 20%; color: white; -webkit-text-stroke: 0.04rem black; font-weight: bold; font-size: 4.23vh; position: absolute; } @keyframes subtitle { from { visibility: visible } to { visibility: visible } } `) videoStageNode.appendChild(subtitleNode) console.log('add subtitleNode') // 观察变化 const totalSec = toSeconds(totalTimeNode.textContent) console.log(`total time is ${totalSec}s`) let insertSubtitle = function (mutationsList, observer) { // 00:00:00 => 秒 let timeSec = totalSec * parseFloat(percentNode.style.width.replace('%', '')) / 100 // 保护时间,防止重复 if (timeSec > window.endTime || timeSec < window.startTime){ // 此时用户可能在拖动进度条,反之拖动后重叠,清空subtitleNode subtitleNode.innerHTML = "" } let binarySearch = function (target, arr) { var from = 0; var to = arr.length - 1; while (from <= to) { var mid = parseInt(from + (to - from) / 2); if (target >= arr[mid].from && target <= arr[mid].to) { return mid } else if (target > arr[mid].to) { from = mid + 1; } else { to = mid - 1; } } return -1; } var index = binarySearch(timeSec, subtitles) if (index == -1) { return } let oneSubtitle = subtitles[index] if (oneSubtitle.content == window.currentSubtitle && subtitleNode.children.length) { return } let subtitleText = document.createElement('p') subtitleText.setAttribute('class', 'subtitleText') subtitleText.innerHTML = oneSubtitle.content let duration = oneSubtitle.to - oneSubtitle.from - (timeSec - oneSubtitle.from) subtitleNode.appendChild(subtitleText) let offsetStyle = `bottom: ${3 * Number(oneSubtitle.from > window.startTime && oneSubtitle.from < window.endTime && window.endTime - oneSubtitle.from > 0.1 && mutationsList !== null && observer !== null)}em;` subtitleText.style = `animation: subtitle ${duration}s linear; visibility: hidden; ${offsetStyle}` // 记录结束时间 window.endTime = oneSubtitle.to window.startTime = oneSubtitle.from window.currentSubtitle = oneSubtitle.content } var config = { attributes: true, childList: true, subtree: true } var observer = new MutationObserver(insertSubtitle) observer.observe(percentNode, config) // 暂停播放事件 let playBtnEvent = () => { setTimeout(() => { let isPlay = !videoStageNode.querySelector("video").paused if (isPlay) { console.log('play') if (subtitleNode.lastChild) { let bottom = subtitleNode.lastChild.style.bottom subtitleNode.innerHTML = "" insertSubtitle(null, null) subtitleNode.lastChild.style.bottom = bottom } else { insertSubtitle(null, null) } } else { console.log('pause') if (subtitleNode.lastChild) { let bottom = subtitleNode.lastChild.style.bottom subtitleNode.innerHTML = "" insertSubtitle(null, null) subtitleNode.lastChild.style.visibility = 'visible' subtitleNode.lastChild.style.bottom = bottom } else { insertSubtitle(null, null) } } }, 0) } 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 = [] const callback = function (mutationList, observer) { // add subtitle let subtitleNode = document.querySelector('#subtitle') if (subtitleNode) {subtitleNode.parentNode.removeChild(subtitleNode)} let Node = mutationList[0].addedNodes[0] if (!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 modal = Node // find title name let filename = modal.querySelector('[class^=header-file-name]').innerText let title = filename.split('.').slice(0, -1).join('.') console.log(title) console.log(fileInfoList) // search the corresponding ass url let fileInfo = fileInfoList.filter((fileInfo) => { return fileInfo.name !== filename && fileInfo.name.includes(title) }) // no ass file, exist if (!fileInfo.length) {console.log('subtitle exit...'); return} fileInfo = fileInfo[0] console.log(fileInfo) subtitleType = fileInfo.name.split('.').slice(-1) console.log(`[subtitleType] ${subtitleType}`) // download ass file fetch(fileInfo.download_url, {headers: {Referer: 'https://www.aliyundrive.com/'}}) .then(e => e.text()) .then(text => { console.log('parse subtitle text...') console.log(text) let subtitles = parseTextToArray(text) let obs = addSubtitle(subtitles) obsArray.push(obs) }) let obs = new MutationObserver((mutationList, obs) => { if (modal.querySelector('[class^=header-file-name]').innerText !== filename) { callback([{addedNodes: [modal]}], null) } }) obs.observe(modal, {subtree: true, childList: true}) obsArray.push(obs) } const observer = new MutationObserver(callback) observer.observe(rootNode, {childList: true}) })();