YouTube Transcript Downloader

Downloads and copies YouTube video transcripts.

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         YouTube Transcript Downloader
// @namespace    https://github.com/blarer/youtube-transcript-downloader
// @version      1.1
// @description  Downloads and copies YouTube video transcripts.
// @author       Blareware aka blare
// @match        https://www.youtube.com/watch?v=*
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Add styles using GM_addStyle
    GM_addStyle(`
        #download_btn, #copy_btn {
            color: var(--yt-spec-text-primary);
            background: var(--yt-spec-brand-button-background);
            border: none;
            border-radius: 18px;
            padding: 8px 16px;
            font-size: 14px;
            cursor: pointer;
            margin: 10px 15px; /* Added 15px margin-right */
            transition: opacity 0.2s;
        }
        #download_btn:hover, #copy_btn:hover {
            opacity: 0.8;
        }
    `);

    function init() {
        // Updated selectors for 2023 YouTube structure
        const possibleSelectors = [
            "ytd-watch-metadata",
            "#above-the-fold",
            "#title.ytd-watch-metadata",
            "#container.ytd-watch-metadata",
            "ytd-video-primary-info-renderer"
        ];

        console.log('Searching for YouTube containers...');

        let container = null;
        for (const selector of possibleSelectors) {
            container = document.querySelector(selector);
            if (container) {
                console.log('Found container using selector:', selector);
                break;
            }
        }

        if (!container) {
            console.log('Container not found, retrying...');
            setTimeout(init, 1000);
            return;
        }

        // Add event listener to the transcript button
        const transcriptButton = document.querySelector('button[aria-label="Show transcript"]');
        if (transcriptButton) {
            transcriptButton.addEventListener('click', handleTranscriptButtonClick);
        } else {
            console.error('Transcript button not found.');
        }
    }

    async function handleTranscriptButtonClick() {
        // Check if button already exists
        if (document.getElementById('download_btn')) {
            console.log('Download button already exists');
            return;
        }

        console.log('Creating download button...');

        // Wait for the transcript container to load (up to 5 seconds)
        const maxWaitTime = 5000;
        const startTime = Date.now();
        let transcriptContainer = null;

        while (Date.now() - startTime < maxWaitTime) {
            transcriptContainer = document.querySelector('div#segments-container');
            if (transcriptContainer) {
                break; // Transcript container found, exit loop
            }
            // Wait 200ms before retrying
            await new Promise(resolve => setTimeout(resolve, 200));
        }

        if (!transcriptContainer) {
            console.error('Transcript container not found.');
            return;
        }

        // Create the download button
        const downloadBtn = document.createElement('button');
        downloadBtn.id = 'download_btn';
        downloadBtn.textContent = 'Download Transcript';
        downloadBtn.addEventListener('click', handleDownload);

        // Create the copy button
        const copyBtn = document.createElement('button');
        copyBtn.id = 'copy_btn';
        copyBtn.textContent = 'Copy Transcript';
        copyBtn.addEventListener('click', handleCopy);

        // Create a wrapper div for the buttons
        const wrapper = document.createElement('div');
        wrapper.style.display = 'flex';
        wrapper.style.justifyContent = 'flex-start';
        wrapper.style.alignItems = 'center';
        wrapper.style.marginTop = '10px';
        wrapper.appendChild(downloadBtn);
        wrapper.appendChild(copyBtn);

        // Insert the buttons into the transcript container
        transcriptContainer.insertAdjacentElement('afterbegin', wrapper);
        console.log('Buttons successfully added to page');
    }


    async function handleDownload() {
        try {
            // Wait for the transcript to load (up to 5 seconds)
            const maxWaitTime = 5000;
            const startTime = Date.now();
            let transcriptElements = [];

            while (Date.now() - startTime < maxWaitTime) {
                transcriptElements = Array.from(document.querySelectorAll('div#segments-container ytd-transcript-segment-renderer div.segment'))
                    .filter(el => el.textContent.trim() !== '');
                if (transcriptElements.length > 0) {
                    break; // Transcript found, exit loop
                }
                await new Promise(resolve => setTimeout(resolve, 200)); // Wait 200ms before retrying
            }

            if (!transcriptElements.length) {
                alert('No transcript found. Please open the transcript panel first.');
                return;
            }

            const text = transcriptElements.map(el => el.textContent.trim()).join('\n');

            const filename = `${document.title.replace(/[/\\?%*:|"<>]/g, '-')} - Transcript.txt`;

            // Save to file
            download(text, filename, "text/plain");

        } catch (err) {
            console.error('Failed to process transcript:', err);
        }
    }

    async function handleCopy() {
         try {
            // Wait for the transcript to load (up to 5 seconds)
            const maxWaitTime = 5000;
            const startTime = Date.now();
            let transcriptElements = [];

            while (Date.now() - startTime < maxWaitTime) {
                transcriptElements = Array.from(document.querySelectorAll('div#segments-container ytd-transcript-segment-renderer div.segment'))
                    .filter(el => el.textContent.trim() !== '');
                if (transcriptElements.length > 0) {
                    break; // Transcript found, exit loop
                }
                await new Promise(resolve => setTimeout(resolve, 200)); // Wait 200ms before retrying
            }

            if (!transcriptElements.length) {
                alert('No transcript found. Please open the transcript panel first.');
                return;
            }

            const text = transcriptElements.map(el => el.textContent.trim()).join('\n');

            // Copy to clipboard
            await navigator.clipboard.writeText(text);
            console.log('Successfully copied transcript to clipboard');
        } catch (err) {
            console.error('Failed to process transcript:', err);
        }
    }

    function download(data, filename, type) {
        try {
            const blob = new Blob([data], {type: type});
            const url = URL.createObjectURL(blob);
            const link = document.createElement("a");

            link.href = url;
            link.download = filename;

            document.body.appendChild(link);
            link.click();

            requestAnimationFrame(() => {
                URL.revokeObjectURL(url);
                document.body.removeChild(link);
            });
        } catch (err) {
            console.error('Failed to download file:', err);
        }
    }

    // Start the initialization when page loads
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();