阿里云盘字幕

aliyun subtitle

目前为 2021-08-29 提交的版本。查看 最新版本

// ==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
// ==/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', '<br/>').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', '<br/>').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')
                } else {
                    console.log('pause')
                    subtitleNode.childNodes.forEach((p) => {
                        p.style.visibility = 'visible'
                    })
                }
            }, 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 || !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...')
            let subtitles = parseTextToArray(text)
            let obs = addSubtitle(subtitles)
            obsArray.push(obs)
        })
        let obs = new MutationObserver((mutationList, obs) => {
            let filenameNode = modal.querySelector('[class^=header-file-name]')
            if (filenameNode && filenameNode.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})
})();