Youtube Playlist Duration Calculator

Calculate the duration of a playlist and display it next to the number of videos

  1. // ==UserScript==
  2. // @name Youtube Playlist Duration Calculator
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.1.5
  5. // @description Calculate the duration of a playlist and display it next to the number of videos
  6. // @author DenverCoder1
  7. // @match https://www.youtube.com/playlist
  8. // @include *://youtube.com/playlist*
  9. // @include *://*.youtube.com/playlist*
  10. // @grant none
  11. // @license MIT
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict";
  16.  
  17. /**
  18. * Calculate the duration of a playlist
  19. *
  20. * @returns {string} Duration of the playlist in a human readable format
  21. */
  22. function calculateDuration() {
  23. // get data object stored on Youtube's website
  24. const data = window.ytInitialData;
  25.  
  26. // locate the list of videos in the object
  27. const vids =
  28. data.contents.twoColumnBrowseResultsRenderer.tabs[0].tabRenderer.content.sectionListRenderer.contents[0]
  29. .itemSectionRenderer.contents[0].playlistVideoListRenderer.contents;
  30.  
  31. // add up the lengths of each video in seconds
  32. const seconds = vids.reduce(function (x, y) {
  33. return x + (!isNaN(y.playlistVideoRenderer.lengthSeconds) ? parseInt(y.playlistVideoRenderer.lengthSeconds) : 0);
  34. }, 0);
  35.  
  36. // divide by 60 and round to get the number of minutes
  37. const minutes = Math.round(seconds / 60);
  38.  
  39. // if there is at least 1 hour, display hours and minutes, otherwise display minutes and seconds.
  40. const durationString =
  41. minutes >= 60 // if minutes is 60 or more
  42. ? Math.floor(minutes / 60) + "h " + (minutes % 60) + "m" // calculate hours and minutes
  43. : Math.floor(seconds / 60) + "m " + (seconds % 60) + "s"; // calculate minutes and seconds
  44.  
  45. return durationString;
  46. }
  47.  
  48. /**
  49. * Append the duration to the playlist stats
  50. *
  51. * @param {HTMLElement} statsEl Element to append the duration after
  52. * @param {string} duration Duration of the playlist
  53. */
  54. function appendDurationToStats(statsEl, duration) {
  55. // create a new "yt-formatted-string" element
  56. const newStat = document.createElement("yt-formatted-string");
  57.  
  58. // set it's class to match the other elements
  59. newStat.className = "style-scope ytd-playlist-sidebar-primary-info-renderer byline-item ytd-playlist-byline-renderer";
  60.  
  61. // find the first child of the stats element (the number of videos) and insert the new element after it
  62. statsEl.after(newStat);
  63.  
  64. // set the text of the new element to contain the duration
  65. newStat.innerText = duration;
  66.  
  67. // make sure it is not hidden
  68. newStat.style.display = "inline-block";
  69. }
  70.  
  71. /**
  72. * Wait for an element using an observer
  73. *
  74. * @param {string} selector Selector to wait for
  75. *
  76. * @see https://stackoverflow.com/a/61511955
  77. */
  78. function waitForElement(selector) {
  79. return new Promise((resolve) => {
  80. if (document.querySelector(selector)) {
  81. return resolve(document.querySelector(selector));
  82. }
  83. const observer = new MutationObserver((_) => {
  84. if (document.querySelector(selector)) {
  85. resolve(document.querySelector(selector));
  86. observer.disconnect();
  87. }
  88. });
  89. observer.observe(document.body, {
  90. childList: true,
  91. subtree: true,
  92. });
  93. });
  94. }
  95.  
  96. // add duration to stats when the stats appear
  97. waitForElement("#stats > yt-formatted-string:first-of-type, .metadata-stats > yt-formatted-string:first-of-type").then((statsEl) => {
  98. const durationString = calculateDuration();
  99. appendDurationToStats(statsEl, durationString);
  100. });
  101.  
  102. // prevent ellipsis if line becomes too long to display
  103. document.head.insertAdjacentHTML("beforeend", `<style>.metadata-stats.ytd-playlist-byline-renderer { max-height: unset; -webkit-line-clamp: unset; }</style>`);
  104. })();