[GH-ISSUE #1770] [BUG]: YouTube Link Issue #1153

Closed
opened 2026-02-22 18:23:23 -05:00 by yindo · 6 comments
Owner

Originally created by @Bai1026 on GitHub (Jun 27, 2024).
Original GitHub issue: https://github.com/Mintplex-Labs/anything-llm/issues/1770

How are you running AnythingLLM?

Docker (local)

What happened?

I tried to transcribed these 3 yt links but encountered:

  1. Failed to locate a transcript for this video
  2. Response could not be completed

I am wondering what cause these issues!

Best regards,
Vincent Pai

Are there known steps to reproduce?

No response

Originally created by @Bai1026 on GitHub (Jun 27, 2024). Original GitHub issue: https://github.com/Mintplex-Labs/anything-llm/issues/1770 ### How are you running AnythingLLM? Docker (local) ### What happened? I tried to transcribed these 3 yt links but encountered: 1. Failed to locate a transcript for this video 2. Response could not be completed I am wondering what cause these issues! Best regards, Vincent Pai ### Are there known steps to reproduce? _No response_
yindo added the possible bug label 2026-02-22 18:23:23 -05:00
yindo closed this issue 2026-02-22 18:23:23 -05:00
Author
Owner

@timothycarambat commented on GitHub (Jun 27, 2024):

The video very likely just does not have a transcription! Just tried a known-good video and the transcription tool works 👍

Not all videos have transcriptions - even automated transcriptions

@timothycarambat commented on GitHub (Jun 27, 2024): The video very likely just does not have a transcription! Just tried a known-good video and the transcription tool works 👍 Not all videos have transcriptions - even automated transcriptions
Author
Owner

@Bai1026 commented on GitHub (Jun 28, 2024):

Got it! thx for the rapidly reply!!! Appreaciate!

@Bai1026 commented on GitHub (Jun 28, 2024): Got it! thx for the rapidly reply!!! Appreaciate!
Author
Owner

@Bai1026 commented on GitHub (Jun 28, 2024):

But I've tried
https://www.youtube.com/watch?v=f6a3B499nwY&list=PLWnOcKaHZjvy8slu6dJSBu2y1Jky42ZYw
, which has 29M views but still got the transcription error, is there might be any other issues about the video transcription?

@Bai1026 commented on GitHub (Jun 28, 2024): But I've tried `https://www.youtube.com/watch?v=f6a3B499nwY&list=PLWnOcKaHZjvy8slu6dJSBu2y1Jky42ZYw` , which has 29M views but still got the transcription error, is there might be any other issues about the video transcription?
Author
Owner

@timothycarambat commented on GitHub (Jun 28, 2024):

View count has no bearing on the transcription of a video - this looks like a live event which for sure is not transcribed. You can actually check by expanding the video description.

Example with transcript
Screenshot 2024-06-28 at 12 53 00 PM

When uploading a view the creator can opt-in for auto-transcript or upload their own. They can of course leave it off and do nothing and thus the video cannot be collected.

@timothycarambat commented on GitHub (Jun 28, 2024): View count has no bearing on the transcription of a video - this looks like a live event which for sure is not transcribed. You can actually check by expanding the video description. Example with transcript <img width="454" alt="Screenshot 2024-06-28 at 12 53 00 PM" src="https://github.com/Mintplex-Labs/anything-llm/assets/16845892/385f4411-10ed-487d-a1c3-bfd5d8d5c2f5"> When uploading a view the creator can opt-in for auto-transcript or upload their own. They can of course leave it off and do nothing and thus the video cannot be collected.
Author
Owner

@Bai1026 commented on GitHub (Jun 29, 2024):

I tried
https://www.youtube.com/watch?v=oFyj-qGMqAA&list=PLhjeIHf6kJlXt6a5pZKcBqZw5tmrRDHhh
this video, which has the transcription, but I still encounter the transcription error. May I ask the reason? Appreciate!

@Bai1026 commented on GitHub (Jun 29, 2024): I tried https://www.youtube.com/watch?v=oFyj-qGMqAA&list=PLhjeIHf6kJlXt6a5pZKcBqZw5tmrRDHhh this video, which has the transcription, but I still encounter the transcription error. May I ask the reason? Appreciate!
Author
Owner

@AoiYamada commented on GitHub (Sep 26, 2025):

this video, which has the transcription, but I still encounter the transcription error

Because these params are wrong, hardcoded:
https://github.com/Mintplex-Labs/anything-llm/blob/v1.8.5/collector/utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js#L101-L102

The transcript could be obtained by modifying the transcript class, demo:

Image

full code:

class YoutubeTranscriptError extends Error {
  constructor(message) {
    super(`[YoutubeTranscript] ${message}`);
  }
}

/**
 * Handles fetching and parsing YouTube video transcripts
 */
class YoutubeTranscript {
  /**
   * Encodes a string as a protobuf field
   * @param {number} fieldNumber - The protobuf field number
   * @param {string} str - The string to encode
   * @returns {Buffer} Encoded protobuf field
   */
  static #encodeProtobufString(fieldNumber, str) {
    const utf8Bytes = Buffer.from(str, "utf8");
    const tag = (fieldNumber << 3) | 2; // wire type 2 for string
    const lengthBytes = this.#encodeVarint(utf8Bytes.length);

    return Buffer.concat([
      Buffer.from([tag]),
      Buffer.from(lengthBytes),
      utf8Bytes,
    ]);
  }

  /**
   * Encodes a number as a protobuf varint
   * @param {number} value - The number to encode
   * @returns {number[]} Encoded varint bytes
   */
  static #encodeVarint(value) {
    const bytes = [];
    while (value >= 0x80) {
      bytes.push((value & 0x7f) | 0x80);
      value >>>= 7;
    }
    bytes.push(value);
    return bytes;
  }

  /**
   * Creates a base64 encoded protobuf message
   * @param {Object} param - The parameters to encode
   * @param {string} param.param1 - First parameter
   * @param {string} param.param2 - Second parameter
   * @returns {string} Base64 encoded protobuf
   */
  static #getBase64Protobuf({ param1, param2 }) {
    const field1 = this.#encodeProtobufString(1, param1);
    const field2 = this.#encodeProtobufString(2, param2);
    return Buffer.concat([field1, field2]).toString("base64");
  }

  /**
   * Extracts transcript text from YouTube API response
   * @param {Object} responseData - The YouTube API response
   * @returns {string} Combined transcript text
   */
  static #extractTranscriptFromResponse(responseData) {
    const transcriptRenderer =
      responseData.actions?.[0]?.updateEngagementPanelAction?.content
        ?.transcriptRenderer;
    if (!transcriptRenderer) {
      throw new Error("No transcript data found in response");
    }

    const segments =
      transcriptRenderer.content?.transcriptSearchPanelRenderer?.body
        ?.transcriptSegmentListRenderer?.initialSegments;
    if (!segments) {
      throw new Error("Transcript segments not found in response");
    }

    return segments
      .map((segment) => {
        const runs = segment.transcriptSegmentRenderer?.snippet?.runs;
        return runs ? runs.map((run) => run.text).join("") : "";
      })
      .filter((text) => text)
      .join(" ")
      .trim()
      .replace(/\s+/g, " ");
  }

  /**
   * Fetch transcript from YouTube video
   * @param {string} videoId - Video URL or video identifier
   * @param {Object} config - Configuration options
   * @param {string} [config.lang='en'] - Language code (e.g., 'en', 'es', 'fr')
   * @returns {Promise<string>} Video transcript text
   */
  static async fetchTranscript(videoId, config = {}) {
    const videoResponse = await fetch(videoId, { credentials: "omit" });
    const videoBody = await videoResponse.text();
    const captionsConfigJson = videoBody.match(
      /"captions":(.*?),"videoDetails":/s
    );
    const captionsConfig = captionsConfigJson?.[1]
      ? JSON.parse(captionsConfigJson[1])
      : null;
    const captionTracks = captionsConfig
      ? captionsConfig.playerCaptionsTracklistRenderer.captionTracks
      : null;
    const calculateValue = (a) => {
      let value = preferredLanguages.indexOf(a.languageCode);
      value = value === -1 ? 9999 : value;
      value += a.kind === "asr" ? 0.5 : 0;
      return value;
    };

    // Sort the caption tracks by the preferred languages and the kind
    captionTracks.sort((a, b) => {
      const valueA = calculateValue(a);
      const valueB = calculateValue(b);
      return valueA - valueB;
    });
    const identifier = this.retrieveVideoId(videoId);

    try {
      const innerProto = this.#getBase64Protobuf({
        param1: captionTracks[0].kind ? captionTracks[0].kind : "",
        param2: captionTracks[0].languageCode,
      });
      const params = this.#getBase64Protobuf({
        param1: identifier,
        param2: innerProto,
      });

      // console.log(params);
      // CgtEMTExYW82d1dIMBIMQ2dOaGMzSVNBbVZ1

      const response = await fetch(
        "https://www.youtube.com/youtubei/v1/get_transcript",
        {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
            "User-Agent":
              "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36,gzip(gfe)",
          },
          body: JSON.stringify({
            context: {
              client: {
                clientName: "WEB",
                clientVersion: "2.20240826.01.00",
              },
            },
            params,
            externalVideoId: identifier,
          }),
        }
      );

      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }

      const responseData = await response.json();
      return this.#extractTranscriptFromResponse(responseData);
    } catch (e) {
      throw new YoutubeTranscriptError(e.message || e);
    }
  }

  /**
   * Extract video ID from a YouTube URL or verify an existing ID
   * @param {string} videoId - Video URL or ID
   * @returns {string} YouTube video ID
   */
  static retrieveVideoId(videoId) {
    if (videoId.length === 11) return videoId;

    const RE_YOUTUBE =
      /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i;
    const matchId = videoId.match(RE_YOUTUBE);

    if (matchId?.[1]) return matchId[1];
    throw new YoutubeTranscriptError(
      "Impossible to retrieve Youtube video ID."
    );
  }
}

