feat: integrate analysis cache into video session processing

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-22 14:45:37 -04:00
parent b7a5caf4ae
commit b180d4324c

View File

@@ -10,6 +10,12 @@ import {
} from '$lib/bindings/mediaAnalysis';
import { clearThumbnailCache } from '$lib/timeline/thumbnailRenderer';
import { clearAll as clearAllClips } from '$lib/stores/clips.svelte';
import {
loadCachedAnalysis,
saveAnalysisToCache,
getThumbnailCacheDir,
clearVideoCache,
} from '$lib/bindings/cache';
export type SessionStatus = 'idle' | 'resolving' | 'ready' | 'error';
export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed';
@@ -159,7 +165,7 @@ export function dismissUpgradeToast() {
session.showUpgradeToast = false;
}
async function loadCaptions(filePath: string) {
async function loadCaptionsWithCache(filePath: string) {
session.processingStep = 'captions';
// Priority 1: Check for embedded subtitle streams in the file
@@ -187,10 +193,34 @@ async function loadCaptions(filePath: string) {
async function triggerPostDownloadProcessing(filePath: string) {
const duration = session.duration;
// Run processing SEQUENTIALLY to avoid overloading the CPU with
// concurrent ffmpeg subprocesses. Each one decodes the full file.
// Order: waveform first (most useful for editing), then keyframes, then thumbnails.
// Check cache first
try {
const cached = await loadCachedAnalysis(session.url);
if (cached) {
console.log('[triggerPostDownloadProcessing] cache hit for', session.url);
if (cached.waveformTiers) {
session.waveformTiers = cached.waveformTiers;
}
if (cached.keyframePositions.length > 0) {
session.keyframePositions = cached.keyframePositions;
}
if (cached.thumbnailSpritesheets.length > 0) {
session.thumbnailSpritesheets = cached.thumbnailSpritesheets;
}
if (cached.captionFilePath) {
session.captionFilePath = cached.captionFilePath;
}
session.processingStep = 'done';
if (session.exportStatus === 'complete' && session.exportFilePath) {
session.showUpgradeToast = true;
}
return;
}
} catch (err) {
console.error('Cache load failed, proceeding with extraction:', err);
}
// Cache miss — run extraction sequentially
session.processingStep = 'waveform';
session.processingProgress = 0;
try {
@@ -214,18 +244,34 @@ async function triggerPostDownloadProcessing(filePath: string) {
session.processingStep = 'thumbnails';
session.processingProgress = 0;
try {
const sheets = await extractThumbnails(filePath, duration);
const thumbDir = await getThumbnailCacheDir(session.url);
const sheets = await extractThumbnails(filePath, duration, thumbDir);
session.thumbnailSpritesheets = sheets;
} catch (err) {
console.error('Thumbnail extraction failed:', err);
}
// Captions can run after analysis since they use yt-dlp, not ffmpeg
await loadCaptions(filePath);
// Captions — use cache dir for output
await loadCaptionsWithCache(filePath);
session.processingStep = 'done';
// If export download already completed while we were processing, show upgrade toast
// Save to cache
try {
await saveAnalysisToCache(
session.url,
session.title,
session.duration,
session.fps,
session.waveformTiers,
session.keyframePositions,
session.thumbnailSpritesheets,
session.captionFilePath
);
} catch (err) {
console.error('Failed to save analysis to cache:', err);
}
if (session.exportStatus === 'complete' && session.exportFilePath) {
session.showUpgradeToast = true;
}
@@ -353,3 +399,26 @@ export async function beginDownload() {
console.error('Export download failed:', e);
});
}
/** Clear cache for the current video and re-extract all analysis data. */
export async function reprocessCurrentVideo() {
if (!session.url || !session.activeVideoPath) return;
const filePath = session.activeVideoPath;
// Clear cache
try {
await clearVideoCache(session.url);
} catch (err) {
console.error('Failed to clear video cache:', err);
}
// Reset analysis state
clearThumbnailCache();
session.keyframePositions = [];
session.waveformTiers = null;
session.thumbnailSpritesheets = [];
session.captionFilePath = null;
// Re-run processing (will miss cache and extract fresh)
await triggerPostDownloadProcessing(filePath);
}