Skip to content

Fix unreceivable captions/transcript (iv-org/invidious#5571)#259

Open
shiny-comic wants to merge 4 commits intoiv-org:masterfrom
shiny-comic:fix-caption
Open

Fix unreceivable captions/transcript (iv-org/invidious#5571)#259
shiny-comic wants to merge 4 commits intoiv-org:masterfrom
shiny-comic:fix-caption

Conversation

@shiny-comic
Copy link
Copy Markdown

This fixes subtitles and transcript not receivable due to updated Youtube API (iv-org/invidious#5571).
This is based on FreeTube's fix for subtitles (FreeTubeApp/FreeTube#7484).

⚠️ NOTE
This may eat up the po tokens on public instance and leads to 429 error
or similar problem (I only tested this on my private instance).

Sorry for my poor English.

This fixes subtitles and transcript not receivable due to updated Youtube
API (iv-org/invidious#5571).
> NOTE:
> This may eat up the po tokens on public instance and leads to 429 error
  or similar problem.

Sorry for my poor English.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes an issue where captions and transcripts were not being retrieved due to changes in YouTube's API. The fix is based on FreeTube's solution and implements support for PO (Proof of Origin) tokens when fetching captions.

  • Adds PO token and client name parameters to caption fetching logic
  • Implements new caption retrieval path using YouTube's updated API with token authentication
  • Adds VTT positioning data cleanup function to process fetched captions

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 8 comments.

File Description
src/routes/invidious_routes/captions.ts Retrieves PO token and client name when available and passes them to the transcript handler
src/lib/helpers/youtubeTranscriptsHandling.ts Implements new token-based caption fetching logic with fallback to existing method, adds VTT processing function

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -24,11 +25,56 @@ const ESCAPE_SUBSTITUTIONS = {
"\u00A0": " ",
};

Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The newly added "shiftVttToCenter" function lacks documentation. Please add a JSDoc comment explaining what the function does, its parameters, and return value. This is especially important since the function name doesn't clearly convey its purpose of stripping positioning data from VTT timing lines.

Suggested change
/**
* Normalizes WebVTT timing lines by stripping any additional cue settings
* (such as position, alignment, or line information) after the timing range.
*
* For example, a line like:
* `00:00:01.000 --> 00:00:04.000 line:0% position:50% align:middle`
* becomes:
* `00:00:01.000 --> 00:00:04.000`
*
* This is useful when you want subtitles to render with default (typically
* centered) positioning by removing any explicit positioning data.
*
* @param vtt - Full WebVTT file contents as a string.
* @returns The VTT string with timing lines cleaned of trailing cue settings.
*/

Copilot uses AI. Check for mistakes.
Comment thread src/lib/helpers/youtubeTranscriptsHandling.ts Outdated
Comment thread src/lib/helpers/youtubeTranscriptsHandling.ts Outdated
Comment thread src/lib/helpers/youtubeTranscriptsHandling.ts Outdated
Comment thread src/lib/helpers/youtubeTranscriptsHandling.ts Outdated
Comment thread src/routes/invidious_routes/captions.ts
Comment on lines +28 to +44
function shiftVttToCenter(vtt: string): string {
const lines = vtt.split('\n');
const updatedLines: string[] = [];
const timingRegex = /^((?:\d{1,2}:)?\d{2}:\d{2}\.\d{3} --> (?:\d{1,2}:)?\d{2}:\d{2}\.\d{3})(.*)$/;

for (const line of lines) {
const match = line.match(timingRegex);
if (match) {
updatedLines.push(match[1]);
} else {
updatedLines.push(line);
}
}

return updatedLines.join('\n');
}

Copy link

Copilot AI Jan 6, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function name "shiftVttToCenter" is misleading. The function doesn't shift captions to center position - it strips positioning information from VTT timing lines. According to the regex pattern, it removes any positioning data after the timestamp (the second capture group that's discarded). Consider renaming to something more descriptive like "stripVttPositioning" or "removeVttPositionData".

Suggested change
function shiftVttToCenter(vtt: string): string {
const lines = vtt.split('\n');
const updatedLines: string[] = [];
const timingRegex = /^((?:\d{1,2}:)?\d{2}:\d{2}\.\d{3} --> (?:\d{1,2}:)?\d{2}:\d{2}\.\d{3})(.*)$/;
for (const line of lines) {
const match = line.match(timingRegex);
if (match) {
updatedLines.push(match[1]);
} else {
updatedLines.push(line);
}
}
return updatedLines.join('\n');
}
/**
* Strips positioning and additional cue settings from VTT timing lines,
* leaving only the raw timestamp range (e.g. "00:00:01.000 --> 00:00:02.000").
*/
function stripVttPositioning(vtt: string): string {
const lines = vtt.split('\n');
const updatedLines: string[] = [];
const timingRegex =
/^((?:\d{1,2}:)?\d{2}:\d{2}\.\d{3} --> (?:\d{1,2}:)?\d{2}:\d{2}\.\d{3})(.*)$/;
for (const line of lines) {
const match = line.match(timingRegex);
if (match) {
updatedLines.push(match[1]);
} else {
updatedLines.push(line);
}
}
return updatedLines.join('\n');
}
/**
* @deprecated This function does not actually "shift" captions to the center.
* It strips positioning information from VTT timing lines.
* Use {@link stripVttPositioning} instead.
*/
function shiftVttToCenter(vtt: string): string {
return stripVttPositioning(vtt);
}

Copilot uses AI. Check for mistakes.
Comment thread src/lib/helpers/youtubeTranscriptsHandling.ts
shiny-comic and others added 2 commits January 7, 2026 09:15
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@pgrandin
Copy link
Copy Markdown

pgrandin commented Feb 9, 2026

Tested this on a private instance and can confirm it fixes captions.

Built from this branch (commit f8e1699) and deployed as the companion for a private Invidious instance. Captions are fully functional again — tested with dQw4w9WgXcQ and a few more.

  $ curl '/api/v1/captions/dQw4w9WgXcQ?label=English' | head -15                                                                                                                                                   
  WEBVTT                                                                               
  Kind: captions
  Language: en

  00:00:18.640 --> 00:00:21.880
  ♪ We're no strangers to love ♪

  00:00:22.640 --> 00:00:26.960
  ♪ You know the rules
  and so do I ♪

Before this patch the VTT response was header-only with no cues. After, full subtitle cues are returned. Thanks for the fix!

@Fijxu
Copy link
Copy Markdown
Member

Fijxu commented Feb 24, 2026

@fl0wfr
Copy link
Copy Markdown

fl0wfr commented Mar 6, 2026

Tested this on a private instance and can confirm it fixes captions.

Built from this branch (commit f8e1699) and deployed as the companion for a private Invidious instance. Captions are fully functional again — tested with dQw4w9WgXcQ and a few more.

  $ curl '/api/v1/captions/dQw4w9WgXcQ?label=English' | head -15                                                                                                                                                   
  WEBVTT                                                                               
  Kind: captions
  Language: en

  00:00:18.640 --> 00:00:21.880
  ♪ We're no strangers to love ♪

  00:00:22.640 --> 00:00:26.960
  ♪ You know the rules
  and so do I ♪

Before this patch the VTT response was header-only with no cues. After, full subtitle cues are returned. Thanks for the fix!

Are you sure? I just build the image with the fix right now, but still no subtitles displayed :(

@shiny-comic
Copy link
Copy Markdown
Author

Tested this on a private instance and can confirm it fixes captions.
Built from this branch (commit f8e1699) and deployed as the companion for a private Invidious instance. Captions are fully functional again — tested with dQw4w9WgXcQ and a few more.

  $ curl '/api/v1/captions/dQw4w9WgXcQ?label=English' | head -15                                                                                                                                                   
  WEBVTT                                                                               
  Kind: captions
  Language: en

  00:00:18.640 --> 00:00:21.880
  ♪ We're no strangers to love ♪

  00:00:22.640 --> 00:00:26.960
  ♪ You know the rules
  and so do I ♪

Before this patch the VTT response was header-only with no cues. After, full subtitle cues are returned. Thanks for the fix!

Are you sure? I just build the image with the fix right now, but still no subtitles displayed :(

@fl0wfr
It maybe due to new commits on original repo I haven't rebase yet.

@leonghui
Copy link
Copy Markdown

leonghui commented Mar 8, 2026

I've just rebased this PR on top of #275, captions are still working fine on my self-hosted instance.

@fl0wfr could you share the video ID and timestamp where this isn't working?

@fl0wfr
Copy link
Copy Markdown

fl0wfr commented Mar 8, 2026

I've just rebased this PR on top of #275, captions are still working fine on my self-hosted instance.

@fl0wfr could you share the video ID and timestamp where this isn't working?

uGWZzn_GPQs, no english or french subs.

I just checked the logs, I can see an error:

InnertubeError: Request to https://www.youtube.com/youtubei/v1/get_transcript?prettyPrint=false&alt=json failed with status code 400
    at HTTPClient.fetch (https://cdn.jsdelivr.net/gh/LuanRT/YouTube.js@v16.0.0-deno/deno/src/utils/HTTPClient.ts:153:11)
    at eventLoopTick (ext:core/01_core.js:187:7)
    at async Actions.execute (https://cdn.jsdelivr.net/gh/LuanRT/YouTube.js@v16.0.0-deno/deno/src/core/Actions.ts:154:22)
    at async VideoInfo.getTranscript (https://cdn.jsdelivr.net/gh/LuanRT/YouTube.js@v16.0.0-deno/deno/src/core/mixins/MediaInfo.ts:203:22)
    at async handleTranscripts (file:///tmp/deno-compile-invidious_companion/src/lib/helpers/youtubeTranscriptsHandling.ts:81:35)
    at async file:///tmp/deno-compile-invidious_companion/src/routes/invidious_routes/captions.ts:110:9
    at async dispatch (https://jsr.io/@hono/hono/4.7.4/src/compose.ts:51:17)
    at async logger (https://jsr.io/@hono/hono/4.7.4/src/middleware/logger/index.ts:91:5)
    at async dispatch (https://jsr.io/@hono/hono/4.7.4/src/compose.ts:51:17)
    at async file:///tmp/deno-compile-invidious_companion/src/main.ts:165:5 {
  date: 2026-03-08T15:44:01.658Z,
  version: "16.0.0",
  info: "{\n" +
    '  "error": {\n' +
    '    "code": 400,\n' +
--> GET /companion/api/v1/captions/uGWZzn_GPQs?label=English&check=56wIAT7pn34tFqvQRlgL9jmJikPFrQUOj5HChL0pTwo= 500 1s
    '    "message": "Precondition check failed.",\n' +
    '    "errors": [\n' +
    "      {\n" +
    '        "message": "Precondition check failed.",\n' +
    '        "domain": "global",\n' +
    '        "reason": "failedPrecondition"\n' +
    "      }\n" +
    "    ],\n" +
    '    "status": "FAILED_PRECONDITION"\n' +
    "  }\n" +

@leonghui
Copy link
Copy Markdown

leonghui commented Mar 8, 2026

That's strange, captions are working here:
captions

/companion/api/v1/captions/uGWZzn_GPQs?label=French&check=Zdss9vcpVEN7HLddK98Vb-L5TTvWsrcfJeLdUJK8Kto=

WEBVTT
Kind: captions
Language: fr

00:00:17.550 --> 00:00:23.620
GARE AUX GARDIENS

00:00:24.650 --> 00:00:27.180
Tu es revenu avec les artéfacts, Garrett.

00:00:27.180 --> 00:00:29.620
Parfait. Suis-moi.

00:00:29.620 --> 00:00:31.950
Les Gardiens me laissent 
entrer dans leur club secret

00:00:31.950 --> 00:00:34.580
après avoir réduit en cendres 
les deux autres factions de la ville.

00:00:34.580 --> 00:00:36.780
Il m'est arrivé un truc drôle 
en faisant cette vidéo,

00:00:36.780 --> 00:00:39.320
j'ai enregistré toute ma partie 
de Thief 3 en un seul bloc

00:00:39.320 --> 00:00:41.520
pour pouvoir faire 
les vidéos plus rapidement

@shiny-comic
Copy link
Copy Markdown
Author

For anyone who don't like karaoke-like captions from auto-generated one, you can elimilate word by word time syncing with this tweak on top of this PR,

diff --git a/src/lib/helpers/youtubeTranscriptsHandling.ts b/src/lib/helpers/youtubeTranscriptsHandling.ts
index 7dec9d2..6f18409 100644
--- a/src/lib/helpers/youtubeTranscriptsHandling.ts
+++ b/src/lib/helpers/youtubeTranscriptsHandling.ts
@@ -25,6 +25,10 @@ const ESCAPE_SUBSTITUTIONS = {
     "\u00A0": "&nbsp;",
 };

+function cleanTimedTextCue(vtt: string): string {
+    return vtt.replace(/<\d{2}:\d{2}:\d{2}.\d{3}><c>([^<]+)<\/c>/g, "$1");
+}
+
 function shiftVttToCenter(vtt: string): string {
     const lines = vtt.split("\n");
     const updatedLines: string[] = [];
@@ -74,6 +78,7 @@ export async function handleTranscripts(
             });
         }

+        vttText = cleanTimedTextCue(vttText);
         vttText = shiftVttToCenter(vttText);

         return vttText;

Somehow I couldn't see any captions from original YT sites (maybe I'm blocked?) so I don't know if this new behavior is as same as YT ones. Please let me know I should include this in PR.

@fl0wfr
Copy link
Copy Markdown

fl0wfr commented Mar 31, 2026

That's strange, captions are working here:

Well, I don't understand. I built the image from @shiny-comic fork (branch fix-caption), and now it works.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants