From 8d973f2bc57a7b932448849921e7c04543498d75 Mon Sep 17 00:00:00 2001 From: jzocb Date: Sun, 22 Mar 2026 15:52:30 -0400 Subject: [PATCH] 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 } --- skills/baoyu-youtube-transcript/scripts/main.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/skills/baoyu-youtube-transcript/scripts/main.ts b/skills/baoyu-youtube-transcript/scripts/main.ts index d96a804..53ada2d 100644 --- a/skills/baoyu-youtube-transcript/scripts/main.ts +++ b/skills/baoyu-youtube-transcript/scripts/main.ts @@ -44,6 +44,7 @@ interface TranscriptInfo { interface Chapter { title: string; start: number; + end: number; } interface VideoMeta { @@ -249,16 +250,21 @@ async function fetchTranscriptSnippets(info: TranscriptInfo, translateTo?: strin // --- Metadata & chapters --- -function parseChapters(description: string): Chapter[] { - const chapters: Chapter[] = []; +function parseChapters(description: string, duration: number = 0): Chapter[] { + const raw: { title: string; start: number }[] = []; for (const line of description.split("\n")) { const m = line.trim().match(/^(?:(\d{1,2}):)?(\d{1,2}):(\d{2})\s+(.+)$/); if (m) { 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[] { @@ -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 result = await fetchTranscriptSnippets(info, opts.translate || undefined); 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 meta = buildVideoMeta(data, videoId, langInfo, chapters);