Forked from kugland/download-youtube-subtitles.js
Last active
January 15, 2024 07:43
-
-
Save iqiancheng/bb3d7e4fe895f0f4f7a51f5ee6055965 to your computer and use it in GitHub Desktop.
Download YouTube subtitles (GreaseMonkey/TamperMonkey script)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Download YouTube subtitles | |
// @namespace http://tampermonkey.net/ | |
// @version 0.1 | |
// @description Now you can download YouTube subtitles | |
// @author André Kugland | |
// @match http*://*.youtube.com/* | |
// @grant none | |
// @require https://cdn.jsdelivr.net/npm/[email protected]/dist/FileSaver.min.js#sha256=bbf27552b76b9379c260579fa68793320239be2535ba3083bb67d75e84898e18 | |
// ==/UserScript== | |
(function() { | |
'use strict'; | |
const buttonId = 'download-subtitles-userscript'; | |
const parentSelector = 'ytd-transcript-footer-renderer div#label-text.yt-dropdown-menu'; | |
const buttonSelector = '#' + buttonId; | |
function formatOffset(offset, sep) { | |
sep = sep || '.'; | |
const millis = offset % 1000; | |
offset = Math.floor(offset / 1000); | |
const seconds = offset % 60; | |
offset = Math.floor(offset / 60); | |
const minutes = offset % 60; | |
const hours = Math.floor(offset / 60); | |
return (`${hours < 10 ? '0' : ''}${hours}:` | |
+ `${minutes < 10 ? '0' : ''}${minutes}:` | |
+ `${seconds < 10 ? '0' : ''}${seconds}${sep}` | |
+ `${millis < 100 ? '0' : ''}${millis < 10 ? '0' : ''}${millis}`); | |
} | |
function absorbEvent(event) { | |
event.preventDefault(); | |
event.stopPropagation(); | |
} | |
function getSubtitles() { | |
const cueElements = document.querySelectorAll('.cues > div[start-offset]'); | |
const cues = Array.from(cueElements, elm => ({ | |
startOffset: parseInt(elm.getAttribute('start-offset')), | |
text: elm.innerHTML.trim(), | |
})); | |
const result = cues.map(cue => `${formatOffset(cue.startOffset)} --> ${cue.text}`).join('\n'); | |
return result; | |
} | |
function saveSubtitles(text) { | |
const blob = new Blob([text], {type: "text/plain;charset=utf-8"}); | |
window.saveAs(blob, "subtitle.txt"); | |
} | |
function createButton() { | |
if (document.querySelector(buttonSelector)) { return; } | |
const parentElement = document.querySelector(parentSelector); | |
if (!parentElement) { return; } | |
const button = document.createElement('div'); | |
button.id = 'download-subtitles-userscript'; | |
button.innerText = 'Download'; | |
button.style.textTransform = 'uppercase'; | |
button.style.display = 'inline-block'; | |
button.style.fontSize = '80%'; | |
button.style.margin = '0rem 1rem'; | |
button.style.letterSpacing = '0.05rem'; | |
button.style.cursor = 'pointer'; | |
button.addEventListener('mousedown', absorbEvent); | |
button.addEventListener('click', function (event) { | |
absorbEvent(event); | |
saveSubtitles(getSubtitles()); | |
}); | |
parentElement.append(button); | |
} | |
// Select the node that will be observed for mutations | |
const targetNode = document.querySelector('#panels'); | |
// Create an observer instance linked to the callback function | |
const observer = new MutationObserver((mutationsList, observer) => { | |
for (let mutation of mutationsList) { | |
if (mutation.target.tagName === 'IRON-DROPDOWN') { | |
createButton(); | |
} | |
} | |
}); | |
// Start observing the target node for configured mutations | |
observer.observe(targetNode, { | |
childList: true, | |
subtree: true, | |
}); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment