immich-app/immich · error · NotFoundException
Asset not found or asset is not a video
Error message
Asset not found or asset is not a video
What it means
Thrown by AssetMediaService.playbackVideo as NotFoundException (HTTP 404) when assetRepository.getForVideo(id) returns null. getForVideo only returns rows that are videos (or have an encodedVideoPath), so the asset either does not exist or is not a video type. Playback is only valid for video assets.
Source
Thrown at server/src/services/asset-media.service.ts:306
const fileNameBase =
auth.sharedLink && !auth.sharedLink.showExif ? id : getFileNameWithoutExtension(originalFileName);
const fileName = `${fileNameBase}_${size}${getFilenameExtension(path)}`;
return new ImmichFileResponse({
fileName,
path,
contentType: mimeTypes.lookup(path),
cacheControl: CacheControl.PrivateWithCache,
});
}
async playbackVideo(auth: AuthDto, id: string): Promise<ImmichFileResponse> {
await this.requireAccess({ auth, permission: Permission.AssetView, ids: [id] });
const asset = await this.assetRepository.getForVideo(id);
if (!asset) {
throw new NotFoundException('Asset not found or asset is not a video');
}
const filepath = asset.encodedVideoPath || asset.originalPath;
return new ImmichFileResponse({
path: filepath,
contentType: mimeTypes.lookup(filepath),
cacheControl: CacheControl.PrivateWithCache,
});
}
async bulkUploadCheck(auth: AuthDto, dto: AssetBulkUploadCheckDto): Promise<AssetBulkUploadCheckResponseDto> {
const checksums: Buffer[] = dto.assets.map((asset) => fromChecksum(asset.checksum));
const results = await this.assetRepository.getByChecksums(auth.user.id, checksums);
const checksumMap: Record<string, { id: string; isTrashed: boolean }> = {};
for (const { id, deletedAt, checksum } of results) {
checksumMap[checksum.toString('hex')] = { id, isTrashed: !!deletedAt };View on GitHub (pinned to 199723261c)
Solutions
- Check asset.type === 'VIDEO' (or has encodedVideoPath) before calling playback
- Use the appropriate endpoint for photos (thumbnail/original)
- Confirm the asset id exists via GET /assets/:id first
Example fix
// before
await sdk.playbackAsset(maybePhotoId); // 404
// after
const asset = await sdk.getAsset(maybePhotoId);
if (asset.type !== 'VIDEO') throw new Error('not a video');
await sdk.playbackAsset(maybePhotoId); Defensive patterns
Strategy: validation
Validate before calling
// Only call playback for video assets
const asset = await sdk.getAsset(id);
if (!asset || asset.type !== 'VIDEO') {
throw new Error('asset is not a video');
}
await sdk.playbackAsset(id); Type guard
function isVideoAsset(a: { type?: string; encodedVideoPath?: string | null }): boolean {
return a?.type === 'VIDEO' || !!a?.encodedVideoPath;
} Prevention
- Check asset.type === 'VIDEO' before invoking playback
- Use the thumbnail/original endpoint for photos
- Confirm the asset id exists before requesting playback
When it happens
Trigger: GET /assets/:id/playback where :id is a photo (not a video), or the asset id does not exist at all. Also possible if the asset exists but has no encoded video path and is not classified as a video.
Common situations: UI invoking playback on a non-video asset; live-photo motion asset referenced incorrectly; client guessing that an id is a video without checking type.
Related errors
- Both assets must exist
- May not request original file
- Asset media not found
- Asset not found
- Source and target id must be distinct
AI-assisted analysis of immich-app/immich@199723261c (2026-08-12).
Data as JSON: /api/errors/d3a03e4f56f42467.
Report an issue: GitHub.