Skip to content

Fetch YouTube video transcripts and captions with ytscrape

Download timed caption snippets for any YouTube video, list available language tracks, and translate them server-side with ytscrape.

ytscrape can download the full timed transcript for any video that has captions enabled — manually created or auto-generated. No API key is required; the library talks directly to the same timedtext endpoint the YouTube web player uses.

Fetching a transcript

Pass a video id or URL together with a language preference list. yt.transcript() returns a Transcript object containing every timed caption snippet:

from ytscrape import YouTube

with YouTube() as yt:
    transcript = yt.transcript("dQw4w9WgXcQ", languages=["en"])
    print(f"{transcript.language} ({transcript.language_code})")
    print(f"Auto-generated: {transcript.is_generated}")
    print(f"Snippets: {len(transcript)}")
    print(transcript.text[:200])

Language preference list

languages is tried in order. Within each language code, a manually created caption track is preferred over an auto-generated (ASR) one — the same behaviour as youtube-transcript-api. If none of the requested languages are available, NoTranscriptFound is raised.

# Prefer Ukrainian; fall back to English if no Ukrainian track exists.
transcript = yt.transcript(video_id, languages=["uk", "en"])

The Transcript object

Attribute / method Type Description
snippets tuple[TranscriptSnippet, ...] All timed caption lines.
video_id str The video this transcript belongs to.
language str Human-readable language name (e.g. "English").
language_code str ISO 639-1 code (e.g. "en").
is_generated bool True for auto-generated (ASR) tracks.
text str (property) All snippets joined with spaces — useful for full-text search.
to_raw_data() list[dict] Returns snippets as a list of {"text", "start", "duration"} dicts.

Transcript also supports len(), iteration, indexing, and slicing directly over its snippets.

TranscriptSnippet fields

Each element of transcript.snippets is a frozen dataclass:

Field Type Description
text str The caption line text.
start float Wall-clock start time in seconds.
duration float How long the line is shown (may overlap the next snippet).

Iterating snippets

The snippet below is taken from examples/09_transcript.py:

from ytscrape import YouTube


def main() -> None:
    with YouTube() as yt:
        video = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

        # List every available caption track first.
        print("Available tracks:")
        print(yt.transcripts(video))
        print()

        # Prefer Ukrainian, fall back to English (manual captions beat ASR).
        transcript = yt.transcript(video, languages=["uk", "en"])
        print(
            f"Fetched: {transcript.language} ({transcript.language_code}), "
            f"generated={transcript.is_generated}, lines={len(transcript)}"
        )
        print()
        for snippet in transcript[:8]:
            print(f"[{snippet.start:6.1f}s] {snippet.text}")
        print("...")
        print(transcript.text[:240], "…")


if __name__ == "__main__":
    main()

Listing available tracks

Use yt.transcripts() to inspect what caption tracks exist for a video without downloading any of them. It returns a TranscriptList:

with YouTube() as yt:
    tracks = yt.transcripts("dQw4w9WgXcQ")
    for track in tracks:
        print(track.language_code, track.language, track.is_generated)

TranscriptList methods

Method Description
find_transcript(language_codes) Returns the best matching TranscriptTrack — manual tracks preferred over generated, in the given language order.
find_generated_transcript(language_codes) Returns only auto-generated (ASR) tracks.
find_manually_created_transcript(language_codes) Returns only manually created tracks.

All three raise NoTranscriptFound when no match exists.

TranscriptTrack attributes and properties

Attribute / property Type Description
video_id str The video this track belongs to.
language str Human-readable language name.
language_code str ISO 639-1 code.
is_generated bool True for auto-generated (ASR) tracks.
is_translatable bool (property) True if YouTube can translate this track server-side.
translation_languages tuple[dict, ...] (property) Available translation targets as {"language", "language_code"} dicts.

Call .fetch() on a TranscriptTrack to download its snippets and get back a Transcript.

Server-side translation

When is_translatable is True, YouTube can translate the track into another language on the fly. Use TranscriptTrack.translate(language_code) to get a translated TranscriptTrack, then call .fetch() on the result:

with YouTube() as yt:
    tracks = yt.transcripts("dQw4w9WgXcQ")
    track = tracks.find_transcript(["en"])
    if track.is_translatable:
        translated = track.translate("fr")
        french_transcript = translated.fetch()
        print(french_transcript.text[:200])

translate() raises NoTranscriptFound if the requested target language is not in the track's translation list.

Preserving HTML formatting

Auto-generated transcripts sometimes include inline HTML tags (<i>, <b>, etc.). By default they are stripped. Pass preserve_formatting=True to keep them:

transcript = yt.transcript(video_id, languages=["en"], preserve_formatting=True)

Async transcripts

With AsyncYouTube (requires pip install "ytscrape[async]"):

import asyncio
from ytscrape import AsyncYouTube


async def main() -> None:
    async with AsyncYouTube() as yt:
        transcript = await yt.transcript("dQw4w9WgXcQ", languages=["en"])
        print(transcript.text[:200])

        tracks = await yt.transcripts("dQw4w9WgXcQ")
        track = tracks.find_transcript(["en"])
        # Async download of a specific track:
        data = await track.afetch()


asyncio.run(main())

Prefer await yt.transcript(...) or await track.afetch() in async code. Sync track.fetch() is for the YouTube / requests path. Details: Async API.

Exceptions

Exception Raised when
TranscriptsDisabled The video has no caption tracks at all, or captions are disabled. Carries a .video_id attribute.
NoTranscriptFound None of the requested language codes match any available track. Carries .requested (tuple of requested codes) and .available (tuple of available codes) attributes.
from ytscrape import YouTube, TranscriptsDisabled, NoTranscriptFound

with YouTube() as yt:
    try:
        transcript = yt.transcript("dQw4w9WgXcQ", languages=["zh"])
    except TranscriptsDisabled as exc:
        print(f"No captions for video: {exc.video_id}")
    except NoTranscriptFound as exc:
        print(f"Requested: {exc.requested}")
        print(f"Available: {exc.available}")