SuperRealCo commited on
Commit
cde7bcd
·
verified ·
1 Parent(s): 4f06bb4

Delete comfy_api

Browse files
comfy_api/feature_flags.py DELETED
@@ -1,69 +0,0 @@
1
- """
2
- Feature flags module for ComfyUI WebSocket protocol negotiation.
3
-
4
- This module handles capability negotiation between frontend and backend,
5
- allowing graceful protocol evolution while maintaining backward compatibility.
6
- """
7
-
8
- from typing import Any, Dict
9
-
10
- from comfy.cli_args import args
11
-
12
- # Default server capabilities
13
- SERVER_FEATURE_FLAGS: Dict[str, Any] = {
14
- "supports_preview_metadata": True,
15
- "max_upload_size": args.max_upload_size * 1024 * 1024, # Convert MB to bytes
16
- }
17
-
18
-
19
- def get_connection_feature(
20
- sockets_metadata: Dict[str, Dict[str, Any]],
21
- sid: str,
22
- feature_name: str,
23
- default: Any = False
24
- ) -> Any:
25
- """
26
- Get a feature flag value for a specific connection.
27
-
28
- Args:
29
- sockets_metadata: Dictionary of socket metadata
30
- sid: Session ID of the connection
31
- feature_name: Name of the feature to check
32
- default: Default value if feature not found
33
-
34
- Returns:
35
- Feature value or default if not found
36
- """
37
- if sid not in sockets_metadata:
38
- return default
39
-
40
- return sockets_metadata[sid].get("feature_flags", {}).get(feature_name, default)
41
-
42
-
43
- def supports_feature(
44
- sockets_metadata: Dict[str, Dict[str, Any]],
45
- sid: str,
46
- feature_name: str
47
- ) -> bool:
48
- """
49
- Check if a connection supports a specific feature.
50
-
51
- Args:
52
- sockets_metadata: Dictionary of socket metadata
53
- sid: Session ID of the connection
54
- feature_name: Name of the feature to check
55
-
56
- Returns:
57
- Boolean indicating if feature is supported
58
- """
59
- return get_connection_feature(sockets_metadata, sid, feature_name, False) is True
60
-
61
-
62
- def get_server_features() -> Dict[str, Any]:
63
- """
64
- Get the server's feature flags.
65
-
66
- Returns:
67
- Dictionary of server feature flags
68
- """
69
- return SERVER_FEATURE_FLAGS.copy()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
comfy_api/input/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- from .basic_types import ImageInput, AudioInput
2
- from .video_types import VideoInput
3
-
4
- __all__ = [
5
- "ImageInput",
6
- "AudioInput",
7
- "VideoInput",
8
- ]
 
 
 
 
 
 
 
 
 
