mirror of
https://github.com/JimLiu/baoyu-skills.git
synced 2026-07-31 22:29:48 +08:00
feat(youtube-transcript): add end times to chapter data
Add 'end' field to Chapter interface and parseChapters output.
Each chapter's end is derived from the next chapter's start time,
with the last chapter ending at the video's total duration.
This makes chapter data complete and ready for downstream consumers
(e.g. video clipping with ffmpeg) without requiring them to compute
end times from adjacent chapters.
Before: { title: 'Overview', start: 0 }
After: { title: 'Overview', start: 0, end: 21 }
This commit is contained in:
@@ -44,6 +44,7 @@ interface TranscriptInfo {
|
|||||||
interface Chapter {
|
interface Chapter {
|
||||||
title: string;
|
title: string;
|
||||||
start: number;
|
start: number;
|
||||||
|
end: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface VideoMeta {
|
interface VideoMeta {
|
||||||
@@ -249,16 +250,21 @@ async function fetchTranscriptSnippets(info: TranscriptInfo, translateTo?: strin
|
|||||||
|
|
||||||
// --- Metadata & chapters ---
|
// --- Metadata & chapters ---
|
||||||
|
|
||||||
function parseChapters(description: string): Chapter[] {
|
function parseChapters(description: string, duration: number = 0): Chapter[] {
|
||||||
const chapters: Chapter[] = [];
|
const raw: { title: string; start: number }[] = [];
|
||||||
for (const line of description.split("\n")) {
|
for (const line of description.split("\n")) {
|
||||||
const m = line.trim().match(/^(?:(\d{1,2}):)?(\d{1,2}):(\d{2})\s+(.+)$/);
|
const m = line.trim().match(/^(?:(\d{1,2}):)?(\d{1,2}):(\d{2})\s+(.+)$/);
|
||||||
if (m) {
|
if (m) {
|
||||||
const h = m[1] ? parseInt(m[1]) : 0;
|
const h = m[1] ? parseInt(m[1]) : 0;
|
||||||
chapters.push({ title: m[4].trim(), start: h * 3600 + parseInt(m[2]) * 60 + parseInt(m[3]) });
|
raw.push({ title: m[4].trim(), start: h * 3600 + parseInt(m[2]) * 60 + parseInt(m[3]) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return chapters.length >= 2 ? chapters : [];
|
if (raw.length < 2) return [];
|
||||||
|
return raw.map((ch, i) => ({
|
||||||
|
title: ch.title,
|
||||||
|
start: ch.start,
|
||||||
|
end: i < raw.length - 1 ? raw[i + 1].start : duration,
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
function getThumbnailUrls(videoId: string, data: any): string[] {
|
function getThumbnailUrls(videoId: string, data: any): string[] {
|
||||||
@@ -644,7 +650,8 @@ async function fetchAndCache(videoId: string, baseDir: string, opts: Options): P
|
|||||||
const info = findTranscript(transcripts, opts.languages, opts.excludeGenerated, opts.excludeManual);
|
const info = findTranscript(transcripts, opts.languages, opts.excludeGenerated, opts.excludeManual);
|
||||||
const result = await fetchTranscriptSnippets(info, opts.translate || undefined);
|
const result = await fetchTranscriptSnippets(info, opts.translate || undefined);
|
||||||
const description = data?.videoDetails?.shortDescription || "";
|
const description = data?.videoDetails?.shortDescription || "";
|
||||||
const chapters = parseChapters(description);
|
const duration = parseInt(data?.videoDetails?.lengthSeconds || "0");
|
||||||
|
const chapters = parseChapters(description, duration);
|
||||||
const langInfo = { code: result.languageCode, name: result.language, isGenerated: info.isGenerated };
|
const langInfo = { code: result.languageCode, name: result.language, isGenerated: info.isGenerated };
|
||||||
const meta = buildVideoMeta(data, videoId, langInfo, chapters);
|
const meta = buildVideoMeta(data, videoId, langInfo, chapters);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user