Skip to main content

Release and Benchmark Tests License PyPI Version PyPI - Downloads Python Versions Discord

NeLux

NeLux is a high-performance Python library for video processing, leveraging the power of FFmpeg with hardware acceleration (NVDEC/NVENC). It delivers some of the fastest decode times globally, enabling efficient video decoding directly into ML-ready PyTorch tensors.

Originall created by Trentonom0r3


Installation

pip install nelux

Supported platforms:

Platform Backends Notes
Windows x64 CPU + CUDA (NVDEC/NVENC) Requires FFmpeg DLLs on PATH (or pass to os.add_dll_directory).
Linux x86_64 (manylinux_2_28+) CPU + CUDA (NVDEC/NVENC) Install FFmpeg via apt install ffmpeg libavcodec62 libavformat62 libavutil60 libswscale9 libavfilter11 libavdevice62.
macOS arm64 (Apple Silicon, ≥ 12.0) CPU / MPS (via PyTorch) Install FFmpeg via brew install ffmpeg. No CUDA on macOS.

PyTorch must be importable before nelux — the package uses torch's C++ runtime. For CUDA builds, install the matching CUDA torch wheel:

# Linux CUDA
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu132

# macOS / Linux CPU
pip install torch torchvision

Quick Start

Basic Usage

import torch  # must be imported before nelux
from nelux import VideoReader

# Open video with hardware acceleration (CPU path also supported)
reader = VideoReader("input.mp4", decode_accelerator="nvdec")

# Iterate frames — HWC uint8 by default (matches torchcodec convention)
for frame in reader:
    print(frame.shape)   # torch.Size([1080, 1920, 3]) — HWC
    print(frame.dtype)   # torch.uint8 for 8-bit sources; torch.int16 for >8-bit
                         # (override with force_8bit=True to always return uint8)

    # Permute to BCHW + cast to float when feeding to an ML model
    chw = frame.permute(2, 0, 1).unsqueeze(0).to(torch.float32) / 255.0
    output = model(chw)

Batch Frame Reading

import torch
from nelux import VideoReader

vr = VideoReader("video.mp4")

# Get specific frames — returned tensor is [B, H, W, 3] HWC uint8
batch = vr.get_batch([0, 10, 20])           # [3, H, W, 3]
batch = vr.get_batch_range(0, 100, 10)      # [10, H, W, 3]

# Pythonic slice / list notation (delegates to get_batch under the hood)
batch = vr[0:100:10]                        # [10, H, W, 3]
batch = vr[[-3, -2, -1]]                    # Last 3 frames (negative indexing OK)
single = vr[42]                             # Single frame [H, W, 3]

# Properties
print(len(vr))                              # Total frame count
print(vr.shape)                             # (frames, H, W, 3)

Motion Vectors

NeLux exposes the per-frame motion-vector side-data that FFmpeg's CPU decoders emit for inter-coded frames (P/B-frames). This is the raw macroblock / block-vector field the decoder used for prediction — useful for optical-flow pretraining, frame interpolation, video super-resolution, scene-cut detection, and codec-level diagnostics.

CPU decode only. decode_accelerator="nvdec" does not surface side-data, so both methods below require decode_accelerator="cpu". NVDEC/CUVID strips motion-vector export in exchange for the GPU throughput shown in the benchmarks above.

Motion vectors decoded from a P-frame of an H.264 encode of the open movie Big Buck Bunny (© Blender Foundation, CC BY 3.0) — left: source frame, right: nelux-drawn motion-vector field

Preview generated by examples/motion_vector_overlay.py on a 640x360 H.264 clip — left is the raw frame, right overlays every 4th motion vector as a red arrow from the destination block toward its motion-compensated source.

import torch  # must be imported before nelux
from nelux import VideoReader

vr = VideoReader("video.mp4", decode_accelerator="cpu")

# Frame + vectors together — vectors is a list[dict] (one entry per block).
frame, vectors = vr.read_frame_with_motion_vectors()
for mv in vectors:
    print(mv["src_x"], mv["src_y"], "->", mv["dst_x"], mv["dst_y"])

# Vectors only — skips RGB conversion entirely; ~2-3x faster on inter frames.
# Returns (int32 [N, 10] array, frame_type) where frame_type is "I" | "P" | "B".
dense, frame_type = vr.read_motion_vectors()

Field schema. Each entry in vectors (and each row of the dense array) has these 10 columns, matching FFmpeg's AV_FRAME_DATA_MOTION_VECTORS:

