< Summary - Jellyfin

Information
Class: MediaBrowser.Providers.MediaInfo.EmbeddedImageProvider
Assembly: MediaBrowser.Providers
File(s): /srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
Line coverage
94%
Covered lines: 117
Uncovered lines: 7
Coverable lines: 124
Total lines: 245
Line coverage: 94.3%
Branch coverage
84%
Covered branches: 44
Total branches: 52
Branch coverage: 84.6%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Coverage history

Coverage history 0 25 50 75 100 1/23/2026 - 12:11:06 AM Line coverage: 89.3% (42/47) Branch coverage: 62.5% (10/16) Total lines: 2454/19/2026 - 12:14:27 AM Line coverage: 94.3% (117/124) Branch coverage: 84.6% (44/52) Total lines: 245 1/23/2026 - 12:11:06 AM Line coverage: 89.3% (42/47) Branch coverage: 62.5% (10/16) Total lines: 2454/19/2026 - 12:14:27 AM Line coverage: 94.3% (117/124) Branch coverage: 84.6% (44/52) Total lines: 245

Coverage delta

Coverage delta 23 -23

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
.ctor(...)100%11100%
get_Name()100%210%
get_Order()100%210%
GetSupportedImages(...)100%44100%
GetImage(...)50%4475%
GetEmbeddedImage()100%2626100%
ExtractAttachment()80%101090.9%
Supports(...)50%12860%

File(s)

/srv/git/jellyfin/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs

