阿里云盘字幕

aliyun subtitle

目前為 2021-08-28 提交的版本,檢視 最新版本

// ==UserScript==
// @name         阿里云盘字幕
// @namespace    http://tampermonkey.net/
// @version      0.2
// @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,,.*?{.+?}([^\n]+)/,
            pureContent(content) {return content.replace('{\\r}\\N', '<br/>').replace('{\\r}', '').replace(/{.+?}/, '')}
        },
        other: {
            line: null,
            info: null,
            pureContent() {}
        }
    }
    const subtitleType = 'ass'
    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.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 parseAssToArray = (assText) => {
        // Dialogue: 0,0:00:05.75,0:00:07.42,Default,,0000,0000,0000,,{\fnMicrosoft YaHei\fs20\bord1\shad1\3c&HFF8000&\b0}- 大家好 - 菲比{\r}\N{\fnTahoma\fs12\bord1\shad1\1c&HC0C0C0&\b0}- Hi, guys. JOEY: Hey, Phoebe.{\r}
        let assLineArray = assText.match(regex[subtitleType].line)
        let assInfoArray = []
        assLineArray.forEach((assLine) => {
            let [_, from, to, content] = regex[subtitleType].info.exec(assLine)
            assInfoArray.push({
                from: toSeconds(from),
                to: toSeconds(to),
                content: regex[subtitleType].pureContent(content)
            })
        })
        console.log(assInfoArray)
        return assInfoArray
    }

    // add subtitle to video
    let addSubtitle = (subtitles) => {
        console.log('addSubtitle...')
        window.startTime = 0
        window.endTime = 0
        if (!subtitles.length) { return }
        // 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')
        subtitleNode.innerHTML = `
                                <p class="subtitleText">
                                </p>                  
                                 `
        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)
            console.log(mutationsList)
            let offsetStyle = `bottom: ${3 * Number(oneSubtitle.from < window.endTime - 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 = () => {
            let timeSec = totalSec * parseFloat(percentNode.style.width.replace('%', '')) / 100
            let isPlay = !videoStageNode.querySelector("video").paused
            // isPlay = (window.event.type == 'click') ? !isPlay : isPlay
            if (isPlay) {
                // 播放状态
                console.log('play')
                if (timeSec < window.endTime) {
                    subtitleNode.innerHTML = ""
                    insertSubtitle(null, null)
                } 
            } else {
                // 暂停状态
                console.log('pause')
                if (timeSec < window.endTime) {
                    if (subtitleNode.lastChild) { (subtitleNode.lastChild.style.visibility = 'visible') }
                }  
            }
        }
        window.addEventListener('keydown', () => {
            if (window.event.which == 32 | window.event.which == 39 | window.event.which == 37) {
                window.endTime = 0
                setTimeout(() => {
                    playBtnEvent()
                }, 0)
            }
        })
        document.querySelector('[class^=video-player]').addEventListener('click', () => {
            setTimeout(() => {
                playBtnEvent()
            }, 0)
        }, false)
    }
    // observer root
    const rootNode = document.querySelector('#root')
    // no root, exist
    if (!rootNode) { return }
    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 }
        console.log('add a video modal')
        let modal = Node
        // find title name
        let title = modal.querySelector('[class^=header-file-name]').innerText
        let assFilename = title.replace(title.split('.').slice(-1)[0], subtitleType)
        console.log(assFilename)
        console.log(fileInfoList)
        // search the corresponding ass url
        let assInfo = fileInfoList.filter((fileInfo) => {
            return fileInfo.name == assFilename
        })
        // no ass file, exist
        console.log(assInfo)
        if (!assInfo.length) {console.log('subtitle exit...'); return}
        assInfo = assInfo[0]
        console.log(assInfo)
        // download ass file
        fetch(assInfo.download_url, {headers: {Referer: 'https://www.aliyundrive.com/'}})
        .then(e => e.text())
        .then(text => {
            console.log('parse ass text...')
            let assInfoArray = parseAssToArray(text)
            addSubtitle(assInfoArray)
        })
        rootNode.querySelector('[class*=next]').addEventListener('click', () => {
            console.log('next')
            console.log(modal.querySelector('[class^=header-file-name]').innerText)
            console.log(title)
            setTimeout(() => {
                while (modal.querySelector('[class^=header-file-name]').innerText == title) {
                    console.log('loading')
                }
                callback([{addedNodes: [modal]}], null)
            }, 0)
        })

    }
    const observer = new MutationObserver(callback)
    observer.observe(rootNode, {attributes: false, childList: true, subtree: false})
})();

QingJ © 2025

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