diff --git a/src/spdl/io/__init__.py b/src/spdl/io/__init__.py index bde97852..fcaa3552 100644 --- a/src/spdl/io/__init__.py +++ b/src/spdl/io/__init__.py @@ -40,28 +40,43 @@ def __dir__(): return __all__ -_deprecated = { - "streaming_demux_audio", - "streaming_demux_video", - "async_streaming_demux_audio", - "async_streaming_demux_video", - "streaming_load_audio", - "streaming_load_video", - "async_streaming_load_audio", - "async_streaming_load_video", +_deprecated_core = { + "async_demux_audio", + "async_demux_video", + "async_demux_image", + "async_decode_packets", + "async_decode_packets_nvdec", + "async_streaming_decode_packets", + "async_decode_image_nvjpeg", + "async_convert_array", + "async_convert_frames", + "async_transfer_buffer", + "async_transfer_buffer_cpu", + "async_encode_image", + "run_async", +} +_deprecated_composite = { + "async_load_audio", + "async_load_video", + "async_load_image", + "async_load_image_batch", + "async_load_image_batch_nvdec", + "async_load_image_batch_nvjpeg", + "async_sample_decode_video", } def __getattr__(name: str) -> Any: - if name in _deprecated: + if name in _deprecated_core or name in _deprecated_composite: import warnings warnings.warn( - f"`{name}` has been deprecated. Please use `spdl.io.Demuxer`.", + f"`{name}` has been deprecated. Please use synchronous variant.", category=FutureWarning, stacklevel=2, ) - if "demux" in name: + + if name in _deprecated_core: return getattr(_core, name) return getattr(_composite, name) diff --git a/src/spdl/io/_composite.py b/src/spdl/io/_composite.py index 7e29c00a..45547097 100644 --- a/src/spdl/io/_composite.py +++ b/src/spdl/io/_composite.py @@ -32,14 +32,7 @@ "load_audio", "load_video", "load_image", - "async_load_audio", - "async_load_video", - "async_load_image", - "async_load_image_batch", - "async_load_image_batch_nvdec", "load_image_batch_nvjpeg", - "async_load_image_batch_nvjpeg", - "async_sample_decode_video", "sample_decode_video", ] @@ -967,10 +960,95 @@ def sample_decode_video( decode_config: DecodeConfig | None = None, filter_desc: str | None = _FILTER_DESC_DEFAULT, ) -> list[ImagePackets]: - """Synchronous version of :py:func:`~async_sample_decode_video`. + """Decode specified frames from the packets. + + This function decodes the input video packets and returns the frames + specified by ``indices``. Internally, it splits the packets into + smaller set of packets and decode the minimum number of frames to + retrieve the specified frames. + + .. mermaid:: + + block-beta + columns 15 + A["Input Packets"]:15 + + space:15 + + block:B1:3 + columns 3 + P1["1"] P2["2"] P3["3"] + end + block:B2:3 + columns 3 + P4["4"] P5["5"] P6["6"] + end + block:B3:3 + columns 3 + P7["7"] P8["8"] P9["9"] + end + block:B4:3 + columns 3 + P10["10"] P11["11"] P12["12"] + end + block:B5:3 + columns 3 + P13["13"] P14["14"] P15["15"] + end + + space:15 + + block:d1:3 + columns 3 + F1["Frame 1"] space:2 + end + space:3 + block:d2:3 + columns 3 + F7["Frame 7"] F8["Frame 8"] space + end + space:3 + block:d3:3 + columns 3 + F13["Frame 13"] F14["Frame 14"] F15["Frame 15"] + end + + space:15 + + space:6 + block:out:3 + columns 3 + O1["Frame 1"] O8["Frame 8"] O15["Frame 15"] + end + space:6 + A -- "Split 1" --> B1 + A -- "Split 2" --> B2 + A -- "Split 3" --> B3 + A -- "Split 4" --> B4 + A -- "Split 5" --> B5 - This function performs the same operation as :py:func:`~async_sample_decode_video`, - but the operations are performed sequentially. + B1 -- "Decode 1" --> d1 + B3 -- "Decode 3" --> d2 + B5 -- "Decode 5" --> d3 + + F1 --> O1 + F8 --> O8 + F15 --> O15 + + Args: + packets: The input video packets. + indices: The list of frame indices. + decode_config: + *Optional:* Decode config. + See :py:func:`~spdl.io.decode_config`. + filter_desc: *Optional:* Filter description. + See :py:func:`~spdl.io.decode_packets` for detail. + + strict: *Optional:* If True, raise an error + if any of the frames failed to decode. + + Returns: + Decoded frames. """ if not indices: raise ValueError("Frame indices must be non-empty.") diff --git a/src/spdl/io/_core.py b/src/spdl/io/_core.py index 3fee4c66..6dc47ea4 100644 --- a/src/spdl/io/_core.py +++ b/src/spdl/io/_core.py @@ -55,33 +55,19 @@ "demux_audio", "demux_video", "demux_image", - "async_demux_audio", - "async_demux_video", - "async_demux_image", # DECODING "decode_packets", - "async_decode_packets", "decode_packets_nvdec", - "async_decode_packets_nvdec", "streaming_decode_packets", - "async_streaming_decode_packets", "decode_image_nvjpeg", - "async_decode_image_nvjpeg", # FRAME CONVERSION "convert_array", "convert_frames", - "async_convert_array", - "async_convert_frames", # DATA TRANSFER "transfer_buffer", - "async_transfer_buffer", "transfer_buffer_cpu", - "async_transfer_buffer_cpu", # ENCODING "encode_image", - "async_encode_image", - # MISC - "run_async", ] _LG = logging.getLogger(__name__) @@ -699,7 +685,7 @@ def encode_image(path: str, data: Array, pix_fmt: str = "rgb24", **kwargs): >>> import spdl.io >>> >>> data = np.random.randint(255, size=(32, 16, 3), dtype=np.uint8) - >>> coro = spdl.io.async_encode_image( + >>> img = spdl.io.encode_image( ... "foo.png", ... data, ... pix_fmt="rgb24", @@ -709,7 +695,6 @@ def encode_image(path: str, data: Array, pix_fmt: str = "rgb24", **kwargs): ... scale_algo="neighbor", ... ), ... ) - >>> asyncio.run(coro) >>> Example - Save CUDA tensor as image @@ -718,15 +703,15 @@ def encode_image(path: str, data: Array, pix_fmt: str = "rgb24", **kwargs): >>> >>> data = torch.randint(255, size=(32, 16, 3), dtype=torch.uint8, device="cuda") >>> - >>> async def encode(data): - >>> buffer = await spdl.io.async_transfer_buffer_cpu(data) - >>> return await spdl.io.async_encode_image( + >>> def encode(data): + ... buffer = spdl.io.transfer_buffer_cpu(data) + ... return spdl.io.encode_image( ... "foo.png", ... buffer, ... pix_fmt="rgb24", ... ) ... - >>> asyncio.run(encode(data)) + >>> encode(data) >>> """ return _libspdl.encode_image(path, data, pix_fmt=pix_fmt, **kwargs) diff --git a/src/spdl/io/_type_stub.py b/src/spdl/io/_type_stub.py index 0c94ff9c..ce2d1cb8 100644 --- a/src/spdl/io/_type_stub.py +++ b/src/spdl/io/_type_stub.py @@ -38,15 +38,6 @@ class Packets: Decode functions receive Packets objects and generate audio samples and visual frames. - The ``Packets`` objects are exposed to public API to allow composing - demux/decoding functions in ways that they are executed concurrently. - - For example, when decoding multiple clips from a single audio and video file, - by emitting ``Packets`` objects in between, decoding can be started while the - demuxer is demuxing the subsequent windows. - - The following code will kick-off the decoding job as soon as the streaming - demux function yields a ``VideoPackets`` object. .. admonition:: Example @@ -57,14 +48,12 @@ class Packets: ... (13, 15), ... ] >>> - >>> tasks = [] - >>> async for packets in spdl.io.async_streaming_demux_video(src, windows): - >>> # Kick off decoding job while demux function is demuxing the next window - >>> task = asyncio.create_task(async_decode_packets(packets)) - >>> task.append(task) + >>> demuxer = spdl.io.Demuxer(src) + >>> for window in windows: + ... packets = demuxer.demux_video(window) + ... frames = decode_packets(packets) + ... >>> - >>> # Wait for all the decoding to be complete - >>> asyncio.wait(tasks) .. important::