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
- Re-run the Video Metadata (metadata extraction) job for the asset, then retry transcoding
- Check ffprobe output for the file: the container may be corrupt or unsupported
- Verify the metadata queue is ordered before transcoding for new uploads
- 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
- Keep the metadata queue ordered before transcoding
- Re-run video metadata after server/schema upgrades
- Verify ffprobe succeeds on all ingested videos
- Alert when transcode failures correlate with null stream rows
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
- Skipped transcoding for asset
- Asset dimensions are not available for editing
- Asset does not have valid dimensions
- Asset metadata is not yet ready for streaming
- Asset not found or asset is not a video
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)