阿里云盘字幕

aliyun subtitle

La data de 28-08-2021. Vezi ultima versiune.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

Advertisement:

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

Advertisement:

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