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);
})();