immich-app/immich · warning

Skipped transcoding for asset

Error message

Skipped transcoding for asset ${asset.id}: missing metadata; re-run extraction first

What it means

Video transcoding requires the asset's video stream and container format metadata, which are populated by a prior metadata-extraction step. If either is missing, the handler logs this warning and returns JobStatus.Failed — it cannot build a valid ffmpeg command without stream/format info.

Solutions

  1. Re-run the Video Metadata (metadata extraction) job for the asset, then retry transcoding
  2. Check ffprobe output for the file: the container may be corrupt or unsupported
  3. Verify the metadata queue is ordered before transcoding for new uploads
  4. If many assets hit this after an upgrade, run a full metadata re-extraction

Example fix

// before
if (!videoStream || !format) { this.logger.warn(`Skipped transcoding for asset ${asset.id}: missing metadata; re-run extraction first`); return JobStatus.Failed; }
// after
if (!videoStream || !format) { this.logger.warn(`Skipped transcoding for asset ${asset.id}: missing metadata; re-run extraction first`); await this.jobRepository.queue({ name: JobName.VideoMetadata, data: { id: asset.id } }); return JobStatus.Failed; }
Defensive patterns

Strategy: retry

Validate before calling

if (!asset.videoStream || !asset.format) {
  await jobRepository.queue({ name: JobName.VideoMetadata, data: { id: asset.id } }); // re-extract first
}

Try / catch

if (!videoStream || !format) {
  this.logger.warn(`Skipped transcoding for asset ${asset.id}: missing metadata`);
  await this.jobRepository.queue({ name: JobName.VideoMetadata, data: { id: asset.id } });
  return JobStatus.Failed;
}

Prevention

When it happens

Trigger: handleVideoConversion runs on an asset whose videoStream or format is null because metadata extraction never ran, failed, or predates the schema that stores this metadata.

Common situations: Transcode job executed before Video Metadata job completed; ffprobe failed silently during extraction; assets imported from an older schema lacking stream rows; corrupted video ffprobe cannot parse.

Understand the failure class

Background: "not installed", "pip install", "required for": how missing-dependency errors surface across open-source libraries — this error's family across 34 libraries.

Related errors


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

Appendix: source

Thrown at server/src/services/media.service.ts:561

    return JobStatus.Success;
  }

  @OnJob({ name: JobName.AssetEncodeVideo, queue: QueueName.VideoConversion })
  async handleVideoConversion({ id }: JobOf<JobName.AssetEncodeVideo>): Promise<JobStatus> {
    const asset = await this.assetJobRepository.getForVideoConversion(id);
    if (!asset) {
      return JobStatus.Failed;
    }

    const input = asset.originalPath;
    const output = StorageCore.getEncodedVideoPath(asset);
    this.storageCore.ensureFolders(output);

    const { videoStream, format } = asset;
    const audioStream = asset.audioStream ?? undefined;
    if (!videoStream || !format) {
      this.logger.warn(`Skipped transcoding for asset ${asset.id}: missing metadata; re-run extraction first`);
      return JobStatus.Failed;
    }
    if (!videoStream.height || !videoStream.width) {
      this.logger.warn(`Skipped transcoding for asset ${asset.id}: no video dimensions`);
      return JobStatus.Failed;
    }

    let { ffmpeg } = await this.getConfig({ withCache: true });
    const target = this.getTranscodeTarget(ffmpeg, videoStream, audioStream);
    if (target === TranscodeTarget.None && !this.isRemuxRequired(ffmpeg, format)) {
      const encodedVideo = getAssetFile(asset.files, AssetFileType.EncodedVideo, { isEdited: false });
      if (encodedVideo) {
        this.logger.log(`Transcoded video exists for asset ${asset.id}, but is no longer required. Deleting...`);
        await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [encodedVideo.path] } });
        await this.assetRepository.deleteFiles([encodedVideo]);
      } else {
        this.logger.verbose(`Asset ${asset.id} does not require transcoding based on current policy, skipping`);
      }

View on GitHub (pinned to e55ac299a4)