YouTube Transcript Downloader

Downloads and copies YouTube video transcripts.

Verzia zo dňa 31.12.2024. Pozri najnovšiu verziu.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         YouTube Transcript Downloader
// @namespace    https://github.com/blarer/youtube-transcript-downloader
// @version      1.0
// @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 {
            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 0;
            transition: opacity 0.2s;
        }
        #download_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 a wrapper div for the button
        const wrapper = document.createElement('div');
        wrapper.style.display = 'flex';
        wrapper.style.justifyContent = 'flex-start'; // Align to the start
        wrapper.style.alignItems = 'center';
        wrapper.style.marginTop = '10px';
        wrapper.appendChild(downloadBtn);

        // Insert the button into the transcript container
        transcriptContainer.insertAdjacentElement('afterbegin', wrapper);
        console.log('Button 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");

            // 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();
    }
})();