nvidia-codec 0.0.19__tar.gz → 0.0.21__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {nvidia_codec-0.0.19/src/nvidia_codec.egg-info → nvidia_codec-0.0.21}/PKG-INFO +1 -1
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/setup.cfg +1 -1
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/core/decode.py +69 -17
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/include/libavcodec.py +9 -4
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/libavcodec.py +4 -1
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/utils/color.py +38 -0
- nvidia_codec-0.0.21/src/nvidia_codec/utils/decode.py +768 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/utils/demux.py +83 -22
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21/src/nvidia_codec.egg-info}/PKG-INFO +1 -1
- nvidia_codec-0.0.19/src/nvidia_codec/utils/decode.py +0 -470
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/LICENSE +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/README.md +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/pyproject.toml +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/__init__.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/common.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/core/__init__.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/core/common.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/core/cuda.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/core/cuviddec.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/core/nvcuvid.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/__init__.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/adapt.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/common.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/include/__init__.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/include/libavformat.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/include/libavutil.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/libavformat.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/libavutil.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/ffmpeg/test.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/utils/__init__.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/utils/compat.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec/utils/player.py +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec.egg-info/SOURCES.txt +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec.egg-info/dependency_links.txt +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec.egg-info/requires.txt +0 -0
- {nvidia_codec-0.0.19 → nvidia_codec-0.0.21}/src/nvidia_codec.egg-info/top_level.txt +0 -0
|
@@ -17,6 +17,7 @@ from .common import *
|
|
|
17
17
|
from .. import CodecNotSupportedError
|
|
18
18
|
import numpy as np
|
|
19
19
|
from ctypes import *
|
|
20
|
+
import weakref
|
|
20
21
|
|
|
21
22
|
import logging
|
|
22
23
|
|
|
@@ -123,12 +124,19 @@ class Surface:
|
|
|
123
124
|
class Picture:
|
|
124
125
|
"""Decoded frame before post-processing.
|
|
125
126
|
|
|
126
|
-
A data object —
|
|
127
|
-
|
|
127
|
+
A pure data object — index, proc params, pts. Deliberately holds NO
|
|
128
|
+
reference to the decoder: the decoder tracks live Pictures in
|
|
129
|
+
_slots/_recent, and a back-reference here would form a cycle that
|
|
130
|
+
keeps the decoder (and its large NVDEC context) alive past the
|
|
131
|
+
refcount-zero point, deferring cleanup to the cyclic GC. To turn a
|
|
132
|
+
Picture into a Surface use Decoder.map(pic) (the decoder is always
|
|
133
|
+
in scope where mapping happens).
|
|
134
|
+
|
|
135
|
+
Slot ownership is managed by the caller via pre_decode/pic_release
|
|
136
|
+
callbacks.
|
|
128
137
|
"""
|
|
129
138
|
|
|
130
|
-
def __init__(self,
|
|
131
|
-
self.decoder = decoder
|
|
139
|
+
def __init__(self, index, proc_params, pts):
|
|
132
140
|
self.index = index
|
|
133
141
|
self.params = proc_params
|
|
134
142
|
self.pts = pts
|
|
@@ -137,9 +145,6 @@ class Picture:
|
|
|
137
145
|
"""No-op. Slot ownership is managed by the caller (player.py)."""
|
|
138
146
|
pass
|
|
139
147
|
|
|
140
|
-
def map(self, stream: int = 0):
|
|
141
|
-
return Surface(self.decoder, self.index, self.params, stream, self.pts)
|
|
142
|
-
|
|
143
148
|
|
|
144
149
|
def decide_surface_format(
|
|
145
150
|
chroma_format, bit_depth, supported_surface_formats, allow_high=False
|
|
@@ -192,12 +197,27 @@ class BaseDecoder:
|
|
|
192
197
|
"""
|
|
193
198
|
|
|
194
199
|
def catch_exception(self, func, return_on_error=0):
|
|
200
|
+
# `func` is a bound method of self, and the wrapper records errors
|
|
201
|
+
# on self.exception — both strong refs to self. The wrapper is
|
|
202
|
+
# wrapped in a CFUNCTYPE and stored on self (self.handleXxxCallback),
|
|
203
|
+
# so capturing self strongly here forms a self-cycle that makes the
|
|
204
|
+
# decoder uncollectable by refcounting (only the cyclic GC can take
|
|
205
|
+
# it — which under streaming load lags and balloons VRAM). Hold
|
|
206
|
+
# self weakly instead; the callback only ever fires during a
|
|
207
|
+
# send()/cuvidParseVideoData while a caller keeps the decoder
|
|
208
|
+
# alive, so the weakref always resolves then. The None branch is
|
|
209
|
+
# just belt-and-suspenders for a callback racing finalization.
|
|
210
|
+
selfref = weakref.ref(self)
|
|
211
|
+
fn = func.__func__
|
|
195
212
|
def wrapper(*args, **kwargs):
|
|
213
|
+
s = selfref()
|
|
214
|
+
if s is None:
|
|
215
|
+
return return_on_error
|
|
196
216
|
try:
|
|
197
|
-
return
|
|
217
|
+
return fn(s, *args, **kwargs)
|
|
198
218
|
except BaseException as e:
|
|
199
219
|
log.debug(f"callback exception logged {e}")
|
|
200
|
-
|
|
220
|
+
s.exception = e
|
|
201
221
|
log.debug(f"returning error code {return_on_error}")
|
|
202
222
|
return return_on_error
|
|
203
223
|
|
|
@@ -410,10 +430,16 @@ class BaseDecoder:
|
|
|
410
430
|
unpaired_field=di.repeat_first_field < 0,
|
|
411
431
|
)
|
|
412
432
|
|
|
413
|
-
picture = Picture(
|
|
433
|
+
picture = Picture(di.picture_index, params, di.timestamp)
|
|
414
434
|
self.post_decode(picture)
|
|
415
435
|
return 1
|
|
416
436
|
|
|
437
|
+
def map(self, pic, stream: int = 0):
|
|
438
|
+
"""Map a decoded Picture to a Surface (post-processed frame).
|
|
439
|
+
Lives on the decoder (not Picture.map) so Picture needn't hold a
|
|
440
|
+
decoder back-reference — see Picture's docstring."""
|
|
441
|
+
return Surface(self, pic.index, pic.params, stream, pic.pts)
|
|
442
|
+
|
|
417
443
|
def handleOperatingPoint(self, pUserData, pOPInfo):
|
|
418
444
|
opi = pOPInfo.contents
|
|
419
445
|
if opi.codec == cudaVideoCodec.AV1:
|
|
@@ -528,18 +554,41 @@ class BaseDecoder:
|
|
|
528
554
|
payload=data.ctypes.data_as(POINTER(c_uint8)),
|
|
529
555
|
timestamp=pts,
|
|
530
556
|
)
|
|
557
|
+
# self.exception relays an error out of a ctypes callback (which
|
|
558
|
+
# can't propagate through the C parser). Clear it as we consume
|
|
559
|
+
# it: a retained exception keeps its __traceback__ alive, whose
|
|
560
|
+
# frames reference self, pinning the decoder. Raise it WITHOUT
|
|
561
|
+
# binding it to a local here — a `e = self.exception; raise e`
|
|
562
|
+
# would put `e` in this frame, and the raised exception's
|
|
563
|
+
# traceback includes this same frame → e ↔ frame cycle, defeating
|
|
564
|
+
# the point. `try: raise self.exception finally: clear` keeps the
|
|
565
|
+
# exception out of this frame's locals.
|
|
531
566
|
self.exception = None
|
|
532
|
-
|
|
533
|
-
|
|
567
|
+
try:
|
|
568
|
+
with cuda.Device(self.device):
|
|
534
569
|
cuda.check(nvcuvid.cuvidParseVideoData(self.cuvid_parser, byref(p)))
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
raise self.exception
|
|
570
|
+
except cuda.CUError:
|
|
571
|
+
if self.exception is None:
|
|
538
572
|
raise
|
|
573
|
+
# else: a callback captured a more specific exception — it's
|
|
574
|
+
# raised (and cleared) below in preference to the CUError.
|
|
539
575
|
if self.exception is not None:
|
|
540
|
-
|
|
576
|
+
try:
|
|
577
|
+
raise self.exception
|
|
578
|
+
finally:
|
|
579
|
+
self.exception = None
|
|
541
580
|
|
|
542
581
|
def free(self):
|
|
582
|
+
# __del__ can call this during interpreter shutdown, by which
|
|
583
|
+
# point module globals (nvcuvid, cuda, the ctypes types) may be
|
|
584
|
+
# torn down to None — calling through them then raises
|
|
585
|
+
# "NoneType is not callable". The OS reclaims the GPU context on
|
|
586
|
+
# process exit regardless, so just bail if our entry points are
|
|
587
|
+
# gone. (Now reachable because the decoder is refcount-collectable
|
|
588
|
+
# — its self-reference cycles were removed — so __del__ actually
|
|
589
|
+
# runs at shutdown instead of the object leaking.)
|
|
590
|
+
if nvcuvid is None or cuda is None:
|
|
591
|
+
return
|
|
543
592
|
if getattr(self, 'cuvid_parser', None):
|
|
544
593
|
cuda.check(nvcuvid.cuvidDestroyVideoParser(self.cuvid_parser))
|
|
545
594
|
self.cuvid_parser = CUvideoparser()
|
|
@@ -549,7 +598,10 @@ class BaseDecoder:
|
|
|
549
598
|
self.cuvid_decoder = CUvideodecoder()
|
|
550
599
|
|
|
551
600
|
def __del__(self):
|
|
552
|
-
|
|
601
|
+
try:
|
|
602
|
+
self.free()
|
|
603
|
+
except Exception:
|
|
604
|
+
pass # best-effort during finalization / shutdown
|
|
553
605
|
|
|
554
606
|
def __enter__(self):
|
|
555
607
|
return self
|
|
@@ -15,6 +15,10 @@ class AVMediaType(TypedCEnumeration(c_int)):
|
|
|
15
15
|
NB = 5
|
|
16
16
|
|
|
17
17
|
class AVCodecID(TypedCEnumeration(c_int)):
|
|
18
|
+
# Values track the ffmpeg 9 enum (libavcodec 63, pinned via the versioned
|
|
19
|
+
# libavcodec.so.63 load in ffmpeg/libavcodec.py — ffmpeg 8 removed entries,
|
|
20
|
+
# shifting WEBP/VP9/HEVC/HQ_HQA/AV1 down). A mismatch surfaces as
|
|
21
|
+
# CodecNotSupportedError ("<AVCodecID.(unknown): N>") on NVDEC-supported files.
|
|
18
22
|
MPEG1 = 1
|
|
19
23
|
MPEG2 = 2
|
|
20
24
|
MPEG4 = 12
|
|
@@ -30,11 +34,12 @@ class AVCodecID(TypedCEnumeration(c_int)):
|
|
|
30
34
|
VP6F = 92
|
|
31
35
|
GIF = 97
|
|
32
36
|
PRORES = 147
|
|
33
|
-
WEBP =
|
|
37
|
+
WEBP = 170
|
|
34
38
|
VP8 = 139
|
|
35
|
-
VP9 =
|
|
36
|
-
HEVC =
|
|
37
|
-
|
|
39
|
+
VP9 = 166
|
|
40
|
+
HEVC = 172
|
|
41
|
+
HQ_HQA = 185
|
|
42
|
+
AV1 = 222
|
|
38
43
|
# Audio codecs — values must match ffmpeg's avcodec.h
|
|
39
44
|
PCM_S16LE = 0x10000
|
|
40
45
|
PCM_S16BE = 0x10001
|
|
@@ -8,7 +8,10 @@ from errno import EAGAIN
|
|
|
8
8
|
import logging
|
|
9
9
|
log = logging.getLogger(__name__)
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
# Pin the ABI major: enum values + struct layouts in include/libavcodec.py
|
|
12
|
+
# track ffmpeg 9 (libavcodec 63). Loading the versioned .so makes a system
|
|
13
|
+
# ffmpeg upgrade fail loudly here instead of mis-decoding with shifted enums.
|
|
14
|
+
lib = cdll.LoadLibrary('libavcodec.so.63')
|
|
12
15
|
|
|
13
16
|
|
|
14
17
|
av_packet_alloc = lib.av_packet_alloc
|
|
@@ -23,6 +23,44 @@ from ..ffmpeg.include.libavutil import AVColorRange, AVColorSpace
|
|
|
23
23
|
from ..core.cuviddec import cudaVideoSurfaceFormat
|
|
24
24
|
from ..core.decode import Surface
|
|
25
25
|
|
|
26
|
+
def to_yuv420p(surface : Surface):
|
|
27
|
+
"""De-interleave/subsample a decoded surface into a contiguous planar I420
|
|
28
|
+
(yuv420p) uint8 CUDA tensor: Y (H·W) ++ U (H·W/4) ++ V (H·W/4).
|
|
29
|
+
|
|
30
|
+
Unlike convert() — which applies the YUV→RGB color matrix — this does NO
|
|
31
|
+
color math, only plane layout: for NV12 (semi-planar 4:2:0) it splits the
|
|
32
|
+
interleaved chroma; for YUV444 (planar 4:4:4) it box-averages the full-res
|
|
33
|
+
chroma 2×2 down to 4:2:0. 8-bit only (NV12 / YUV444). Half the bytes of rgb24
|
|
34
|
+
and no matrix round-trip; the encoder consumes yuv420p directly. Returns a 1D
|
|
35
|
+
tensor of length H·W·3//2, ready for `.cpu().numpy().tobytes()`."""
|
|
36
|
+
fmt = surface.format
|
|
37
|
+
width, height = surface.width, surface.height
|
|
38
|
+
YUV = torch.as_tensor(surface, device=f'cuda:{surface.decoder.device}')
|
|
39
|
+
if fmt == cudaVideoSurfaceFormat.NV12:
|
|
40
|
+
Y = YUV[:height, :] # (H, W)
|
|
41
|
+
UV = YUV[height:, :] # (H/2, W): U,V interleaved
|
|
42
|
+
U = UV[:, 0::2] # (H/2, W/2)
|
|
43
|
+
V = UV[:, 1::2] # (H/2, W/2)
|
|
44
|
+
elif fmt == cudaVideoSurfaceFormat.YUV444:
|
|
45
|
+
# planar 4:4:4 → 4:2:0: keep Y full-res, box-average chroma 2×2. Plane
|
|
46
|
+
# layout mirrors convert() (3-plane tensor, or 3 planes stacked H·3).
|
|
47
|
+
if YUV.ndim == 3:
|
|
48
|
+
Y, U444, V444 = YUV[0], YUV[1], YUV[2] # each (H, W)
|
|
49
|
+
else:
|
|
50
|
+
Y = YUV[:height, :]
|
|
51
|
+
U444 = YUV[height:height * 2, :]
|
|
52
|
+
V444 = YUV[height * 2:height * 3, :]
|
|
53
|
+
eh, ew = height // 2 * 2, width // 2 * 2
|
|
54
|
+
def _sub(c): # (H, W) 4:4:4 → (H/2, W/2) box-average, uint8
|
|
55
|
+
c = c[:eh, :ew].contiguous().to(torch.float32).reshape(eh // 2, 2, ew // 2, 2)
|
|
56
|
+
return c.mean(dim=(1, 3)).round().clamp(0, 255).to(torch.uint8)
|
|
57
|
+
U, V = _sub(U444), _sub(V444)
|
|
58
|
+
else:
|
|
59
|
+
raise ValueError(f"to_yuv420p: NV12/YUV444 8-bit only, got {fmt}")
|
|
60
|
+
# reshape(-1) copies the (possibly pitch-strided) planes to contiguous.
|
|
61
|
+
return torch.cat([Y.reshape(-1), U.reshape(-1), V.reshape(-1)])
|
|
62
|
+
|
|
63
|
+
|
|
26
64
|
def convert(surface : Surface,
|
|
27
65
|
source_space : AVColorSpace,
|
|
28
66
|
source_range : AVColorRange,
|