1212# confusing, and we should definitely not expose this one as-is. We should either:
1313# - keep it private but rename it to something that's not stream_index
1414# - make it absolute per container, if we ever want to expose it.
15- class _VideoStream :
15+ class VideoStream :
16+ """A video stream within an :class:`Encoder`.
17+
18+ Returned by :meth:`Encoder.add_video`. Use :meth:`add_frames` to feed
19+ video frames into this stream.
20+ """
21+
1622 def __init__ (self , encoder_tensor : Tensor , stream_index : int ):
1723 self ._encoder_tensor = encoder_tensor
1824 self ._stream_index = stream_index
1925
2026 def add_frames (self , frames : Tensor ) -> None :
27+ """Add video frames to this stream.
28+
29+ Args:
30+ frames (``torch.Tensor``): The frames to encode. This must be a 4D
31+ tensor of shape ``(N, C, H, W)`` where N is the number of
32+ frames, C is 3 channels (RGB), H is height, and W is width.
33+ Values must be uint8 in the range ``[0, 255]``. The device of
34+ the tensor must match the ``device`` passed to
35+ :meth:`Encoder.add_video`.
36+ """
2137 _core .streaming_encoder_add_frames (
2238 self ._encoder_tensor , frames , self ._stream_index
2339 )
2440
2541
26- class _AudioStream :
42+ class AudioStream :
43+ """An audio stream within an :class:`Encoder`.
44+
45+ Returned by :meth:`Encoder.add_audio`. Use :meth:`add_samples` to feed
46+ audio samples into this stream.
47+ """
48+
2749 def __init__ (self , encoder_tensor : Tensor , stream_index : int ):
2850 self ._encoder_tensor = encoder_tensor
2951 self ._stream_index = stream_index
3052
3153 def add_samples (self , samples : Tensor ) -> None :
54+ """Add audio samples to this stream.
55+
56+ Args:
57+ samples (``torch.Tensor``): The samples to encode. This must be a
58+ 2D tensor of shape ``(num_channels, num_samples)``. Values must
59+ be float values in ``[-1, 1]``. The number of channels must
60+ match the ``num_channels`` passed to :meth:`Encoder.add_audio`.
61+ """
3262 _core .streaming_encoder_add_samples (
3363 self ._encoder_tensor , samples , self ._stream_index
3464 )
3565
3666
3767class Encoder :
68+ """A multi-stream encoder for encoding video and/or audio into a file or file-like object.
69+
70+ Unlike :class:`VideoEncoder` and :class:`AudioEncoder` which encode a
71+ single stream in one shot, ``Encoder`` supports multiple streams and
72+ incremental (streaming) encoding. Frames and samples can be added
73+ progressively, which is useful when data is generated on-the-fly or when
74+ encoding both audio and video into the same container.
75+
76+ Use :meth:`add_video` and :meth:`add_audio` to configure output streams,
77+ then open an output destination with :meth:`open_file` or
78+ :meth:`open_file_like`, feed data via the returned stream objects, and
79+ finally call :meth:`close` (or use the encoder as a context manager).
80+
81+ Example:
82+
83+ .. code-block:: python
84+
85+ with Encoder() as encoder:
86+ video_stream = encoder.add_video(height=256, width=256, frame_rate=30)
87+ audio_stream = encoder.add_audio(sample_rate=16000, num_channels=1)
88+ encoder.open_file("output.mp4")
89+ video_stream.add_frames(frames_tensor)
90+ audio_stream.add_samples(samples_tensor)
91+ """
92+
3893 def __init__ (self ):
3994 self ._encoder_tensor = _core .create_streaming_encoder ()
4095
@@ -50,7 +105,43 @@ def add_video(
50105 crf : int | float | None = None ,
51106 preset : str | int | None = None ,
52107 extra_options : dict [str , Any ] | None = None ,
53- ) -> _VideoStream :
108+ ) -> VideoStream :
109+ """Add a video stream to the encoder.
110+
111+ Must be called before :meth:`open_file` or :meth:`open_file_like`.
112+
113+ Args:
114+ height (int): The height of the **input** video frames.
115+ width (int): The width of the **input** video frames.
116+ frame_rate (float): The frame rate of the **input** video frames.
117+ Also defines the encoded **output** frame rate.
118+ device (str, optional): The device to use for encoding, e.g.
119+ ``"cpu"`` or ``"cuda"``. Default: ``"cpu"``.
120+ codec (str, optional): The codec to use for encoding (e.g.,
121+ ``"libx264"``). If not specified, the default codec for the
122+ container format will be used.
123+ See :ref:`codec_selection` for details.
124+ pixel_format (str, optional): The pixel format for encoding (e.g.,
125+ ``"yuv420p"``). If not specified, uses codec's default format.
126+ Must be left as ``None`` when encoding on CUDA.
127+ See :ref:`pixel_format` for details.
128+ crf (int or float, optional): Constant Rate Factor for encoding
129+ quality. Lower values mean better quality. Valid range depends
130+ on the encoder (e.g. 0-51 for libx264). Defaults to None (which
131+ will use encoder's default). See :ref:`crf` for details.
132+ preset (str or int, optional): Encoder option that controls the
133+ tradeoff between encoding speed and compression (output size).
134+ Commonly a string: ``"fast"``, ``"medium"``, ``"slow"``.
135+ Defaults to None (which will use encoder's default).
136+ See :ref:`preset` for details.
137+ extra_options (dict[str, Any], optional): A dictionary of additional
138+ encoder options to pass, e.g. ``{"qp": 5, "tune": "film"}``.
139+ See :ref:`extra_options` for details.
140+
141+ Returns:
142+ A video stream object. Use its :meth:`~VideoStream.add_frames`
143+ method to feed frames into the stream.
144+ """
54145 preset = str (preset ) if isinstance (preset , int ) else preset
55146 stream_index = _core .streaming_encoder_add_video_stream (
56147 self ._encoder_tensor ,
@@ -66,7 +157,7 @@ def add_video(
66157 str (x ) for k , v in (extra_options or {}).items () for x in (k , v )
67158 ],
68159 )
69- return _VideoStream (self ._encoder_tensor , stream_index )
160+ return VideoStream (self ._encoder_tensor , stream_index )
70161
71162 def add_audio (
72163 self ,
@@ -76,7 +167,27 @@ def add_audio(
76167 bit_rate : int | None = None ,
77168 out_num_channels : int | None = None ,
78169 out_sample_rate : int | None = None ,
79- ) -> _AudioStream :
170+ ) -> AudioStream :
171+ """Add an audio stream to the encoder.
172+
173+ Must be called before :meth:`open_file` or :meth:`open_file_like`.
174+
175+ Args:
176+ sample_rate (int): The sample rate of the **input** samples.
177+ num_channels (int): The number of channels of the **input** samples.
178+ bit_rate (int, optional): The output bit rate. Encoders typically
179+ support a finite set of bit rate values, so ``bit_rate`` will be
180+ matched to one of those supported values. The default is chosen
181+ by FFmpeg.
182+ out_num_channels (int, optional): The number of channels of the
183+ encoded output. By default, the input ``num_channels`` is used.
184+ out_sample_rate (int, optional): The sample rate of the encoded
185+ output. By default, the input ``sample_rate`` is used.
186+
187+ Returns:
188+ An audio stream object. Use its :meth:`~AudioStream.add_samples`
189+ method to feed samples into the stream.
190+ """
80191 stream_index = _core .streaming_encoder_add_audio_stream (
81192 self ._encoder_tensor ,
82193 sample_rate = sample_rate ,
@@ -85,17 +196,52 @@ def add_audio(
85196 output_num_channels = out_num_channels ,
86197 output_sample_rate = out_sample_rate ,
87198 )
88- return _AudioStream (self ._encoder_tensor , stream_index )
199+ return AudioStream (self ._encoder_tensor , stream_index )
89200
90201 def open_file (self , dest : str | Path ) -> "Encoder" :
202+ """Open a file for writing the encoded output.
203+
204+ Must be called after all streams have been added via :meth:`add_video`
205+ and/or :meth:`add_audio`. The file extension determines the container
206+ format (e.g. ``.mp4``, ``.mkv``).
207+
208+ Args:
209+ dest (str or ``pathlib.Path``): The path to the output file.
210+
211+ Returns:
212+ Encoder: Returns ``self`` for method chaining.
213+ """
91214 _core .streaming_encoder_open_file (self ._encoder_tensor , str (dest ))
92215 return self
93216
94217 def open_file_like (self , dest , * , format : str ) -> "Encoder" :
218+ """Open a file-like object for writing the encoded output.
219+
220+ Must be called after all streams have been added via :meth:`add_video`
221+ and/or :meth:`add_audio`.
222+
223+ Args:
224+ dest: A file-like object that supports ``write()`` and ``seek()``
225+ methods, such as ``io.BytesIO()``, an open file in binary write
226+ mode, etc. Methods must have the following signature:
227+ ``write(data: bytes) -> int`` and ``seek(offset: int, whence:
228+ int = 0) -> int``.
229+ format (str): The container format of the encoded output, e.g.
230+ ``"mp4"``, ``"mov"``, ``"mkv"``, ``"avi"``, ``"webm"``, etc.
231+
232+ Returns:
233+ Encoder: Returns ``self`` for method chaining.
234+ """
95235 _core .streaming_encoder_open_file_like (self ._encoder_tensor , format , dest )
96236 return self
97237
98238 def close (self ) -> None :
239+ """Flush all remaining data and close the encoder.
240+
241+ This must be called when encoding is complete to ensure all buffered
242+ data is written. Using the encoder as a context manager (``with``
243+ statement) calls this automatically.
244+ """
99245 _core .streaming_encoder_close (self ._encoder_tensor )
100246
101247 def __enter__ (self ) -> "Encoder" :
0 commit comments