YouTube Transcript Downloader

Downloads and copies YouTube video transcripts.

  1. // ==UserScript==
  2. // @name YouTube Transcript Downloader
  3. // @namespace https://github.com/blarer/youtube-transcript-downloader
  4. // @version 1.1
  5. // @description Downloads and copies YouTube video transcripts.
  6. // @author Blareware aka blare
  7. // @match https://www.youtube.com/watch?v=*
  8. // @grant GM_addStyle
  9. // @run-at document-end
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. (function() {
  14. 'use strict';
  15.  
  16. // Add styles using GM_addStyle
  17. GM_addStyle(`
  18. #download_btn, #copy_btn {
  19. color: var(--yt-spec-text-primary);
  20. background: var(--yt-spec-brand-button-background);
  21. border: none;
  22. border-radius: 18px;
  23. padding: 8px 16px;
  24. font-size: 14px;
  25. cursor: pointer;
  26. margin: 10px 15px; /* Added 15px margin-right */
  27. transition: opacity 0.2s;
  28. }
  29. #download_btn:hover, #copy_btn:hover {
  30. opacity: 0.8;
  31. }
  32. `);
  33.  
  34. function init() {
  35. // Updated selectors for 2023 YouTube structure
  36. const possibleSelectors = [
  37. "ytd-watch-metadata",
  38. "#above-the-fold",
  39. "#title.ytd-watch-metadata",
  40. "#container.ytd-watch-metadata",
  41. "ytd-video-primary-info-renderer"
  42. ];
  43.  
  44. console.log('Searching for YouTube containers...');
  45.  
  46. let container = null;
  47. for (const selector of possibleSelectors) {
  48. container = document.querySelector(selector);
  49. if (container) {
  50. console.log('Found container using selector:', selector);
  51. break;
  52. }
  53. }
  54.  
  55. if (!container) {
  56. console.log('Container not found, retrying...');
  57. setTimeout(init, 1000);
  58. return;
  59. }
  60.  
  61. // Add event listener to the transcript button
  62. const transcriptButton = document.querySelector('button[aria-label="Show transcript"]');
  63. if (transcriptButton) {
  64. transcriptButton.addEventListener('click', handleTranscriptButtonClick);
  65. } else {
  66. console.error('Transcript button not found.');
  67. }
  68. }
  69.  
  70. async function handleTranscriptButtonClick() {
  71. // Check if button already exists
  72. if (document.getElementById('download_btn')) {
  73. console.log('Download button already exists');
  74. return;
  75. }
  76.  
  77. console.log('Creating download button...');
  78.  
  79. // Wait for the transcript container to load (up to 5 seconds)
  80. const maxWaitTime = 5000;
  81. const startTime = Date.now();
  82. let transcriptContainer = null;
  83.  
  84. while (Date.now() - startTime < maxWaitTime) {
  85. transcriptContainer = document.querySelector('div#segments-container');
  86. if (transcriptContainer) {
  87. break; // Transcript container found, exit loop
  88. }
  89. // Wait 200ms before retrying
  90. await new Promise(resolve => setTimeout(resolve, 200));
  91. }
  92.  
  93. if (!transcriptContainer) {
  94. console.error('Transcript container not found.');
  95. return;
  96. }
  97.  
  98. // Create the download button
  99. const downloadBtn = document.createElement('button');
  100. downloadBtn.id = 'download_btn';
  101. downloadBtn.textContent = 'Download Transcript';
  102. downloadBtn.addEventListener('click', handleDownload);
  103.  
  104. // Create the copy button
  105. const copyBtn = document.createElement('button');
  106. copyBtn.id = 'copy_btn';
  107. copyBtn.textContent = 'Copy Transcript';
  108. copyBtn.addEventListener('click', handleCopy);
  109.  
  110. // Create a wrapper div for the buttons
  111. const wrapper = document.createElement('div');
  112. wrapper.style.display = 'flex';
  113. wrapper.style.justifyContent = 'flex-start';
  114. wrapper.style.alignItems = 'center';
  115. wrapper.style.marginTop = '10px';
  116. wrapper.appendChild(downloadBtn);
  117. wrapper.appendChild(copyBtn);
  118.  
  119. // Insert the buttons into the transcript container
  120. transcriptContainer.insertAdjacentElement('afterbegin', wrapper);
  121. console.log('Buttons successfully added to page');
  122. }
  123.  
  124.  
  125. async function handleDownload() {
  126. try {
  127. // Wait for the transcript to load (up to 5 seconds)
  128. const maxWaitTime = 5000;
  129. const startTime = Date.now();
  130. let transcriptElements = [];
  131.  
  132. while (Date.now() - startTime < maxWaitTime) {
  133. transcriptElements = Array.from(document.querySelectorAll('div#segments-container ytd-transcript-segment-renderer div.segment'))
  134. .filter(el => el.textContent.trim() !== '');
  135. if (transcriptElements.length > 0) {
  136. break; // Transcript found, exit loop
  137. }
  138. await new Promise(resolve => setTimeout(resolve, 200)); // Wait 200ms before retrying
  139. }
  140.  
  141. if (!transcriptElements.length) {
  142. alert('No transcript found. Please open the transcript panel first.');
  143. return;
  144. }
  145.  
  146. const text = transcriptElements.map(el => el.textContent.trim()).join('\n');
  147.  
  148. const filename = `${document.title.replace(/[/\\?%*:|"<>]/g, '-')} - Transcript.txt`;
  149.  
  150. // Save to file
  151. download(text, filename, "text/plain");
  152.  
  153. } catch (err) {
  154. console.error('Failed to process transcript:', err);
  155. }
  156. }
  157.  
  158. async function handleCopy() {
  159. try {
  160. // Wait for the transcript to load (up to 5 seconds)
  161. const maxWaitTime = 5000;
  162. const startTime = Date.now();
  163. let transcriptElements = [];
  164.  
  165. while (Date.now() - startTime < maxWaitTime) {
  166. transcriptElements = Array.from(document.querySelectorAll('div#segments-container ytd-transcript-segment-renderer div.segment'))
  167. .filter(el => el.textContent.trim() !== '');
  168. if (transcriptElements.length > 0) {
  169. break; // Transcript found, exit loop
  170. }
  171. await new Promise(resolve => setTimeout(resolve, 200)); // Wait 200ms before retrying
  172. }
  173.  
  174. if (!transcriptElements.length) {
  175. alert('No transcript found. Please open the transcript panel first.');
  176. return;
  177. }
  178.  
  179. const text = transcriptElements.map(el => el.textContent.trim()).join('\n');
  180.  
  181. // Copy to clipboard
  182. await navigator.clipboard.writeText(text);
  183. console.log('Successfully copied transcript to clipboard');
  184. } catch (err) {
  185. console.error('Failed to process transcript:', err);
  186. }
  187. }
  188.  
  189. function download(data, filename, type) {
  190. try {
  191. const blob = new Blob([data], {type: type});
  192. const url = URL.createObjectURL(blob);
  193. const link = document.createElement("a");
  194.  
  195. link.href = url;
  196. link.download = filename;
  197.  
  198. document.body.appendChild(link);
  199. link.click();
  200.  
  201. requestAnimationFrame(() => {
  202. URL.revokeObjectURL(url);
  203. document.body.removeChild(link);
  204. });
  205. } catch (err) {
  206. console.error('Failed to download file:', err);
  207. }
  208. }
  209.  
  210. // Start the initialization when page loads
  211. if (document.readyState === 'loading') {
  212. document.addEventListener('DOMContentLoaded', init);
  213. } else {
  214. init();
  215. }
  216. })();

QingJ © 2025

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