This page covers thumbnail and preview generation for photos and videos, video transcoding with FFmpeg, supported image and video formats, and hardware acceleration integration for transcoding. For EXIF and metadata extraction see 3.3 For the job queue system that drives these operations see 3.2 For hardware acceleration setup and deployment see 5.2
All media processing is orchestrated by MediaService server/src/services/media.service.ts61 It uses @OnJob() decorators to register handlers onto BullMQ queues. The actual image and video operations are delegated to MediaRepository server/src/repositories/media.repository.ts69 which wraps sharp (image processing) and fluent-ffmpeg (video probing and transcoding).
Job handler summary:
| Job Name (enum) | Queue | Handler Method |
|---|---|---|
AssetGenerateThumbnailsQueueAll | ThumbnailGeneration | handleQueueGenerateThumbnails |
AssetGenerateThumbnails | ThumbnailGeneration | handleGenerateThumbnails |
AssetEditThumbnailGeneration | Editor | handleAssetEditThumbnailGeneration |
PersonGenerateThumbnail | ThumbnailGeneration | handleGeneratePersonThumbnail |
AssetEncodeVideo | VideoConversion | handleEncodeVideo |
FileMigrationQueueAll | Migration | handleQueueMigration |
AssetFileMigration | Migration | handleAssetMigration |
Sources: server/src/services/media.service.ts69-120 server/src/services/media.service.ts154-170
The handleGenerateThumbnails method dispatches to one of two code paths depending on asset type:
asset.type === AssetType.Video or GIF → generateVideoThumbnails()asset.type === AssetType.Image → generateImageThumbnails()Hidden assets (AssetVisibility.Hidden) are skipped. Edited assets trigger an additional generateEditedThumbnails() call. After all files are written, the thumbhash column on the asset is updated server/src/services/media.service.ts246-248
Thumbnail generation flow:
Sources: server/src/services/media.service.ts210-250 server/src/repositories/media.repository.ts178-187
Three file types, stored as asset_file records (type AssetFileType), are produced per asset:
AssetFileType | Default Format | Default Size | Description |
|---|---|---|---|
Preview | JPEG | 1440 px | Used in asset viewer |
Thumbnail | WebP | 250 px | Used in grid timeline |
FullSize | JPEG | original | Non-web-native formats and panoramas |
FullSize is generated when image.fullsize.enabled is true, or when the source is a RAW format not natively viewable in a browser server/src/queries/asset.job.repository.sql118-172 Storage paths follow the pattern from StorageCore.
Sources: server/src/services/media.service.ts314-406 server/src/repositories/asset-job.repository.ts117-145
decodeImage() in MediaRepository uses Sharp to decode the image to a raw pixel buffer server/src/repositories/media.repository.ts151-153 The colorspace is determined by the configured image.colorspace (defaulting to sRGB or P3 depending on bit depth) server/src/repositories/media.repository.ts197-198
For RAW files, MediaRepository.extract() attempts to pull an embedded binary tag (e.g., JpgFromRaw, PreviewImage) server/src/repositories/media.repository.ts79-94
Sources: server/src/repositories/media.repository.ts79-94 server/src/repositories/media.repository.ts189-210
MediaRepository.generateThumbhash() computes a compact perceptual hash from the decoded image buffer. This is stored on the asset.thumbhash column server/src/services/media.service.ts199-201 and used by clients to display a blurry placeholder before the full thumbnail loads.
handleGeneratePersonThumbnail server/src/services/media.service.ts408 generates face crop thumbnails. The face bounding box from the database is used to crop using FACE_THUMBNAIL_SIZE (250px) server/src/constants.ts3
handleEncodeVideo reads config.ffmpeg.transcode to determine whether to transcode. The TranscodePolicy enum controls this server/src/enum.ts20
| Policy | Behavior |
|---|---|
Disabled | Never transcode; serve original |
All | Always transcode every video |
Optimal | Transcode only if codec/container not web-compatible |
BitrateThreshold | Transcode if bitrate exceeds config.ffmpeg.maxBitrate |
BaseConfig.create() server/src/utils/media.ts28-33 is the factory entry point. It returns a VideoCodecSWConfig or VideoCodecHWConfig depending on the accel setting. All configurations implement getCommand() which returns a TranscodeCommand server/src/utils/media.ts95-121
Codec class hierarchy:
Sources: server/src/utils/media.ts24-93
On AppBootstrap, MediaService.onBootstrap() server/src/services/media.service.ts65-67 scans for available hardware interfaces using storageCore.getVideoInterfaces().
TranscodeHardwareAcceleration | SW Decode Class | HW Decode Class |
|---|---|---|
Nvenc | NvencSwDecodeConfig | NvencHwDecodeConfig |
Qsv | QsvSwDecodeConfig | QsvHwDecodeConfig |
Vaapi | VaapiSwDecodeConfig | VaapiHwDecodeConfig |
Rkmpp | RkmppSwDecodeConfig | RkmppHwDecodeConfig |
Sources: server/src/utils/media.ts55-93
getCommand() in BaseConfig builds options in layers server/src/utils/media.ts95-121:
-movflags faststart, stream mapping server/src/utils/media.ts128-170Immich's format support is defined in mimeTypes server/src/utils/mime-types.ts:
.arw, .cr2, .dng, .nef, .raf, etc. server/src/queries/asset.job.repository.sql129-158.jpeg, .png, .webp, .heic, .jxl, .avif server/src/queries/asset.job.repository.sql159-170.mp4, .mov, .webm, .avi, .mkv, etc.
When the storage template or output format configuration changes, the Migration queue handles moving existing generated files to their new paths.
handleQueueMigration server/src/services/media.service.ts120-152 streams all assets via assetJobRepository.streamForMigrationJob() and queues AssetFileMigration jobs. It also removes empty directories from the Thumbnails and EncodedVideo storage folders server/src/services/media.service.ts123-124
handleAssetMigration server/src/services/media.service.ts155-168 calls StorageCore.moveAssetImage() for each of the image file types and StorageCore.moveAssetVideo() for the encoded video.
Refresh this wiki