Bilibili 视频时长

从Bilibili网页中提取并求和所有视频时长,并通过按钮在左下角显示结果

目前为 2024-12-24 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Bilibili 视频时长
  3. // @namespace http://tampermonkey.net/
  4. // @license MIT
  5. // @version 0.2
  6. // @description 从Bilibili网页中提取并求和所有视频时长,并通过按钮在左下角显示结果
  7. // @author 阿查&gn
  8. // @match https://www.bilibili.com/video/*
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. 'use strict';
  14.  
  15. // 创建一个按钮,点击后计算并显示总时长
  16. const button = document.createElement('button');
  17. button.innerText = '显示总时长';
  18. button.style.position = 'fixed';
  19. button.style.bottom = '20px';
  20. button.style.right = '20px';
  21. button.style.padding = '10px 20px';
  22. button.style.backgroundColor = '#007BFF';
  23. button.style.color = 'white';
  24. button.style.border = 'none';
  25. button.style.borderRadius = '5px';
  26. button.style.cursor = 'pointer';
  27. button.style.zIndex = '1000';
  28.  
  29. // 将按钮添加到页面中
  30. document.body.appendChild(button);
  31.  
  32. // 创建一个div用于显示总时长
  33. const infoDiv = document.createElement('div');
  34. infoDiv.style.position = 'fixed';
  35. infoDiv.style.left = '20px';
  36. infoDiv.style.bottom = '20px';
  37. infoDiv.style.padding = '10px';
  38. infoDiv.style.backgroundColor = '#f8f9fa';
  39. infoDiv.style.border = '1px solid #ccc';
  40. infoDiv.style.borderRadius = '5px';
  41. infoDiv.style.boxShadow = '0 4px 6px rgba(0, 0, 0, 0.1)';
  42. infoDiv.style.display = 'none'; // 初始时隐藏
  43. infoDiv.style.zIndex = '1000';
  44.  
  45. // 初始时不显示任何内容
  46. infoDiv.innerHTML = '<strong>正在计算时长...</strong>';
  47.  
  48. // 将div添加到页面中
  49. document.body.appendChild(infoDiv);
  50.  
  51. // 时间解析函数,将 "HH:MM:SS" 或 "MM:SS" 格式转换为秒
  52. function parseTimeToSeconds(timeStr) {
  53. const timeParts = timeStr.split(':');
  54. let seconds = 0;
  55.  
  56. if (timeParts.length === 2) {
  57. // "MM:SS" 格式
  58. const minutes = parseInt(timeParts[0], 10);
  59. const secs = parseInt(timeParts[1], 10);
  60. seconds = minutes * 60 + secs;
  61. } else if (timeParts.length === 3) {
  62. // "HH:MM:SS" 格式
  63. const hours = parseInt(timeParts[0], 10);
  64. const minutes = parseInt(timeParts[1], 10);
  65. const secs = parseInt(timeParts[2], 10);
  66. seconds = hours * 3600 + minutes * 60 + secs;
  67. }
  68.  
  69. return seconds;
  70. }
  71.  
  72. // 时间格式化函数,将总秒数转换为 "HH:MM:SS" 或 "MM:SS" 格式
  73. function formatSecondsToTime(seconds) {
  74. const hours = Math.floor(seconds / 3600);
  75. const minutes = Math.floor((seconds % 3600) / 60);
  76. const secs = seconds % 60;
  77.  
  78. if (hours > 0) {
  79. // 格式为 HH:MM:SS
  80. return `${padZero(hours)}:${padZero(minutes)}:${padZero(secs)}`;
  81. } else {
  82. // 格式为 MM:SS
  83. return `${padZero(minutes)}:${padZero(secs)}`;
  84. }
  85. }
  86.  
  87. // 补零函数,确保数字是两位数
  88. function padZero(num) {
  89. return num < 10 ? `0${num}` : num;
  90. }
  91.  
  92. // 获取视频总时长和已看时长的函数
  93. function getVideoDurations() {
  94. const videoPods = document.querySelectorAll('.video-pod.video-pod');
  95. //console.log("videoPods_len=" + videoPods.length);
  96.  
  97. // 定义一个变量存储总时长
  98. let totalDuration = 0;
  99. let lookedDuration = 0;
  100.  
  101. const pod = videoPods[0];
  102.  
  103. // 获取每个 video-pod 下的所有具有 "stat-item duration" 类名的 div
  104. const durationItems = pod.querySelectorAll('.simple-base-item.video-pod__item');
  105. //console.log("durationItems_len=" + durationItems.length);
  106.  
  107. // 遍历这些时间块
  108. durationItems.forEach(item => {
  109. // 获取文本内容并尝试提取数字
  110. const durationText = item.querySelectorAll('.stat-item.duration')[0].innerText.trim();
  111.  
  112. // 假设是以秒为单位的时长,例如 "3秒"
  113. const parsedDuration = parseTimeToSeconds(durationText);
  114.  
  115. if (!isNaN(parsedDuration)) {
  116. if (item.classList.contains('active')) {
  117. lookedDuration = totalDuration;
  118. }
  119. totalDuration += parsedDuration;
  120. }
  121. });
  122.  
  123. // 获取当前播放器的已观看时间
  124. const currentTimeText = document.querySelectorAll('.bpx-player-ctrl-time-current')[0].innerText.trim();
  125. const currentTimeInSeconds = parseTimeToSeconds(currentTimeText);
  126.  
  127. // 返回时长数据
  128. return {
  129. totalDuration,
  130. lookedDuration: lookedDuration + currentTimeInSeconds
  131. };
  132. }
  133.  
  134. // 判断是否为视频集合页面
  135. function isVideoCollectionPage() {
  136. // 查找页面中是否有包含视频集合的容器
  137. return document.querySelector('.video-pod__header') !== null || document.querySelector('.video-list') !== null;
  138. }
  139.  
  140. // 按钮点击事件
  141. button.addEventListener('click', function() {
  142. // 检查当前左下角div的显示状态
  143. let isVisible = infoDiv.style.display === 'block';
  144.  
  145. let refreshInterval;
  146.  
  147. if (isVisible) {
  148. // 如果左下角div已经显示,隐藏它并修改按钮文字
  149. clearInterval(refreshInterval);
  150. infoDiv.style.display = 'none';
  151. button.innerText = '显示总时长';
  152. } else {
  153. // 如果左下角div未显示,显示它并修改按钮文字
  154. infoDiv.style.display = 'block';
  155. button.innerText = '隐藏总时长';
  156.  
  157. // 判断并输出结果
  158. if (isVideoCollectionPage()) {
  159. // console.log('这是一个视频集合页面');
  160.  
  161. // 刷新数据的定时器
  162. refreshInterval = setInterval(() => {
  163.  
  164. const { totalDuration, lookedDuration } = getVideoDurations();
  165.  
  166. // 将总时长转换为 "HH:MM:SS" 或 "MM:SS" 格式
  167. const formattedTime = formatSecondsToTime(totalDuration);
  168. const formattedLookedTime = formatSecondsToTime(lookedDuration);
  169. const restTime = formatSecondsToTime(totalDuration - lookedDuration);
  170.  
  171. // 更新显示的总时长
  172. infoDiv.innerHTML = `
  173. <strong style="display: inline-block; width: 3em;">总长:</strong><span style="display: inline-block; width: 5em; text-align: right;">${formattedTime}</span><br/>
  174. <strong style="display: inline-block; width: 3em;">已看:</strong><span style="display: inline-block; width: 5em; text-align: right;">${formattedLookedTime}</span><br/>
  175. <strong style="display: inline-block; width: 3em;">剩余:</strong><span style="display: inline-block; width: 5em; text-align: right;">${restTime}</span>
  176. `;
  177. // console.log(infoDiv.style.display);
  178. if (infoDiv.style.display === 'none') {
  179. clearInterval(refreshInterval);
  180. }
  181. }, 1000); // 每1s刷新一次
  182.  
  183. } else {
  184. // console.log('这是一个单个视频页面');
  185.  
  186. // 刷新数据的定时器
  187. refreshInterval = setInterval(() => {
  188.  
  189. const totalDuration = parseTimeToSeconds(document.querySelectorAll('.bpx-player-ctrl-time-duration')[0].innerText.trim());
  190. const lookedDuration = parseTimeToSeconds(document.querySelectorAll('.bpx-player-ctrl-time-current')[0].innerText.trim());
  191.  
  192. // 将总时长转换为 "HH:MM:SS" 或 "MM:SS" 格式
  193. const formattedTime = formatSecondsToTime(totalDuration);
  194. const formattedLookedTime = formatSecondsToTime(lookedDuration);
  195. const restTime = formatSecondsToTime(totalDuration - lookedDuration);
  196.  
  197. // 更新显示的总时长
  198. infoDiv.innerHTML = `
  199. <strong style="display: inline-block; width: 3em;">总长:</strong><span style="display: inline-block; width: 5em; text-align: right;">${formattedTime}</span><br/>
  200. <strong style="display: inline-block; width: 3em;">已看:</strong><span style="display: inline-block; width: 5em; text-align: right;">${formattedLookedTime}</span><br/>
  201. <strong style="display: inline-block; width: 3em;">剩余:</strong><span style="display: inline-block; width: 5em; text-align: right;">${restTime}</span>
  202. `;
  203. // console.log(infoDiv.style.display);
  204. if (infoDiv.style.display === 'none') {
  205. clearInterval(refreshInterval);
  206. }
  207. }, 1000); // 每1s刷新一次
  208.  
  209. }
  210. }
  211. });
  212. })();

QingJ © 2025

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