YoutubeTranscript.fetchTranscript(
  "https://www.youtube.com/watch?v=oFyj-qGMqAA"
).then((text) => console.log(text));

The UI should provide options for user to select transcript kind and language instead of hardcoded asr and en
also it should provide auto-detect mode like the script above

you could fix it by edit the collector.js in your anythingllm's folder and restart it :)

@AoiYamada commented on GitHub (Sep 26, 2025): > this video, which has the transcription, but I still encounter the transcription error Because these params are wrong, hardcoded: https://github.com/Mintplex-Labs/anything-llm/blob/v1.8.5/collector/utils/extensions/YoutubeTranscript/YoutubeLoader/youtube-transcript.js#L101-L102 The transcript could be obtained by modifying the transcript class, demo: <img width="1064" height="913" alt="Image" src="https://github.com/user-attachments/assets/7890649a-c479-4b93-95ef-4063c972e04c" /> full code: ``` class YoutubeTranscriptError extends Error { constructor(message) { super(`[YoutubeTranscript] ${message}`); } } /** * Handles fetching and parsing YouTube video transcripts */ class YoutubeTranscript { /** * Encodes a string as a protobuf field * @param {number} fieldNumber - The protobuf field number * @param {string} str - The string to encode * @returns {Buffer} Encoded protobuf field */ static #encodeProtobufString(fieldNumber, str) { const utf8Bytes = Buffer.from(str, "utf8"); const tag = (fieldNumber << 3) | 2; // wire type 2 for string const lengthBytes = this.#encodeVarint(utf8Bytes.length); return Buffer.concat([ Buffer.from([tag]), Buffer.from(lengthBytes), utf8Bytes, ]); } /** * Encodes a number as a protobuf varint * @param {number} value - The number to encode * @returns {number[]} Encoded varint bytes */ static #encodeVarint(value) { const bytes = []; while (value >= 0x80) { bytes.push((value & 0x7f) | 0x80); value >>>= 7; } bytes.push(value); return bytes; } /** * Creates a base64 encoded protobuf message * @param {Object} param - The parameters to encode * @param {string} param.param1 - First parameter * @param {string} param.param2 - Second parameter * @returns {string} Base64 encoded protobuf */ static #getBase64Protobuf({ param1, param2 }) { const field1 = this.#encodeProtobufString(1, param1); const field2 = this.#encodeProtobufString(2, param2); return Buffer.concat([field1, field2]).toString("base64"); } /** * Extracts transcript text from YouTube API response * @param {Object} responseData - The YouTube API response * @returns {string} Combined transcript text */ static #extractTranscriptFromResponse(responseData) { const transcriptRenderer = responseData.actions?.[0]?.updateEngagementPanelAction?.content ?.transcriptRenderer; if (!transcriptRenderer) { throw new Error("No transcript data found in response"); } const segments = transcriptRenderer.content?.transcriptSearchPanelRenderer?.body ?.transcriptSegmentListRenderer?.initialSegments; if (!segments) { throw new Error("Transcript segments not found in response"); } return segments .map((segment) => { const runs = segment.transcriptSegmentRenderer?.snippet?.runs; return runs ? runs.map((run) => run.text).join("") : ""; }) .filter((text) => text) .join(" ") .trim() .replace(/\s+/g, " "); } /** * Fetch transcript from YouTube video * @param {string} videoId - Video URL or video identifier * @param {Object} config - Configuration options * @param {string} [config.lang='en'] - Language code (e.g., 'en', 'es', 'fr') * @returns {Promise<string>} Video transcript text */ static async fetchTranscript(videoId, config = {}) { const videoResponse = await fetch(videoId, { credentials: "omit" }); const videoBody = await videoResponse.text(); const captionsConfigJson = videoBody.match( /"captions":(.*?),"videoDetails":/s ); const captionsConfig = captionsConfigJson?.[1] ? JSON.parse(captionsConfigJson[1]) : null; const captionTracks = captionsConfig ? captionsConfig.playerCaptionsTracklistRenderer.captionTracks : null; const calculateValue = (a) => { let value = preferredLanguages.indexOf(a.languageCode); value = value === -1 ? 9999 : value; value += a.kind === "asr" ? 0.5 : 0; return value; }; // Sort the caption tracks by the preferred languages and the kind captionTracks.sort((a, b) => { const valueA = calculateValue(a); const valueB = calculateValue(b); return valueA - valueB; }); const identifier = this.retrieveVideoId(videoId); try { const innerProto = this.#getBase64Protobuf({ param1: captionTracks[0].kind ? captionTracks[0].kind : "", param2: captionTracks[0].languageCode, }); const params = this.#getBase64Protobuf({ param1: identifier, param2: innerProto, }); // console.log(params); // CgtEMTExYW82d1dIMBIMQ2dOaGMzSVNBbVZ1 const response = await fetch( "https://www.youtube.com/youtubei/v1/get_transcript", { method: "POST", headers: { "Content-Type": "application/json", "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36,gzip(gfe)", }, body: JSON.stringify({ context: { client: { clientName: "WEB", clientVersion: "2.20240826.01.00", }, }, params, externalVideoId: identifier, }), } ); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const responseData = await response.json(); return this.#extractTranscriptFromResponse(responseData); } catch (e) { throw new YoutubeTranscriptError(e.message || e); } } /** * Extract video ID from a YouTube URL or verify an existing ID * @param {string} videoId - Video URL or ID * @returns {string} YouTube video ID */ static retrieveVideoId(videoId) { if (videoId.length === 11) return videoId; const RE_YOUTUBE = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/i; const matchId = videoId.match(RE_YOUTUBE); if (matchId?.[1]) return matchId[1]; throw new YoutubeTranscriptError( "Impossible to retrieve Youtube video ID." ); } } YoutubeTranscript.fetchTranscript( "https://www.youtube.com/watch?v=oFyj-qGMqAA" ).then((text) => console.log(text)); ``` The UI should provide options for user to select transcript kind and language instead of hardcoded asr and en also it should provide auto-detect mode like the script above you could fix it by edit the collector.js in your anythingllm's folder and restart it :)
yindo changed title from [BUG]: YouTube Link Issue to [GH-ISSUE #1770] [BUG]: YouTube Link Issue 2026-06-05 14:39:15 -04:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: Mintplex-Labs/anything-llm#1153