文章发布于:2024年5月31日

B站的审核最近比较松懈,有不少电影视频,但都是分集的,每一集十几分钟,而且又在末尾添加了一些杂七杂八的内容,可能是为了应对审核规则吧。这就导致自动下一集没有意义,因为不可能每集都去等那些杂七杂八的内容播完,所以就必须每次手动跳过。

我发现每一集正片内容的时间是固定的,突然想,可以写个什么脚本,检测到视频播放到某个时间,然后自动下一集。脚本可以用“油猴”插件自动执行。

于是让ChatGPT写了一个,稍稍修改可以使用,又让它写了一个界面,方便输入时间,以及执行。

脚本会先检测视频是否有分集,如果没有,则什么都不做,如果有,会弹出一个输入窗口,这个时候,只需要输入需要跳转的时间即可(格式:分钟:秒,例如:12:20)。通过鼠标指针在进度栏上移动,可以看到视频预览和时间,通过画面就可以判断出要跳转的时间点。

注意:进度条的预览是每五秒取样一次,这意味着通过鼠标预览会有较大的误差,如果想获得好的体验,可以拖动视频到正片结尾处,查看精确的结束时间,这样切片观看效果更好。

只需要输入一次,后续的分集会自动跳转。如果选择取消,则所有分集都取消。


//更新 2024-6-1

我以为UP主们上传的视频都是固定长度的,这样跳转也是固定时间,但实际上也存在很多不固定长度。又发现对于不固定长度的选集视频,末尾长度是固定的,于是就想以末尾长度为基准,计算跳转时间。这一操作对相同长度选集视频也适应。

思路是通过第一次用户输入,计算出末尾长度(选集总长减去用户输入),然后在每次跳转前,获取下一个要播放视频的长度,用它减去末尾长度,获得跳转时间,然后更新作为跳转时间的全局变量,为下一次跳转做准备。

脚本在一个浏览器标签内只会执行一次,和刷新无关,但 video 标签的 timeupdate 事件回调却是不断执行,用它就可以为每次跳转做计算。

使用Edge浏览器下的篡改猴扩展,代码如下:

注意:代码头部备注信息很重要,其中@match是用来告诉扩展哪些网站被激活,是代码运行的关键。

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      2024-05-31
// @description  try to take over the world!
// @author       You
// @match        https://www.bilibili.com/video/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==
(function() {

    'use strict';

    const QS = name => document.querySelector(name);
    //检测是否有分集
    const muti_page = QS('#multi_page');
        //没有就不必要运行插件
    if (!muti_page) {
        console.log('脚本信息:未检测到分集!')
        return;
    }

    function main(jump) {

        const jump_time = convertTimeToSeconds(jump);
        //获取当前在播(通常第一个)选集的长度
        const play = QS(".list-box li.on");
        let play_d = play.querySelector('.duration').innerText;
        play_d = convertTimeToSeconds(play_d);
        //计算末尾长度
        const end_d = play_d - jump_time;
        console.log("脚本信息|末尾长度: " + end_d);
        //第一次跳转以输入
        let real_jump = jump_time;

        const video = document.querySelector('video');

        video.addEventListener('timeupdate', () => {
            // 如果当前时间超过跳转时间点
            if (video.currentTime >= real_jump) {
                //获取当前在播项目
                const play = QS(".list-box li.on");
                //获取下一个选集的长度
                const next = play.nextElementSibling;
                let next_d = next.querySelector('.duration').innerText;
                next_d = convertTimeToSeconds(next_d);
                //更新下一次跳转的时间
                real_jump = next_d - end_d;
                console.log('脚本跳转: ' + next_d);
                // 尝试跳转到下一集
                const nextButton = document.querySelector('.bpx-player-ctrl-next');
                if (nextButton) {
                    nextButton.click();
                } else {
                    // 如果没有下一集按钮,重新播放当前集
                    video.currentTime = 0;
                    video.play();
                }
            }
        });
    }

    function convertTimeToSeconds(timeStr) {
        const timeParts = timeStr.split(/[::]/);
        let hours = 0, minutes = 0, seconds = 0;
        if (timeParts.length === 2) {
            minutes = parseInt(timeParts[0], 10);
            seconds = parseInt(timeParts[1], 10);
        } else if (timeParts.length === 3) {
            hours = parseInt(timeParts[0], 10);
            minutes = parseInt(timeParts[1], 10);
            seconds = parseInt(timeParts[2], 10);
        } else {
            throw new Error("时间格式不正确,请使用 'HH:MM' 或 'HH:MM:SS' 格式。");
        }
        return hours * 3600 + minutes * 60 + seconds;
    }

    const container = document.createElement('div');
    container.style.position = 'fixed';
    container.style.top = '10px';
    container.style.left = '50%';
    container.style.transform = 'translateX(-50%)';
    container.style.width = '300px';
    container.style.padding = '20px';
    container.style.backgroundColor = 'white';
    container.style.border = '1px solid #ccc';
    container.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
    container.style.zIndex = '9999';
    container.style.fontFamily = 'Arial, sans-serif';
    container.style.fontSize = '14px';
    document.body.appendChild(container);

    // 创建输入框
    const input = document.createElement('input');
    input.type = 'text';
    input.style.width = '100%';
    input.style.marginBottom = '10px';
    input.style.padding = '5px';
    input.placeholder = '格式 分:秒,例如10:12';
    container.appendChild(input);

    // 创建执行按钮
    const executeButton = document.createElement('button');
    executeButton.innerText = '执行';
    executeButton.style.marginRight = '10px';
    executeButton.style.padding = '5px 10px';
    executeButton.style.cursor = 'pointer';
    executeButton.onclick = function() {
        const inputValue = input.value;
        if (inputValue) {
            main(inputValue);
            document.body.removeChild(container);
        }
    };
    container.appendChild(executeButton);

    // 创建取消按钮
    const cancelButton = document.createElement('button');
    cancelButton.innerText = '取消';
    cancelButton.style.padding = '5px 10px';
    cancelButton.style.cursor = 'pointer';
    cancelButton.onclick = function() {
        document.body.removeChild(container);
    };
    container.appendChild(cancelButton);
})();