@tacone/prosey 0.8.1 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/prosey +75 -10
- package/package.json +1 -1
- package/src/channel-description.test.ts +50 -0
- package/src/channel-description.ts +22 -0
- package/src/html.ts +29 -6
- package/src/index.ts +41 -2
package/bin/prosey
CHANGED
|
@@ -103177,14 +103177,21 @@ var LOGO_DATA_URI = `data:image/svg+xml;charset=utf-8,${encodeURIComponent(LOGO_
|
|
|
103177
103177
|
function escapeHtml(text) {
|
|
103178
103178
|
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
103179
103179
|
}
|
|
103180
|
-
function watchMetaHtml(duration, wordCount, videoId) {
|
|
103180
|
+
function watchMetaHtml(duration, wordCount, videoId, channelName, channelId, channelDescription) {
|
|
103181
|
+
const sep = '<span style="opacity: 0.4"> | </span>';
|
|
103181
103182
|
const h2 = Math.floor(duration / 3600);
|
|
103182
103183
|
const m3 = Math.round(duration % 3600 / 60);
|
|
103183
103184
|
const durStr = h2 > 0 ? `${h2} h ${m3} min` : `${m3} min`;
|
|
103184
103185
|
const readTime = Math.ceil(wordCount / 200);
|
|
103185
|
-
const
|
|
103186
|
-
|
|
103187
|
-
|
|
103186
|
+
const watchUrl = `https://youtube.com/watch?v=${videoId}`;
|
|
103187
|
+
let parts = "";
|
|
103188
|
+
if (channelName) {
|
|
103189
|
+
const chUrl = channelId ? `https://youtube.com/channel/${channelId}` : `https://youtube.com/@${encodeURIComponent(channelName)}`;
|
|
103190
|
+
const chDesc = channelDescription || channelName;
|
|
103191
|
+
parts += `<a href="${chUrl}" class="watch-link" title="${escapeHtml(chDesc)}">${escapeHtml(channelName)}</a>${sep}`;
|
|
103192
|
+
}
|
|
103193
|
+
parts += `<a href="${watchUrl}" class="watch-link">${durStr} watch</a>${sep}${readTime} min read`;
|
|
103194
|
+
return parts;
|
|
103188
103195
|
}
|
|
103189
103196
|
async function generateHtml(markdown, title, watchMeta) {
|
|
103190
103197
|
const [css, body] = await Promise.all([getPicoCss(), g2.parse(markdown)]);
|
|
@@ -103275,7 +103282,7 @@ img[alt="Prosey"]:hover, img[alt="Prosey"]:active, img[alt="Prosey"]:focus { fil
|
|
|
103275
103282
|
<body>
|
|
103276
103283
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem 0">
|
|
103277
103284
|
<img src="${LOGO_DATA_URI}" alt="Prosey" title="Prosey" style="vertical-align:top;flex-shrink:0">
|
|
103278
|
-
<div style="flex:1;text-align:center;font-size:.85rem;color:var(--pico-muted-color)">${watchMeta ? watchMetaHtml(watchMeta.duration, watchMeta.wordCount, watchMeta.videoId) : ""}</div>
|
|
103285
|
+
<div style="flex:1;text-align:center;font-size:.85rem;color:var(--pico-muted-color)">${watchMeta ? watchMetaHtml(watchMeta.duration, watchMeta.wordCount, watchMeta.videoId, watchMeta.channelName, watchMeta.channelId, watchMeta.channelDescription) : ""}</div>
|
|
103279
103286
|
<button id="theme-btn" type="button" style="background:none;border:none;cursor:pointer;padding:0;line-height:1;opacity:.5;filter:grayscale(100%);transition:all 0.3s;flex-shrink:0">\uD83D\uDCA1</button>
|
|
103280
103287
|
</div>
|
|
103281
103288
|
<main class="content">
|
|
@@ -103310,10 +103317,30 @@ function openInBrowser(htmlPath) {
|
|
|
103310
103317
|
setTimeout(() => resolve(), 5000);
|
|
103311
103318
|
});
|
|
103312
103319
|
}
|
|
103320
|
+
|
|
103321
|
+
// src/channel-description.ts
|
|
103322
|
+
async function fetchChannelDescription(channelId) {
|
|
103323
|
+
try {
|
|
103324
|
+
const resp = await fetch(`https://www.youtube.com/youtubei/v1/browse`, {
|
|
103325
|
+
method: "POST",
|
|
103326
|
+
headers: { "Content-Type": "application/json" },
|
|
103327
|
+
body: JSON.stringify({
|
|
103328
|
+
context: {
|
|
103329
|
+
client: { clientName: "WEB", clientVersion: "2.20240101.00.00" }
|
|
103330
|
+
},
|
|
103331
|
+
browseId: channelId
|
|
103332
|
+
})
|
|
103333
|
+
});
|
|
103334
|
+
const data = await resp.json();
|
|
103335
|
+
return data?.metadata?.channelMetadataRenderer?.description;
|
|
103336
|
+
} catch {
|
|
103337
|
+
return;
|
|
103338
|
+
}
|
|
103339
|
+
}
|
|
103313
103340
|
// package.json
|
|
103314
103341
|
var package_default = {
|
|
103315
103342
|
name: "@tacone/prosey",
|
|
103316
|
-
version: "0.
|
|
103343
|
+
version: "0.9.1",
|
|
103317
103344
|
description: "Download YouTube video transcripts from the CLI",
|
|
103318
103345
|
module: "src/index.ts",
|
|
103319
103346
|
type: "module",
|
|
@@ -122340,15 +122367,23 @@ try {
|
|
|
122340
122367
|
let structuredContent;
|
|
122341
122368
|
let videoTitle;
|
|
122342
122369
|
let videoDuration = 0;
|
|
122370
|
+
let channelName;
|
|
122371
|
+
let channelId;
|
|
122372
|
+
let channelDescription;
|
|
122343
122373
|
if (!segments2) {
|
|
122344
122374
|
info("Fetching transcript...");
|
|
122345
122375
|
const opts = lang ? { lang, videoDetails: true } : { videoDetails: true };
|
|
122346
122376
|
const result = await fetchTranscript(videoId, opts);
|
|
122347
122377
|
segments2 = result.segments;
|
|
122348
122378
|
videoDuration = result.videoDetails.lengthSeconds;
|
|
122379
|
+
channelName = result.videoDetails.author;
|
|
122380
|
+
channelId = result.videoDetails.channelId;
|
|
122381
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
122349
122382
|
const infoJson = JSON.stringify({
|
|
122350
122383
|
title: result.videoDetails.title,
|
|
122351
|
-
channel:
|
|
122384
|
+
channel: channelName,
|
|
122385
|
+
channelId,
|
|
122386
|
+
channelDescription,
|
|
122352
122387
|
description: result.videoDetails.description,
|
|
122353
122388
|
duration: videoDuration
|
|
122354
122389
|
});
|
|
@@ -122385,9 +122420,14 @@ ${structuredContent}
|
|
|
122385
122420
|
const fallbackOpts = lang ? { lang, videoDetails: true } : { videoDetails: true };
|
|
122386
122421
|
const fallbackResult = await fetchTranscript(videoId, fallbackOpts);
|
|
122387
122422
|
videoDuration = fallbackResult.videoDetails.lengthSeconds;
|
|
122423
|
+
channelName = fallbackResult.videoDetails.author;
|
|
122424
|
+
channelId = fallbackResult.videoDetails.channelId;
|
|
122425
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
122388
122426
|
cachedInfo = JSON.stringify({
|
|
122389
122427
|
title: fallbackResult.videoDetails.title,
|
|
122390
122428
|
channel: fallbackResult.videoDetails.author,
|
|
122429
|
+
channelId,
|
|
122430
|
+
channelDescription,
|
|
122391
122431
|
description: fallbackResult.videoDetails.description,
|
|
122392
122432
|
duration: videoDuration
|
|
122393
122433
|
});
|
|
@@ -122403,6 +122443,9 @@ ${structuredContent}
|
|
|
122403
122443
|
const cachedInfoObj = JSON.parse(cachedInfo);
|
|
122404
122444
|
videoTitle = cachedInfoObj.title;
|
|
122405
122445
|
videoDuration = cachedInfoObj.duration ?? 0;
|
|
122446
|
+
channelName = cachedInfoObj.channel;
|
|
122447
|
+
channelId = cachedInfoObj.channelId;
|
|
122448
|
+
channelDescription = cachedInfoObj.channelDescription;
|
|
122406
122449
|
const truncatedInfo = JSON.stringify({
|
|
122407
122450
|
title: cachedInfoObj.title,
|
|
122408
122451
|
channel: cachedInfoObj.channel,
|
|
@@ -122439,7 +122482,10 @@ ${structuredContent}
|
|
|
122439
122482
|
const htmlContent = await generateHtml(formatted, videoTitle, {
|
|
122440
122483
|
videoId,
|
|
122441
122484
|
duration: videoDuration,
|
|
122442
|
-
wordCount
|
|
122485
|
+
wordCount,
|
|
122486
|
+
channelName,
|
|
122487
|
+
channelId,
|
|
122488
|
+
channelDescription
|
|
122443
122489
|
});
|
|
122444
122490
|
const htmlPath = join5(dir2, "summary.html");
|
|
122445
122491
|
await writeFile3(htmlPath, htmlContent, "utf8");
|
|
@@ -122473,6 +122519,9 @@ ${structuredContent}
|
|
|
122473
122519
|
let md = null;
|
|
122474
122520
|
let videoTitle;
|
|
122475
122521
|
let videoDuration = 0;
|
|
122522
|
+
let channelName;
|
|
122523
|
+
let channelId;
|
|
122524
|
+
let channelDescription;
|
|
122476
122525
|
startTimer();
|
|
122477
122526
|
let cachedInfo = null;
|
|
122478
122527
|
if (!noCache) {
|
|
@@ -122501,9 +122550,14 @@ ${structuredContent}
|
|
|
122501
122550
|
const result = await fetchTranscript(videoId, opts);
|
|
122502
122551
|
segments2 = result.segments;
|
|
122503
122552
|
videoDuration = result.videoDetails.lengthSeconds;
|
|
122553
|
+
channelName = result.videoDetails.author;
|
|
122554
|
+
channelId = result.videoDetails.channelId;
|
|
122555
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
122504
122556
|
const infoJson = JSON.stringify({
|
|
122505
122557
|
title: result.videoDetails.title,
|
|
122506
|
-
channel:
|
|
122558
|
+
channel: channelName,
|
|
122559
|
+
channelId,
|
|
122560
|
+
channelDescription,
|
|
122507
122561
|
description: result.videoDetails.description,
|
|
122508
122562
|
duration: videoDuration
|
|
122509
122563
|
});
|
|
@@ -122555,9 +122609,14 @@ ${structuredContent}
|
|
|
122555
122609
|
const fallbackOpts = lang ? { lang, videoDetails: true } : { videoDetails: true };
|
|
122556
122610
|
const fallbackResult = await fetchTranscript(videoId, fallbackOpts);
|
|
122557
122611
|
videoDuration = fallbackResult.videoDetails.lengthSeconds;
|
|
122612
|
+
channelName = fallbackResult.videoDetails.author;
|
|
122613
|
+
channelId = fallbackResult.videoDetails.channelId;
|
|
122614
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
122558
122615
|
cachedInfo = JSON.stringify({
|
|
122559
122616
|
title: fallbackResult.videoDetails.title,
|
|
122560
122617
|
channel: fallbackResult.videoDetails.author,
|
|
122618
|
+
channelId,
|
|
122619
|
+
channelDescription,
|
|
122561
122620
|
description: fallbackResult.videoDetails.description,
|
|
122562
122621
|
duration: videoDuration
|
|
122563
122622
|
});
|
|
@@ -122573,6 +122632,9 @@ ${structuredContent}
|
|
|
122573
122632
|
const cachedInfoObj = JSON.parse(cachedInfo);
|
|
122574
122633
|
videoTitle = cachedInfoObj.title;
|
|
122575
122634
|
videoDuration = cachedInfoObj.duration ?? 0;
|
|
122635
|
+
channelName = cachedInfoObj.channel;
|
|
122636
|
+
channelId = cachedInfoObj.channelId;
|
|
122637
|
+
channelDescription = cachedInfoObj.channelDescription;
|
|
122576
122638
|
const truncatedInfo = JSON.stringify({
|
|
122577
122639
|
title: cachedInfoObj.title,
|
|
122578
122640
|
channel: cachedInfoObj.channel,
|
|
@@ -122605,7 +122667,10 @@ ${transcriptText}`;
|
|
|
122605
122667
|
const htmlContent = await generateHtml(formatted, videoTitle, {
|
|
122606
122668
|
videoId,
|
|
122607
122669
|
duration: videoDuration,
|
|
122608
|
-
wordCount
|
|
122670
|
+
wordCount,
|
|
122671
|
+
channelName,
|
|
122672
|
+
channelId,
|
|
122673
|
+
channelDescription
|
|
122609
122674
|
});
|
|
122610
122675
|
const htmlPath = join5(dir2, "transcript.html");
|
|
122611
122676
|
await writeFile3(htmlPath, htmlContent, "utf8");
|
package/package.json
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, expect, test, mock } from "bun:test";
|
|
2
|
+
import { fetchChannelDescription } from "./channel-description";
|
|
3
|
+
|
|
4
|
+
function mockFetchResponse(data: unknown): void {
|
|
5
|
+
globalThis.fetch = mock(() =>
|
|
6
|
+
Promise.resolve({
|
|
7
|
+
ok: true,
|
|
8
|
+
json: () => Promise.resolve(data),
|
|
9
|
+
}),
|
|
10
|
+
) as unknown as typeof fetch;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
describe("fetchChannelDescription", () => {
|
|
14
|
+
test("calls the browse endpoint without an API key", async () => {
|
|
15
|
+
let capturedUrl = "";
|
|
16
|
+
let capturedInit: RequestInit | undefined;
|
|
17
|
+
globalThis.fetch = ((url: any, init: any) => {
|
|
18
|
+
capturedUrl = String(url);
|
|
19
|
+
capturedInit = init;
|
|
20
|
+
return Promise.resolve({
|
|
21
|
+
ok: true,
|
|
22
|
+
json: () =>
|
|
23
|
+
Promise.resolve({
|
|
24
|
+
metadata: {
|
|
25
|
+
channelMetadataRenderer: { description: "Channel bio" },
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
}) as unknown as typeof fetch;
|
|
30
|
+
|
|
31
|
+
const desc = await fetchChannelDescription("UC123");
|
|
32
|
+
expect(desc).toBe("Channel bio");
|
|
33
|
+
|
|
34
|
+
expect(capturedUrl).toBe("https://www.youtube.com/youtubei/v1/browse");
|
|
35
|
+
expect(capturedUrl).not.toContain("key=");
|
|
36
|
+
expect(capturedInit!.method).toBe("POST");
|
|
37
|
+
const body = JSON.parse(String(capturedInit!.body));
|
|
38
|
+
expect(body.browseId).toBe("UC123");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("returns undefined when response has no description", async () => {
|
|
42
|
+
mockFetchResponse({ metadata: {} });
|
|
43
|
+
expect(await fetchChannelDescription("UC123")).toBeUndefined();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("returns undefined on network error", async () => {
|
|
47
|
+
globalThis.fetch = mock(() => Promise.reject(new Error("boom"))) as unknown as typeof fetch;
|
|
48
|
+
expect(await fetchChannelDescription("UC123")).toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export interface ChannelDescriptionResult {
|
|
2
|
+
description?: string;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
export async function fetchChannelDescription(channelId: string): Promise<string | undefined> {
|
|
6
|
+
try {
|
|
7
|
+
const resp = await fetch(`https://www.youtube.com/youtubei/v1/browse`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: { "Content-Type": "application/json" },
|
|
10
|
+
body: JSON.stringify({
|
|
11
|
+
context: {
|
|
12
|
+
client: { clientName: "WEB", clientVersion: "2.20240101.00.00" },
|
|
13
|
+
},
|
|
14
|
+
browseId: channelId,
|
|
15
|
+
}),
|
|
16
|
+
});
|
|
17
|
+
const data: ChannelDescriptionResult = (await resp.json()) as ChannelDescriptionResult;
|
|
18
|
+
return (data as any)?.metadata?.channelMetadataRenderer?.description;
|
|
19
|
+
} catch {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
}
|
package/src/html.ts
CHANGED
|
@@ -34,20 +34,43 @@ function escapeHtml(text: string): string {
|
|
|
34
34
|
.replace(/"/g, """);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
function watchMetaHtml(
|
|
37
|
+
function watchMetaHtml(
|
|
38
|
+
duration: number,
|
|
39
|
+
wordCount: number,
|
|
40
|
+
videoId: string,
|
|
41
|
+
channelName?: string,
|
|
42
|
+
channelId?: string,
|
|
43
|
+
channelDescription?: string,
|
|
44
|
+
): string {
|
|
45
|
+
const sep = '<span style="opacity: 0.4"> | </span>';
|
|
38
46
|
const h = Math.floor(duration / 3600);
|
|
39
47
|
const m = Math.round((duration % 3600) / 60);
|
|
40
48
|
const durStr = h > 0 ? `${h} h ${m} min` : `${m} min`;
|
|
41
49
|
const readTime = Math.ceil(wordCount / 200);
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const watchUrl = `https://youtube.com/watch?v=${videoId}`;
|
|
51
|
+
let parts = "";
|
|
52
|
+
if (channelName) {
|
|
53
|
+
const chUrl = channelId
|
|
54
|
+
? `https://youtube.com/channel/${channelId}`
|
|
55
|
+
: `https://youtube.com/@${encodeURIComponent(channelName)}`;
|
|
56
|
+
const chDesc = channelDescription || channelName;
|
|
57
|
+
parts += `<a href="${chUrl}" class="watch-link" title="${escapeHtml(chDesc)}">${escapeHtml(channelName)}</a>${sep}`;
|
|
58
|
+
}
|
|
59
|
+
parts += `<a href="${watchUrl}" class="watch-link">${durStr} watch</a>${sep}${readTime} min read`;
|
|
60
|
+
return parts;
|
|
45
61
|
}
|
|
46
62
|
|
|
47
63
|
export async function generateHtml(
|
|
48
64
|
markdown: string,
|
|
49
65
|
title?: string,
|
|
50
|
-
watchMeta?: {
|
|
66
|
+
watchMeta?: {
|
|
67
|
+
videoId: string;
|
|
68
|
+
duration: number;
|
|
69
|
+
wordCount: number;
|
|
70
|
+
channelName?: string;
|
|
71
|
+
channelId?: string;
|
|
72
|
+
channelDescription?: string;
|
|
73
|
+
},
|
|
51
74
|
): Promise<string> {
|
|
52
75
|
const [css, body] = await Promise.all([getPicoCss(), marked.parse(markdown)]);
|
|
53
76
|
|
|
@@ -138,7 +161,7 @@ img[alt="Prosey"]:hover, img[alt="Prosey"]:active, img[alt="Prosey"]:focus { fil
|
|
|
138
161
|
<body>
|
|
139
162
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:1rem 1rem 0">
|
|
140
163
|
<img src="${LOGO_DATA_URI}" alt="Prosey" title="Prosey" style="vertical-align:top;flex-shrink:0">
|
|
141
|
-
<div style="flex:1;text-align:center;font-size:.85rem;color:var(--pico-muted-color)">${watchMeta ? watchMetaHtml(watchMeta.duration, watchMeta.wordCount, watchMeta.videoId) : ""}</div>
|
|
164
|
+
<div style="flex:1;text-align:center;font-size:.85rem;color:var(--pico-muted-color)">${watchMeta ? watchMetaHtml(watchMeta.duration, watchMeta.wordCount, watchMeta.videoId, watchMeta.channelName, watchMeta.channelId, watchMeta.channelDescription) : ""}</div>
|
|
142
165
|
<button id="theme-btn" type="button" style="background:none;border:none;cursor:pointer;padding:0;line-height:1;opacity:.5;filter:grayscale(100%);transition:all 0.3s;flex-shrink:0">💡</button>
|
|
143
166
|
</div>
|
|
144
167
|
<main class="content">
|
package/src/index.ts
CHANGED
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
import { cacheDir, readCache, writeCache, extractVideoId } from "./cache";
|
|
22
22
|
import { extractChapters, formatChaptersAsText, formatChaptersAsJson } from "./extract-chapters";
|
|
23
23
|
import { generateHtml, openInBrowser } from "./html";
|
|
24
|
+
import { fetchChannelDescription } from "./channel-description";
|
|
24
25
|
import { checkVersion } from "./version-check";
|
|
25
26
|
import pkg from "../package.json";
|
|
26
27
|
import prettier from "prettier";
|
|
@@ -484,6 +485,9 @@ try {
|
|
|
484
485
|
let structuredContent: string;
|
|
485
486
|
let videoTitle: string | undefined;
|
|
486
487
|
let videoDuration = 0;
|
|
488
|
+
let channelName: string | undefined;
|
|
489
|
+
let channelId: string | undefined;
|
|
490
|
+
let channelDescription: string | undefined;
|
|
487
491
|
|
|
488
492
|
if (!segments) {
|
|
489
493
|
info("Fetching transcript...");
|
|
@@ -494,9 +498,14 @@ try {
|
|
|
494
498
|
};
|
|
495
499
|
segments = result.segments;
|
|
496
500
|
videoDuration = result.videoDetails.lengthSeconds;
|
|
501
|
+
channelName = result.videoDetails.author;
|
|
502
|
+
channelId = result.videoDetails.channelId;
|
|
503
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
497
504
|
const infoJson = JSON.stringify({
|
|
498
505
|
title: result.videoDetails.title,
|
|
499
|
-
channel:
|
|
506
|
+
channel: channelName,
|
|
507
|
+
channelId: channelId,
|
|
508
|
+
channelDescription: channelDescription,
|
|
500
509
|
description: result.videoDetails.description,
|
|
501
510
|
duration: videoDuration,
|
|
502
511
|
});
|
|
@@ -533,9 +542,14 @@ try {
|
|
|
533
542
|
segments: TranscriptSegment[];
|
|
534
543
|
};
|
|
535
544
|
videoDuration = fallbackResult.videoDetails.lengthSeconds;
|
|
545
|
+
channelName = fallbackResult.videoDetails.author;
|
|
546
|
+
channelId = fallbackResult.videoDetails.channelId;
|
|
547
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
536
548
|
cachedInfo = JSON.stringify({
|
|
537
549
|
title: fallbackResult.videoDetails.title,
|
|
538
550
|
channel: fallbackResult.videoDetails.author,
|
|
551
|
+
channelId: channelId,
|
|
552
|
+
channelDescription: channelDescription,
|
|
539
553
|
description: fallbackResult.videoDetails.description,
|
|
540
554
|
duration: videoDuration,
|
|
541
555
|
});
|
|
@@ -553,6 +567,9 @@ try {
|
|
|
553
567
|
const cachedInfoObj = JSON.parse(cachedInfo);
|
|
554
568
|
videoTitle = cachedInfoObj.title;
|
|
555
569
|
videoDuration = cachedInfoObj.duration ?? 0;
|
|
570
|
+
channelName = cachedInfoObj.channel;
|
|
571
|
+
channelId = cachedInfoObj.channelId;
|
|
572
|
+
channelDescription = cachedInfoObj.channelDescription;
|
|
556
573
|
const truncatedInfo = JSON.stringify({
|
|
557
574
|
title: cachedInfoObj.title,
|
|
558
575
|
channel: cachedInfoObj.channel,
|
|
@@ -586,6 +603,9 @@ try {
|
|
|
586
603
|
videoId,
|
|
587
604
|
duration: videoDuration,
|
|
588
605
|
wordCount,
|
|
606
|
+
channelName,
|
|
607
|
+
channelId,
|
|
608
|
+
channelDescription,
|
|
589
609
|
});
|
|
590
610
|
const htmlPath = join(dir, "summary.html");
|
|
591
611
|
await writeFile(htmlPath, htmlContent, "utf8");
|
|
@@ -622,6 +642,9 @@ try {
|
|
|
622
642
|
let md: string | null = null;
|
|
623
643
|
let videoTitle: string | undefined;
|
|
624
644
|
let videoDuration = 0;
|
|
645
|
+
let channelName: string | undefined;
|
|
646
|
+
let channelId: string | undefined;
|
|
647
|
+
let channelDescription: string | undefined;
|
|
625
648
|
|
|
626
649
|
startTimer();
|
|
627
650
|
|
|
@@ -660,9 +683,14 @@ try {
|
|
|
660
683
|
};
|
|
661
684
|
segments = result.segments;
|
|
662
685
|
videoDuration = result.videoDetails.lengthSeconds;
|
|
686
|
+
channelName = result.videoDetails.author;
|
|
687
|
+
channelId = result.videoDetails.channelId;
|
|
688
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
663
689
|
const infoJson = JSON.stringify({
|
|
664
690
|
title: result.videoDetails.title,
|
|
665
|
-
channel:
|
|
691
|
+
channel: channelName,
|
|
692
|
+
channelId: channelId,
|
|
693
|
+
channelDescription: channelDescription,
|
|
666
694
|
description: result.videoDetails.description,
|
|
667
695
|
duration: videoDuration,
|
|
668
696
|
});
|
|
@@ -712,9 +740,14 @@ try {
|
|
|
712
740
|
segments: TranscriptSegment[];
|
|
713
741
|
};
|
|
714
742
|
videoDuration = fallbackResult.videoDetails.lengthSeconds;
|
|
743
|
+
channelName = fallbackResult.videoDetails.author;
|
|
744
|
+
channelId = fallbackResult.videoDetails.channelId;
|
|
745
|
+
channelDescription = await fetchChannelDescription(channelId);
|
|
715
746
|
cachedInfo = JSON.stringify({
|
|
716
747
|
title: fallbackResult.videoDetails.title,
|
|
717
748
|
channel: fallbackResult.videoDetails.author,
|
|
749
|
+
channelId: channelId,
|
|
750
|
+
channelDescription: channelDescription,
|
|
718
751
|
description: fallbackResult.videoDetails.description,
|
|
719
752
|
duration: videoDuration,
|
|
720
753
|
});
|
|
@@ -732,6 +765,9 @@ try {
|
|
|
732
765
|
const cachedInfoObj = JSON.parse(cachedInfo);
|
|
733
766
|
videoTitle = cachedInfoObj.title;
|
|
734
767
|
videoDuration = cachedInfoObj.duration ?? 0;
|
|
768
|
+
channelName = cachedInfoObj.channel;
|
|
769
|
+
channelId = cachedInfoObj.channelId;
|
|
770
|
+
channelDescription = cachedInfoObj.channelDescription;
|
|
735
771
|
const truncatedInfo = JSON.stringify({
|
|
736
772
|
title: cachedInfoObj.title,
|
|
737
773
|
channel: cachedInfoObj.channel,
|
|
@@ -760,6 +796,9 @@ try {
|
|
|
760
796
|
videoId,
|
|
761
797
|
duration: videoDuration,
|
|
762
798
|
wordCount,
|
|
799
|
+
channelName,
|
|
800
|
+
channelId,
|
|
801
|
+
channelDescription,
|
|
763
802
|
});
|
|
764
803
|
const htmlPath = join(dir, "transcript.html");
|
|
765
804
|
await writeFile(htmlPath, htmlContent, "utf8");
|