-
Notifications
You must be signed in to change notification settings - Fork 6.4k
Expand file tree
/
Copy pathYouTube Transcript to Executive Summary (Free).json
More file actions
188 lines (188 loc) · 7.46 KB
/
Copy pathYouTube Transcript to Executive Summary (Free).json
File metadata and controls
188 lines (188 loc) · 7.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
{
"name": "YouTube Transcript → Executive Summary (Free)",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "yt-transcript-lite",
"responseMode": "responseNode",
"options": {}
},
"id": "webhook",
"name": "Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
250,
300
],
"webhookId": "yt-transcript-lite"
},
{
"parameters": {
"jsCode": "const url = $input.first().json.body.url;\n// Extract video ID from various YouTube URL formats\nlet videoId = null;\nconst patterns = [\n /(?:youtube\\.com\\/watch\\?v=|youtu\\.be\\/|youtube\\.com\\/embed\\/|youtube\\.com\\/v\\/)([a-zA-Z0-9_-]{11})/,\n /^([a-zA-Z0-9_-]{11})$/\n];\nfor (const pattern of patterns) {\n const match = url.match(pattern);\n if (match) {\n videoId = match[1];\n break;\n }\n}\nif (!videoId) {\n throw new Error('Invalid YouTube URL. Could not extract video ID.');\n}\nreturn { videoId, url };"
},
"id": "extract-id",
"name": "Extract Video ID",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
450,
300
]
},
{
"parameters": {
"jsCode": "// ============================================================\n// Fetch Transcript with automatic fallback (v9)\n// Primary: youtubetranscript.com (free, keyless — same as paid)\n// Fallback: local bridge using youtube-transcript-api (optional)\n// ============================================================\nconst videoId = $json.videoId;\n// Optional secondary source. Leave empty to disable.\n// Point at any transcript API you have (e.g. a local bridge or\n// transcriptapi.com with your key). Primary source is free & keyless.\nconst FALLBACK_URL = '';\n\nlet raw = null;\nlet source = 'youtubetranscript.com';\n\n// 1) Primary source (free, no API key)\ntry {\n const resp = await this.helpers.httpRequest({\n method: 'GET',\n url: `https://youtubetranscript.com/?server_vid2=${videoId}`,\n responseFormat: 'text',\n timeout: 20000,\n });\n const isBlocked = /currently blocking us|working on a fix|can't fetch|unable to fetch/i.test(resp) && resp.length < 400;\n if (!isBlocked && resp.includes('<text')) {\n raw = resp;\n }\n} catch (e) {\n // primary unavailable -> try fallback\n}\n\n// 2) Fallback source (local bridge / any configured transcript API)\nif (!raw && FALLBACK_URL) {\n try {\n const fb = await this.helpers.httpRequest({\n method: 'GET',\n url: `${FALLBACK_URL}?video_id=${videoId}`,\n json: true,\n timeout: 30000,\n });\n if (fb && fb.ok && fb.transcript) {\n raw = fb.transcript;\n source = 'local-bridge';\n }\n } catch (e) {\n // fallback unavailable\n }\n}\n\nif (!raw) {\n throw new Error('Could not fetch a transcript for this video. The free transcript service is temporarily blocked by YouTube — try a video with captions, or try again later.');\n}\n\n// Parse: XML <text> tags (primary) or plain text (fallback)\nlet transcript = '';\nconst textMatches = raw.match(/<text[^>]*>([\\s\\S]*?)<\\/text>/g) || [];\nif (textMatches.length) {\n transcript = textMatches\n .map(t => t.replace(/<[^>]+>/g, '').trim())\n .filter(t => t.length > 0)\n .join(' ');\n} else {\n transcript = raw\n .split('\\n')\n .filter(l => l.trim() && !l.includes('[') && !l.includes('((') && l.length > 5)\n .join(' ');\n}\ntranscript = transcript.trim();\nif (!transcript) {\n throw new Error('Could not extract a transcript for this video. It may have no captions, or the transcript service is unavailable.');\n}\n\nreturn {\n transcript: transcript.substring(0, 25000),\n wordCount: transcript.split(' ').length,\n source,\n};"
},
"id": "fetch-transcript",
"name": "Fetch Transcript",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
650,
300
]
},
{
"parameters": {
"resource": "chat",
"operation": "complete",
"chatModel": "gpt-4o-mini",
"messages": {
"messages": [
{
"role": "system",
"content": "You are a YouTube content analyst. Analyze the provided transcript and produce an **Executive Summary** (3-5 paragraphs) that captures the key points, insights, and main message of the video. Return as structured JSON with key: summary"
},
{
"role": "user",
"content": "={{ $json.transcript }}"
}
]
},
"simplifyOutput": true,
"options": {
"temperature": 0.3,
"maxTokens": 1000
}
},
"id": "ai-analysis",
"name": "AI Analysis",
"type": "n8n-nodes-base.openAi",
"typeVersion": 2,
"position": [
850,
300
]
},
{
"parameters": {
"jsCode": "// OpenAI node (simplifyOutput: true) returns { message: { role, content } }\n// The model is instructed to return JSON with key \"summary\".\nconst msg = $input.first().json.message;\nconst content = (msg && msg.content) || '';\nlet summary = content.trim();\ntry {\n const parsed = JSON.parse(content);\n if (parsed && parsed.summary) {\n summary = parsed.summary;\n }\n} catch (e) {\n // content was plain text\n}\n\nconst prev = $('Fetch Transcript').first().json;\nreturn {\n videoUrl: $('Extract Video ID').first().json.url || '',\n videoId: $('Extract Video ID').first().json.videoId || '',\n wordCount: prev.wordCount || 0,\n summary,\n};"
},
"id": "format-output",
"name": "Format Output",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1050,
300
]
},
{
"parameters": {
"options": {
"response": {
"responseCode": 200,
"respondMode": "allJson",
"responseHeaders": {
"entries": [
{
"name": "Content-Type",
"value": "application/json"
}
]
}
}
},
"jsCode": "const output = $input.first().json;\nreturn output;"
},
"id": "respond",
"name": "Respond to Webhook",
"type": "n8n-nodes-base.respondToWebhook",
"typeVersion": 1,
"position": [
1250,
300
]
}
],
"connections": {
"Webhook": {
"main": [
[
{
"node": "Extract Video ID",
"type": "main",
"index": 0
}
]
]
},
"Extract Video ID": {
"main": [
[
{
"node": "Fetch Transcript",
"type": "main",
"index": 0
}
]
]
},
"Fetch Transcript": {
"main": [
[
{
"node": "AI Analysis",
"type": "main",
"index": 0
}
]
]
},
"AI Analysis": {
"main": [
[
{
"node": "Format Output",
"type": "main",
"index": 0
}
]
]
},
"Format Output": {
"main": [
[
{
"node": "Respond to Webhook",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
},
"staticData": null,
"tags": [
"youtube",
"transcript",
"summary",
"openai",
"content-creation"
]
}