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

playbackVideo streams the video rendition (encoded video, falling back to the original path). getForVideo returns null when the id does not exist or the record is not a video asset, so the service throws NotFoundException('Asset not found or asset is not a video').

Solutions

  1. Check asset.type === 'VIDEO' client-side before calling the playback endpoint.
  2. Verify the asset id is valid and the asset still exists (GET /api/assets/:id).
  3. If the asset should be a video, confirm the upload finished and the type was detected correctly.
  4. Fall back to the thumbnail/preview endpoint for non-video assets.

Example fix

// before
await fetch(`${base}/api/assets/${id}/video/playback`);
// after
const asset = await (await fetch(`${base}/api/assets/${id}`)).json();
if (asset.type === 'VIDEO') await fetch(`${base}/api/assets/${id}/video/playback`);
Defensive patterns

Strategy: validation

Validate before calling

const asset = await getAssetInfo(id);
if (asset.type !== 'VIDEO') throw new Error(`Asset ${id} is not a video`);

Type guard

const isVideo = (a: { type: string }): a is { type: 'VIDEO' } => a.type === 'VIDEO';

Try / catch

try { return await getVideoPlayback(id); } catch (e) { if (e.status === 404) return null; throw e; }

Prevention

When it happens

Trigger: GET /api/assets/:id/video/playback with an id belonging to a photo/JPEG asset, a deleted asset, or a malformed/nonexistent id.

Common situations: Clients hardcoding a video endpoint for mixed asset lists; stale ids after an asset was deleted or the library was re-scanned; shared-link access to an asset excluded from the link.

Understand the failure class

Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.

Related errors


AI-assisted analysis of immich-app/immich@e55ac299a4 (2026-09-15). Data as JSON: /api/errors/d3a03e4f56f42467. Report an issue: GitHub.

Appendix: 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 e55ac299a4)