Billionmail/BillionMail · error
lip sync failed: %s
Error message
lip sync failed: %s
What it means
The lip-sync provider explicitly reported the job as 'failed'. pollLipSync wraps the provider-supplied error message (resp.Error) in this error and surfaces it to RunPipeline. The real cause is inside the %s payload.
Source
Thrown at core/internal/service/video_gen/orchestrator.go:419
deadline := time.Now().Add(lipSyncTimeout)
for {
if time.Now().After(deadline) {
return "", fmt.Errorf("lip sync timed out after %v", lipSyncTimeout)
}
resp, err := CheckLipSyncStatus(ctx, cfg, jobID)
if err != nil {
return "", fmt.Errorf("check lip sync status: %w", err)
}
switch resp.Status {
case "completed":
if resp.VideoURL == "" {
return "", fmt.Errorf("lip sync completed but no video URL")
}
return resp.VideoURL, nil
case "failed":
return "", fmt.Errorf("lip sync failed: %s", resp.Error)
}
time.Sleep(lipSyncPollDelay)
}
}
// withRetry retries a function up to maxRetries times with exponential backoff.
func withRetry[T any](fn func() (T, error)) (T, error) {
var result T
var err error
delay := retryBaseDelay
for attempt := 0; attempt <= maxRetries; attempt++ {
result, err = fn()
if err == nil {
return result, nil
}
if attempt < maxRetries {View on GitHub (pinned to fc36c76c05)
Solutions
- Inspect the %s message for the provider's specific failure reason
- Re-encode/validate the input media (duration, codec, resolution) before submitting
- Re-submit the job — some failures are transient provider errors
- Test with a known-good small input to isolate whether the input or the provider is at fault
Example fix
// before
jobID, err := SubmitLipSync(ctx, cfg, videoURL, voiceID)
// after
if err := validateLipSyncInput(videoURL, voiceID); err != nil {
return fmt.Errorf("invalid lip sync input: %w", err)
}
jobID, err := SubmitLipSync(ctx, cfg, videoURL, voiceID) Defensive patterns
Strategy: retry
Validate before calling
if videoDurationSec < minSupportedSec || videoDurationSec > maxSupportedSec {
return fmt.Errorf("video duration %ds outside supported range", videoDurationSec)
}
if voiceID == "" {
return fmt.Errorf("voice ID required before lip sync")
} Type guard
func isRetryableLipSyncFailure(providerErrMsg string) bool {
msg := strings.ToLower(providerErrMsg)
return strings.Contains(msg, "capacity") || strings.Contains(msg, "internal") || strings.Contains(msg, "timeout")
} Try / catch
url, err := pollLipSync(ctx, cfg, jobID)
var lipErr *LipSyncFailedError
if errors.As(err, &lipErr) && isRetryableLipSyncFailure(lipErr.ProviderMessage) {
return resubmitLipSync(ctx, cfg)
} Prevention
- Validate input media duration/codec/format before submitting jobs
- Ensure voice samples resolve to live URLs at submission time
- Classify provider error messages: retry transient ones, surface input errors to users
- Keep a known-good sample input for end-to-end provider health checks
When it happens
Trigger: CheckLipSyncStatus returns Status=="failed"; the provider rejected or crashed the job (bad audio/video input, unsupported format, content policy rejection, provider-side error).
Common situations: Input video/audio too short or too long for the model; corrupted screenshot/footage file; voices whose sample URL expired; provider capacity errors reported as job failure.
Related errors
AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05).
Data as JSON: /api/errors/86ec6116e735a5b3.
Report an issue: GitHub.