yikart/AiToEarn · error · BadRequestException

Asset is not a video

Error message

Asset is not a video

What it means

BadRequestException('Asset is not a video') is thrown by the thumbnail endpoint when the asset resolved from query.url has a mimeType that is not video/*. Thumbnails/covers are generated only for video assets, so images, PDFs, or assets with unknown mime type are rejected.

Source

Thrown at project/aitoearn-backend/libs/assets/src/http/assets-http.controller-base.ts:136

  }

  @ApiDoc({
    summary: 'Get Video Thumbnail',
    description: 'Get or extract thumbnail from a video by URL. If thumbnail already exists in metadata.cover, returns it directly. Otherwise extracts a new thumbnail.',
    query: GetThumbnailQueryDto.schema,
    response: ThumbnailResultVo,
  })
  @Get('/thumbnail')
  async getThumbnail(
    @GetToken() token: TokenInfo,
    @Query() query: GetThumbnailQueryDto,
    @Res({ passthrough: true }) res: Response,
  ) {
    const path = this.assetsService.parsePathFromUrl(query.url)
    const asset = await this.assetsService.getOrCreateAssetByPath(path, token.id, this.userType)

    if (!asset.mimeType?.startsWith('video/')) {
      throw new BadRequestException('Asset is not a video')
    }

    const existingCover = (asset.metadata as { cover?: string } | undefined)?.cover
    if (existingCover) {
      const thumbnailUrl = this.assetsService.buildUrl(existingCover)
      if (query.redirect) {
        res.redirect(302, thumbnailUrl)
        return
      }
      return ThumbnailResultVo.create({ thumbnailUrl })
    }

    const result = await this.videoMetadataService.extractAndSaveThumbnail(
      asset,
      token.id,
      this.userType,
      { timeInSeconds: query.timeInSeconds },
    )

View on GitHub (pinned to d3aa8bea5b)

Solutions

  1. Only request thumbnails when the asset mimeType starts with 'video/'
  2. Ensure uploads preserve correct file extensions so mime.lookup yields video/*
  3. Check the asset's mimeType via the asset detail endpoint before calling this endpoint
  4. If a cover is needed for images, use the image URL directly instead of this endpoint

Example fix

// before
await api.get(`/assets/thumbnail?url=${encodeURIComponent(anyAsset.url)}`)
// after
if (asset.mimeType?.startsWith('video/')) {
  await api.get(`/assets/thumbnail?url=${encodeURIComponent(asset.url)}`)
}
Defensive patterns

Strategy: type-guard

Validate before calling

const asset = await getAssetByUrl(url)
if (!asset.mimeType?.startsWith('video/')) throw new Error('thumbnail only available for video assets')

Type guard

function isVideoAsset(a: { mimeType?: string | null }): boolean {
  return typeof a.mimeType === 'string' && a.mimeType.startsWith('video/')
}

Try / catch

try {
  await api.get(`/assets/thumbnail?url=${encodeURIComponent(url)}`)
} catch (e) {
  if (e.response?.status === 400 && e.response.data?.message === 'Asset is not a video') {
    // fall back to showing the original asset inline
  } else throw e
}

Prevention

When it happens

Trigger: GET thumbnail endpoint with query.url pointing to an image or non-media file; the asset's mimeType is null/undefined (unknown type); passing a URL whose path resolves to a non-video asset for the given user.

Common situations: Frontend blindly requests thumbnails for every attachment including images; asset was uploaded without a recognizable extension so mimeType detection fell back to a non-video type; URL points at the wrong file after a path refactor.

Related errors


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