From 8d70cc2dde81aad0f484a2f0d0c5b90e6e1b97cd Mon Sep 17 00:00:00 2001
From: Joe Rogers <1337joe@gmail.com>
Date: Thu, 7 Oct 2021 22:37:59 +0200
Subject: [PATCH] Add support for non-jpg image extractions
---
.../MediaEncoding/IMediaEncoder.cs | 3 +-
.../Encoder/MediaEncoder.cs | 21 +++----
.../Probing/ProbeResultNormalizer.cs | 18 ++----
.../MediaInfo/EmbeddedImageProvider.cs | 60 +++++++++++++------
4 files changed, 62 insertions(+), 40 deletions(-)
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index c5522bc3c..638588560 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -95,9 +95,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Media source information.
/// Media stream information.
/// Index of the stream to extract from.
+ /// The extension of the file to write.
/// CancellationToken to use for operation.
/// Location of video image.
- Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
+ Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken);
///
/// Extracts the video images on interval.
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 06fe95ce8..30bc7125d 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -468,17 +468,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
Protocol = MediaProtocol.File
};
- return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, cancellationToken);
+ return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", cancellationToken);
}
public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream videoStream, Video3DFormat? threedFormat, TimeSpan? offset, CancellationToken cancellationToken)
{
- return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, cancellationToken);
+ return ExtractImage(inputFile, container, videoStream, null, mediaSource, false, threedFormat, offset, "jpg", cancellationToken);
}
- public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken)
+ public Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken)
{
- return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, cancellationToken);
+ return ExtractImage(inputFile, container, imageStream, imageStreamIndex, mediaSource, false, null, null, outputExtension, cancellationToken);
}
private async Task ExtractImage(
@@ -490,6 +490,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
bool isAudio,
Video3DFormat? threedFormat,
TimeSpan? offset,
+ string outputExtension,
CancellationToken cancellationToken)
{
var inputArgument = GetInputArgument(inputFile, mediaSource);
@@ -499,7 +500,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
// The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -512,7 +513,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -525,7 +526,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, outputExtension, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -537,17 +538,17 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, outputExtension, cancellationToken).ConfigureAwait(false);
}
- private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, CancellationToken cancellationToken)
+ private async Task ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, string outputExtension, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
throw new ArgumentNullException(nameof(inputPath));
}
- var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + ".jpg");
+ var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension);
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar.
diff --git a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 5f6539495..775689095 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -582,7 +582,8 @@ namespace MediaBrowser.MediaEncoding.Probing
/// MediaAttachments.
private MediaAttachment GetMediaAttachment(MediaStreamInfo streamInfo)
{
- if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase))
+ if (!string.Equals(streamInfo.CodecType, "attachment", StringComparison.OrdinalIgnoreCase) &&
+ !(streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1))
{
return null;
}
@@ -735,15 +736,14 @@ namespace MediaBrowser.MediaEncoding.Probing
else if (string.Equals(stream.Codec, "mjpeg", StringComparison.OrdinalIgnoreCase))
{
// How to differentiate between video and embedded image?
- // check disposition, alternately: presence of codec tag, also embedded images have high (unusual) framerates
- if ((streamInfo.Disposition != null && streamInfo.Disposition.GetValueOrDefault("attached_pic") == 1) ||
- string.IsNullOrWhiteSpace(stream.CodecTag))
+ // The only difference I've seen thus far is presence of codec tag, also embedded images have high (unusual) framerates
+ if (!string.IsNullOrWhiteSpace(stream.CodecTag))
{
- stream.Type = MediaStreamType.EmbeddedImage;
+ stream.Type = MediaStreamType.Video;
}
else
{
- stream.Type = MediaStreamType.Video;
+ stream.Type = MediaStreamType.EmbeddedImage;
}
}
else
@@ -812,12 +812,6 @@ namespace MediaBrowser.MediaEncoding.Probing
{
stream.ColorPrimaries = streamInfo.ColorPrimaries;
}
-
- // workaround for mkv attached_pics losing filename due to being classified as video based on codec
- if (stream.Type == MediaStreamType.EmbeddedImage && streamInfo.Tags != null && string.IsNullOrEmpty(stream.Comment))
- {
- stream.Comment = GetDictionaryValue(streamInfo.Tags, "filename");
- }
}
else
{
diff --git a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
index 11fdfcdf9..ad95cdb06 100644
--- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
+using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -15,6 +16,7 @@ using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
+using MediaBrowser.Model.Net;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
@@ -115,25 +117,49 @@ namespace MediaBrowser.Providers.MediaInfo
Protocol = item.PathProtocol ?? MediaProtocol.File,
};
- string[] imageFileNames;
- switch (type)
+ string[] imageFileNames = type switch
{
- case ImageType.Backdrop:
- imageFileNames = _backdropImageFileNames;
- break;
- case ImageType.Logo:
- imageFileNames = _logoImageFileNames;
- break;
- case ImageType.Primary:
- default:
- imageFileNames = _primaryImageFileNames;
- break;
+ ImageType.Primary => _primaryImageFileNames,
+ ImageType.Backdrop => _backdropImageFileNames,
+ ImageType.Logo => _logoImageFileNames,
+ _ => _primaryImageFileNames
+ };
+
+ // Try attachments first
+ var attachmentSources = item.GetMediaSources(false).SelectMany(source => source.MediaAttachments).ToList();
+ var attachmentStream = attachmentSources
+ .Where(stream => !string.IsNullOrEmpty(stream.FileName))
+ .First(stream => imageFileNames.Any(name => stream.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)));
+
+ if (attachmentStream != null)
+ {
+ var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ?
+ Path.GetExtension(attachmentStream.FileName) :
+ MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "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
+ };
}
- var imageStreams =
- item.GetMediaStreams()
- .Where(i => i.Type == MediaStreamType.EmbeddedImage)
- .ToList();
+ // Fall back to EmbeddedImage streams
+ var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage);
if (!imageStreams.Any())
{
@@ -160,7 +186,7 @@ namespace MediaBrowser.Providers.MediaInfo
}
}
- string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false);
+ string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, "jpg", cancellationToken).ConfigureAwait(false);
return new DynamicImageResponse
{