阿里云盘字幕

aliyun subtitle

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

// ==UserScript==
// @name         阿里云盘字幕
// @namespace    http://tampermonkey.net/
// @version      0.3.1
// @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: {
            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', '<br/>')
                }
            },
        },
        srt: {
            getItems(text) { return text.split('\r\n\r\n') },
            getInfo(item) {  
                let lineArray = item.split('\r\n').slice(1)
                let [from, to] = lineArray[0].split(' --> ')
                return {
                    from: toSeconds(from),
                    to: toSeconds(to),
                    content: lineArray.slice(1).join('<br/>').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
                }
            })
        }
        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) => {
        console.log(text)
        let itemArray = regex[subtitleType].getItems(text)
        let InfoArray = []
        itemArray.forEach((item) => {
            try {
                let info = regex[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
        // 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: column-reverse; 
                align-items: flex-end; 
                color: white; 
                width: 100%; 
                height: 100%; 
                z-index: 9;
                padding-bottom: 4vh;
            }
            #subtitle .subtitleText {
                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: 4.23vh;
                margin-top: 0px;
            }
            @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 = ""
            } else {
                let pTags = subtitleNode.querySelectorAll('[animationend]')
                for (let i=0;i<pTags.length;i++) {
                    subtitleNode.removeChild(pTags[i])
                }
            }
            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 ++
                        while (true) {
                            if (target >= arr[mid].from && target <= arr[mid].to) {
                                mid ++
                                continue
                            } else {
                                mid --
                                break
                            }
                        }
                        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 }
            // 遍历当前防止重复
            if (subtitleNode.childNodes.length) {
                for (let i=0;i<subtitleNode.childNodes.length;i++) {
                    if (subtitleNode.childNodes[i].getAttribute('index') == String(index)) {
                        return
                    }
                }
            }
            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', '')
            })
            // subtitleNode.appendChild(subtitleText)
            if (subtitleNode.firstChild) {
                subtitleNode.insertBefore(subtitleText, subtitleNode.firstChild)
            } else {
                subtitleNode.appendChild(subtitleText)
            }
            subtitleText.style = `animation: subtitle ${duration}s linear; 
                                  visibility: hidden;`
            // 记录结束时间
            window.startTime = oneSubtitle.from
            window.endTime = Math.max.apply(null, [oneSubtitle.to, window.endTime])
        }
        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.blob())
        .then(blob => {
            let reader = new FileReader()
            console.log('read subtitle text...')
            reader.onload = function(e) {
                let text = reader.result
                console.log('parse subtitle text...')
                let subtitles = parseTextToArray(text)
                let obs = addSubtitle(subtitles)
                obsArray.push(obs)
            }
            reader.readAsText(blob, fileInfo.content_type.includes('text/plain') ? 'GBK' : 'UTF-8')
        })
        let obs = new MutationObserver((mutationList, obs) => {
            let filenameNode = modal.querySelector('[class^=header-file-name]')
            if (filenameNode && filenameNode.innerText !== filename) {
                setTimeout(() => {
                    callback([{addedNodes: [modal]}], null)
                }, 0)
            }
        })
        obs.observe(modal, {subtree: true, childList: true})
        obsArray.push(obs)
    }
    const observer = new MutationObserver(callback)
    observer.observe(rootNode, {childList: true})
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址