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
- Only request thumbnails when the asset mimeType starts with 'video/'
- Ensure uploads preserve correct file extensions so mime.lookup yields video/*
- Check the asset's mimeType via the asset detail endpoint before calling this endpoint
- 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
- Check asset.mimeType before requesting thumbnails
- Preserve file extensions on upload so mime detection works
- Only use the thumbnail endpoint for videos; use direct URL for images
- Handle unknown mimeType as non-video defensively
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
- No subtitle entries in response
- No response from Gemini
- Invalid subtitle data: ${z.prettifyError(result.error)}
- Canvas not provided and no vid:// video source found in Trac
- Canvas not provided and failed to retrieve video dimensions
AI-assisted analysis of yikart/AiToEarn@d3aa8bea5b (2026-08-31).
Data as JSON: /api/errors/0296e26e78d2d35f.
Report an issue: GitHub.