From 27e0f3af75ed94b7bd3c22b23ace722247cff7f8 Mon Sep 17 00:00:00 2001 From: RUFFY-369 Date: Sun, 22 Feb 2026 20:08:29 +0530 Subject: [PATCH] fix: issue #470 --- src/scope/core/pipelines/base_schema.py | 20 +++++ .../pipelines/krea_realtime_video/schema.py | 2 + src/scope/core/pipelines/longlive/schema.py | 2 + src/scope/core/pipelines/memflow/schema.py | 2 + .../core/pipelines/reward_forcing/schema.py | 2 + .../pipelines/streamdiffusionv2/schema.py | 2 + tests/test_pipeline_schema_metadata.py | 88 +++++++++++++++++++ 7 files changed, 118 insertions(+) create mode 100644 tests/test_pipeline_schema_metadata.py diff --git a/src/scope/core/pipelines/base_schema.py b/src/scope/core/pipelines/base_schema.py index 714e988d0..93812523f 100644 --- a/src/scope/core/pipelines/base_schema.py +++ b/src/scope/core/pipelines/base_schema.py @@ -245,6 +245,11 @@ class BasePipelineConfig(BaseModel): # to appear in the preprocessor dropdown. usage: ClassVar[list[UsageType]] = [] + # Frame chunk configuration — exposed via get_schema_with_metadata() so + # external clients can discover frames_per_chunk without hardcoding. + num_frame_per_block: ClassVar[int | None] = None + vae_temporal_downsample_factor: ClassVar[int | None] = None + # Mode configuration - keys are mode names, values are ModeDefaults with field overrides # Use default=True to mark the default mode. Only include fields that differ from base. modes: ClassVar[dict[str, ModeDefaults]] = {"text": ModeDefaults(default=True)} @@ -384,6 +389,21 @@ def get_schema_with_metadata(cls) -> dict[str, Any]: metadata["usage"] = [usage.value for usage in cls.usage] if cls.usage else [] metadata["config_schema"] = cls.model_json_schema() + # Expose frame chunk metadata for external clients (e.g. ComfyUI) + if ( + cls.num_frame_per_block is not None + and cls.vae_temporal_downsample_factor is not None + ): + metadata["frames_per_chunk"] = ( + cls.num_frame_per_block * cls.vae_temporal_downsample_factor + ) + metadata["temporal_downsample_factor"] = ( + cls.vae_temporal_downsample_factor + ) + else: + metadata["frames_per_chunk"] = None + metadata["temporal_downsample_factor"] = None + # Include mode-specific defaults (excluding None values and the "default" flag) mode_defaults = {} for mode_name, mode_config in cls.modes.items(): diff --git a/src/scope/core/pipelines/krea_realtime_video/schema.py b/src/scope/core/pipelines/krea_realtime_video/schema.py index 52a9f24ff..9171cff18 100644 --- a/src/scope/core/pipelines/krea_realtime_video/schema.py +++ b/src/scope/core/pipelines/krea_realtime_video/schema.py @@ -48,6 +48,8 @@ class KreaRealtimeVideoConfig(BasePipelineConfig): min_dimension = 16 modified = True recommended_quantization_vram_threshold = 40.0 + num_frame_per_block = 3 + vae_temporal_downsample_factor = 4 default_temporal_interpolation_method = "linear" default_temporal_interpolation_steps = 4 diff --git a/src/scope/core/pipelines/longlive/schema.py b/src/scope/core/pipelines/longlive/schema.py index c2598d2a9..5742b7868 100644 --- a/src/scope/core/pipelines/longlive/schema.py +++ b/src/scope/core/pipelines/longlive/schema.py @@ -43,6 +43,8 @@ class LongLiveConfig(BasePipelineConfig): supports_quantization = True min_dimension = 16 modified = True + num_frame_per_block = 3 + vae_temporal_downsample_factor = 4 # Configuration fields with UI metadata (order, component, modes) vace_context_scale: float = Field( diff --git a/src/scope/core/pipelines/memflow/schema.py b/src/scope/core/pipelines/memflow/schema.py index 1aeef7106..56d0dbc80 100644 --- a/src/scope/core/pipelines/memflow/schema.py +++ b/src/scope/core/pipelines/memflow/schema.py @@ -43,6 +43,8 @@ class MemFlowConfig(BasePipelineConfig): supports_quantization = True min_dimension = 16 modified = True + num_frame_per_block = 3 + vae_temporal_downsample_factor = 4 vace_context_scale: float = Field( default=1.0, diff --git a/src/scope/core/pipelines/reward_forcing/schema.py b/src/scope/core/pipelines/reward_forcing/schema.py index 8b2148bc1..3dd888aa7 100644 --- a/src/scope/core/pipelines/reward_forcing/schema.py +++ b/src/scope/core/pipelines/reward_forcing/schema.py @@ -42,6 +42,8 @@ class RewardForcingConfig(BasePipelineConfig): supports_quantization = True min_dimension = 16 modified = True + num_frame_per_block = 3 + vae_temporal_downsample_factor = 4 vace_context_scale: float = Field( default=1.0, diff --git a/src/scope/core/pipelines/streamdiffusionv2/schema.py b/src/scope/core/pipelines/streamdiffusionv2/schema.py index 2c8612fbd..edcfcb894 100644 --- a/src/scope/core/pipelines/streamdiffusionv2/schema.py +++ b/src/scope/core/pipelines/streamdiffusionv2/schema.py @@ -48,6 +48,8 @@ class StreamDiffusionV2Config(BasePipelineConfig): supports_quantization = True min_dimension = 16 modified = True + num_frame_per_block = 1 + vae_temporal_downsample_factor = 4 vace_context_scale: float = Field( default=1.0, diff --git a/tests/test_pipeline_schema_metadata.py b/tests/test_pipeline_schema_metadata.py new file mode 100644 index 000000000..c3d31bcda --- /dev/null +++ b/tests/test_pipeline_schema_metadata.py @@ -0,0 +1,88 @@ +"""Tests for frames_per_chunk and temporal_downsample_factor in pipeline schema metadata.""" + +from scope.core.pipelines.base_schema import BasePipelineConfig + + +class TestSchemaMetadataFrameChunk: + """Tests for frames_per_chunk and temporal_downsample_factor in get_schema_with_metadata().""" + + def test_base_config_returns_none_for_frame_chunk_fields(self): + """BasePipelineConfig should return None for both fields by default.""" + metadata = BasePipelineConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] is None + assert metadata["temporal_downsample_factor"] is None + + def test_custom_config_computes_frames_per_chunk(self): + """Config with num_frame_per_block and vae_temporal_downsample_factor should compute correctly.""" + + class TestConfig(BasePipelineConfig): + pipeline_id = "test-frames" + num_frame_per_block = 3 + vae_temporal_downsample_factor = 4 + + metadata = TestConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] == 12 + assert metadata["temporal_downsample_factor"] == 4 + + def test_partial_config_returns_none(self): + """Config with only one of the two fields set should return None for both.""" + + class OnlyBlockConfig(BasePipelineConfig): + pipeline_id = "test-partial-block" + num_frame_per_block = 3 + + metadata = OnlyBlockConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] is None + assert metadata["temporal_downsample_factor"] is None + + class OnlyVaeConfig(BasePipelineConfig): + pipeline_id = "test-partial-vae" + vae_temporal_downsample_factor = 4 + + metadata = OnlyVaeConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] is None + assert metadata["temporal_downsample_factor"] is None + + def test_longlive_config_values(self): + """LongLiveConfig should have frames_per_chunk=12, temporal_downsample_factor=4.""" + from scope.core.pipelines.longlive.schema import LongLiveConfig + + metadata = LongLiveConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] == 12 + assert metadata["temporal_downsample_factor"] == 4 + + def test_streamdiffusionv2_config_values(self): + """StreamDiffusionV2Config should have frames_per_chunk=4, temporal_downsample_factor=4.""" + from scope.core.pipelines.streamdiffusionv2.schema import ( + StreamDiffusionV2Config, + ) + + metadata = StreamDiffusionV2Config.get_schema_with_metadata() + assert metadata["frames_per_chunk"] == 4 + assert metadata["temporal_downsample_factor"] == 4 + + def test_krea_realtime_video_config_values(self): + """KreaRealtimeVideoConfig should have frames_per_chunk=12, temporal_downsample_factor=4.""" + from scope.core.pipelines.krea_realtime_video.schema import ( + KreaRealtimeVideoConfig, + ) + + metadata = KreaRealtimeVideoConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] == 12 + assert metadata["temporal_downsample_factor"] == 4 + + def test_reward_forcing_config_values(self): + """RewardForcingConfig should have frames_per_chunk=12, temporal_downsample_factor=4.""" + from scope.core.pipelines.reward_forcing.schema import RewardForcingConfig + + metadata = RewardForcingConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] == 12 + assert metadata["temporal_downsample_factor"] == 4 + + def test_memflow_config_values(self): + """MemFlowConfig should have frames_per_chunk=12, temporal_downsample_factor=4.""" + from scope.core.pipelines.memflow.schema import MemFlowConfig + + metadata = MemFlowConfig.get_schema_with_metadata() + assert metadata["frames_per_chunk"] == 12 + assert metadata["temporal_downsample_factor"] == 4