Skip to content

Commit 2b6c4cb

Browse files
authored
Fix youtube captions (#1297)
* fix: separating caption loaders and leaving them untreated * fix: ajust te getFotmated loader
1 parent 7766a9d commit 2b6c4cb

8 files changed

Lines changed: 266 additions & 98 deletions

File tree

airtable/utils/ui-templates/selection-page.template.html

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@
1212

1313
body {
1414
font-family:
15-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
15+
Inter,
16+
-apple-system,
17+
BlinkMacSystemFont,
18+
"Segoe UI",
19+
Roboto,
1620
sans-serif;
1721
margin: 0;
1822
padding: 20px;
@@ -90,7 +94,11 @@
9094
text-align: left;
9195
margin: 0 16px;
9296
font-family:
93-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
97+
Inter,
98+
-apple-system,
99+
BlinkMacSystemFont,
100+
"Segoe UI",
101+
Roboto,
94102
sans-serif;
95103
}
96104

@@ -148,7 +156,11 @@
148156
font-size: 14px;
149157
text-align: left;
150158
font-family:
151-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
159+
Inter,
160+
-apple-system,
161+
BlinkMacSystemFont,
162+
"Segoe UI",
163+
Roboto,
152164
sans-serif;
153165
}
154166

@@ -183,7 +195,11 @@
183195
color: #f5f5f4;
184196
transition: border-color 0.2s;
185197
font-family:
186-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
198+
Inter,
199+
-apple-system,
200+
BlinkMacSystemFont,
201+
"Segoe UI",
202+
Roboto,
187203
sans-serif;
188204
}
189205

@@ -336,7 +352,11 @@
336352
font-size: 14px;
337353
color: #f5f5f4;
338354
font-family:
339-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
355+
Inter,
356+
-apple-system,
357+
BlinkMacSystemFont,
358+
"Segoe UI",
359+
Roboto,
340360
sans-serif;
341361
}
342362

@@ -471,7 +491,11 @@
471491
font-size: 14px;
472492
color: #f5f5f4;
473493
font-family:
474-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
494+
Inter,
495+
-apple-system,
496+
BlinkMacSystemFont,
497+
"Segoe UI",
498+
Roboto,
475499
sans-serif;
476500
}
477501

@@ -486,7 +510,11 @@
486510
color: #737373;
487511
font-size: 14px;
488512
font-family:
489-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
513+
Inter,
514+
-apple-system,
515+
BlinkMacSystemFont,
516+
"Segoe UI",
517+
Roboto,
490518
sans-serif;
491519
}
492520

