feat(thumbs): video thumbnails, .mkv/.mov by default (v0.13.0) - #12
Conversation
Video files rendered a generic clapperboard icon in the grid: the whole thumbnail pipeline β WebP cache, mtime keying, eviction, prune β was already container-agnostic, only the decoder was image-only. Grab a frame with PyAV (a ComfyUI core dependency, already used by metadata.py) and feed it to the existing encode path. Seek to the midpoint rather than frame 0, which on a clip that fades in from black is a featureless rectangle. The seek is guarded: an unseekable container settles for frame 0 instead of losing its thumbnail. Also promote .mkv/.mov to video by default. VideoHelperSuite writes both and metadata.py already reads workflows out of them, but they showed as anonymous files with no thumbnail and no preview unless files.video_extensions was written by hand. Thumbnails decode server side so they always work; inline playback depends on the browser, so the preview pane now falls back to a "download it to view" message instead of a silent blank player. Fixes the README's roots[] row while in there: it advertised an auto-mount default that only applies when no config.json exists at all.
There was a problem hiding this comment.
Pull request overview
This PR adds first-class video thumbnails to FileManaty by decoding a representative mid-clip frame via PyAV while reusing the existing WebP thumbnail cache and HTTP thumbnail route. It also expands default video extension handling (notably .mkv/.mov) and improves the preview UX when a browser cannot decode a container.
Changes:
- Add PyAV-backed video frame extraction to the thumbnail generator and plumb a
videoflag through the thumbnail API route. - Treat
.mkvand.movas video by default (config defaults + example config + tests). - Update the frontend to request thumbnails for video entries and show a clear fallback message when inline playback fails; refresh docs/changelog/version accordingly.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| web/filemanaty.js | Requests thumbnails for video tiles and adds a playback-error fallback message in the preview pane. |
| filemanaty/thumbs.py | Implements video-frame decoding via PyAV and routes it through the existing WebP thumbnail pipeline. |
| filemanaty/api.py | Extends the thumbnail endpoint to allow video extensions and invoke the decoder with video=True. |
| filemanaty/config.py | Expands default video extensions to include .mkv and .mov. |
| tests/test_thumbs.py | Adds unit coverage for midpoint seek, unseekable fallback, and missing-PyAV behavior. |
| tests/test_api.py | Verifies video extensions pass the route gate and reach the decoder with video=True. |
| tests/test_config.py | Updates expectations for default video extensions after config fallback. |
| README.md | Documents video thumbnails and clarifies roots[] behavior once config.json exists. |
| config.example.json | Updates example config to include .mkv/.mov in video_extensions. |
| CHANGELOG.md | Adds v0.13.0 release notes for video thumbnails, defaults, and preview fallback message. |
| filemanaty/init.py | Bumps package version to 0.13.0. |
π‘ Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
β¦essage From Copilot review on #12: - `generate_thumbnail` reported every failure as "cannot read image". PyAV raises OSError subclasses, so a bad .mp4 logged as an image problem and sent log readers hunting for a corrupt PNG. - The preview fallback claimed "can't play this format", but the media error event also fires on network and decode failures. Reworded to cover any failure β the advice (download it) holds either way. - Dropped the `ponytail:` marker on VIDEO_FRAME_POSITION; that prefix is a convention from my tooling, not this repo's, and appears nowhere else here. Same content, no jargon.
|
Addressed in 6aa612b.
Player error message β valid, fixed. The
Null-check on |
What
Video files got a generic π¬ icon in the grid. They now get a real thumbnail.
The thumbnail pipeline β WebP cache, mtime keying, eviction, prune, the HTTP route, the icon fallback β was already container-agnostic. Only the decoder was image-only, so this swaps in a PyAV frame grab and reuses everything downstream.
VIDEO_FRAME_POSITION = 0.5, a module constant rather than a config key β it would have to join the cache-path filename to be tunable, and nobody tunes this.metadata.pyalready uses it. Installs without it degrade to the old icon via the existingonerror..mkv/.movare video by default now. VideoHelperSuite writes both, andmetadata.pyalready read workflows out of them, but they showed as anonymous π files with no thumbnail and no preview unlessfiles.video_extensionswas hand-written.Verification
Unit tests fake
avthe waytests/test_metadata.pydoes, so the suite stays PyAV-free: midpoint seek offset, unseekable fallback, missing-PyAV, and a route test that the extension gate reaches the decoder withvideo=True.Fakes can lie, so this was also run end to end against the real thing β ComfyUI in Docker with PyAV 18.0.0, driving the actual UI:
.movcarrying three extra data streams alongside video/audio: decodes fine in 80 ms;streams.video[0]picks the right one..mp4/.mkv/.movso the container was the only variable, and loaded all three through/previewin Chromium: all play.canPlayType("video/quicktime")returns""yet the file plays β Chromium sniffs the container.pytest: 297 passed, 1 skipped (pre-existing, platform-specific).node --test: 3/3.Also
README.md'sroots[]row claimed the field was optional with an auto-mount default. Auto-mounting only applies when there is noconfig.jsonβ a config file that omitsrootslogs a warning and leaves the UI empty (config.py:100). Hit firsthand while testing.Not included
A βΆ badge on video tiles. They're now visually identical to image tiles; only the filename distinguishes them.