YouTube Playlist Calculator

Get the total length/duration of a YouTube playlist.

Tính đến 06-05-2023. Xem phiên bản mới nhất.

  1. // ==UserScript==
  2. // @name YouTube Playlist Calculator
  3. // @version 1.0.1
  4. // @author sapondanaisriwan
  5. // @namespace https://github.com/sapondanaisriwan/Youtube-Playlist-Calculator
  6. // @description Get the total length/duration of a YouTube playlist.
  7. // @match https://www.youtube.com/*
  8. // @grant none
  9. // @license MIT
  10. // @homepageURL https://github.com/sapondanaisriwan/Youtube-Playlist-Calculator
  11. // @supportURL https://github.com/sapondanaisriwan/Youtube-Playlist-Calculator/issues
  12. // @icon https://i.imgur.com/I9uDrsq.png
  13. // ==/UserScript==
  14.  
  15. /*
  16. If you want to submit a bug or request a feature please report via github issue. Since I receive so many emails, I can't reply to them all.
  17. Contact: sapondanaisriwan@gmail.com
  18. Support me: https://ko-fi.com/sapondanaisriwan
  19. Support me: https://ko-fi.com/sapondanaisriwan
  20. Support me: https://ko-fi.com/sapondanaisriwan
  21. Support me: https://ko-fi.com/sapondanaisriwan
  22. Support me: https://ko-fi.com/sapondanaisriwan
  23. */
  24.  
  25. "use strict";
  26.  
  27. const config = { childList: true, subtree: true };
  28.  
  29. const selectors = {
  30. playlistPage: "ytd-browse[page-subtype='playlist']",
  31. overlayTime: "ytd-playlist-header-renderer #overlays .duration-text",
  32. playlistOverlay: "ytd-playlist-header-renderer #overlays",
  33. timestampOverlay:
  34. "ytd-playlist-video-list-renderer #text.ytd-thumbnail-overlay-time-status-renderer",
  35. thumbnail:
  36. "ytd-playlist-video-list-renderer ytd-thumbnail-overlay-hover-text-renderer",
  37. };
  38.  
  39. const styles = {
  40. log: "color: #fff; font-size: 16px;",
  41. duration: `
  42. .duration-overlay {
  43. margin: 4px;
  44. position: absolute;
  45. bottom: 0;
  46. right: 0;
  47. color: var(--yt-spec-static-brand-white);
  48. background-color: var(--yt-spec-static-overlay-background-heavy);
  49. padding: 3px 4px;
  50. height: 12px;
  51. border-radius: 2px;
  52. font-size: var(--yt-badge-font-size,1.2rem);
  53. font-weight: var(--yt-badge-font-weight,500);
  54. line-height: var(--yt-badge-line-height-size,1.2rem);
  55. letter-spacing: var(--yt-badge-letter-spacing,0.5px);
  56. display: flex;
  57. flex-direction: row;
  58. align-items: center;
  59. }
  60. .duration-text {
  61. max-height: 1.2rem;
  62. overflow: hidden;
  63. }
  64. `,
  65. };
  66.  
  67. const cLog = (msg) => console.log(`%c${msg}`, styles.log);
  68.  
  69. const select = (selector) => document.querySelector(selector);
  70.  
  71. const selectAll = (selector) => document.querySelectorAll(selector);
  72.  
  73. const addStyles = (css) => {
  74. const style = document.createElement("style");
  75. style.type = "text/css";
  76. style.textContent = css;
  77. document.documentElement.appendChild(style);
  78. };
  79.  
  80. const getData = (selector) => {
  81. return selector?.__data?.data?.contents?.twoColumnBrowseResultsRenderer
  82. ?.tabs[0]?.tabRenderer?.content?.sectionListRenderer?.contents[0]
  83. ?.itemSectionRenderer?.contents[0]?.playlistVideoListRenderer?.contents;
  84. };
  85.  
  86. const formatDuration = (sum) => {
  87. const hours = Math.floor(sum / 3600);
  88. const minutes = Math.floor((sum % 3600) / 60);
  89. const seconds = sum % 60;
  90. let formattedDuration = "";
  91. if (hours > 0) {
  92. formattedDuration += hours + ":";
  93. }
  94. if (minutes < 10 && hours > 0) {
  95. formattedDuration += "0";
  96. }
  97. formattedDuration += minutes + ":";
  98. if (seconds < 10) {
  99. formattedDuration += "0";
  100. }
  101. formattedDuration += seconds;
  102. return formattedDuration;
  103. };
  104.  
  105. const newOverlayContainer = document.createElement("div");
  106. newOverlayContainer.setAttribute("class", "duration-overlay");
  107.  
  108. const newTextEle = document.createElement("span");
  109. newTextEle.setAttribute("class", "duration-text");
  110.  
  111. newOverlayContainer.appendChild(newTextEle);
  112.  
  113. const addDurationOverlay = (duration) => {
  114. const overlayTimeEle = select(selectors.overlayTime);
  115. const overlayCon = select(selectors.playlistOverlay);
  116.  
  117. if (!overlayCon) return;
  118.  
  119. if (overlayTimeEle) {
  120. overlayTimeEle.textContent = duration;
  121. } else {
  122. overlayCon.prepend(newOverlayContainer);
  123. }
  124. };
  125.  
  126. const sumResult = (data) =>
  127. data.reduce((pre, cur) => {
  128. return (
  129. pre +
  130. (!!cur.playlistVideoRenderer
  131. ? +cur.playlistVideoRenderer.lengthSeconds
  132. : 0)
  133. );
  134. }, 0);
  135.  
  136. const main = () => {
  137. const playlistEle = select(selectors.playlistPage);
  138. if (!playlistEle) return;
  139.  
  140. const data = getData(playlistEle);
  141. const sum = sumResult(data);
  142.  
  143. const duration = formatDuration(sum);
  144. addDurationOverlay(duration);
  145. };
  146.  
  147. const run = () => {
  148. addStyles(styles.duration);
  149. const observer = new MutationObserver(main);
  150. observer.observe(document.body, config);
  151. };
  152.  
  153. run();