#LineLine coverage
 1#nullable disable
 2
 3using System;
 4using System.Collections.Generic;
 5using System.IO;
 6using System.Linq;
 7using System.Threading;
 8using System.Threading.Tasks;
 9using MediaBrowser.Controller.Entities;
 10using MediaBrowser.Controller.Entities.TV;
 11using MediaBrowser.Controller.Library;
 12using MediaBrowser.Controller.MediaEncoding;
 13using MediaBrowser.Controller.Persistence;
 14using MediaBrowser.Controller.Providers;
 15using MediaBrowser.Model.Drawing;
 16using MediaBrowser.Model.Dto;
 17using MediaBrowser.Model.Entities;
 18using MediaBrowser.Model.MediaInfo;
 19using MediaBrowser.Model.Net;
 20using Microsoft.Extensions.Logging;
 21
 22namespace MediaBrowser.Providers.MediaInfo
 23{
 24    /// <summary>
 25    /// Uses <see cref="IMediaEncoder"/> to extract embedded images.
 26    /// </summary>
 27    public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder
 28    {
 129        private static readonly string[] _primaryImageFileNames =
 130        {
 131            "poster",
 132            "folder",
 133            "cover",
 134            "default",
 135            "movie",
 136            "show"
 137        };
 38
 139        private static readonly string[] _backdropImageFileNames =
 140        {
 141            "backdrop",
 142            "background",
 143            "art"
 144        };
 45
 146        private static readonly string[] _logoImageFileNames =
 147        {
 148            "logo",
 149        };
 50
 51        private readonly IMediaSourceManager _mediaSourceManager;
 52        private readonly IMediaEncoder _mediaEncoder;
 53        private readonly ILogger<EmbeddedImageProvider> _logger;
 54
 55        /// <summary>
 56        /// Initializes a new instance of the <see cref="EmbeddedImageProvider"/> class.
 57        /// </summary>
 58        /// <param name="mediaSourceManager">The media source manager for fetching item streams and attachments.</param>
 59        /// <param name="mediaEncoder">The media encoder for extracting attached/embedded images.</param>
 60        /// <param name="logger">The logger.</param>
 61        public EmbeddedImageProvider(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ILogger<Embedde
 62        {
 4363            _mediaSourceManager = mediaSourceManager;
 4364            _mediaEncoder = mediaEncoder;
 4365            _logger = logger;
 4366        }
 67
 68        /// <inheritdoc />
 069        public string Name => "Embedded Image Extractor";
 70
 71        /// <inheritdoc />
 72        // Default to after internet image providers but before Screen Grabber
 073        public int Order => 99;
 74
 75        /// <inheritdoc />
 76        public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
 77        {
 678            if (item is Video)
 79            {
 280                if (item is Episode)
 81                {
 182                    return new[]
 183                    {
 184                        ImageType.Primary,
 185                    };
 86                }
 87
 188                return new[]
 189                {
 190                    ImageType.Primary,
 191                    ImageType.Backdrop,
 192                    ImageType.Logo,
 193                };
 94            }
 95
 496            return Array.Empty<ImageType>();
 97        }
 98
 99        /// <inheritdoc />
 100        public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
 101        {
 16102            var video = (Video)item;
 103
 104            // No support for these
 16105            if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd)
 106            {
 0107                return Task.FromResult(new DynamicImageResponse { HasImage = false });
 108            }
 109
 16110            return GetEmbeddedImage(video, type, cancellationToken);
 111        }
 112
 113        private async Task<DynamicImageResponse> GetEmbeddedImage(Video item, ImageType type, CancellationToken cancella
 114        {
 16115            MediaSourceInfo mediaSource = new MediaSourceInfo
 16116            {
 16117                VideoType = item.VideoType,
 16118                IsoType = item.IsoType,
 16119                Protocol = item.PathProtocol ?? MediaProtocol.File,
 16120            };
 121
 16122            string[] imageFileNames = type switch
 16123            {
 10124                ImageType.Primary => _primaryImageFileNames,
 3125                ImageType.Backdrop => _backdropImageFileNames,
 1126                ImageType.Logo => _logoImageFileNames,
 2127                _ => Array.Empty<string>()
 16128            };
 129
 16130            if (imageFileNames.Length == 0)
 131            {
 2132                _logger.LogWarning("Attempted to load unexpected image type: {Type}", type);
 2133                return new DynamicImageResponse { HasImage = false };
 134            }
 135
 136            // Try attachments first
 14137            var attachmentStream = _mediaSourceManager.GetMediaAttachments(item.Id)
 14138                .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName)
 14139                    && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)
 140
 14141            if (attachmentStream is not null)
 142            {
 3143                return await ExtractAttachment(item, attachmentStream, mediaSource, cancellationToken).ConfigureAwait(fa
 144            }
 145
 146            // Fall back to EmbeddedImage streams
 11147            var imageStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery
 11148            {
 11149                ItemId = item.Id,
 11150                Type = MediaStreamType.EmbeddedImage
 11151            });
 152
 11153            if (imageStreams.Count == 0)
 154            {
 155                // Can't extract if we don't have any EmbeddedImage streams
 2156                return new DynamicImageResponse { HasImage = false };
 157            }
 158
 159            // Extract first stream containing an element of imageFileNames
 9160            var imageStream = imageStreams
 9161                .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment)
 9162                    && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase)));
 163
 164            // Primary type only: default to first image if none found by label
 9165            if (imageStream is null)
 166            {
 7167                if (type == ImageType.Primary)
 168                {
 6169                    imageStream = imageStreams[0];
 170                }
 171                else
 172                {
 173                    // No streams matched, abort
 1174                    return new DynamicImageResponse { HasImage = false };
 175                }
 176            }
 177
 8178            var format = imageStream.Codec switch
 8179            {
 1180                "bmp" => ImageFormat.Bmp,
 1181                "gif" => ImageFormat.Gif,
 1182                "mjpeg" => ImageFormat.Jpg,
 1183                "png" => ImageFormat.Png,
 1184                "webp" => ImageFormat.Webp,
 3185                _ => ImageFormat.Jpg
 8186            };
 187
 8188            string extractedImagePath =
 8189                await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.I
 8190                    .ConfigureAwait(false);
 191
 8192            return new DynamicImageResponse
 8193            {
 8194                Format = format,
 8195                HasImage = true,
 8196                Path = extractedImagePath,
 8197                Protocol = MediaProtocol.File
 8198            };
 16199        }
 200
 201        private async Task<DynamicImageResponse> ExtractAttachment(Video item, MediaAttachment attachmentStream, MediaSo
 202        {
 3203            var extension = string.IsNullOrEmpty(attachmentStream.MimeType)
 3204                ? Path.GetExtension(attachmentStream.FileName)
 3205                : MimeTypes.ToExtension(attachmentStream.MimeType);
 206
 3207            ImageFormat format = extension switch
 3208            {
 1209                ".bmp" => ImageFormat.Bmp,
 0210                ".gif" => ImageFormat.Gif,
 1211                ".png" => ImageFormat.Png,
 0212                ".webp" => ImageFormat.Webp,
 1213                _ => ImageFormat.Jpg
 3214            };
 215
 3216            string extractedAttachmentPath =
 3217                await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Ind
 3218                    .ConfigureAwait(false);
 219
 3220            return new DynamicImageResponse
 3221            {
 3222                Format = format,
 3223                HasImage = true,
 3224                Path = extractedAttachmentPath,
 3225                Protocol = MediaProtocol.File
 3226            };
 3227        }
 228
 229        /// <inheritdoc />
 230        public bool Supports(BaseItem item)
 231        {
 57232            if (item.IsShortcut)
 233            {
 0234                return false;
 235            }
 236
 57237            if (!item.IsFileProtocol)
 238            {
 0239                return false;
 240            }
 241
 57242            return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia;
 243        }
 244    }
 245}