v1.3.0 — decord → PyAV+TOC swap
Video-reader backend swap: drop the decord runtime dependency in favour of an in-tree PyAV+TOC reader. Public API is preserved — all existing call sites (vr[i].asnumpy(), slicing, vr.get_batch([...]).asnumpy(), len(vr), iteration, vr.get_avg_fps(), vr.get_frame_timestamp([...]), Video(VideoReader) subclass) continue to work unchanged.
The swap is motivated by the 2026-05-16 frame-parity test: decord disagrees with the ffmpeg-CLI oracle on 8/28 frames of a production VFR telemed clip (PSNR 32–37 dB on the divergent frames); PyAV+TOC matches the oracle pixel-exact on all 28 frames across 11 test clips and every codec exercised (libx264 / libx264+B / h264_nvenc ±B / hevc_nvenc / paired CPU↔GPU). The swap is therefore not just a dependency cleanup — it is a correctness improvement on real lab videos.
Added
VideoReaderandcpure-exported at thedatanavigatorpackage root (from datanavigator import VideoReader, cpu). Decord-compatible API surface —VideoReader(uri, ctx, width, height, num_threads, fault_tol)constructor;vr[i]/ slice /get_batch([...])returning objects with.asnumpy();len(), iteration,get_avg_fps().cpu(0)is a no-op sentinel for call-site parity (PyAV is CPU-only).datanavigator/_vendor/pims_pyav_reader.py—PyAVReaderIndexedclass vendored from PIMS (BSD-3-Clause, upstream commitd599596, 2023-01-24). Full license reproduced indatanavigator/_vendor/LICENSE-PIMS. PIMS itself is not a runtime dependency.- TOC sidecar cache (schema v2). PyAV's TOC build cost is paid once per video and cached as
<video>.dnav-toc(JSON content; the.jsonextension is intentionally omitted so*.jsonwalkers in downstream tooling don't pick the sidecar up). Keyed on path + size + mtime + SHA-256 of the first/last 64 KiB. Cache miss printsdatanavigator: building TOC for <name>...and saves a sidecar; cache hit is silent and sub-second. v2 also records per-frame pts / duration / time_base soget_frame_timestampis a cache hit on second open.datanavigator.precompute_toc(paths, force=False)batch-warms the cache for a sequence of videos before an interactive session. VideoReader.get_frame_timestamp(indices)— decord-compatible per-frame timestamp lookup. Returns an(N, 2)float64ndarray of[start, end]times in seconds. Backed by per-frameframe.pts(display-order, strictly more accurate thanpacket.pts * time_baseon B-frame streams).- Unit-test coverage at
tests/test_video_reader.pyfor the shim and the sidecar flow.
Changed
- Video-reading backend swapped from
decordto PyAV+TOC. Public API preserved. Internal imports inutils.py,videos.py, andopticalflow.pyroute through the in-tree shim. - CI matrix re-adds
macos-latest.continue-on-errorremoved from Windows and macOS runners — test failures now block on all three OSes.
Fixed
Video.grayandopticalflow.lucas_kanadegrayscale conversion channel order. Both previously calledcv.COLOR_BGR2GRAYon RGB-decoded frames, silently swapping R and B before the grayscale weighting. Now usecv.COLOR_RGB2GRAY. Observable behavior change for any caller depending on the buggy luminance.
Removed
decordruntime dependency (replaced by in-tree PyAV+TOC reader).numpy<2pin inpyproject.tomlandrequirements.yml(held bydecord+tables<3.10; both gates are now gone).- Implicit
tables<3.10upper bound: dep is nowtables>=3.10.
Infrastructure
pyproject.toml:decord→av;numpy<2→numpy;tables→tables>=3.10.requirements.yml: mirrored..github/workflows/ci.yml:macos-latestre-added;continue-on-errordropped on all runners; brew ffmpeg step added for macOS.
📦 PyPI 1.3.0 · 📋 CHANGELOG