yikart/AiToEarn · error · AppException

S3DownloadFileFailed

S3DownloadFileFailed

Error message

S3DownloadFileFailed

What it means

createVideo fetches the input_reference image URL before uploading it to OpenAI. If the fetch returns a non-OK HTTP status (404, 403, timeout page, etc.), the service throws AppException(ResponseCode.S3DownloadFileFailed) because the reference image could not be downloaded for the video generation request.

Source

Thrown at project/aitoearn-backend/apps/aitoearn-ai/src/core/ai/video/openai/openai.service.ts:82

    return { id: result.id }
  }

  /**
   * OpenAI 视频创建
   */
  async createVideo(request: UserOpenAIVideoCreateRequestDto) {
    const { userId, userType, model, prompt, input_reference, seconds, size } = request

    const startedAt = new Date()

    // 如果 input_reference 是 URL,需要先 fetch 后传入 Response
    let inputReferenceUploadable: Response | undefined
    if (input_reference) {
      const resolvedInputReference = await this.resolveRelayText(input_reference)
      const response = await fetch(resolvedInputReference)
      if (!response.ok) {
        throw new AppException(ResponseCode.S3DownloadFileFailed)
      }
      inputReferenceUploadable = response
    }

    const result = await this.aiAvailability.executeAsync(
      { provider: 'openai', operation: 'videoGeneration', model: model || 'sora-2' },
      () => this.openaiLibService.createVideo({
        prompt,
        input_reference: inputReferenceUploadable,
        model: model as 'sora-2' | 'sora-2-pro',
        // SDK 类型定义有误,实际支持 '10' | '15' | '25'
        seconds: seconds as '4' | '8' | '12' | undefined,
        size,
      }),
      r => r.id,
    )

    const aiLog = await this.aiLogRepo.create({

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Regenerate a fresh presigned/accessible URL for the reference image before calling createVideo (see toAccessibleUrl)
  2. Verify the image URL is publicly fetchable from the AI service (curl it from the host)
  3. Ensure RelayMediaResolverService is provided so relay/internal URLs are resolved before fetching
  4. Check bucket ACL/policy so the object is readable by the service

Example fix

// before
input_reference: 'https://s3.aitoearn.cn/private/ref.png' // expired presigned URL
// after
input_reference: await this.toAccessibleUrl(assetPath) // fresh presigned URL
Defensive patterns

Strategy: validation

Validate before calling

const res = await fetch(inputReferenceUrl, { method: 'HEAD' })
if (!res.ok) throw new Error(`Reference image unreachable: HTTP ${res.status}`)

Try / catch

try {
  await openaiVideoService.createVideo({ input_reference: url, ... })
} catch (e) {
  if (e instanceof AppException && e.code === ResponseCode.S3DownloadFileFailed) {
    // refresh presigned URL and retry once
  }
}

Prevention

When it happens

Trigger: Calling createVideo with input_reference set to a URL whose fetch fails: presigned URL expired, private S3/OSS object not accessible, DNS failure, or relay media resolution returned an unreachable URL. Any non-2xx response.ok triggers the throw.

Common situations: Presigned URLs generated minutes earlier have expired by generation time; storage bucket permissions changed; relay media resolver disabled (@Optional dependency missing) so an internal/private URL is fetched directly; typo in stored asset path.

Related errors


AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31). Data as JSON: /api/errors/5c71544cea3dd82e. Report an issue: GitHub.