B站外挂字幕

为B站视频加载本地字幕文件,支持实时滚动显示

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         B站外挂字幕
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  为B站视频加载本地字幕文件,支持实时滚动显示
// @author       You
// @match        https://www.bilibili.com/video/*
// @match        https://www.bilibili.com/bangumi/play/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 创建字幕显示区域
    let subtitleDiv = document.createElement('div');
    subtitleDiv.style.position = 'absolute';
    subtitleDiv.style.bottom = '10%';
    subtitleDiv.style.left = '50%';
    subtitleDiv.style.transform = 'translateX(-50%)';
    subtitleDiv.style.color = 'white';
    subtitleDiv.style.fontSize = '24px';
    subtitleDiv.style.textShadow = '2px 2px 4px black';
    subtitleDiv.style.zIndex = '9999';
    subtitleDiv.style.textAlign = 'center';
    subtitleDiv.style.width = '80%';
    subtitleDiv.style.minHeight = '30px';
    subtitleDiv.innerHTML = '';

    // 将字幕显示区域添加到视频容器中
    let videoContainer = document.querySelector('.bpx-player-video-wrap');
    if (videoContainer) {
        videoContainer.style.position = 'relative';
        videoContainer.appendChild(subtitleDiv);
    }

    // 创建文件输入框,用于加载字幕文件
    let fileInput = document.createElement('input');
    fileInput.type = 'file';
    fileInput.accept = '.srt,.vtt,.ass';
    fileInput.style.position = 'fixed';
    fileInput.style.top = '10px';
    fileInput.style.right = '10px';
    fileInput.style.zIndex = '9999';
    document.body.appendChild(fileInput);

    let subtitles = []; // 存储解析后的字幕数组

    // 解析SRT字幕文件
    function parseSRT(text) {
        let lines = text.split(/\r?\n/);
        let subtitles = [];
        let current = {};

        for (let i = 0; i < lines.length; i++) {
            let line = lines[i].trim();
            if (!line) continue;

            // 序号
            if (!current.index) {
                current.index = parseInt(line);
                continue;
            }

            // 时间轴
            if (line.includes('-->')) {
                let times = line.split('-->');
                current.start = parseTime(times[0].trim());
                current.end = parseTime(times[1].trim());
                continue;
            }

            // 字幕文本
            if (current.start !== undefined) {
                if (current.text) {
                    current.text += '<br>' + line;
                } else {
                    current.text = line;
                }

                // 如果下一行是空行或者新的序号,则保存当前字幕
                if (i+1 < lines.length && (lines[i+1].trim() === '' || /^\d+$/.test(lines[i+1].trim()))) {
                    subtitles.push(current);
                    current = {};
                }
            }
        }

        // 添加最后一个字幕
        if (current.text) {
            subtitles.push(current);
        }

        return subtitles;
    }

    // 时间格式转换,将SRT时间格式转换为秒
    function parseTime(timeStr) {
        let parts = timeStr.split(':');
        let seconds = 0;
        if (parts.length === 3) {
            seconds += parseFloat(parts[0]) * 3600;
            seconds += parseFloat(parts[1]) * 60;
            seconds += parseFloat(parts[2].replace(',', '.'));
        }
        return seconds;
    }

    // 文件输入变化时,读取字幕文件
    fileInput.addEventListener('change', function(e) {
        let file = e.target.files[0];
        if (!file) return;

        let reader = new FileReader();
        reader.onload = function(e) {
            let text = e.target.result;
            subtitles = parseSRT(text);
        };
        reader.readAsText(file);
    });

    // 监听视频时间更新
    let video = document.querySelector('video');
    if (video) {
        video.addEventListener('timeupdate', function() {
            let currentTime = video.currentTime;
            let currentSubtitle = '';

            for (let i = 0; i < subtitles.length; i++) {
                if (currentTime >= subtitles[i].start && currentTime <= subtitles[i].end) {
                    currentSubtitle = subtitles[i].text;
                    break;
                }
            }

            subtitleDiv.innerHTML = currentSubtitle;
        });
    }
})();// ==UserScript==
// @name        New script
// @namespace   Violentmonkey Scripts
// @match       *://example.org/*
// @grant       none
// @version     1.0
// @author      -
// @description 2025/11/15 06:23:15
// ==/UserScript==