calesthio/OpenMontage · error · ValueError

Conflicting values between top-level input and…

Error message

Conflicting {key} values between top-level input and face_choose[0]

What it means

Raised by KlingLipSyncTool._copy_timing_fields when sound_start_time or sound_insert_time is set both at the top level of the inputs and inside face_choose[0], and the two values differ after int() coercion. The tool refuses to guess which value wins, so it fails fast rather than silently picking one.

Solutions

  1. Keep timing values in exactly one place: either top-level or face_choose[0], not both
  2. If both must exist, make them equal so the conflict check passes
  3. Prefer per-face values in face_choose when syncing multiple faces, and delete the top-level keys

Example fix

# before
inputs = {"sound_insert_time": 1000, "face_choose": [{"face_id": "f1", "sound_insert_time": 2000}]}

# after
inputs = {"face_choose": [{"face_id": "f1", "sound_insert_time": 2000}]}
Defensive patterns

Strategy: validation

Validate before calling

face_item = (inputs.get("face_choose") or [{}])[0]
for key in ("sound_start_time", "sound_insert_time"):
    nested, top = face_item.get(key), inputs.get(key)
    if nested is not None and top is not None and int(nested) != int(top):
        raise ValueError(f"conflicting {key}: pick one scope")

Prevention

When it happens

Trigger: advanced_lip_sync with e.g. inputs['sound_insert_time']=1000 plus face_choose=[{'face_id': 'f1', 'sound_insert_time': 2000}] — 1000 != 2000 raises. Note the error names the concrete key (sound_start_time or sound_insert_time) in {key}.

Common situations: Copying a working top-level payload and later adding per-face overrides without removing the top-level copy; agent pipelines merging user settings with template defaults at both levels.

Related errors


AI-assisted analysis of calesthio/OpenMontage@95e1c3d0ab (2026-08-15). Data as JSON: /api/errors/1da43e6bcd292485. Report an issue: GitHub.

Appendix: source

Thrown at tools/avatar/kling_lip_sync.py:540

    def _local_audio_duration_ms(inputs: dict[str, Any]) -> int | None:
        sound_path = inputs.get("sound_file_path") or inputs.get("audio_path")
        if not sound_path:
            return None
        path = Path(sound_path)
        if not path.is_file():
            return None
        seconds = probe_output(path).get("duration_seconds")
        if not seconds:
            return None
        return int(round(float(seconds) * 1000))

    @staticmethod
    def _copy_timing_fields(inputs: dict[str, Any], face_item: dict[str, Any]) -> None:
        for key, default in (("sound_start_time", 0), ("sound_insert_time", 0)):
            nested = face_item.get(key)
            top_level = inputs.get(key)
            if nested is not None and top_level is not None and int(nested) != int(top_level):
                raise ValueError(
                    f"Conflicting {key} values between top-level input and face_choose[0]"
                )
            value = nested if nested is not None else top_level
            if value is None:
                value = default
            face_item[key] = int(value)

        nested_end = face_item.get("sound_end_time")
        top_level_end = inputs.get("sound_end_time")
        if (
            nested_end is not None
            and top_level_end is not None
            and int(nested_end) != int(top_level_end)
        ):
            raise ValueError(
                "Conflicting sound_end_time values between top-level input and face_choose[0]"
            )
        sound_end = nested_end if nested_end is not None else top_level_end

View on GitHub (pinned to 95e1c3d0ab)