Index Dict key Meaning
0 source 1 = motion from past reference, 2 = from future reference
1 w block width in pixels
2 h block height in pixels
3 src_x source block x (the reference position)
4 src_y source block y
5 dst_x destination block x (this frame's position)
6 dst_y destination block y
7 motion_x signed horizontal motion, in motion_scale units
8 motion_y signed vertical motion, in motion_scale units
9 motion_scale divisor for motion_x / motion_y (e.g. 4 for quarter-pel H.264)

To recover pixel-space displacement, divide motion_x/motion_y by motion_scale. I-frames (and codecs/decoder builds that don't export the side-data, e.g. some mpeg4 builds) return an empty list / zero-row array; frame_type lets you branch on that without inspecting the array.

Tip. read_motion_vectors() is the hot path when you only need the flow field — it skips libswscale RGB conversion and the host tensor copy, so on a typical 1080p P-frame it returns in a fraction of the read_frame() time.

Video Encoding

import torch
from nelux import VideoReader

reader = VideoReader("input.mp4")

# `create_encoder` pre-configures dimensions / fps / pixel format from the source.
with reader.create_encoder("output.mp4") as enc:
    for frame in reader:
        enc.encode_frame(frame)            # frame is [H, W, 3] uint8

print("Done!")

Carrying audio / subtitles across

add_passthrough copies (or transcodes) the source's audio and subtitle streams into the encoded output. Call it before the first encode_frame:

with reader.create_encoder("output.mp4") as enc:
    enc.add_passthrough("input.mp4")       # copy audio + subtitle streams
    for frame in reader:
        enc.encode_frame(frame)

# Trim a window + keep audio only (rebased to t=0):
reader.set_range(2.0, 6.0)                 # both float → seconds
with reader.create_encoder("clip.mp4") as enc:
    enc.add_passthrough("input.mp4", audio=True, subtitles=False, start=2.0, end=6.0)
    for frame in reader:
        enc.encode_frame(frame)

allow_transcode=True (default) re-encodes streams the output container can't stream-copy (e.g. AAC→WebM) instead of dropping them. One passthrough source per encoder; a second call raises.


Features

Core Features

  • Hardware Acceleration: NVDEC (decode) and NVENC (encode) on NVIDIA GPUs
  • Native HWC uint8 Output: frames decoded directly into a torch.Tensor of shape [H, W, 3] (or [H, W, 3] int16 for >8-bit sources; force_8bit=True clamps to uint8 always). No implicit float conversion — you cast/normalize on your side based on your model's expected input
  • RGB or Grayscale Output: color_format="rgb" (default) or color_format="gray" for single-channel [H, W, 1] luma (libswscale BT.601/709-correct, not a channel average). CPU decode only. encode_frame also accepts single-channel input ([H, W, 1] or [H, W]). With a grayscale output pixel_format ("gray"/"gray16le") it's a verbatim, full-range data path — values stored exactly, up to true 16-bit, with a lossless (ffv1) round-trip — ideal for depth maps and masks; with a color pixel_format the gray input is replicated to RGB
  • CPU Path Matches ffmpeg Byte-for-Byte: pure libswscale convert pipeline, default SWS_BILINEAR flags; output is bit-identical to ffmpeg -vf format=rgb24 on every common YUV/RGB format (see CHANGELOG v0.11.0)
  • Batch Decoding: get_batch([...]) / vr[start:stop:step] returns [B, H, W, 3] with seek minimization, deduplication, and a dedicated random-access decoder
  • Motion Vector Export: read_frame_with_motion_vectors() returns (frame, vectors) from FFmpeg decoder side-data; read_motion_vectors() skips RGB conversion and returns a dense [N, 10] array plus frame type. See preview + schema above and examples/motion_vector_overlay.py
  • Audio / Subtitle Passthrough: encoder.add_passthrough(source, audio, subtitles, start, end) copies (or transcodes) audio + subtitle streams from a source into the output, with optional [start, end) trim + rebase to t=0

Performance Knobs

  • prefetch=True: background producer thread (off by default — queue handoff costs ~2.5× more than the parallelism saves at typical decode speeds)
  • convert_workers=N: explicit control over the CPU convert-pool size. None (default) uses min(hw_concurrency, 16) for throughput-max; 0 matches torchcodec's polite single-threaded convert footprint; positive N pins to that count. See CHANGELOG v0.11.0 for measured tradeoffs
  • NVDEC fused convert: CUDA kernels for NV12 / P010 → RGB run in-line on the GPU; output stays on cuda:0 as a torch tensor — no CPU round-trip when decode_accelerator="nvdec"
  • Decoder-side resize=(W, H): CPU path scales in libswscale; NVDEC uses cuvid's built-in resize=WxH — single pass, no post-decode F.interpolate/cv2.resize needed

Supported Codecs & Formats

CPU path supports anything libavcodec can decode (h264, hevc, vp8/9, av1, mpeg2/4, prores, …). NVDEC support depends on your GPU generation.

Feature CPU path NVDEC path
Codecs any libavcodec decoder H.264, H.265/HEVC, VP9, AV1 (GPU-dependent)
Pixel formats all common YUV/RGB (yuv420p[10le]/yuv422p/yuv444p[10le]/nv12/nv21/rgb24/bgr24/gbrp/yuvj*) NV12, P010, P016, YUV444 (8/10/12/16-bit)
Containers anything libavformat can demux same

Benchmarks

H.264 decode → RGB tensor throughput, measured on Intel i9-13900K (24 logical cores) + RTX 3090, Windows 11, FFmpeg 8.x, PyTorch 2.11+cu130, nelux 0.11.0. Each row is the median of 5 fresh subprocess runs, 600 frames per run (300 at 4K). Output is HWC uint8 for every decoder (apples-to-apples).

Headline: nelux default vs torchcodec vs ffmpeg (CPU)

Resolution Decoder fps CPU% avg RSS MB
720p nelux (default) 3422 874 2350
torchcodec 2924 344 2395
ffmpeg-rgb24 (subprocess) 2273
1080p nelux (default) 2642 1426 4480
torchcodec 1589 502 4502
ffmpeg-rgb24 (subprocess) 1102
4K nelux (default) 607 1656 9205
torchcodec 367 487 9098
ffmpeg-rgb24 (subprocess) 254

nelux fan-outs libswscale convert across cores → +14–67% fps over torchcodec at every res. The trade: ~2.5–3× CPU. RSS is essentially identical.

Polite mode (convert_workers=0) vs torchcodec

Disabling the convert worker pool matches torchcodec's single-threaded convert architecture exactly. fps + CPU + RSS land within ~2%:

Resolution Decoder fps CPU% RSS MB
720p nelux (convert_workers=0) 3167 366 598
torchcodec 3090 343 673
1080p nelux (convert_workers=0) 1755 435 659
torchcodec 1728 432 732
4K nelux (convert_workers=0) 394 440 1022
torchcodec 401 477 1095

So the "+14–67% fps" win above is entirely the convert worker pool — strip it and nelux ≈ torchcodec on every dimension. Pick the trade you want via convert_workers=N.

NVDEC (GPU decode) vs ffmpeg-nvdec

Resolution Decoder fps CPU% GPU mem MB
720p nelux (decode_accelerator="nvdec") 1651 45 2886
ffmpeg-nvdec (subprocess) 1253 2902
1080p nelux 667 40 2911
ffmpeg-nvdec 592 2967
4K nelux 175 24 3052
ffmpeg-nvdec 162 3259

nelux NVDEC beats raw ffmpeg-nvdec by 8–32% on fps at lower CPU (NV12→RGB runs as a fused CUDA kernel; output stays on the GPU as a torch.Tensor, no host round-trip).

Quality (vs ffmpeg -vf format=rgb24 reference, 30-frame compare)

Across 14 (pix_fmt × colorspace) combos: 12 / 14 PSNR = ∞, SSIM = 1.000 — byte-identical to ffmpeg. The two exceptions are yuv420p10le (PSNR 47.9–48.3 dB / VMAF 99.85+) where 10→8-bit downconvert rounds differently from ffmpeg's direct 10-bit YUV→RGB path; perceptually identical. See tests/output/pixfmt_matrix/REPORT.md for the full table.

Caveats

  • ffmpeg-rgb24 CPU% omitted — it runs as a subprocess; the psutil sampler ticks every 100 ms and ffmpeg startup is short, so the few samples it gets are not representative. fps is valid (time wall-clock).
  • Single hardware data point — your numbers will differ. Reproduce with python tests/comprehensive_bench.py --tag mybox (full table) or python tests/bench_thread_modes.py (decoder-architecture comparison).
  • Default prefetch=False matches typical use. With prefetch=True nelux can squeeze another ~3–5% fps on big clips but burns more RAM (background producer queue).

API Reference

VideoReader

VideoReader(
    input_path: str,
    num_threads: int = 0,                          # 0 = ffmpeg auto-detect
    force_8bit: bool = False,                      # cast >8-bit YUV down to uint8
    backend: Literal["pytorch", "numpy"] = "pytorch",
    decode_accelerator: Literal["cpu", "nvdec"] = "cpu",
    cuda_device_index: int = 0,                    # NVDEC GPU index
    resize: tuple[int, int] | None = None,         # decoder-side scale to (W, H)
    prefetch: bool = False,                        # background producer thread
    convert_workers: int | None = None,            # None = min(hw, 16); 0 = polite
    color_format: Literal["rgb", "gray"] = "rgb",  # "gray" = [H, W, 1] luma (CPU only)
)

Properties:

  • width, height, fps, min_fps, max_fps, duration, total_frames
  • pixel_format, bit_depth, aspect_ratio, codec, has_audio
  • properties (full VideoProperties struct)
  • shape(frame_count, H, W, 3) (Python-side BatchMixin)
  • frame_count → cached get_frame_count() (Python-side BatchMixin)

Methods:

  • read_frame() / __next__() / iteration → next [H, W, 3] frame
  • frame_at(timestamp: float | index: int) → random-access frame via secondary decoder (doesn't disturb iteration)
  • __getitem__(int | float | slice | list | range) → single frame OR [B, H, W, 3] batch
  • decode_batch(indices: list[int]) → C++ batch path; called by get_batch after validation
  • get_batch(indices) / get_batch_range(start, end, step) → batch decode with seek minimization
  • set_range(start, end) / reset() → bound iteration
  • reconfigure(...) → reuse this VideoReader for a different file (10-50× faster than re-constructing)
  • create_encoder(output_path)VideoEncoder pre-configured to this source's dims/fps/format
  • start_prefetch() / stop_prefetch() / prefetch_buffered / is_prefetching → runtime prefetch control
  • supported_codecs() → list of codecs the linked libavcodec can decode

Documentation


Requirements

  • Python: 3.13+ (see pyproject.toml requires-python)
  • PyTorch: 2.13+ (import torch must precede import nelux; the matching CUDA wheel provides the CUDA runtime nelux's NVDEC path needs)
  • CUDA: 13.x (for NVDEC/NVENC builds). CPU-only builds drop this requirement.
  • OS: Windows 10/11, Linux (manylinux_2_28+ / Ubuntu 22.04+), macOS 12+ (Apple Silicon, CPU only)

Building from Source

Build system is scikit-build-core + CMake + Ninja + vcpkg. There is no setup.py.

git clone https://github.com/NevermindNilas/NeLux.git
cd NeLux

# Editable install — invokes scikit-build-core, which configures CMake + Ninja
# and runs vcpkg under the hood. Set NELUX_ENABLE_CUDA=ON to build NVDEC/NVENC.
NELUX_ENABLE_CUDA=ON pip install -e .

# Or build a wheel
NELUX_ENABLE_CUDA=ON pip wheel . -w dist/

On Windows the build needs MSVC 18 (or compatible), and FFmpeg headers/libs under external/ffmpeg/ (see tools/download_ffmpeg.ps1).

See BUILD.md for detailed build instructions.


License

This project is licensed under the GNU Affero General Public License v3.0 (AGPL-3.0). See the LICENSE file for details.


Acknowledgments

  • FFmpeg: The backbone of video processing in NeLux
  • PyTorch: For tensor operations and CUDA integration
  • Contributors: Thanks to everyone who has contributed to NeLux!

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distributions

No source distribution files available for this release.See tutorial on generating distribution archives.

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

nelux-0.14.3-213torch-cp314-cp314-win_amd64.whl (942.0 kB view details)

Uploaded CPython 3.14Windows x86-64

nelux-0.14.3-213torch-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.14manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nelux-0.14.3-213torch-cp314-cp314-macosx_14_0_arm64.whl (3.6 MB view details)

Uploaded CPython 3.14macOS 14.0+ ARM64

nelux-0.14.3-213torch-cp313-cp313-win_amd64.whl (916.1 kB view details)

Uploaded CPython 3.13Windows x86-64

nelux-0.14.3-213torch-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (1.4 MB view details)

Uploaded CPython 3.13manylinux: glibc 2.27+ x86-64manylinux: glibc 2.28+ x86-64

nelux-0.14.3-213torch-cp313-cp313-macosx_14_0_arm64.whl (3.6 MB view details)

Uploaded CPython 3.13macOS 14.0+ ARM64

File details

Details for the file nelux-0.14.3-213torch-cp314-cp314-win_amd64.whl.

File metadata

File hashes

Hashes for nelux-0.14.3-213torch-cp314-cp314-win_amd64.whl
Algorithm Hash digest
SHA256 5926aff3c2bb7a07387e33b7e147ede05201cc1e3e502c7e0f715a63b42f440a
MD5 4c86525bbfa440f0acb9b4fe3c8efa16
BLAKE2b-256 cff4d09f6711c9254ee1e63b378c953cf972dd30d915e323989ca06cf5ddcb3f

See more details on using hashes here.

File details

Details for the file nelux-0.14.3-213torch-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nelux-0.14.3-213torch-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 6ad9da49f10d9778a548b55da3560e2e205e90ecffa3311e760b72c21fab4393
MD5 df8af773847ca91570d2af0975da2fa9
BLAKE2b-256 b2df78666f16727ae8034e892c00fe5570386712d4e415a87c4c64b6dc5443c5

See more details on using hashes here.

File details

Details for the file nelux-0.14.3-213torch-cp314-cp314-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for nelux-0.14.3-213torch-cp314-cp314-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 8f962ff77e7b822d5f3a06f63c3fa00afbc577d14cc868630d2956e466571065
MD5 8a26e84c8ef4ea14e242ede2d476ae63
BLAKE2b-256 f9a1be70f2fde7d9458d5bbfe328c8b1b4033a1404455192bfcdf4a6d87f4f8d

See more details on using hashes here.

File details

Details for the file nelux-0.14.3-213torch-cp313-cp313-win_amd64.whl.

File metadata

File hashes

Hashes for nelux-0.14.3-213torch-cp313-cp313-win_amd64.whl
Algorithm Hash digest
SHA256 547c1664fd7747591ee97367bb30e38be1c06c77ae06bfd2fc7ee12c0fe56db1
MD5 d79446d561ed100139b3a8c34c2ba52e
BLAKE2b-256 832dda5b6ebeb443b710d9dfe72de1f979f7d618864f171ee3d7a256685cd701

See more details on using hashes here.

File details

Details for the file nelux-0.14.3-213torch-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.

File metadata

File hashes

Hashes for nelux-0.14.3-213torch-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
Algorithm Hash digest
SHA256 2d99053261f58ce6572e424d0bc615952d16cf09a5e307017e7f79fd399568e7
MD5 37e3cbf89f739ebfa5328d53e603ca2a
BLAKE2b-256 27852e499243d5b4183d0bc3db72e44ea43fd943f245be01754088b70356822d

See more details on using hashes here.

File details

Details for the file nelux-0.14.3-213torch-cp313-cp313-macosx_14_0_arm64.whl.

File metadata

File hashes

Hashes for nelux-0.14.3-213torch-cp313-cp313-macosx_14_0_arm64.whl
Algorithm Hash digest
SHA256 8331d70e005889a2c6e4521f66cea879bf9599067bac3e7abed66a1a2681c432
MD5 f4dba9370c7bfd57d9b872ec930829b6
BLAKE2b-256 456e8932543c9660ecd396bc16cd692c933d46e7cac28c24f9c60a024051d5a6

See more details on using hashes here.

Release history Release notifications | RSS feed

0.18.0

6 files

0.17.0

6 files

0.16.0

6 files

0.15.1

6 files

0.15.0

6 files

This release

0.14.3 This release

6 files

0.14.2

6 files

0.14.1

6 files

0.14.0

6 files

0.13.0

6 files

0.12.11

6 files

0.12.10

6 files

0.12.9

6 files

0.12.8

6 files

0.12.7

6 files

0.12.6

6 files

0.12.5

6 files

0.12.4

6 files

0.12.2

6 files

0.12.1

6 files

0.12.0

6 files

0.11.0

6 files

0.10.1

6 files

0.10.0

6 files

0.9.2

6 files

0.9.1

6 files

0.9.0

6 files

0.8.10

2 files

0.8.9

2 files

0.8.8

2 files

0.8.7

2 files

0.8.6

2 files

0.8.5

2 files

0.8.4

1 file

0.8.3

1 file

0.8.2

1 file

0.8.1

1 file

0.8.0

1 file

0.7.9

1 file

0.7.8

1 file

0.7.7

1 file

0.7.6

1 file

0.7.5

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page