B站视频时长计算器

计算B站视频列表中到指定集数的剩余时长

目前为 2025-01-25 提交的版本。查看 最新版本

// ==UserScript==
// @name         B站视频时长计算器
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  计算B站视频列表中到指定集数的剩余时长
// @author       Your name
// @match        https://www.bilibili.com/video/*
// @license      MIT
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // 将时长从MM:SS转换为秒
    function parseDuration(duration) {
        const [minutes, seconds] = duration.split(':').map(Number);
        return minutes * 60 + seconds;
    }

    // 将总秒数转换为HH:MM:SS格式
    function formatDuration(totalSeconds) {
        const hours = Math.floor(totalSeconds / 3600);
        const minutes = Math.floor((totalSeconds % 3600) / 60);
        const seconds = totalSeconds % 60;
        return `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
    }

    // 获取当前播放的集数
    function getCurrentEpisode() {
        const urlParams = new URLSearchParams(window.location.search);
        const p = urlParams.get('p');
        return p ? parseInt(p) : 1;
    }

    // 主要计算逻辑
    function calculateTotalDuration(startPage) {
        // 更新选择器以匹配B站视频列表的时长元素
        const videoItems = document.querySelectorAll('.video-episode-card__info-duration');
        let totalSeconds = 0;
        let totalEpisodes = videoItems.length;
        
        // 如果没有找到视频元素,返回空结果
        if (totalEpisodes === 0) {
            console.log('未找到视频时长元素,尝试其他选择器');
            // 尝试备用选择器
            const alternativeItems = document.querySelectorAll('.duration');
            if (alternativeItems.length === 0) {
                return {
                    duration: '0:00:00',
                    totalEpisodes: 0,
                    remainingEpisodes: 0
                };
            }
            videoItems = alternativeItems;
            totalEpisodes = alternativeItems.length;
        }

        console.log('找到视频元素数量:', totalEpisodes);

        // 确保起始页码有效
        startPage = Math.max(1, Math.min(startPage, totalEpisodes));

        // 计算从起始页到最后的总时长
        let remainingEpisodes = 0;
        videoItems.forEach((item, index) => {
            const pageNum = index + 1;
            // 只计算从起始页(不包含)到最后的时长
            if (pageNum > startPage) {  // 改为大于而不是大于等于
                const duration = item.textContent.trim();
                console.log(`第${pageNum}集时长:`, duration);
                totalSeconds += parseDuration(duration);
                remainingEpisodes++;
            }
        });

        const result = {
            duration: formatDuration(totalSeconds),
            totalEpisodes: totalEpisodes,
            remainingEpisodes: remainingEpisodes
        };
        
        console.log('计算结果:', result);
        return result;
    }

    // 创建控件
    function createControls() {
        const container = document.createElement('div');
        container.style.cssText = `
            display: inline-flex;
            gap: 10px;
            align-items: center;
            margin-left: 20px;
            vertical-align: middle;
        `;

        // 创建计算按钮(左)
        const button = document.createElement('button');
        button.textContent = '计算剩余时长';
        button.style.cssText = `
            padding: 5px 10px;
            background: #00A1D6;
            color: white;
            border: none;
            border-radius: 3px;
            cursor: pointer;
            font-size: 14px;
            order: 1;
        `;

        // 创建输入框(中)
        const input = document.createElement('input');
        input.type = 'number';
        input.placeholder = '目标集数';
        input.style.cssText = `
            width: 80px;
            padding: 5px;
            border: 1px solid #ccc;
            border-radius: 3px;
            font-size: 14px;
            text-align: center;
            order: 2;
        `;

        // 创建结果显示框(右)
        const resultBox = document.createElement('div');
        resultBox.style.cssText = `
            display: none;
            padding: 5px 10px;
            background: rgba(0, 0, 0, 0.7);
            color: white;
            border-radius: 3px;
            font-size: 14px;
            line-height: 1.5;
            white-space: nowrap;
            vertical-align: middle;
            order: 3;
        `;

        container.appendChild(button);
        container.appendChild(input);
        container.appendChild(resultBox);

        const titleContainer = document.querySelector('.video-info-title-inner');
        if (titleContainer) {
            titleContainer.appendChild(container);
        }

        return { button, input, resultBox };
    }

    // 更新显示
    function updateDisplay(resultBox, input) {
        const currentPage = parseInt(input.value) || getCurrentEpisode();
        const result = calculateTotalDuration(currentPage);
        
        if (result.totalEpisodes === 0) {
            resultBox.textContent = '未找到视频时长信息';
        } else if (currentPage >= result.totalEpisodes) {
            resultBox.textContent = '已经是最后一集了';
        } else {
            resultBox.textContent = `从第${currentPage}集之后,还有${result.remainingEpisodes}集,` +
                                   `总时长: ${result.duration}`;
        }
        resultBox.style.display = 'block';
    }

    // 初始化
    function init() {
        // 等待页面加载完成
        setTimeout(() => {
            const { button, input, resultBox } = createControls();

            // 设置初始值并显示
            const currentEpisode = getCurrentEpisode();
            input.value = currentEpisode;
            updateDisplay(resultBox, input);

            // 点击按钮更新
            button.addEventListener('click', () => {
                updateDisplay(resultBox, input);
            });

            // 回车更新
            input.addEventListener('keypress', (e) => {
                if (e.key === 'Enter') {
                    button.click();
                }
            });

            // URL变化监听
            let lastUrl = location.href;
            new MutationObserver(() => {
                const url = location.href;
                if (url !== lastUrl) {
                    lastUrl = url;
                    setTimeout(() => {
                        const newEpisode = getCurrentEpisode();
                        input.value = newEpisode;
                        updateDisplay(resultBox, input);
                    }, 1000);
                }
            }).observe(document, { subtree: true, childList: true });

            // 定期检查更新(每秒检查一次)
            setInterval(() => {
                const newEpisode = getCurrentEpisode();
                if (newEpisode !== parseInt(input.value)) {
                    input.value = newEpisode;
                    updateDisplay(resultBox, input);
                }
            }, 1000);
        }, 2000);
    }

    // 启动
    window.addEventListener('load', init);
})(); 

QingJ © 2025

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