diff --git a/plugins/video_gen/fal/__init__.py b/plugins/video_gen/fal/__init__.py index 1290d92d182b..1a67a5260fb5 100644 --- a/plugins/video_gen/fal/__init__.py +++ b/plugins/video_gen/fal/__init__.py @@ -180,7 +180,11 @@ def _clamp_duration(family: Dict[str, Any], duration: Optional[int]) -> Optional if not durations: return duration if duration is None: - return durations[0] + # Range families (e.g. pixverse-v6 (1,15)) should omit the field so + # the FAL endpoint applies its own default rather than receiving the + # minimum value. Enum families (e.g. veo3.1 (4,6,8)) keep sending + # their first entry as the default. + return None if _is_duration_range(durations) else durations[0] if _is_duration_range(durations): lo, hi = durations return max(lo, min(hi, duration)) diff --git a/tests/plugins/video_gen/test_fal_plugin.py b/tests/plugins/video_gen/test_fal_plugin.py index 7af1bd409714..8f522a8e59ca 100644 --- a/tests/plugins/video_gen/test_fal_plugin.py +++ b/tests/plugins/video_gen/test_fal_plugin.py @@ -322,6 +322,29 @@ class TestPayloadBuilder: assert p["generate_audio"] is True assert p["negative_prompt"] == "ugly" + def test_range_families_omit_duration_when_unspecified(self): + """Range-based families must omit `duration` when the caller doesn't + specify one so FAL applies its endpoint default, not the minimum.""" + from plugins.video_gen.fal import FAL_FAMILIES, _build_payload + + for family_id in ("pixverse-v6", "seedance-2.0", "kling-v3-4k"): + meta = FAL_FAMILIES[family_id] + p = _build_payload( + meta, + prompt="x", + image_url=None, + duration=None, + aspect_ratio="16:9", + resolution="720p", + negative_prompt=None, + audio=None, + seed=None, + ) + assert "duration" not in p, ( + f"{family_id}: duration=None should omit the field, " + f"got {p.get('duration')!r}" + ) + def test_happy_horse_minimal_payload(self): """Happy Horse has sparse docs — payload should be minimal.""" from plugins.video_gen.fal import FAL_FAMILIES, _build_payload