aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs')
-rw-r--r--MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs222
1 files changed, 222 insertions, 0 deletions
diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
new file mode 100644
index 000000000..c41d36d76
--- /dev/null
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -0,0 +1,222 @@
+#nullable disable
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
+
+namespace MediaBrowser.Providers.MediaInfo
+{
+ /// <summary>
+ /// Uses <see cref="IMediaEncoder"/> to extract embedded images.
+ /// </summary>
+ public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder
+ {
+ private static readonly string[] _primaryImageFileNames =
+ {
+ "poster",
+ "folder",
+ "cover",
+ "default"
+ };
+
+ private static readonly string[] _backdropImageFileNames =
+ {
+ "backdrop",
+ "fanart",
+ "background",
+ "art"
+ };
+
+ private static readonly string[] _logoImageFileNames =
+ {
+ "logo",
+ };
+
+ private readonly IMediaEncoder _mediaEncoder;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="EmbeddedImageProvider"/> class.
+ /// </summary>
+ /// <param name="mediaEncoder">The media encoder for extracting attached/embedded images.</param>
+ public EmbeddedImageProvider(IMediaEncoder mediaEncoder)
+ {
+ _mediaEncoder = mediaEncoder;
+ }
+
+ /// <inheritdoc />
+ public string Name => "Embedded Image Extractor";
+
+ /// <inheritdoc />
+ // Default to after internet image providers but before Screen Grabber
+ public int Order => 99;
+
+ /// <inheritdoc />
+ public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
+ {
+ if (item is Video)
+ {
+ if (item is Episode)
+ {
+ return new[]
+ {
+ ImageType.Primary,
+ };
+ }
+
+ return new[]
+ {
+ ImageType.Primary,
+ ImageType.Backdrop,
+ ImageType.Logo,
+ };
+ }
+
+ return Array.Empty<ImageType>();
+ }
+
+ /// <inheritdoc />
+ public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
+ {
+ var video = (Video)item;
+
+ // No support for these
+ if (video.IsPlaceHolder || video.VideoType == VideoType.Dvd)
+ {
+ return Task.FromResult(new DynamicImageResponse { HasImage = false });
+ }
+
+ return GetEmbeddedImage(video, type, cancellationToken);
+ }
+
+ private async Task<DynamicImageResponse> GetEmbeddedImage(Video item, ImageType type, CancellationToken cancellationToken)
+ {
+ MediaSourceInfo mediaSource = new MediaSourceInfo
+ {
+ VideoType = item.VideoType,
+ IsoType = item.IsoType,
+ Protocol = item.PathProtocol ?? MediaProtocol.File,
+ };
+
+ string[] imageFileNames = type switch
+ {
+ ImageType.Primary => _primaryImageFileNames,
+ ImageType.Backdrop => _backdropImageFileNames,
+ ImageType.Logo => _logoImageFileNames,
+ _ => _primaryImageFileNames
+ };
+
+ // Try attachments first
+ var attachmentStream = item.GetMediaSources(false)
+ .SelectMany(source => source.MediaAttachments)
+ .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName)
+ && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ if (attachmentStream != null)
+ {
+ return await ExtractAttachment(item, cancellationToken, attachmentStream, mediaSource);
+ }
+
+ // Fall back to EmbeddedImage streams
+ var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage);
+
+ if (imageStreams.Count == 0)
+ {
+ // Can't extract if we don't have any EmbeddedImage streams
+ return new DynamicImageResponse { HasImage = false };
+ }
+
+ // Extract first stream containing an element of imageFileNames
+ var imageStream = imageStreams
+ .FirstOrDefault(stream => !string.IsNullOrEmpty(stream.Comment)
+ && imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ // Primary type only: default to first image if none found by label
+ if (imageStream == null)
+ {
+ if (type == ImageType.Primary)
+ {
+ imageStream = imageStreams[0];
+ }
+ else
+ {
+ // No streams matched, abort
+ return new DynamicImageResponse { HasImage = false };
+ }
+ }
+
+ string extractedImagePath =
+ await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, ".jpg", cancellationToken)
+ .ConfigureAwait(false);
+
+ return new DynamicImageResponse
+ {
+ Format = ImageFormat.Jpg,
+ HasImage = true,
+ Path = extractedImagePath,
+ Protocol = MediaProtocol.File
+ };
+ }
+
+ private async Task<DynamicImageResponse> ExtractAttachment(Video item, CancellationToken cancellationToken, MediaAttachment attachmentStream, MediaSourceInfo mediaSource)
+ {
+ var extension = string.IsNullOrEmpty(attachmentStream.MimeType)
+ ? Path.GetExtension(attachmentStream.FileName)
+ : MimeTypes.ToExtension(attachmentStream.MimeType);
+
+ if (string.IsNullOrEmpty(extension))
+ {
+ extension = ".jpg";
+ }
+
+ string extractedAttachmentPath =
+ await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, null, attachmentStream.Index, extension, cancellationToken)
+ .ConfigureAwait(false);
+
+ ImageFormat format = extension switch
+ {
+ ".bmp" => ImageFormat.Bmp,
+ ".gif" => ImageFormat.Gif,
+ ".jpg" => ImageFormat.Jpg,
+ ".png" => ImageFormat.Png,
+ ".webp" => ImageFormat.Webp,
+ _ => ImageFormat.Jpg
+ };
+
+ return new DynamicImageResponse
+ {
+ Format = format,
+ HasImage = true,
+ Path = extractedAttachmentPath,
+ Protocol = MediaProtocol.File
+ };
+ }
+
+ /// <inheritdoc />
+ public bool Supports(BaseItem item)
+ {
+ if (item.IsShortcut)
+ {
+ return false;
+ }
+
+ if (!item.IsFileProtocol)
+ {
+ return false;
+ }
+
+ return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia;
+ }
+ }
+}