From 9ea4d4ab3322914ca09a24fa0692423bb34fb6f7 Mon Sep 17 00:00:00 2001 From: Kilo Code Cloud Date: Wed, 14 Jan 2026 20:05:28 +0000 Subject: [PATCH] feat: output top 10 segment candidates with timestamps and duration --- src/cli/args.ts | 10 +++++-- src/cli/downloader.ts | 67 ++++++++++++++++++++++++------------------- src/cli/index.ts | 7 +++-- 3 files changed, 50 insertions(+), 34 deletions(-) diff --git a/src/cli/args.ts b/src/cli/args.ts index 67f7e0c..aad9328 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -2,12 +2,14 @@ export interface CliArgs { url?: string; output: string; format: string; + topN: number; } export function parseArgs(): CliArgs { const args: CliArgs = { output: "./downloads", format: "best", + topN: 10, }; const rawArgs = Bun.argv; @@ -22,8 +24,11 @@ export function parseArgs(): CliArgs { } else if (arg === "-f" || arg === "--format") { args.format = nextArg || "best"; i++; + } else if (arg === "-n" || arg === "--top") { + args.topN = parseInt(nextArg || "10", 10); + i++; } else if (arg === "-h" || arg === "--help") { - console.log(`YouTube Most Watched Segment Downloader + console.log(`YouTube Most Watched Segments Downloader Usage: yt-segments [options] @@ -33,11 +38,12 @@ Arguments: Options: -o, --output Output directory (default: ./downloads) -f, --format Video format (default: best) + -n, --top Number of top segments to show (default: 10) -h, --help Show this help message Examples: yt-segments "https://www.youtube.com/watch?v=abc123" - yt-segments "https://youtu.be/abc123" -o ./videos -f mp4 + yt-segments "https://youtu.be/abc123" -o ./videos -n 5 `); process.exit(0); } else if (!arg.startsWith("-") && !arg.includes("bun")) { diff --git a/src/cli/downloader.ts b/src/cli/downloader.ts index d103091..e17fed8 100644 --- a/src/cli/downloader.ts +++ b/src/cli/downloader.ts @@ -6,6 +6,7 @@ export interface DownloadOptions { url: string; outputDir: string; format: string; + topN: number; } interface RawHeatmapSegment { @@ -142,9 +143,10 @@ function getIntensity(segment: RawHeatmapSegment): number { return segment.intensity ?? segment.heat ?? segment.value ?? 0; } -function findHighestIntegralJump( - segments: RawHeatmapSegment[] -): ProcessedSegment | null { +function getTopSegmentsByIntegral( + segments: RawHeatmapSegment[], + topN: number +): ProcessedSegment[] { // Convert to processed format and filter valid segments const validSegments = segments .map(seg => ({ @@ -161,10 +163,10 @@ function findHighestIntegralJump( ); if (validSegments.length === 0) { - return null; + return []; } - // Calculate integral jump for each segment (intensity × duration) + // Calculate integral for each segment and sort by highest const withIntegral = validSegments.map(seg => { const segmentDuration = seg.end - seg.start; const integralJump = seg.intensity * segmentDuration; @@ -174,15 +176,13 @@ function findHighestIntegralJump( }; }); - // Find the segment with the highest integral jump (biggest bump in the integral) - // This is the segment that contributed most to the total integral - return withIntegral.reduce((max, current) => { - return current.integralJump > max.integralJump ? current : max; - }); + // Sort by integral jump (highest first) and return top N + withIntegral.sort((a, b) => b.integralJump - a.integralJump); + return withIntegral.slice(0, topN); } export async function downloadMostWatchedSegment(options: DownloadOptions): Promise { - const { url, outputDir, format } = options; + const { url, outputDir, format, topN } = options; // Create output directory if it doesn't exist if (!existsSync(outputDir)) { @@ -208,38 +208,47 @@ export async function downloadMostWatchedSegment(options: DownloadOptions): Prom } console.log(`\nHeatmap data found: ${info.heatmap.length} segments`); + console.log(`\nTop ${topN} segments by integral jump:\n`); - // Find segment with highest integral jump (biggest bump) - const topSegment = findHighestIntegralJump(info.heatmap); + // Get top segments + const topSegments = getTopSegmentsByIntegral(info.heatmap, topN); - if (!topSegment) { + if (topSegments.length === 0) { console.log("No valid segments found. Downloading full video..."); const outputPath = join(outputDir, `${safeTitle}.%(ext)s`); await downloadSegment(url, outputPath, 0, info.duration, format); return; } - console.log(`\nSegment with highest integral jump:`); - console.log(` Time: ${formatTime(topSegment.start)} - ${formatTime(topSegment.end)}`); - console.log(` Duration: ${formatTime(topSegment.end - topSegment.start)}`); - console.log(` Intensity: ${(topSegment.intensity * 100).toFixed(1)}%`); - console.log(` Integral Jump: ${topSegment.integralJump.toFixed(4)}`); + // Output the top segments + for (let i = 0; i < topSegments.length; i++) { + const seg = topSegments[i]; + const duration = seg.end - seg.start; + console.log(`${i + 1}. ${formatTime(seg.start)} - ${formatTime(seg.end)} | Duration: ${formatTime(duration)} | Integral: ${seg.integralJump.toFixed(4)}`); + } - // Download the segment + console.log(""); + + // Download the top segment + const topSegment = topSegments[0]; const outputPath = join(outputDir, `${safeTitle}_most_watched.%(ext)s`); - console.log(`\nDownloading segment...`); + console.log(`Downloading segment: ${formatTime(topSegment.start)} - ${formatTime(topSegment.end)}`); await downloadSegment(url, outputPath, topSegment.start, topSegment.end, format); // Save segment info - const segmentInfoPath = join(outputDir, `${safeTitle}_segment_info.txt`); - const segmentInfo = `# ${info.title}\n\n` + - `Segment with highest integral jump (biggest bump in heatmap):\n` + - ` Start: ${formatTime(topSegment.start)} (${topSegment.start.toFixed(1)}s)\n` + - ` End: ${formatTime(topSegment.end)} (${topSegment.end.toFixed(1)}s)\n` + - ` Duration: ${formatTime(topSegment.end - topSegment.start)}\n` + - ` Intensity: ${(topSegment.intensity * 100).toFixed(1)}%\n` + - ` Integral Jump: ${topSegment.integralJump.toFixed(4)}\n`; + const segmentInfoPath = join(outputDir, `${safeTitle}_top_segments.txt`); + let segmentInfo = `# ${info.title}\n\n`; + segmentInfo += `Top ${topN} segments by integral jump:\n\n`; + + for (let i = 0; i < topSegments.length; i++) { + const seg = topSegments[i]; + const duration = seg.end - seg.start; + segmentInfo += `${i + 1}. ${formatTime(seg.start)} - ${formatTime(seg.end)}\n`; + segmentInfo += ` Duration: ${formatTime(duration)}\n`; + segmentInfo += ` Integral: ${seg.integralJump.toFixed(4)}\n`; + segmentInfo += ` Intensity: ${(seg.intensity * 100).toFixed(1)}%\n\n`; + } writeFileSync(segmentInfoPath, segmentInfo); console.log(`\nSegment info saved to: ${segmentInfoPath}`); diff --git a/src/cli/index.ts b/src/cli/index.ts index 6567a65..b82acf9 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -12,20 +12,21 @@ async function main() { console.log("Options:"); console.log(" -o, --output Output directory (default: ./downloads)"); console.log(" -f, --format Video format (default: best)"); + console.log(" -n, --top Number of top segments (default: 10)"); console.log(" -h, --help Show help"); process.exit(1); } - console.log(`Downloading most watched segment from: ${args.url}`); + console.log(`Analyzing video: ${args.url}`); console.log(`Output directory: ${args.output}`); - console.log(`Format: ${args.format}`); - console.log(""); + console.log(`Top ${args.topN} segments by integral jump\n`); try { await downloadMostWatchedSegment({ url: args.url, outputDir: args.output, format: args.format, + topN: args.topN, }); } catch (error) { console.error("Error:", error instanceof Error ? error.message : error);