comfy_api/input/basic_types.py DELETED
@@ -1,20 +0,0 @@
1
- import torch
2
- from typing import TypedDict
3
-
4
- ImageInput = torch.Tensor
5
- """
6
- An image in format [B, H, W, C] where B is the batch size, C is the number of channels,
7
- """
8
-
9
- class AudioInput(TypedDict):
10
- """
11
- TypedDict representing audio input.
12
- """
13
-
14
- waveform: torch.Tensor
15
- """
16
- Tensor in the format [B, C, T] where B is the batch size, C is the number of channels,
17
- """
18
-
19
- sample_rate: int
20
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
comfy_api/input/video_types.py DELETED
@@ -1,72 +0,0 @@
1
- from __future__ import annotations
2
- from abc import ABC, abstractmethod
3
- from typing import Optional, Union
4
- import io
5
- from comfy_api.util import VideoContainer, VideoCodec, VideoComponents
6
-
7
- class VideoInput(ABC):
8
- """
9
- Abstract base class for video input types.
10
- """
11
-
12
- @abstractmethod
13
- def get_components(self) -> VideoComponents:
14
- """
15
- Abstract method to get the video components (images, audio, and frame rate).
16
-
17
- Returns:
18
- VideoComponents containing images, audio, and frame rate
19
- """
20
- pass
21
-
22
- @abstractmethod
23
- def save_to(
24
- self,
25
- path: str,
26
- format: VideoContainer = VideoContainer.AUTO,
27
- codec: VideoCodec = VideoCodec.AUTO,
28
- metadata: Optional[dict] = None
29
- ):
30
- """
31
- Abstract method to save the video input to a file.
32
- """
33
- pass
34
-
35
- def get_stream_source(self) -> Union[str, io.BytesIO]:
36
- """
37
- Get a streamable source for the video. This allows processing without
38
- loading the entire video into memory.
39
-
40
- Returns:
41
- Either a file path (str) or a BytesIO object that can be opened with av.
42
-
43
- Default implementation creates a BytesIO buffer, but subclasses should
44
- override this for better performance when possible.
45
- """
46
- buffer = io.BytesIO()
47
- self.save_to(buffer)
48
- buffer.seek(0)
49
- return buffer
50
-
51
- # Provide a default implementation, but subclasses can provide optimized versions
52
- # if possible.
53
- def get_dimensions(self) -> tuple[int, int]:
54
- """
55
- Returns the dimensions of the video input.
56
-
57
- Returns:
58
- Tuple of (width, height)
59
- """
60
- components = self.get_components()
61
- return components.images.shape[2], components.images.shape[1]
62
-
63
- def get_duration(self) -> float:
64
- """
65
- Returns the duration of the video in seconds.
66
-
67
- Returns:
68
- Duration in seconds
69
- """
70
- components = self.get_components()
71
- frame_count = components.images.shape[0]
72
- return float(frame_count / components.frame_rate)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
comfy_api/input_impl/__init__.py DELETED
@@ -1,7 +0,0 @@
1
- from .video_types import VideoFromFile, VideoFromComponents
2
-
3
- __all__ = [
4
- # Implementations
5
- "VideoFromFile",
6
- "VideoFromComponents",
7
- ]
 
 
 
 
 
 
 
 
comfy_api/input_impl/video_types.py DELETED
@@ -1,312 +0,0 @@
1
- from __future__ import annotations
2
- from av.container import InputContainer
3
- from av.subtitles.stream import SubtitleStream
4
- from fractions import Fraction
5
- from typing import Optional
6
- from comfy_api.input import AudioInput
7
- import av
8
- import io
9
- import json
10
- import numpy as np
11
- import torch
12
- from comfy_api.input import VideoInput
13
- from comfy_api.util import VideoContainer, VideoCodec, VideoComponents
14
-
15
-
16
- def container_to_output_format(container_format: str | None) -> str | None:
17
- """
18
- A container's `format` may be a comma-separated list of formats.
19
- E.g., iso container's `format` may be `mov,mp4,m4a,3gp,3g2,mj2`.
20
- However, writing to a file/stream with `av.open` requires a single format,
21
- or `None` to auto-detect.
22
- """
23
- if not container_format:
24
- return None # Auto-detect
25
-
26
- if "," not in container_format:
27
- return container_format
28
-
29
- formats = container_format.split(",")
30
- return formats[0]
31
-
32
-
33
- def get_open_write_kwargs(
34
- dest: str | io.BytesIO, container_format: str, to_format: str | None
35
- ) -> dict:
36
- """Get kwargs for writing a `VideoFromFile` to a file/stream with `av.open`"""
37
- open_kwargs = {
38
- "mode": "w",
39
- # If isobmff, preserve custom metadata tags (workflow, prompt, extra_pnginfo)
40
- "options": {"movflags": "use_metadata_tags"},
41
- }
42
-
43
- is_write_to_buffer = isinstance(dest, io.BytesIO)
44
- if is_write_to_buffer:
45
- # Set output format explicitly, since it cannot be inferred from file extension
46
- if to_format == VideoContainer.AUTO:
47
- to_format = container_format.lower()
48
- elif isinstance(to_format, str):
49
- to_format = to_format.lower()
50
- open_kwargs["format"] = container_to_output_format(to_format)
51
-
52
- return open_kwargs
53
-
54
-
55
- class VideoFromFile(VideoInput):
56
- """
57
- Class representing video input from a file.
58
- """
59
-
60
- def __init__(self, file: str | io.BytesIO):
61
- """
62
- Initialize the VideoFromFile object based off of either a path on disk or a BytesIO object
63
- containing the file contents.
64
- """
65
- self.__file = file
66
-
67
- def get_stream_source(self) -> str | io.BytesIO:
68
- """
69
- Return the underlying file source for efficient streaming.
70
- This avoids unnecessary memory copies when the source is already a file path.
71
- """
72
- if isinstance(self.__file, io.BytesIO):
73
- self.__file.seek(0)
74
- return self.__file
75
-
76
- def get_dimensions(self) -> tuple[int, int]:
77
- """
78
- Returns the dimensions of the video input.
79
-
80
- Returns:
81
- Tuple of (width, height)
82
- """
83
- if isinstance(self.__file, io.BytesIO):
84
- self.__file.seek(0) # Reset the BytesIO object to the beginning
85
- with av.open(self.__file, mode='r') as container:
86
- for stream in container.streams:
87
- if stream.type == 'video':
88
- assert isinstance(stream, av.VideoStream)
89
- return stream.width, stream.height
90
- raise ValueError(f"No video stream found in file '{self.__file}'")
91
-
92
- def get_duration(self) -> float:
93
- """
94
- Returns the duration of the video in seconds.
95
-
96
- Returns:
97
- Duration in seconds
98
- """
99
- if isinstance(self.__file, io.BytesIO):
100
- self.__file.seek(0)
101
- with av.open(self.__file, mode="r") as container:
102
- if container.duration is not None:
103
- return float(container.duration / av.time_base)
104
-
105
- # Fallback: calculate from frame count and frame rate
106
- video_stream = next(
107
- (s for s in container.streams if s.type == "video"), None
108
- )
109
- if video_stream and video_stream.frames and video_stream.average_rate:
110
- return float(video_stream.frames / video_stream.average_rate)
111
-
112
- # Last resort: decode frames to count them
113
- if video_stream and video_stream.average_rate:
114
- frame_count = 0
115
- container.seek(0)
116
- for packet in container.demux(video_stream):
117
- for _ in packet.decode():
118
- frame_count += 1
119
- if frame_count > 0:
120
- return float(frame_count / video_stream.average_rate)
121
-
122
- raise ValueError(f"Could not determine duration for file '{self.__file}'")
123
-
124
- def get_components_internal(self, container: InputContainer) -> VideoComponents:
125
- # Get video frames
126
- frames = []
127
- for frame in container.decode(video=0):
128
- img = frame.to_ndarray(format='rgb24') # shape: (H, W, 3)
129
- img = torch.from_numpy(img) / 255.0 # shape: (H, W, 3)
130
- frames.append(img)
131
-
132
- images = torch.stack(frames) if len(frames) > 0 else torch.zeros(0, 3, 0, 0)
133
-
134
- # Get frame rate
135
- video_stream = next(s for s in container.streams if s.type == 'video')
136
- frame_rate = Fraction(video_stream.average_rate) if video_stream and video_stream.average_rate else Fraction(1)
137
-
138
- # Get audio if available
139
- audio = None
140
- try:
141
- container.seek(0) # Reset the container to the beginning
142
- for stream in container.streams:
143
- if stream.type != 'audio':
144
- continue
145
- assert isinstance(stream, av.AudioStream)
146
- audio_frames = []
147
- for packet in container.demux(stream):
148
- for frame in packet.decode():
149
- assert isinstance(frame, av.AudioFrame)
150
- audio_frames.append(frame.to_ndarray()) # shape: (channels, samples)
151
- if len(audio_frames) > 0:
152
- audio_data = np.concatenate(audio_frames, axis=1) # shape: (channels, total_samples)
153
- audio_tensor = torch.from_numpy(audio_data).unsqueeze(0) # shape: (1, channels, total_samples)
154
- audio = AudioInput({
155
- "waveform": audio_tensor,
156
- "sample_rate": int(stream.sample_rate) if stream.sample_rate else 1,
157
- })
158
- except StopIteration:
159
- pass # No audio stream
160
-
161
- metadata = container.metadata
162
- return VideoComponents(images=images, audio=audio, frame_rate=frame_rate, metadata=metadata)
163
-
164
- def get_components(self) -> VideoComponents:
165
- if isinstance(self.__file, io.BytesIO):
166
- self.__file.seek(0) # Reset the BytesIO object to the beginning
167
- with av.open(self.__file, mode='r') as container:
168
- return self.get_components_internal(container)
169
- raise ValueError(f"No video stream found in file '{self.__file}'")
170
-
171
- def save_to(
172
- self,
173
- path: str | io.BytesIO,
174
- format: VideoContainer = VideoContainer.AUTO,
175
- codec: VideoCodec = VideoCodec.AUTO,
176
- metadata: Optional[dict] = None
177
- ):
178
- if isinstance(self.__file, io.BytesIO):
179
- self.__file.seek(0) # Reset the BytesIO object to the beginning
180
- with av.open(self.__file, mode='r') as container:
181
- container_format = container.format.name
182
- video_encoding = container.streams.video[0].codec.name if len(container.streams.video) > 0 else None
183
- reuse_streams = True
184
- if format != VideoContainer.AUTO and format not in container_format.split(","):
185
- reuse_streams = False
186
- if codec != VideoCodec.AUTO and codec != video_encoding and video_encoding is not None:
187
- reuse_streams = False
188
-
189
- if not reuse_streams:
190
- components = self.get_components_internal(container)
191
- video = VideoFromComponents(components)
192
- return video.save_to(
193
- path,
194
- format=format,
195
- codec=codec,
196
- metadata=metadata
197
- )
198
-
199
- streams = container.streams
200
-
201
- open_kwargs = get_open_write_kwargs(path, container_format, format)
202
- with av.open(path, **open_kwargs) as output_container:
203
- # Copy over the original metadata
204
- for key, value in container.metadata.items():
205
- if metadata is None or key not in metadata:
206
- output_container.metadata[key] = value
207
-
208
- # Add our new metadata
209
- if metadata is not None:
210
- for key, value in metadata.items():
211
- if isinstance(value, str):
212
- output_container.metadata[key] = value
213
- else:
214
- output_container.metadata[key] = json.dumps(value)
215
-
216
- # Add streams to the new container
217
- stream_map = {}
218
- for stream in streams:
219
- if isinstance(stream, (av.VideoStream, av.AudioStream, SubtitleStream)):
220
- out_stream = output_container.add_stream_from_template(template=stream, opaque=True)
221
- stream_map[stream] = out_stream
222
-
223
- # Write packets to the new container
224
- for packet in container.demux():
225
- if packet.stream in stream_map and packet.dts is not None:
226
- packet.stream = stream_map[packet.stream]
227
- output_container.mux(packet)
228
-
229
- class VideoFromComponents(VideoInput):
230
- """
231
- Class representing video input from tensors.
232
- """
233
-
234
- def __init__(self, components: VideoComponents):
235
- self.__components = components
236
-
237
- def get_components(self) -> VideoComponents:
238
- return VideoComponents(
239
- images=self.__components.images,
240
- audio=self.__components.audio,
241
- frame_rate=self.__components.frame_rate
242
- )
243
-
244
- def save_to(
245
- self,
246
- path: str,
247
- format: VideoContainer = VideoContainer.AUTO,
248
- codec: VideoCodec = VideoCodec.AUTO,
249
- metadata: Optional[dict] = None
250
- ):
251
- if format != VideoContainer.AUTO and format != VideoContainer.MP4:
252
- raise ValueError("Only MP4 format is supported for now")
253
- if codec != VideoCodec.AUTO and codec != VideoCodec.H264:
254
- raise ValueError("Only H264 codec is supported for now")
255
- with av.open(path, mode='w', options={'movflags': 'use_metadata_tags'}) as output:
256
- # Add metadata before writing any streams
257
- if metadata is not None:
258
- for key, value in metadata.items():
259
- output.metadata[key] = json.dumps(value)
260
-
261
- frame_rate = Fraction(round(self.__components.frame_rate * 1000), 1000)
262
- # Create a video stream
263
- video_stream = output.add_stream('h264', rate=frame_rate)
264
- video_stream.width = self.__components.images.shape[2]
265
- video_stream.height = self.__components.images.shape[1]
266
- video_stream.pix_fmt = 'yuv420p'
267
-
268
- # Create an audio stream
269
- audio_sample_rate = 1
270
- audio_stream: Optional[av.AudioStream] = None
271
- if self.__components.audio:
272
- audio_sample_rate = int(self.__components.audio['sample_rate'])
273
- audio_stream = output.add_stream('aac', rate=audio_sample_rate)
274
- audio_stream.sample_rate = audio_sample_rate
275
- audio_stream.format = 'fltp'
276
-
277
- # Encode video
278
- for i, frame in enumerate(self.__components.images):
279
- img = (frame * 255).clamp(0, 255).byte().cpu().numpy() # shape: (H, W, 3)
280
- frame = av.VideoFrame.from_ndarray(img, format='rgb24')
281
- frame = frame.reformat(format='yuv420p') # Convert to YUV420P as required by h264
282
- packet = video_stream.encode(frame)
283
- output.mux(packet)
284
-
285
- # Flush video
286
- packet = video_stream.encode(None)
287
- output.mux(packet)
288
-
289
- if audio_stream and self.__components.audio:
290
- # Encode audio
291
- samples_per_frame = int(audio_sample_rate / frame_rate)
292
- num_frames = self.__components.audio['waveform'].shape[2] // samples_per_frame
293
- for i in range(num_frames):
294
- start = i * samples_per_frame
295
- end = start + samples_per_frame
296
- # TODO(Feature) - Add support for stereo audio
297
- chunk = (
298
- self.__components.audio["waveform"][0, 0, start:end]
299
- .unsqueeze(0)
300
- .contiguous()
301
- .numpy()
302
- )
303
- audio_frame = av.AudioFrame.from_ndarray(chunk, format='fltp', layout='mono')
304
- audio_frame.sample_rate = audio_sample_rate
305
- audio_frame.pts = i * samples_per_frame
306
- for packet in audio_stream.encode(audio_frame):
307
- output.mux(packet)
308
-
309
- # Flush audio
310
- for packet in audio_stream.encode(None):
311
- output.mux(packet)
312
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
comfy_api/torch_helpers/__init__.py DELETED
@@ -1,5 +0,0 @@
1
- from .torch_compile import set_torch_compile_wrapper
2
-
3
- __all__ = [
4
- "set_torch_compile_wrapper",
5
- ]
 
 
 
 
 
 
comfy_api/torch_helpers/torch_compile.py DELETED
@@ -1,69 +0,0 @@
1
- from __future__ import annotations
2
- import torch
3
-
4
- import comfy.utils
5
- from comfy.patcher_extension import WrappersMP
6
- from typing import TYPE_CHECKING, Callable, Optional
7
- if TYPE_CHECKING:
8
- from comfy.model_patcher import ModelPatcher
9
- from comfy.patcher_extension import WrapperExecutor
10
-
11
-
12
- COMPILE_KEY = "torch.compile"
13
- TORCH_COMPILE_KWARGS = "torch_compile_kwargs"
14
-
15
-
16
- def apply_torch_compile_factory(compiled_module_dict: dict[str, Callable]) -> Callable:
17
- '''
18
- Create a wrapper that will refer to the compiled_diffusion_model.
19
- '''
20
- def apply_torch_compile_wrapper(executor: WrapperExecutor, *args, **kwargs):
21
- try:
22
- orig_modules = {}
23
- for key, value in compiled_module_dict.items():
24
- orig_modules[key] = comfy.utils.get_attr(executor.class_obj, key)
25
- comfy.utils.set_attr(executor.class_obj, key, value)
26
- return executor(*args, **kwargs)
27
- finally:
28
- for key, value in orig_modules.items():
29
- comfy.utils.set_attr(executor.class_obj, key, value)
30
- return apply_torch_compile_wrapper
31
-
32
-
33
- def set_torch_compile_wrapper(model: ModelPatcher, backend: str, options: Optional[dict[str,str]]=None,
34
- mode: Optional[str]=None, fullgraph=False, dynamic: Optional[bool]=None,
35
- keys: list[str]=["diffusion_model"], *args, **kwargs):
36
- '''
37
- Perform torch.compile that will be applied at sample time for either the whole model or specific params of the BaseModel instance.
38
-
39
- When keys is None, it will default to using ["diffusion_model"], compiling the whole diffusion_model.
40
- When a list of keys is provided, it will perform torch.compile on only the selected modules.
41
- '''
42
- # clear out any other torch.compile wrappers
43
- model.remove_wrappers_with_key(WrappersMP.APPLY_MODEL, COMPILE_KEY)
44
- # if no keys, default to 'diffusion_model'
45
- if not keys:
46
- keys = ["diffusion_model"]
47
- # create kwargs dict that can be referenced later
48
- compile_kwargs = {
49
- "backend": backend,
50
- "options": options,
51
- "mode": mode,
52
- "fullgraph": fullgraph,
53
- "dynamic": dynamic,
54
- }
55
- # get a dict of compiled keys
56
- compiled_modules = {}
57
- for key in keys:
58
- compiled_modules[key] = torch.compile(
59
- model=model.get_model_object(key),
60
- **compile_kwargs,
61
- )
62
- # add torch.compile wrapper
63
- wrapper_func = apply_torch_compile_factory(
64
- compiled_module_dict=compiled_modules,
65
- )
66
- # store wrapper to run on BaseModel's apply_model function
67
- model.add_wrapper_with_key(WrappersMP.APPLY_MODEL, COMPILE_KEY, wrapper_func)
68
- # keep compile kwargs for reference
69
- model.model_options[TORCH_COMPILE_KWARGS] = compile_kwargs
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
comfy_api/util/__init__.py DELETED
@@ -1,8 +0,0 @@
1
- from .video_types import VideoContainer, VideoCodec, VideoComponents
2
-
3
- __all__ = [
4
- # Utility Types
5
- "VideoContainer",
6
- "VideoCodec",
7
- "VideoComponents",
8
- ]
 
 
 
 
 
 
 
 
 
