Greasy Fork

Greasy Fork is available in English.

阿里云盘字幕

aliyun subtitle

当前为 2021-08-28 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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.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')
                    subtitleNode.innerHTML = ""
                    insertSubtitle(null, null)
                } else {
                    // 暂停状态
                    console.log('pause')
                    subtitleNode.innerHTML = ""
                    insertSubtitle(null, null)
                    if (subtitleNode.lastChild) {
                        subtitleNode.lastChild.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)
    }
    // observer root
    const rootNode = document.querySelector('#root')
    // no root, exist
    if (!rootNode) { return }
    let obs
    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 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)
            addSubtitle(subtitles)
        })
        if (obs) {obs.disconnect()}
        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})
    }
    const observer = new MutationObserver(callback)
    observer.observe(rootNode, {childList: true})
})();