@@ -519,7 +547,11 @@
519547
align-items: center;
520548
justify-content: center;
521549
font-family:
522-
Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
550+
Inter,
551+
-apple-system,
552+
BlinkMacSystemFont,
553+
"Segoe UI",
554+
Roboto,
523555
sans-serif;
524556
}
525557

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { AppContext } from "../../../mod.ts";
2+
3+
export interface Props {
4+
/**
5+
* @title Caption ID
6+
* @description ID of the caption track to download
7+
*/
8+
id: string;
9+
10+
/**
11+
* @title Format
12+
* @description Output format for the caption
13+
*/
14+
tfmt?: "srt" | "vtt";
15+
16+
/**
17+
* @title Translation Language
18+
* @description ISO 639-1 language code for caption translation
19+
*/
20+
tlang?: string;
21+
22+
/**
23+
* @title On Behalf Of Content Owner
24+
* @description Parameter for YouTube content partners
25+
*/
26+
onBehalfOfContentOwner?: string;
27+
}
28+
29+
/**
30+
* @name GET_CAPTION
31+
* @title Download Caption Track
32+
* @description Downloads a caption track in the specified format and language. If token issues occur, call another YouTube tool first to refresh the token.
33+
*/
34+
export default async function get(
35+
props: Props,
36+
_req: Request,
37+
ctx: AppContext,
38+
) {
39+
const { id, tfmt = "vtt", tlang, onBehalfOfContentOwner } = props;
40+
41+
try {
42+
if (!ctx.tokens?.access_token) {
43+
throw new Error(
44+
"Authentication required. Please authenticate with YouTube first.",
45+
);
46+
}
47+
48+
const accessToken = ctx.tokens.access_token;
49+
50+
let url = `https://www.googleapis.com/youtube/v3/captions/${id}`;
51+
52+
const params = new URLSearchParams();
53+
if (tfmt) params.append("tfmt", tfmt);
54+
if (tlang) params.append("tlang", tlang);
55+
if (onBehalfOfContentOwner) {
56+
params.append("onBehalfOfContentOwner", onBehalfOfContentOwner);
57+
}
58+
59+
const queryString = params.toString();
60+
if (queryString) {
61+
url += `?${queryString}`;
62+
}
63+
64+
const response = await fetch(url, {
65+
headers: {
66+
"Authorization": `Bearer ${accessToken}`,
67+
},
68+
});
69+
70+
if (!response.ok) {
71+
if (response.status === 401) {
72+
throw new Error(
73+
"Authentication token expired or invalid. Please refresh your YouTube authentication.",
74+
);
75+
}
76+
77+
if (response.status === 400 && tfmt !== "vtt") {
78+
const fallbackParams = new URLSearchParams();
79+
fallbackParams.append("tfmt", "vtt");
80+
if (tlang) fallbackParams.append("tlang", tlang);
81+
if (onBehalfOfContentOwner) {
82+
fallbackParams.append(
83+
"onBehalfOfContentOwner",
84+
onBehalfOfContentOwner,
85+
);
86+
}
87+
88+
const fallbackUrl =
89+
`https://www.googleapis.com/youtube/v3/captions/${id}?${fallbackParams.toString()}`;
90+
91+
const fallbackResponse = await fetch(fallbackUrl, {
92+
headers: {
93+
"Authorization": `Bearer ${accessToken}`,
94+
},
95+
});
96+
97+
if (!fallbackResponse.ok) {
98+
let errorDetails = "";
99+
try {
100+
const errorJson = await fallbackResponse.json();
101+
errorDetails = JSON.stringify(errorJson);
102+
} catch {
103+
errorDetails = `Status code: ${fallbackResponse.status}`;
104+
}
105+
106+
throw new Error(
107+
`Failed to fetch caption with fallback format: ${errorDetails}`,
108+
);
109+
}
110+
111+
return await fallbackResponse.text();
112+
}
113+
114+
let errorDetails = "";
115+
try {
116+
const errorJson = await response.json();
117+
errorDetails = JSON.stringify(errorJson);
118+
} catch {
119+
errorDetails = `Status code: ${response.status}`;
120+
}
121+
122+
throw new Error(`Failed to fetch caption: ${errorDetails}`);
123+
}
124+
125+
return await response.text();
126+
} catch (error: unknown) {
127+
if (
128+
error instanceof Error &&
129+
(error.message.includes("Authentication required") ||
130+
error.message.includes("token expired") ||
131+
error.message.includes("invalid"))
132+
) {
133+
ctx.errorHandler.toHttpError(
134+
error,
135+
error.message,
136+
401,
137+
);
138+
}
139+
140+
ctx.errorHandler.toHttpError(
141+
error,
142+
`Error fetching caption with ID ${id}`,
143+
);
144+
}
145+
}

google-youtube/loaders/videos/captions.ts renamed to google-youtube/loaders/videos/captions/getFormated.ts

Lines changed: 13 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { AppContext } from "../../mod.ts";
2-
import { YouTubeCaptionListResponse } from "../../utils/types.ts";
3-
import { COMMON_ERROR_MESSAGES } from "../../utils/constant.ts";
1+
import { AppContext } from "../../../mod.ts";
2+
import { YouTubeCaptionListResponse } from "../../../utils/types.ts";
3+
import { COMMON_ERROR_MESSAGES } from "../../../utils/constant.ts";
44

