feat: add thumbnail extraction and timeline display

Wire ffmpeg frame extraction into post-download processing and render
adaptive thumbnail strips on the timeline via the asset protocol.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-09-21 11:16:10 -04:00
parent 63a89ed433
commit ab053edd14
10 changed files with 245 additions and 8 deletions

View File

@@ -1,7 +1,11 @@
import type { VideoMetadata } from '$lib/bindings/video';
import { startDownload } from '$lib/bindings/video';
import type { ThumbnailSpritesheet } from '$lib/bindings/mediaAnalysis';
import { extractKeyframes, extractWaveform } from '$lib/bindings/mediaAnalysis';
import {
extractKeyframes,
extractThumbnails,
extractWaveform,
} from '$lib/bindings/mediaAnalysis';
export type SessionStatus = 'idle' | 'resolving' | 'ready' | 'error';
export type DownloadStatus = 'idle' | 'downloading' | 'complete' | 'failed';
@@ -79,9 +83,11 @@ export function reset() {
}
async function triggerPostDownloadProcessing(filePath: string) {
const [keyframes, waveform] = await Promise.allSettled([
const duration = session.duration;
const [keyframes, waveform, thumbnails] = await Promise.allSettled([
extractKeyframes(filePath),
extractWaveform(filePath, 8000),
extractThumbnails(filePath, duration),
]);
if (keyframes.status === 'fulfilled') {
@@ -95,6 +101,12 @@ async function triggerPostDownloadProcessing(filePath: string) {
} else {
console.error('Waveform extraction failed:', waveform.reason);
}
if (thumbnails.status === 'fulfilled') {
session.thumbnailSpritesheets = thumbnails.value;
} else {
console.error('Thumbnail extraction failed:', thumbnails.reason);
}
}
export async function beginDownload() {
@@ -105,6 +117,7 @@ export async function beginDownload() {
session.localFilePath = null;
session.keyframePositions = [];
session.waveformPeaks = [];
session.thumbnailSpritesheets = [];
try {
await startDownload(