unslothai/unsloth · warning · TranscriptUnavailable

This video's captions are empty.

Error message

This video's captions are empty.

What it means

TranscriptUnavailable raised after a caption track was successfully selected and fetched, but the resulting text was empty (falsy). The track exists on YouTube's side but yields no usable cues, e.g. all events were filtered out or the payload decoded to nothing.

Source

Thrown at studio/backend/core/youtube_transcript.py:130

    async with httpx.AsyncClient(timeout = _TIMEOUT, follow_redirects = True) as client:
        player = await _fetch_player(client, video_id)
        status = (player.get("playabilityStatus") or {}).get("status")
        if status not in (None, "OK"):
            raise TranscriptUnavailable(
                (player.get("playabilityStatus") or {}).get("reason")
                or "YouTube will not play this video."
            )

        tracklist = (player.get("captions") or {}).get("playerCaptionsTracklistRenderer") or {}
        tracks = [t for t in (tracklist.get("captionTracks") or []) if t.get("baseUrl")]
        if not tracks:
            raise TranscriptUnavailable("This video has no captions.")

        track = _select_track(tracks, tracklist, languages)
        text = await _fetch_track_text(client, str(track["baseUrl"]))

    if not text:
        raise TranscriptUnavailable("This video's captions are empty.")
    text, truncated = _truncate_transcript(text)

    details = player.get("videoDetails") or {}
    return Transcript(
        video_id = video_id,
        title = str(details.get("title") or ""),
        author = str(details.get("author") or ""),
        length_seconds = _as_int(details.get("lengthSeconds")),
        language = _track_label(track),
        language_code = str(track.get("languageCode") or ""),
        is_generated = track.get("kind") == "asr",
        text = text,
        truncated = truncated,
    )


def _truncate_transcript(text: str) -> tuple[str, bool]:
    """Bound the transcript so a long video cannot swallow the model's context."""

View on GitHub (pinned to 203007d190)

Solutions

  1. Try a different language track by passing languages= to fetch_transcript so another track is selected.
  2. Retry once — transient empty responses occur under load.
  3. Treat as 'no transcript' and skip or fall back to local transcription, same as the no-captions case.

Example fix

# before
transcript = await fetch_transcript(video_id)  # selected track is empty -> raises

# after
transcript = await fetch_transcript(video_id, languages=("en", "en-US", ""))
Defensive patterns

Strategy: fallback

Try / catch

try:
    transcript = await fetch_transcript(video_id, languages=("en", "en-US"))
except TranscriptUnavailable as e:
    if "captions are empty" in str(e):
        transcript = await fetch_transcript(video_id)  # retry default track once
    else:
        raise

Prevention

When it happens

Trigger: A caption track whose json3 payload contains only events that flatten to empty text (e.g. only aAppend rolling-newline markers); tracks that exist but were never populated; edge-case payload shapes after the flatten step.

Common situations: Placeholder/disabled caption tracks; extremely short videos; YouTube serving an effectively empty track for restricted content.

Related errors


AI-assisted analysis of unslothai/unsloth@203007d190 (2026-08-15). Data as JSON: /api/errors/d13e3cc6a05f021f. Report an issue: GitHub.