From 8f4344e7635fed788b9ab4b42116cb8b63ff5c28 Mon Sep 17 00:00:00 2001 From: Kilo Code Cloud Date: Wed, 14 Jan 2026 19:54:54 +0000 Subject: [PATCH] feat: use raw intensity values to find most watched segment --- src/cli/args.ts | 22 ++++----- src/cli/downloader.ts | 110 +++++++++++++++--------------------------- src/cli/index.ts | 14 +++--- 3 files changed, 56 insertions(+), 90 deletions(-) diff --git a/src/cli/args.ts b/src/cli/args.ts index 095d399..3b5dd06 100644 --- a/src/cli/args.ts +++ b/src/cli/args.ts @@ -2,14 +2,14 @@ export interface CliArgs { url?: string; output: string; format: string; - peakThreshold: number; + skipStartSeconds: number; } export function parseArgs(): CliArgs { const args: CliArgs = { output: "./downloads", format: "best", - peakThreshold: 0.3, + skipStartSeconds: 30, }; const rawArgs = Bun.argv; @@ -24,11 +24,11 @@ export function parseArgs(): CliArgs { } else if (arg === "-f" || arg === "--format") { args.format = nextArg || "best"; i++; - } else if (arg === "-t" || arg === "--threshold") { - args.peakThreshold = parseFloat(nextArg || "0.3"); + } else if (arg === "-s" || arg === "--skip-start") { + args.skipStartSeconds = parseFloat(nextArg || "30"); i++; } else if (arg === "-h" || arg === "--help") { - console.log(`YouTube Peak Segment Downloader + console.log(`YouTube Most Watched Segment Downloader Usage: yt-segments [options] @@ -36,16 +36,16 @@ Arguments: YouTube video URL (required) Options: - -o, --output Output directory (default: ./downloads) - -f, --format Video format (default: best) - -t, --threshold Peak detection threshold 0.1-1.0 (default: 0.3) - Lower = more segments detected as peaks - -h, --help Show this help message + -o, --output Output directory (default: ./downloads) + -f, --format Video format (default: best) + -s, --skip-start Skip first N seconds (default: 30) + Higher values skip more of the intro + -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://www.youtube.com/watch?v=abc123" -t 0.5 + yt-segments "https://www.youtube.com/watch?v=abc123" -s 60 `); process.exit(0); } else if (!arg.startsWith("-") && !arg.includes("bun")) { diff --git a/src/cli/downloader.ts b/src/cli/downloader.ts index c151561..abc5d12 100644 --- a/src/cli/downloader.ts +++ b/src/cli/downloader.ts @@ -6,7 +6,7 @@ export interface DownloadOptions { url: string; outputDir: string; format: string; - peakThreshold: number; + skipStartSeconds: number; } interface RawHeatmapSegment { @@ -23,7 +23,6 @@ interface ProcessedSegment { start: number; end: number; intensity: number; - peakScore: number; } interface VideoInfo { @@ -143,61 +142,41 @@ function getIntensity(segment: RawHeatmapSegment): number { return segment.intensity ?? segment.heat ?? segment.value ?? 0; } -function findPeakSegments( +function findHighestIntensitySegment( segments: RawHeatmapSegment[], - threshold: number = 0.3 -): ProcessedSegment[] { - if (segments.length < 3) { - return []; - } - - // Convert to processed format - const processed = segments + skipStartSeconds: number +): ProcessedSegment | null { + // Convert to processed format and filter valid segments + const validSegments = segments .map(seg => ({ start: getStartTime(seg), end: getEndTime(seg), intensity: getIntensity(seg), - peakScore: 0, })) .filter(seg => Number.isFinite(seg.start) && Number.isFinite(seg.end) && - Number.isFinite(seg.intensity) + Number.isFinite(seg.intensity) && + seg.start >= 0 && + seg.end > seg.start ); - if (processed.length < 3) { - return []; + if (validSegments.length === 0) { + return null; } - // Calculate peak score for each segment - // A peak is where intensity is significantly higher than neighbors - const scored = processed.map((seg, i) => { - const prevIntensity = i > 0 ? processed[i - 1].intensity : seg.intensity; - const nextIntensity = i < processed.length - 1 ? processed[i + 1].intensity : seg.intensity; - - // Peak score = how much higher this segment is compared to average of neighbors - const avgNeighborIntensity = (prevIntensity + nextIntensity) / 2; - const peakScore = avgNeighborIntensity > 0 - ? (seg.intensity - avgNeighborIntensity) / avgNeighborIntensity - : 0; - - return { - ...seg, - peakScore, - }; - }); + // Sort by intensity (highest first) + validSegments.sort((a, b) => b.intensity - a.intensity); - // Filter segments that are true peaks (higher than neighbors) - const peaks = scored.filter(seg => seg.peakScore > threshold); + // Find the highest intensity segment that starts after skipStartSeconds + const candidate = validSegments.find(seg => seg.start >= skipStartSeconds); - // Sort by peak score (highest peaks first) - peaks.sort((a, b) => b.peakScore - a.peakScore); - - return peaks; + // If all segments are in the skipped region, return the highest one anyway + return candidate || validSegments[0]; } export async function downloadMostWatchedSegment(options: DownloadOptions): Promise { - const { url, outputDir, format, peakThreshold } = options; + const { url, outputDir, format, skipStartSeconds } = options; // Create output directory if it doesn't exist if (!existsSync(outputDir)) { @@ -224,49 +203,36 @@ export async function downloadMostWatchedSegment(options: DownloadOptions): Prom console.log(`\nHeatmap data found: ${info.heatmap.length} segments`); - // Find peak segments (segments that stand out from their neighbors) - const peakSegments = findPeakSegments(info.heatmap, peakThreshold); + // Find the highest intensity segment (using primitive/intensity directly) + const topSegment = findHighestIntensitySegment(info.heatmap, skipStartSeconds); - if (peakSegments.length === 0) { - console.log("No significant peak segments found."); - console.log("Downloading full video..."); + if (!topSegment) { + 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; } - // Get the top peak segment - const topPeak = peakSegments[0]; + console.log(`\nHighest intensity segment:`); + 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(`\nTop peak segment:`); - console.log(` Time: ${formatTime(topPeak.start)} - ${formatTime(topPeak.end)}`); - console.log(` Duration: ${formatTime(topPeak.end - topPeak.start)}`); - console.log(` Peak Score: ${(topPeak.peakScore * 100).toFixed(1)}%`); - console.log(` Base Intensity: ${(topPeak.intensity * 100).toFixed(1)}%`); + // Download the segment + const outputPath = join(outputDir, `${safeTitle}_most_watched.%(ext)s`); - // Download the peak segment - const outputPath = join(outputDir, `${safeTitle}_peak.%(ext)s`); - - console.log(`\nDownloading peak segment...`); - await downloadSegment(url, outputPath, topPeak.start, topPeak.end, format); + console.log(`\nDownloading segment...`); + await downloadSegment(url, outputPath, topSegment.start, topSegment.end, format); // Save segment info - const segmentInfoPath = join(outputDir, `${safeTitle}_peak_info.txt`); - let segmentInfo = `# ${info.title}\n\n`; - segmentInfo += `Peak segment (stands out from surrounding content):\n`; - segmentInfo += ` Start: ${formatTime(topPeak.start)} (${topPeak.start.toFixed(1)}s)\n`; - segmentInfo += ` End: ${formatTime(topPeak.end)} (${topPeak.end.toFixed(1)}s)\n`; - segmentInfo += ` Duration: ${formatTime(topPeak.end - topPeak.start)}\n`; - segmentInfo += ` Peak Score: ${(topPeak.peakScore * 100).toFixed(1)}%\n`; - segmentInfo += ` Intensity: ${(topPeak.intensity * 100).toFixed(1)}%\n\n`; - - if (peakSegments.length > 1) { - segmentInfo += `Other peaks:\n`; - for (let i = 1; i < Math.min(peakSegments.length, 5); i++) { - const seg = peakSegments[i]; - segmentInfo += ` ${formatTime(seg.start)} - ${formatTime(seg.end)} (score: ${(seg.peakScore * 100).toFixed(1)}%)\n`; - } - } + const segmentInfoPath = join(outputDir, `${safeTitle}_segment_info.txt`); + const segmentInfo = `# ${info.title}\n\n` + + `Most watched segment (highest intensity from YouTube 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\n` + + `Note: This segment had the highest watch intensity (excluding first ${skipStartSeconds}s).\n`; writeFileSync(segmentInfoPath, segmentInfo); console.log(`\nSegment info saved to: ${segmentInfoPath}`); diff --git a/src/cli/index.ts b/src/cli/index.ts index 1d93903..97a216e 100644 --- a/src/cli/index.ts +++ b/src/cli/index.ts @@ -10,17 +10,17 @@ async function main() { console.error("Error: YouTube URL is required"); console.log("Usage: yt-segments [options]"); console.log("Options:"); - console.log(" -o, --output Output directory (default: ./downloads)"); - console.log(" -f, --format Video format (default: best)"); - console.log(" -t, --threshold Peak detection threshold (default: 0.3)"); - console.log(" -h, --help Show help"); + console.log(" -o, --output Output directory (default: ./downloads)"); + console.log(" -f, --format Video format (default: best)"); + console.log(" -s, --skip-start Skip first N seconds (default: 30)"); + console.log(" -h, --help Show help"); process.exit(1); } - console.log(`Downloading peak segment from: ${args.url}`); + console.log(`Downloading most watched segment from: ${args.url}`); console.log(`Output directory: ${args.output}`); console.log(`Format: ${args.format}`); - console.log(`Peak threshold: ${args.peakThreshold}`); + console.log(`Skip first ${args.skipStartSeconds} seconds`); console.log(""); try { @@ -28,7 +28,7 @@ async function main() { url: args.url, outputDir: args.output, format: args.format, - peakThreshold: args.peakThreshold, + skipStartSeconds: args.skipStartSeconds, }); } catch (error) { console.error("Error:", error instanceof Error ? error.message : error);