diff --git a/src/lib/stores/videoSession.svelte.ts b/src/lib/stores/videoSession.svelte.ts index e1c6fc2..8f48668 100644 --- a/src/lib/stores/videoSession.svelte.ts +++ b/src/lib/stores/videoSession.svelte.ts @@ -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); +}