feat: client-side video thumbnail generation (no ffmpeg)
Backend:
- get_thumbnail: for non-images, serve cached thumb or 204 (not 400)
- store_external_thumbnail: validate + re-encode to WebP + persist
- get_cached_thumbnail: memory → disk lookup without generation
- PUT /api/files/{id}/thumbnail/{size} endpoint for client uploads
- ThumbnailPort trait: add get_cached_thumbnail + store_external_thumbnail
Frontend (photos.js):
- On <img> error for video tiles, use <video> + <canvas> to extract
a frame at 25% duration using browser's native codec
- Show frame immediately via blob URL
- Fire-and-forget PUT to server for permanent caching
- Subsequent visits serve cached WebP instantly (no re-extraction)
Zero server-side dependencies — CPU distributed across clients.
This commit is contained in:
@@ -145,7 +145,7 @@ pub fn create_api_routes(app_state: &Arc<AppState>) -> Router<Arc<AppState>> {
|
||||
.route("/", get(FileHandler::list_files_query))
|
||||
.route("/upload", post(FileHandler::upload_file_with_thumbnails))
|
||||
.route("/{id}", get(FileHandler::download_file))
|
||||
.route("/{id}/thumbnail/{size}", get(FileHandler::get_thumbnail))
|
||||
.route("/{id}/thumbnail/{size}", get(FileHandler::get_thumbnail).put(FileHandler::upload_thumbnail))
|
||||
.route("/{id}/metadata", get(FileHandler::get_file_metadata))
|
||||
.layer(DefaultBodyLimit::max(10 * 1024 * 1024 * 1024)) // 10 GB for file uploads
|
||||
.with_state(app_state.clone());
|
||||
|
||||
Reference in New Issue
Block a user