Releases: kosm1x/vlmp
Release list
v0.1.9.9.1-r2 — content-addressed thumbnails: fast and correct
Second revision of v0.1.9.9.1 — thumbnails are now both fast and correct.
Fixed
- Thumbnail loading slowed everything down (r1 regression). r1 fixed lingering ghost thumbnails by making the client revalidate on every fetch — which meant one network round-trip per tile on every page load. Big grids crawled.
- Now: content-addressed thumbnails. Each thumbnail URL embeds the item's version (
?v=…), and the server marks those responses immutable, so after the first view your browser serves them from its cache with zero network requests — grids are instant again. Correctness rides in the URL instead of in asking the server: when an item is replaced (deleted category, rescan onto a recycled id), it arrives with a new version, a new URL, and therefore a fresh image. Ghosts from any earlier release are unreachable by construction.
Refreshing happens exactly when a page loads new data — never per render, never on a timer.
Scope
Client + one response-header branch; no data or schema changes. As with r1, the latest/0.1.9/0.1 container pointers stay on 0.1.9.9.1 — this revision publishes the immutable 0.1.9.9.1-r2 image tags, and the fix rides into the next regular release for pointer-pinned Docker users.
Windows
vlmp-setup-0.1.9.9.1-r2-win-x64.exe below; verify with Get-FileHash against the .sha256 asset.
v0.1.9.9.1-r1 — thumbnails: force revalidation on upgrade
Revision of v0.1.9.9.1 — one client-side fix.
Fixed
- Old thumbnails could still appear after upgrading. v0.1.9.9.1 fixed ghost thumbnails on the server, but browsers that had used an earlier version still held those images in their own HTTP cache under the old 24-hour policy — and a "fresh" cache entry is served without ever asking the server, so the fix couldn't reach them until each entry expired. The client now always revalidates thumbnails: stale pre-upgrade entries are refetched on the next page load, current ones cost a cheap 304. No action needed beyond loading the app once.
Scope
Client-only (one fetch-mode change). If your ghosts already disappeared, nothing changes for you. The latest, 0.1.9, and 0.1 container pointers intentionally stay on 0.1.9.9.1 — this revision publishes the immutable 0.1.9.9.1-r1 image tags only, and the fix rides along in the next regular release for pointer-pinned Docker users (whose pre-upgrade cache entries will have expired on their own by then).
Windows
vlmp-setup-0.1.9.9.1-r1-win-x64.exe below; verify with Get-FileHash against the .sha256 asset.
v0.1.9.9.1 — folder structure, silent scan drops, ghost thumbnails, view-triggered rescan
Three user-reported bugs fixed, one new feature, and a three-round adversarial audit of the whole change.
Fixed
- Category pages flattened your folder structure. Grouping was computed during the scan and then thrown away. Category pages now show one section per folder, with numbered files (
01 - …) in their intended sequence — for every "single titles" category, not just Education. Existing libraries heal on their next scan. - Files silently missing after a scan. Six causes closed: symlinked files and folders were invisible to the scanner (external symlinks are now followed — internal aliases are deliberately skipped so the real path stays canonical);
.m2ts/.mtsweren't scanned; the sample filter dropped (and even retro-deleted) sub-2-minute numbered lessons andSxxEyyepisodes;S00specials collided with season 1 and vanished; one unreadable file aborted the rest of the scan; skipped inserts still counted as "added". Every remaining skip now logs its reason. - Thumbnails from a deleted category appearing on new media. Thumbnails were keyed by database row id, which SQLite recycles after a delete. They're now keyed by the file's path — a recycled id cannot resolve to another file's image — old-format thumbnails are cleaned up at boot, and the browser revalidates instead of caching blindly for a day.
New
- View-triggered rescan. Opening a category quietly rescans exactly that category's folders in the background and updates the page when new files land. At most once per folder per cooldown window (
VLMP_AUTO_RESCAN_COOLDOWN_SECONDS, default 300,0disables); folders scan sequentially, never in a burst; hidden folders stay invisible to non-admin viewers on every new surface. The manual Scan button in Settings is unchanged.
Versioning note
First five-part release: the trailing .1 is a hotfix on the published 0.1.9.9 build. Five-part tags are immutable exactly like four-part ones; the moving image pointers remain 0.1.9, 0.1, and latest (all three move to this release). See CONTRIBUTING § Versioning.
Windows
vlmp-setup-0.1.9.9.1-win-x64.exe below (bundled Node 22 + NSSM service install). Verify with Get-FileHash against the .sha256 asset.
Audit
The entire change ran a three-round adversarial audit (two scoped reviewers, then a fix-the-fix round, then verdict verification). Five critical defects were caught before release — all introduced and fixed within this same change set, none shipped in any published version. Details in docs/AUDIT-2026-07-19.md.
v0.1.9.9 — consolidated release (Docker named volume, CI-hang fix, Windows installer hardening)
Consolidates everything shipped since v0.1.9.4 under one audited release, closing the v0.1.9.5–v0.1.9.8 rapid-fix arc.
⚠ Upgrading from v0.1.9.8 or earlier — read this first
Every previous release bind-mounted ./data. This release moves Docker storage to the named volume vlmp-data (it fixes the fresh-clone crash loop), but switching does not move your database — without migration the server starts healthy on empty storage. With the stack stopped:
docker compose down
if [ -f data/vlmp.db ]; then
docker run --rm -v "$PWD/data":/from -v vlmp-data:/to alpine \
sh -c 'cp -a /from/. /to/ && chown -R 1000:1000 /to'
else
echo "no ./data/vlmp.db here — nothing to migrate"
fi
docker compose up -dFull details in the README's Quick start.
What's consolidated since v0.1.9.4
- CI hang root-caused and fixed —
ChildProcess.killedguards signalled reaped, reassigned PIDs; every kill site now gates on real liveness (isProcessAlive). The intermittent exit-137 test kills are gone. - Startup failures exit non-zero — a boot error used to log and exit 0, which supervisors read as a clean stop.
- ffmpeg transcode pacing actually applies — both image stages moved to trixie (ffmpeg 7.1); pacing had been inert in every previous image.
- Security dependency train — fastify 5.10, @fastify/static 10, fast-uri 3.1.4, brace-expansion 5.0.8:
npm auditis clean (was 1 critical + 8 high). - Named-volume quick start — a fresh clone boots on the first
docker compose up, no root-owned./datacrash loop. - Subtitle extraction can no longer hang a scan — the last unbounded ffmpeg spawn is bounded (15 min) with a liveness-gated kill.
- Image pointer tags fixed —
0.1.9and0.1are now gated per-line, so a future hotfix on an older line still reaches users pinned to it (:latestremains globally gated). - Windows installer hardening — service scripts fail closed (
nssmcalls are all checked; no more "installed and started" over a dead service), the firewall step reports failure, and the published.sha256now works withsha256sum -c.
Windows
vlmp-setup-0.1.9.9-win-x64.exe below (SHA-256 in the adjacent .sha256, verifiable with sha256sum -c or Get-FileHash). See docs/WINDOWS.md.
Container image
docker pull ghcr.io/kosm1x/vlmp:0.1.9.9
Immutable pins: v0.1.9.9 / 0.1.9.9 · moving pointers updated: 0.1.9, 0.1, latest.
v0.1.9.8 — Docker quick start fixed, FFmpeg 7.1, security updates
Docker images and a Windows installer for VLMP 0.1.9.8.
Docker
docker pull ghcr.io/kosm1x/vlmp:0.1.9.8Multi-arch (linux/amd64 + linux/arm64), with SLSA provenance and an SBOM.
Windows
vlmp-setup-0.1.9.8-win-x64.exe is attached. SmartScreen warns on unsigned installers — "More info → Run anyway". Verify first:
Get-FileHash vlmp-setup-0.1.9.8-win-x64.exe -Algorithm SHA256
# c996f058bb2e166925ef32ce0f03f0100f6b0281951f261875dbd5a9a778d3b3Since v0.1.9.4
This release rolls up everything from 0.1.9.5 through 0.1.9.8 — the previous four tags published container images only.
Fixes that change behaviour
- The documented Docker quick start now works.
docker compose up -don a fresh clone used to crash-loop:data/is gitignored, so the bind mount target did not exist, so the daemon created it root-owned — and the container runs unprivileged, so it could not write to it. Data now lives in a named volume (vlmp-data), which inherits the image's ownership. See the README for backup/restore and how to opt back into a host folder. - Startup failures now exit non-zero. A boot error used to be logged and then exit 0, which Docker and systemd both read as a clean, intentional stop. Supervisors now see a failure.
- Transcode pacing actually applies.
-readrate_initial_burstneeds FFmpeg >= 6.1, but the runtime image shipped 5.1, so the pacing added in v0.1.4 was silently inactive in every published container. The image is now on Debian 13 (FFmpeg 7.1). - Session teardown no longer signals reaped processes.
ChildProcess.killedrecords that a signal was sent, not that a process is alive, so teardown could signal a PID the kernel had already reassigned to something unrelated.
Security
fastify5.10,@fastify/static10, plus transitivefast-uriandbrace-expansionupdates.npm auditreports 0 vulnerabilities, down from 10 (1 critical, 8 high).
Versioning
- The 4-part
MAJOR.MINOR.PATCH.BUILDscheme is now used verbatim everywhere — the git tag,package.json,/api/infoand the installer filename all read0.1.9.8. Previous builds encoded this as npm pre-release syntax (0.1.9-4), which is why the older installer asset is named differently.
v0.1.9.4 — Auto-transcode when a file can't play directly
A patch on v0.1.9.3.
Videos that "should" play but don't, now play automatically
Even with FFmpeg installed and working, a file can use a codec that VLMP thought the browser could play directly, but the browser actually can't decode (for example AV1 — browser support for it is inconsistent — or 8-bit 4:2:2/4:4:4 H.264). Before, that showed an error. Now the player automatically retries by transcoding the moment direct play fails, so the video just plays (using your GPU if hardware transcoding is on). No setting to change — it's automatic.
If transcoding itself can't run (FFmpeg missing/unreachable by the service), you'll get a clear message instead of a silent failure.
Also fixed
- A very low-resolution source (under 360p) could produce an empty, unplayable transcode — it now falls back to the lowest quality rung.
- A browser with no HLS support now shows a clear message instead of loading forever.
Audited R1+R2; the retry logic is loop-proof and verified live. 376 tests.
Version note: package metadata reads 0.1.9-4 (npm requires three-part semver); this is the v0.1.9.4 release.
SmartScreen warns on the unsigned installer — "More info → Run anyway". FFmpeg is not bundled (the installer offers a winget install).
v0.1.9.3 — Fix videos that wouldn't play (silent codec failure)
A patch on v0.1.9.2.
Videos that showed a blank player now play
If a library folder was scanned before FFmpeg was installed, its files were stored with no codec information. The server would then guess "this can play directly" and hand the raw file to the browser — and for formats the browser can't decode (HEVC / H.265, or 10-bit H.264), it would silently fail: a blank player, and just a "stream closed prematurely" line in the server log.
The server now checks a file's real codec the first time you play it (re-probing with FFmpeg), remembers the result, and correctly transcodes anything the browser can't play directly — so these videos play. If FFmpeg still isn't installed, the player now shows a clear message telling you to install it instead of failing silently.
After upgrading, just play the affected videos — they'll be re-checked on first play (make sure FFmpeg is installed on the server). No rescan needed.
Audited R1+R2. Verified live. 375 tests.
Version note: package metadata reads 0.1.9-3 (npm requires three-part semver); this is the v0.1.9.3 release.
SmartScreen warns on the unsigned installer — "More info → Run anyway". FFmpeg is not bundled (the installer offers a winget install).
v0.1.9.2 — Playback rate-limit precedence, version, category rename, scrollable nav
A patch on v0.1.9.1.
Playback no longer hits a rate limit
The server's rate limiter counted every request — including each HLS segment — so an active stream (or a busy household on one connection) could exhaust it and then get "Too many requests" errors, with playback stopping and new categories failing to load. Media delivery (streaming segments, direct play, thumbnails, subtitles) is now exempt from the limiter, so background work — a metadata fetch, browsing, other viewers — can never throttle what's playing. The limit that remains (for everything else) is raised and tunable via VLMP_RATE_LIMIT_MAX (default 600/min).
Version shown in Settings
The running version now appears at the bottom of the Settings page.
Rename categories
Category names are editable in Settings (Rename button). The URL slug stays fixed so existing links and folders keep working.
Scrollable category bar
The top navigation's category list now scrolls horizontally, so any number of categories stays reachable without crowding out the other sections or the search box.
Audited R1+R2 (PASS); the rate-limit exemption was scoped to delivery paths only (session creation stays limited) and verified with a live flood test. 366 tests.
Version note: package metadata reads 0.1.9-2 (npm requires three-part semver); this is the v0.1.9.2 release.
SmartScreen warns on the unsigned installer — "More info → Run anyway". FFmpeg is not bundled (the installer offers a winget install).
v0.1.9.1 — Incremental metadata, resilient + continuous playback
A patch on v0.1.9.
Metadata fetch only touches what's new
A metadata fetch used to re-walk your entire library every time. It now processes only the newly added and never-matched titles, so a re-run is quick. Titles TMDb can't match are remembered so they aren't re-queried each run — but a transient network/API error is never cached that way, so an outage can't permanently mark a title unmatchable. Send {"full": true} to force a complete re-fetch; the per-item "match" action always re-tries.
Playback recovers instead of erroring out
Because segments are transcoded on demand, a fragment can briefly be unavailable right after a seek while FFmpeg catches up. Previously the player would exhaust its retries and stop with a "Playback error". It now recovers a few times (resetting on real progress) before giving up, with a more generous retry budget.
Continuous playback
Start a series or a playlist and it plays through to the end, auto-advancing item to item until it finishes or you leave (Back exits the player rather than stepping back through the queue). A ⏭ Next button lets you skip manually. Playlists that contain the same title twice advance from the copy you actually played.
Audited R1+R2 (PASS). 361 tests. Verified on the compiled build.
Version note: package metadata reads 0.1.9-1 (npm requires three-part semver); this is the v0.1.9.1 release.
SmartScreen warns on the unsigned installer — "More info → Run anyway". FFmpeg is not bundled (the installer offers a winget install).
v0.1.9 — Series bundling, full-load + sortable category grids
Three browse improvements on top of v0.1.8.
Series no longer scatter across the grid
A series file whose filename has no parseable episode number (bare `1.mkv`, `E01`, a descriptive title, or a number that lives only in the `Season N` folder) used to be treated as an episode but never linked to its show — so it leaked into the flat grid as a loose card below the bundled series. Now every such file bundles under its show via a stable synthetic in-season number, and a genuinely numbered episode always keeps its rightful slot (a synthetic placeholder is evicted if it grabbed that number first).
After upgrading, rescan your library folders — the rescan is what re-links the previously-leaking episodes.
Whole category loads at once, cached
A category page now loads its entire library in one request (no more 60-at-a-time "Load more") and caches it for the session, so re-browsing is instant. The cache is per-user and clears on login/logout/scan.
Sort controls
Sort any category by Title (A–Z), Recently added, Random, or Liked first — a dropdown in the category header. Sorting is instant (client-side over the cached list) and your choice is remembered. Shows and movies sort interleaved (a series is one card wherever its title/date/like places it).
Audited R1+R2 (PASS); SQL, synthetic-number eviction, the schema migration, visibility gates, and a cross-user cache-scoping fix all verified. 355 tests. Verified live on the compiled build.
SmartScreen warns on the unsigned installer — "More info → Run anyway". FFmpeg is not bundled (the installer offers a winget install).