calesthio/OpenMontage · error · ValueError

full_lip_sync could not infer sound_end_time; provide it exp

Error message

full_lip_sync could not infer sound_end_time; provide it explicitly

What it means

Raised by KlingLipSyncTool._apply_face_timing_defaults during full lip-sync when sound_end_time was not supplied at either the top level or in face_choose[0], and the tool cannot infer it. Inference candidates are the local audio file's probed duration (ffprobe on sound_file_path/audio_path) and the detected face's time window (end_time - start_time); if both are unavailable the crop end is unknown.

Source

Thrown at tools/avatar/kling_lip_sync.py:516

        face_start = int(face.get("start_time") or 0)
        face_end = int(face.get("end_time") or 0)
        face_choose = self._normalize_face_choose(inputs)
        face_item = face_choose[0] if face_choose else {}
        if inputs.get("sound_start_time") is None and face_item.get("sound_start_time") is None:
            inputs["sound_start_time"] = 0
        if inputs.get("sound_insert_time") is None and face_item.get("sound_insert_time") is None:
            inputs["sound_insert_time"] = face_start
        if inputs.get("sound_end_time") is not None or face_item.get("sound_end_time") is not None:
            return

        candidates: list[int] = []
        audio_duration = self._local_audio_duration_ms(inputs)
        if audio_duration:
            candidates.append(audio_duration)
        if face_end > face_start:
            candidates.append(face_end - face_start)
        if not candidates:
            raise ValueError(
                "full_lip_sync could not infer sound_end_time; provide it explicitly"
            )
        inputs["sound_end_time"] = min(candidates)

    @staticmethod
    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

View on GitHub (pinned to 95e1c3d0ab)

Solutions

  1. Pass sound_end_time explicitly (milliseconds): inputs['sound_end_time'] = 8000
  2. Or put sound_end_time in the face_choose[0] object
  3. If using a local audio file, verify the path exists and ffprobe can read it so duration inference works
  4. Check the identify_face result has a sane end_time > start_time

Example fix

# before
inputs = {"video_id": vid, "sound_file_url": url, "face_choose": [{"face_id": fid}]}

# after
inputs = {"video_id": vid, "sound_file_url": url, "face_choose": [{"face_id": fid}], "sound_end_time": 8000}
Defensive patterns

Strategy: validation

Validate before calling

import os
needs_end = inputs.get("sound_end_time") is None and not (inputs.get("face_choose") or [{}])[0].get("sound_end_time")
probeable = os.path.isfile(inputs.get("sound_file_path") or inputs.get("audio_path") or "")
if needs_end and not probeable:
    raise ValueError("sound_end_time required: audio is remote/unprobeable")

Prevention

When it happens

Trigger: full_lip_sync with sound_file_url (remote audio, not probe-able locally) on a face record whose end_time <= start_time, and no explicit sound_end_time anywhere. Also when sound_file_path points to a missing file or a file ffprobe cannot read (probe returns no duration_seconds).

Common situations: Using URL-based audio with a short or zero-length detected face segment; passing a relative audio path from the wrong working directory; corrupted audio file.

Related errors


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