comfy_api/util/video_types.py DELETED
@@ -1,51 +0,0 @@
1
- from __future__ import annotations
2
- from dataclasses import dataclass
3
- from enum import Enum
4
- from fractions import Fraction
5
- from typing import Optional
6
- from comfy_api.input import ImageInput, AudioInput
7
-
8
- class VideoCodec(str, Enum):
9
- AUTO = "auto"
10
- H264 = "h264"
11
-
12
- @classmethod
13
- def as_input(cls) -> list[str]:
14
- """
15
- Returns a list of codec names that can be used as node input.
16
- """
17
- return [member.value for member in cls]
18
-
19
- class VideoContainer(str, Enum):
20
- AUTO = "auto"
21
- MP4 = "mp4"
22
-
23
- @classmethod
24
- def as_input(cls) -> list[str]:
25
- """
26
- Returns a list of container names that can be used as node input.
27
- """
28
- return [member.value for member in cls]
29
-
30
- @classmethod
31
- def get_extension(cls, value) -> str:
32
- """
33
- Returns the file extension for the container.
34
- """
35
- if isinstance(value, str):
36
- value = cls(value)
37
- if value == VideoContainer.MP4 or value == VideoContainer.AUTO:
38
- return "mp4"
39
- return ""
40
-
41
- @dataclass
42
- class VideoComponents:
43
- """
44
- Dataclass representing the components of a video.
45
- """
46
-
47
- images: ImageInput
48
- frame_rate: Fraction
49
- audio: Optional[AudioInput] = None
50
- metadata: Optional[dict] = None
51
-