diff --git a/src/cli/downloader.ts b/src/cli/downloader.ts index abc5d12..983d865 100644 --- a/src/cli/downloader.ts +++ b/src/cli/downloader.ts @@ -23,6 +23,7 @@ interface ProcessedSegment { start: number; end: number; intensity: number; + integral: number; } interface VideoInfo { @@ -142,7 +143,7 @@ function getIntensity(segment: RawHeatmapSegment): number { return segment.intensity ?? segment.heat ?? segment.value ?? 0; } -function findHighestIntensitySegment( +function findSegmentByIntegral( segments: RawHeatmapSegment[], skipStartSeconds: number ): ProcessedSegment | null { @@ -165,14 +166,28 @@ function findHighestIntensitySegment( return null; } - // Sort by intensity (highest first) - validSegments.sort((a, b) => b.intensity - a.intensity); + // Calculate the primitive (integral) for each segment + // The integral represents cumulative watch time contribution + let cumulativeIntegral = 0; + const withIntegral = validSegments.map(seg => { + const segmentDuration = seg.end - seg.start; + const segmentIntegral = seg.intensity * segmentDuration; + cumulativeIntegral += segmentIntegral; + return { + ...seg, + integral: cumulativeIntegral, + }; + }); - // Find the highest intensity segment that starts after skipStartSeconds - const candidate = validSegments.find(seg => seg.start >= skipStartSeconds); + // Sort by integral value (highest contribution first) + // The integral tells us which segment contributed most to total watch time + withIntegral.sort((a, b) => b.integral - a.integral); + + // Find the highest integral segment that starts after skipStartSeconds + const candidate = withIntegral.find(seg => seg.start >= skipStartSeconds); // If all segments are in the skipped region, return the highest one anyway - return candidate || validSegments[0]; + return candidate || withIntegral[0]; } export async function downloadMostWatchedSegment(options: DownloadOptions): Promise { @@ -203,8 +218,8 @@ export async function downloadMostWatchedSegment(options: DownloadOptions): Prom console.log(`\nHeatmap data found: ${info.heatmap.length} segments`); - // Find the highest intensity segment (using primitive/intensity directly) - const topSegment = findHighestIntensitySegment(info.heatmap, skipStartSeconds); + // Find the segment with highest integral (using primitive function) + const topSegment = findSegmentByIntegral(info.heatmap, skipStartSeconds); if (!topSegment) { console.log("No valid segments found. Downloading full video..."); @@ -213,10 +228,11 @@ export async function downloadMostWatchedSegment(options: DownloadOptions): Prom return; } - console.log(`\nHighest intensity segment:`); + console.log(`\nSegment with highest integral (primitive):`); 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: ${topSegment.integral.toFixed(4)}`); // Download the segment const outputPath = join(outputDir, `${safeTitle}_most_watched.%(ext)s`); @@ -227,12 +243,13 @@ export async function downloadMostWatchedSegment(options: DownloadOptions): Prom // Save segment info const segmentInfoPath = join(outputDir, `${safeTitle}_segment_info.txt`); const segmentInfo = `# ${info.title}\n\n` + - `Most watched segment (highest intensity from YouTube heatmap):\n` + + `Most watched segment (highest integral 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`; + ` Intensity: ${(topSegment.intensity * 100).toFixed(1)}%\n` + + ` Integral: ${topSegment.integral.toFixed(4)}\n\n` + + `Note: This segment had the highest integral value (cumulative watch contribution).\n`; writeFileSync(segmentInfoPath, segmentInfo); console.log(`\nSegment info saved to: ${segmentInfoPath}`);