B站视频时长计算器

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

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

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

(function() {
    'use strict';

    // 将时长从MM:SS或HH:MM:SS转换为秒
    function parseDuration(duration) {
        const cleanDuration = duration.trim().replace(/^0+/, '');
        const parts = cleanDuration.split(':').map(Number);

        if (parts.length < 2 || parts.length > 3) {
            console.warn('异常时长格式:', duration);
            return 0;
        }

        const isValid = parts.every((part, index) => {
            if (isNaN(part)) return false;
            if (index === 0 && parts.length === 3) return part < 24; // 小时
            return part < 60; // 分钟和秒钟
        });

        if (!isValid) {
            console.warn('异常时长值:', duration);
            return 0;
        }

        if (parts.length === 2) {
            return parts[0] * 60 + parts[1];
        } else {
            return parts[0] * 3600 + parts[1] * 60 + parts[2];
        }
    }

    // 将总秒数转换为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) {
        const videoItems = document.querySelectorAll('.stat-item.duration');
        let totalSeconds = 0;
        let totalEpisodes = videoItems.length;
        let validDurations = 0;

        if (totalEpisodes === 0) {
            console.log('未找到视频时长元素');
            return {
                duration: '0:00:00',
                totalEpisodes: 0,
                remainingEpisodes: 0
            };
        }

        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();
                const seconds = parseDuration(duration);
                if (seconds > 0) {
                    totalSeconds += seconds;
                    remainingEpisodes++;
                    validDurations++;
                    console.log(`第${pageNum}集时长(有效): ${duration} -> ${seconds}秒`);
                } else {
                    console.warn(`第${pageNum}集时长(无效): ${duration}`);
                }
            }
        });

        console.log(`有效时长数: ${validDurations}/${remainingEpisodes}`);

        return {
            duration: formatDuration(totalSeconds),
            totalEpisodes: totalEpisodes,
            remainingEpisodes: remainingEpisodes
        };
    }

    // 创建控件
    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 = `距离撒花还有${result.remainingEpisodes}集,` +
                                   `总时长: ${result.duration} 🌸`;
        }
        resultBox.style.display = 'block';
    }

    // 初始化
    function init() {
        const initInterval = setInterval(() => {
            const durationElements = document.querySelectorAll('.stat-item.duration');
            const titleContainer = document.querySelector('.video-info-title-inner');

            if (durationElements.length > 0 && titleContainer) {
                clearInterval(initInterval);
                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();
                    }
                });

                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);
            }
        }, 1000);
    }

    // 启动时添加延迟
    setTimeout(() => {
        console.log('脚本开始执行');
        window.addEventListener('load', init);
    }, 2000);
})();

QingJ © 2025

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