tap
Back

youtube/transcript

youtubeRead-only

Get video transcript/captions (must be on the video page)

www.youtube.com
Last 7 days
0
Last 30 days
0
All time
0
youtube/transcript.js
/* @meta
{
  "name": "youtube/transcript",
  "description": "Get video transcript/captions (must be on the video page)",
  "domain": "www.youtube.com",
  "args": {
    "lang": {"required": false, "description": "Language code (default: first available, e.g. 'en', 'ja')"}
  },
  "capabilities": ["network"],
  "readOnly": true,
  "example": "tap site youtube/transcript"
}
*/

async function(args) {
  const currentUrl = location.href;
  const match = currentUrl.match(/[?&]v=([a-zA-Z0-9_-]{11})/);
  if (!match) return {error: 'Not on a video page', hint: 'Navigate to a YouTube video page first (youtube.com/watch?v=...)'};

  const videoId = match[1];

  // Get available tracks from ytInitialPlayerResponse
  const playerResp = window.ytInitialPlayerResponse;
  const trackList = playerResp?.captions?.playerCaptionsTracklistRenderer;
  const tracks = trackList?.captionTracks || [];
  const availableTracks = tracks.map(t => ({lang: t.languageCode, name: t.name?.simpleText, kind: t.kind}));

  // Find the transcript engagement panel
  const panel = document.querySelector('ytd-engagement-panel-section-list-renderer[target-id="engagement-panel-searchable-transcript"]');
  if (!panel) return {
    error: 'No transcript panel found',
    hint: 'This video may not have captions/subtitles available.',
    videoId,
    availableTracks
  };

  // If a specific language is requested, try to select it
  if (args.lang && tracks.length > 1) {
    const langTrack = tracks.find(t => t.languageCode === args.lang);
    if (!langTrack) return {
      error: 'Language "' + args.lang + '" not found',
      hint: 'Available: ' + availableTracks.map(t => t.lang + ' (' + (t.name || t.kind || '') + ')').join(', '),
      videoId
    };
  }

  // Expand the transcript panel if hidden
  const wasHidden = panel.getAttribute('visibility') !== 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED';
  if (wasHidden) {
    panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_EXPANDED');
    await new Promise(r => setTimeout(r, 2000));
  }

  // If language selection needed, click the language dropdown
  if (args.lang && tracks.length > 1) {
    const menuBtn = panel.querySelector('yt-sort-filter-sub-menu-renderer button, #menu button');
    if (menuBtn) {
      menuBtn.click();
      await new Promise(r => setTimeout(r, 500));
      const menuItems = panel.querySelectorAll('tp-yt-paper-listbox tp-yt-paper-item, yt-dropdown-menu tp-yt-paper-item');
      for (const item of menuItems) {
        const txt = item.textContent?.trim().toLowerCase();
        const trackMatch = tracks.find(t => t.languageCode === args.lang);
        if (trackMatch && txt.includes(trackMatch.name?.simpleText?.toLowerCase() || args.lang)) {
          item.click();
          await new Promise(r => setTimeout(r, 2000));
          break;
        }
      }
    }
  }

  // Extract transcript segments from DOM
  const segmentEls = panel.querySelectorAll('ytd-transcript-segment-renderer');

  if (!segmentEls.length) {
    // Restore panel state
    if (wasHidden) panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
    return {
      error: 'No transcript segments loaded',
      hint: 'The transcript panel is present but empty. Try refreshing the page.',
      videoId,
      availableTracks
    };
  }

  const segments = Array.from(segmentEls).map(seg => {
    const timeText = seg.querySelector('.segment-timestamp')?.textContent?.trim() || '';
    const text = seg.querySelector('.segment-text')?.textContent?.trim() || '';

    // Parse time string (e.g. "1:23" or "1:02:34") to seconds
    const parts = timeText.split(':').map(Number);
    let startSec = 0;
    if (parts.length === 3) startSec = parts[0] * 3600 + parts[1] * 60 + parts[2];
    else if (parts.length === 2) startSec = parts[0] * 60 + parts[1];
    else if (parts.length === 1) startSec = parts[0];

    return {
      start: startSec,
      startFormatted: timeText,
      text
    };
  }).filter(s => s.text);

  // Restore panel state if we opened it
  if (wasHidden) {
    panel.setAttribute('visibility', 'ENGAGEMENT_PANEL_VISIBILITY_HIDDEN');
  }

  // Determine language from available info
  let language = tracks[0]?.languageCode || 'unknown';
  let languageName = tracks[0]?.name?.simpleText || '';
  let kind = tracks[0]?.kind || 'manual';
  if (args.lang) {
    const langTrack = tracks.find(t => t.languageCode === args.lang);
    if (langTrack) {
      language = langTrack.languageCode;
      languageName = langTrack.name?.simpleText || '';
      kind = langTrack.kind || 'manual';
    }
  }

  // Build full text version
  const fullText = segments.map(s => s.text).join(' ');

  // Estimate total duration from last segment
  const lastSeg = segments[segments.length - 1];
  const totalDuration = lastSeg ? lastSeg.start + 10 : 0;

  return {
    videoId,
    language,
    languageName,
    kind,
    segmentCount: segments.length,
    totalDuration,
    availableTracks,
    segments,
    fullText: fullText.substring(0, 5000)
  };
}
Updated Mar 31, 2026Created Mar 31, 2026SHA-256: 594f5dae1ad1