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

  1. Check asset.type === 'VIDEO' (or has encodedVideoPath) before calling playback
  2. Use the appropriate endpoint for photos (thumbnail/original)
  3. 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

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


AI-assisted analysis of immich-app/immich@199723261c (2026-08-12). Data as JSON: /api/errors/d3a03e4f56f42467. Report an issue: GitHub.