Jalara Studio

veröffentlicht am

In Chrome das Transcript von YouTube in ChatGPT einsetzen

# # # #


Der folgende Artikel zeigt, wie du in YouTube im Browser Chrome eine Schaltfläche erzeugst, die mit einem Klick das Transcript des jeweiligen Videos mit URL kopiert und dann ChatGPT öffnet:

1. Installiere dir eine Chrome Extension mit der du JavaScript ausführen kannst. Zum Beispiel "User JavaScript and CSS" oder ähnliches.
2. Füge dieses Script ein.
3. Rechts neben dem Video erscheint nun das Transcript mit der Schaltfläche "save":


4. Klicke drauf und füge den nun im Zwischenspeicher kopierte Transcript in ChatGPT ein.

Hinweis: Unter OPEN_URL kannst du den Link anpassen, sofern du hier ein Custom-Link bevorzugst.



// ==UserScript==
// @name         YouTube Transcript: Copy Button + Open ChatGPT Tab
// @namespace    yt-transcript-copy-btn
// @version      1.3
// @match        https://www.youtube.com/watch*
// @run-at       document-idle
// @grant        none
// ==/UserScript==

(() => {
  'use strict';

  const OPEN_URL = 'https://chatgpt.com/g/g-677e7f66edc08191bf9620ff748a634b-a';

  const PANEL_SEL =
    'ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"]';

  const HEADER_SEL =
    `${PANEL_SEL} ytd-transcript-search-panel-renderer #header`;

  const SEG_TEXT_SEL =
    `${PANEL_SEL} ytd-transcript-segment-renderer yt-formatted-string.segment-text`;

  const BTN_ID = 'yt-transcript-copy-all-btn';
  const STYLE_ID = 'yt-transcript-copy-btn-style';

  // Transcript: komplett EINZEILIG (Umbrüche -> Whitespace)
  function getTranscriptText() {
    const nodes = Array.from(document.querySelectorAll(SEG_TEXT_SEL));
    const parts = nodes
      .map(n => (n.textContent || '').replace(/\s+/g, ' ').trim())
      .filter(Boolean);

    return parts.join(' ').replace(/\s+/g, ' ').trim();
  }

  // Styles: passen sich an YouTube Light/Dark Theme an (CSS-Variablen + html[dark] Fallback)
  function ensureStyles() {
    if (document.getElementById(STYLE_ID)) return;

    const style = document.createElement('style');
    style.id = STYLE_ID;
    style.textContent = `
      #${BTN_ID}{
        margin-left: 10px;
        padding: 10px 16px;
        min-height: 38px;
        min-width: 88px;

        border-radius: 999px;
        border: 1px solid var(--yt-spec-10-percent-layer, rgba(128,128,128,.45));
        background: var(--yt-spec-badge-chip-background, rgba(0,0,0,.08));
        color: var(--yt-spec-text-primary, #111);

        font: 600 14px/1 system-ui, -apple-system, Segoe UI, Roboto, Arial;
        letter-spacing: .2px;

        cursor: pointer;
        white-space: nowrap;
      }

      #${BTN_ID}:hover{
        background: var(--yt-spec-20-percent-layer, rgba(0,0,0,.14));
        border-color: var(--yt-spec-30-percent-layer, rgba(128,128,128,.55));
      }

      #${BTN_ID}:active{
        transform: translateY(1px);
      }

      #${BTN_ID}:focus-visible{
        outline: 2px solid var(--yt-spec-text-primary, currentColor);
        outline-offset: 2px;
      }

      /* Dark Mode explizit (falls Variablen fehlen / für bessere Kontraste) */
      html[dark] #${BTN_ID}{
        background: var(--yt-spec-badge-chip-background, rgba(255,255,255,.12));
        border-color: var(--yt-spec-10-percent-layer, rgba(255,255,255,.25));
        color: var(--yt-spec-text-primary, #fff);
      }

      html[dark] #${BTN_ID}:hover{
        background: var(--yt-spec-20-percent-layer, rgba(255,255,255,.18));
        border-color: var(--yt-spec-30-percent-layer, rgba(255,255,255,.35));
      }
    `;
    document.head.appendChild(style);
  }

  async function copyToClipboard(text) {
    try {
      await navigator.clipboard.writeText(text);
      return true;
    } catch {}

    try {
      const ta = document.createElement('textarea');
      ta.value = text;
      ta.style.position = 'fixed';
      ta.style.top = '-1000px';
      document.body.appendChild(ta);
      ta.focus();
      ta.select();
      const ok = document.execCommand('copy');
      ta.remove();
      return ok;
    } catch {
      return false;
    }
  }

  function showToast(msg) {
    const t = document.createElement('div');
    t.textContent = msg;
    t.style.cssText = `
      position: fixed; z-index: 999999; left: 50%; top: 16px; transform: translateX(-50%);
      padding: 8px 12px; border-radius: 10px;
      font: 13px/1.2 system-ui, -apple-system, Segoe UI, Roboto, Arial;
      background: rgba(0,0,0,.85); color: white; box-shadow: 0 6px 22px rgba(0,0,0,.25);
    `;
    document.body.appendChild(t);
    setTimeout(() => t.remove(), 1400);
  }

  function openChatGPTTab() {
    // innerhalb User-Klick => meist nicht geblockt
    window.open(OPEN_URL, '_blank', 'noopener,noreferrer');
  }

  function injectButton() {
    const header = document.querySelector(HEADER_SEL);
    if (!header) return false;
    if (document.getElementById(BTN_ID)) return true;

    ensureStyles();

    const btn = document.createElement('button');
    btn.id = BTN_ID;
    btn.type = 'button';
    btn.textContent = 'Save';
    btn.title = 'URL + gesamtes Transkript kopieren (einzeilig) + ChatGPT Tab öffnen';

    btn.addEventListener('click', async () => {
      const transcript = getTranscriptText();
      if (!transcript) {
        showToast('Kein Text gefunden');
        return;
      }

      // URL + Transcript kopieren (Transcript bleibt einzeilig)
      // Wenn du wirklich absolut GAR KEINEN Umbruch willst: nimm statt \n\n einfach ' '.
      const textToCopy = `${location.href}\n\n${transcript}`;

      const ok = await copyToClipboard(textToCopy);

      openChatGPTTab();
      showToast(ok ? 'Kopiert ✓ (ChatGPT geöffnet)' : 'Kopieren fehlgeschlagen (ChatGPT geöffnet)');
    });

    header.appendChild(btn);
    return true;
  }

  function arm() {
    injectButton();
    const mo = new MutationObserver(() => injectButton());
    mo.observe(document.documentElement, { childList: true, subtree: true });
    window.addEventListener('yt-navigate-finish', () => setTimeout(injectButton, 300));
  }

  arm();
})();