feat: integrate analysis cache into video session processing
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -10,6 +10,12 @@ import {
|
|||||||
} from '$lib/bindings/mediaAnalysis';
|
} from '$lib/bindings/mediaAnalysis';
|
||||||
import { clearThumbnailCache } from '$lib/timeline/thumbnailRenderer';
|
import { clearThumbnailCache } from '$lib/timeline/thumbnailRenderer';
|
||||||
import { clearAll as clearAllClips } from '$lib/stores/clips.svelte';
|
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 SessionStatus = 'idle' | 'resolving' | 'ready' | 'error';
|
||||||
export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed';
|
export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed';
|
||||||
@@ -159,7 +165,7 @@ export function dismissUpgradeToast() {
|
|||||||
session.showUpgradeToast = false;
|
session.showUpgradeToast = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadCaptions(filePath: string) {
|
async function loadCaptionsWithCache(filePath: string) {
|
||||||
session.processingStep = 'captions';
|
session.processingStep = 'captions';
|
||||||
|
|
||||||
// Priority 1: Check for embedded subtitle streams in the file
|
// Priority 1: Check for embedded subtitle streams in the file
|
||||||
@@ -187,10 +193,34 @@ async function loadCaptions(filePath: string) {
|
|||||||
async function triggerPostDownloadProcessing(filePath: string) {
|
async function triggerPostDownloadProcessing(filePath: string) {
|
||||||
const duration = session.duration;
|
const duration = session.duration;
|
||||||
|
|
||||||
// Run processing SEQUENTIALLY to avoid overloading the CPU with
|
// Check cache first
|
||||||
// concurrent ffmpeg subprocesses. Each one decodes the full file.
|
try {
|
||||||
// Order: waveform first (most useful for editing), then keyframes, then thumbnails.
|
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.processingStep = 'waveform';
|
||||||
session.processingProgress = 0;
|
session.processingProgress = 0;
|
||||||
try {
|
try {
|
||||||
@@ -214,18 +244,34 @@ async function triggerPostDownloadProcessing(filePath: string) {
|
|||||||
session.processingStep = 'thumbnails';
|
session.processingStep = 'thumbnails';
|
||||||
session.processingProgress = 0;
|
session.processingProgress = 0;
|
||||||
try {
|
try {
|
||||||
const sheets = await extractThumbnails(filePath, duration);
|
const thumbDir = await getThumbnailCacheDir(session.url);
|
||||||
|
const sheets = await extractThumbnails(filePath, duration, thumbDir);
|
||||||
session.thumbnailSpritesheets = sheets;
|
session.thumbnailSpritesheets = sheets;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Thumbnail extraction failed:', err);
|
console.error('Thumbnail extraction failed:', err);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Captions can run after analysis since they use yt-dlp, not ffmpeg
|
// Captions — use cache dir for output
|
||||||
await loadCaptions(filePath);
|
await loadCaptionsWithCache(filePath);
|
||||||
|
|
||||||
session.processingStep = 'done';
|
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) {
|
if (session.exportStatus === 'complete' && session.exportFilePath) {
|
||||||
session.showUpgradeToast = true;
|
session.showUpgradeToast = true;
|
||||||
}
|
}
|
||||||
@@ -353,3 +399,26 @@ export async function beginDownload() {
|
|||||||
console.error('Export download failed:', e);
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user