Billionmail/BillionMail · error

download error %d

Error message

download error %d

What it means

After the GET succeeds, this fires when the video-hosting server replies with any status other than 200 OK. Unlike the submit/status calls, this branch does NOT read the response body, so the server's error detail is lost — you only get the numeric code. Common codes: 403 (signed URL expired/forbidden), 404 (video removed), 5xx (CDN/storage errors).

Source

Thrown at core/internal/service/video_gen/lipsync.go:179

// DownloadLipSyncVideo downloads the completed lip sync video to the output directory.
func DownloadLipSyncVideo(ctx context.Context, cfg LipSyncConfig, videoURL, filename string) (string, error) {
	if err := os.MkdirAll(cfg.OutputDir, 0755); err != nil {
		return "", fmt.Errorf("create output dir: %w", err)
	}

	req, err := http.NewRequestWithContext(ctx, "GET", videoURL, nil)
	if err != nil {
		return "", fmt.Errorf("create download request: %w", err)
	}

	resp, err := cfg.doHTTP(req)
	if err != nil {
		return "", fmt.Errorf("download lipsync video: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return "", fmt.Errorf("download error %d", resp.StatusCode)
	}

	outPath := filepath.Join(cfg.OutputDir, filename)
	f, err := os.Create(outPath)
	if err != nil {
		return "", fmt.Errorf("create output file: %w", err)
	}
	defer f.Close()

	if _, err := io.Copy(f, resp.Body); err != nil {
		return "", fmt.Errorf("write video data: %w", err)
	}

	return outPath, nil
}

View on GitHub (pinned to fc36c76c05)

Solutions

  1. Log the actual status code (and ideally body) — 403/404 almost always mean the URL expired; re-run CheckLipSyncStatus to obtain a fresh video_url and download immediately after 'completed'.
  2. Do not poll long then download late: fetch a new URL right before downloading instead of caching lipVideoURL.
  3. Verify videoURL is the full https URL from resp.VideoURL, not a relative or truncated value.
  4. If 5xx, retry — the pipeline already wraps this in withRetry; persistent 5xx means wait for the provider to recover.
  5. Inspect any proxy/firewall in front of the host if you get unexpected 403s from your own network.

Example fix

// before
if resp.StatusCode != http.StatusOK {
	return "", fmt.Errorf("download error %d", resp.StatusCode)
}
// after
if resp.StatusCode != http.StatusOK {
	body, _ := io.ReadAll(io.LimitReader(resp.Body, 4096))
	return "", fmt.Errorf("download error %d: %s", resp.StatusCode, string(body))
}
Defensive patterns

Strategy: retry

Validate before calling

// verify URL freshness right before downloading
st, err := CheckLipSyncStatus(ctx, lipCfg, lipJobID)
if err != nil { return err }
if st.Status != "completed" || st.VideoURL == "" {
	return fmt.Errorf("lip sync not ready: status=%s", st.Status)
}
videoURL = st.VideoURL // use a freshly issued URL

Type guard

func isRetryableStatus(code int) bool {
	return code == http.StatusTooManyRequests || code >= 500
}

Try / catch

path, err := DownloadLipSyncVideo(ctx, lipCfg, videoURL, "lipsync.mp4")
var herr interface{ ... } // or check message
if err != nil && strings.Contains(err.Error(), "download error 40") {
	// 403/404: URL expired or bad — re-fetch status, get new URL, retry once
} else if err != nil && strings.Contains(err.Error(), "download error 5") {
	// transient server error — retry with backoff
}

Prevention

When it happens

Trigger: resp.StatusCode != http.StatusOK on the GET of the completed lip-sync video URL — typically calling DownloadLipSyncVideo with a stale or wrong videoURL, or Sync Labs' storage returning 403/404/500.

Common situations: Downloading hours after the lip-sync job finished and the signed CDN URL expired; passing an empty or placeholder videoURL (empty string would fail earlier, but a malformed URL hits 404); Sync Labs outage returning 503; firewall/proxy returning a 403 block page.

Related errors


AI-assisted analysis of Billionmail/BillionMail@fc36c76c05 (2026-09-05). Data as JSON: /api/errors/58c63f428d188ba2. Report an issue: GitHub.