feat(refboard): server-generated video posters via ffmpeg

Upload pipeline now extracts poster frame (JPEG) and metadata
(width, height, duration) from videos at upload time using ffmpeg.

Server:
- Add ffmpeg to Docker image (Alpine)
- video-utils.js: probeVideo() and extractPoster() using ffprobe/ffmpeg
- Upload response includes poster_asset_key and duration

Frontend:
- VideoObject gains poster and duration fields in scene format
- VideoSprite accepts posterAssetKey + TextureManager, loads poster
  as a regular image texture on construction (no <video> needed)
- Server poster loaded/released via TextureManager ref counting
- addVideoFromUpload passes poster and duration through to scene data
- image-drop.ts forwards poster_asset_key from upload response

Result: videos with server posters render as images by default.
Zero <video> elements needed for thumbnails. Only explicit play
creates a media element.
This commit is contained in:
Hiren Kangad
2026-03-10 12:10:18 +05:30
parent 9ecb287976
commit 198497381f
7 changed files with 256 additions and 45 deletions
+7 -1
View File
@@ -271,7 +271,7 @@ export class SceneManager {
// Use cached native dimensions if available (avoids re-init just to discover size)
const vw = vidData.nativeW || vidData.w;
const vh = vidData.nativeH || vidData.h;
const videoSprite = new VideoSprite(vidData.asset, vw, vh, videoUrl);
const videoSprite = new VideoSprite(vidData.asset, vw, vh, videoUrl, vidData.poster, this.textures);
// Apply cached native dims to scene data so display matches
if (vidData.nativeW && vidData.nativeH) {
data.w = vw;
@@ -607,6 +607,8 @@ export class SceneManager {
h: number,
x: number,
y: number,
posterAssetKey?: string,
duration?: number,
): SceneItem {
const data: VideoObject = {
id: crypto.randomUUID(),
@@ -626,6 +628,10 @@ export class SceneManager {
asset: assetKey,
muted: true,
loop: true,
nativeW: w,
nativeH: h,
poster: posterAssetKey,
duration,
};
this._createItem(data, true);