55
export interface Props {
66
/**
@@ -21,18 +21,6 @@ export interface Props {
2121
*/
2222
format?: "srt" | "sbv" | "vtt";
2323

24-
/**
25-
* @title Translation Language
26-
* @description Language code for translation
27-
*/
28-
translationLanguage?: string;
29-
30-
/**
31-
* @title Preferred Language
32-
* @description Preferred language code to fetch caption directly
33-
*/
34-
preferredLanguage?: string;
35-
3624
/**
3725
* @title Auto Load Caption
3826
* @description If true, automatically loads the first available caption
@@ -69,8 +57,6 @@ const loader = async (
6957
videoId,
7058
captionId,
7159
format = "srt",
72-
translationLanguage,
73-
preferredLanguage,
7460
autoLoadCaption = true,
7561
} = props;
7662

@@ -82,27 +68,14 @@ const loader = async (
8268
}
8369

8470
try {
85-
const captionsListResponse = await ctx.client["GET /captions"](
86-
{
87-
part: "snippet",
88-
videoId,
89-
},
90-
{
91-
headers: ctx.tokens?.access_token
92-
? { Authorization: `Bearer ${ctx.tokens.access_token}` }
93-
: {},
94-
},
95-
);
96-
97-
if (!captionsListResponse.ok) {
98-
ctx.errorHandler.toHttpError(
99-
captionsListResponse,
100-
`Failed to fetch captions list: ${captionsListResponse.statusText}`,
71+
const captionsListResponse = await ctx.invoke["google-youtube"].loaders
72+
.videos.captions.list(
73+
{
74+
videoId,
75+
},
10176
);
102-
}
10377

104-
const captionsList = await captionsListResponse
105-
.json() as YouTubeCaptionListResponse;
78+
const captionsList = captionsListResponse;
10679

10780
const response: CaptionResponse = {
10881
available: captionsList.items?.length > 0
@@ -111,15 +84,14 @@ const loader = async (
11184
};
11285

11386
const targetCaptionId = captionId ||
114-
findCaptionToLoad(response.available, preferredLanguage, autoLoadCaption);
87+
findCaptionToLoad(response.available, autoLoadCaption);
11588

11689
if (targetCaptionId) {
11790
await loadCaption(
11891
ctx,
11992
response,
12093
targetCaptionId,
12194
format,
122-
translationLanguage,
12395
);
12496
}
12597

@@ -134,18 +106,10 @@ const loader = async (
134106

135107
function findCaptionToLoad(
136108
available: YouTubeCaptionListResponse,
137-
preferredLanguage?: string,
138109
autoLoadCaption = true,
139110
): string | undefined {
140111
if (!autoLoadCaption || !available.items?.length) return undefined;
141112

142-
if (preferredLanguage) {
143-
const preferredCaption = available.items.find(
144-
(item) => item.snippet.language.startsWith(preferredLanguage),
145-
);
146-
if (preferredCaption) return preferredCaption.id;
147-
}
148-
149113
return available.items[0]?.id;
150114
}
151115

@@ -154,31 +118,13 @@ async function loadCaption(
154118
response: CaptionResponse,
155119
captionId: string,
156120
format: "srt" | "sbv" | "vtt",
157-
translationLanguage: string | undefined,
158121
): Promise<void> {
159122
try {
160-
const captionResponse = await ctx.client["GET /captions/:id"](
161-
{
123+
const captionText = await ctx.invoke["google-youtube"].loaders.videos
124+
.captions.get({
162125
id: captionId,
163-
tfmt: format,
164-
tlang: translationLanguage,
165-
},
166-
{
167-
headers: ctx.tokens?.access_token
168-
? { Authorization: `Bearer ${ctx.tokens.access_token}` }
169-
: {},
170-
},
171-
);
172-
173-
if (!captionResponse.ok) {
174-
ctx.errorHandler.toHttpError(
175-
captionResponse,
176-
`Failed to fetch caption text: ${captionResponse.statusText}`,
177-
);
178-
return;
179-
}
126+
});
180127

181-
const captionText = await captionResponse.text();
182128
response.loadedCaptionId = captionId;
183129
response.format = format;
184130

0 commit comments

Comments
 (0)