From e3eee10d05e9ecc7e3fac1f8fdad92329d38a4db Mon Sep 17 00:00:00 2001
From: Joe Rogers <1337joe@gmail.com>
Date: Mon, 11 Oct 2021 12:34:18 +0200
Subject: [PATCH] Add image provider tests and clean up
---
.../MediaEncoding/IMediaEncoder.cs | 2 +-
.../Encoder/MediaEncoder.cs | 6 +-
.../MediaInfo/EmbeddedImageProvider.cs | 41 ++--
.../MediaInfo/VideoImageProvider.cs | 9 +-
.../MediaInfo/EmbeddedImageProviderTests.cs | 211 ++++++++++++++++++
.../MediaInfo/VideoImageProviderTests.cs | 168 ++++++++++++++
6 files changed, 408 insertions(+), 29 deletions(-)
create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
create mode 100644 tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 638588560d..e6511ca8d7 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Media source information.
/// Media stream information.
/// Index of the stream to extract from.
- /// The extension of the file to write.
+ /// The extension of the file to write, including the '.'.
/// CancellationToken to use for operation.
/// Location of video image.
Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, string outputExtension, CancellationToken cancellationToken);
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 30bc7125d7..dac2c6a26a 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -468,12 +468,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
Protocol = MediaProtocol.File
};
- return ExtractImage(path, null, null, imageStreamIndex, mediaSource, true, null, null, "jpg", 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, "jpg", 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, string outputExtension, CancellationToken cancellationToken)
@@ -548,7 +548,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
throw new ArgumentNullException(nameof(inputPath));
}
- var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + "." + outputExtension);
+ 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.Providers/MediaInfo/EmbeddedImageProvider.cs b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
index ad95cdb06f..df87f2d49c 100644
--- a/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
@@ -1,9 +1,7 @@
-#nullable enable
#pragma warning disable CS1591
using System;
using System.Collections.Generic;
-using System.Collections.Immutable;
using System.IO;
using System.Linq;
using System.Threading;
@@ -17,7 +15,6 @@ using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
using MediaBrowser.Model.Net;
-using Microsoft.Extensions.Logging;
namespace MediaBrowser.Providers.MediaInfo
{
@@ -48,12 +45,10 @@ namespace MediaBrowser.Providers.MediaInfo
};
private readonly IMediaEncoder _mediaEncoder;
- private readonly ILogger _logger;
- public EmbeddedImageProvider(IMediaEncoder mediaEncoder, ILogger logger)
+ public EmbeddedImageProvider(IMediaEncoder mediaEncoder)
{
_mediaEncoder = mediaEncoder;
- _logger = logger;
}
///
@@ -84,7 +79,7 @@ namespace MediaBrowser.Providers.MediaInfo
};
}
- return ImmutableList.Empty;
+ return new List();
}
///
@@ -98,13 +93,6 @@ namespace MediaBrowser.Providers.MediaInfo
return Task.FromResult(new DynamicImageResponse { HasImage = false });
}
- // Can't extract if we didn't find any video streams in the file
- if (!video.DefaultVideoStreamIndex.HasValue)
- {
- _logger.LogInformation("Skipping image extraction due to missing DefaultVideoStreamIndex for {Path}.", video.Path ?? string.Empty);
- return Task.FromResult(new DynamicImageResponse { HasImage = false });
- }
-
return GetEmbeddedImage(video, type, cancellationToken);
}
@@ -128,24 +116,29 @@ namespace MediaBrowser.Providers.MediaInfo
// 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)));
+ .Where(attachment => !string.IsNullOrEmpty(attachment.FileName))
+ .FirstOrDefault(attachment => imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)));
if (attachmentStream != null)
{
- var extension = (string.IsNullOrEmpty(attachmentStream.MimeType) ?
+ var extension = string.IsNullOrEmpty(attachmentStream.MimeType) ?
Path.GetExtension(attachmentStream.FileName) :
- MimeTypes.ToExtension(attachmentStream.MimeType)) ?? "jpg";
+ 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,
+ ".bmp" => ImageFormat.Bmp,
+ ".gif" => ImageFormat.Gif,
+ ".jpg" => ImageFormat.Jpg,
+ ".png" => ImageFormat.Png,
+ ".webp" => ImageFormat.Webp,
_ => ImageFormat.Jpg
};
@@ -170,7 +163,7 @@ namespace MediaBrowser.Providers.MediaInfo
// Extract first stream containing an element of imageFileNames
var imageStream = imageStreams
.Where(stream => !string.IsNullOrEmpty(stream.Comment))
- .First(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase)));
+ .FirstOrDefault(stream => imageFileNames.Any(name => stream.Comment.Contains(name, StringComparison.OrdinalIgnoreCase)));
// Primary type only: default to first image if none found by label
if (imageStream == null)
diff --git a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
index 8f20099505..60739f1564 100644
--- a/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
+++ b/MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
@@ -81,7 +81,14 @@ namespace MediaBrowser.Providers.MediaInfo
? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10)
: TimeSpan.FromSeconds(10);
- var videoStream = item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video);
+ var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video);
+
+ if (videoStream == null)
+ {
+ _logger.LogInformation("Skipping image extraction: no video stream found for {Path}.", item.Path ?? string.Empty);
+ return new DynamicImageResponse { HasImage = false };
+ }
+
string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false);
return new DynamicImageResponse
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
new file mode 100644
index 0000000000..fcea1532af
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
@@ -0,0 +1,211 @@
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.Entities.TV;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.MediaInfo;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo
+{
+ public class EmbeddedImageProviderTests
+ {
+ public static TheoryData GetSupportedImages_Empty_TestData =>
+ new ()
+ {
+ new AudioBook(),
+ new BoxSet(),
+ new Series(),
+ new Season(),
+ };
+
+ public static TheoryData> GetSupportedImages_Populated_TestData =>
+ new TheoryData>
+ {
+ { new Episode(), new List { ImageType.Primary } },
+ { new Movie(), new List { ImageType.Logo, ImageType.Backdrop, ImageType.Primary } },
+ };
+
+ private EmbeddedImageProvider GetEmbeddedImageProvider(IMediaEncoder? mediaEncoder)
+ {
+ return new EmbeddedImageProvider(mediaEncoder);
+ }
+
+ [Theory]
+ [MemberData(nameof(GetSupportedImages_Empty_TestData))]
+ public void GetSupportedImages_Empty(BaseItem item)
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+ Assert.False(embeddedImageProvider.GetSupportedImages(item).Any());
+ }
+
+ [Theory]
+ [MemberData(nameof(GetSupportedImages_Populated_TestData))]
+ public void GetSupportedImages_Populated(BaseItem item, IEnumerable expected)
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+ var actual = embeddedImageProvider.GetSupportedImages(item);
+ Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString()));
+ }
+
+ [Fact]
+ public async void GetImage_Empty_NoStreams()
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaSources(It.IsAny()))
+ .Returns(new List());
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List());
+
+ var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_Empty_NoLabeledAttachments()
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+
+ var input = new Mock();
+ // add an attachment without a filename - has a list to look through but finds nothing
+ input.Setup(movie => movie.GetMediaSources(It.IsAny()))
+ .Returns(new List { new () { MediaAttachments = new List { new () } } });
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List());
+
+ var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_Empty_NoEmbeddedLabeledBackdrop()
+ {
+ var embeddedImageProvider = GetEmbeddedImageProvider(null);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaSources(It.IsAny()))
+ .Returns(new List());
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List { new () { Type = MediaStreamType.EmbeddedImage } });
+
+ var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_Attached()
+ {
+ // first tests file extension detection, second uses mimetype, third defaults to jpg
+ MediaAttachment sampleAttachment1 = new () { FileName = "clearlogo.png", Index = 1 };
+ MediaAttachment sampleAttachment2 = new () { FileName = "backdrop", MimeType = "image/bmp", Index = 2 };
+ MediaAttachment sampleAttachment3 = new () { FileName = "poster", Index = 3 };
+ string targetPath1 = "path1.png";
+ string targetPath2 = "path2.bmp";
+ string targetPath3 = "path2.jpg";
+
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 1, ".png", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath1));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 2, ".bmp", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath2));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), 3, ".jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath3));
+ var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaSources(It.IsAny()))
+ .Returns(new List { new () { MediaAttachments = new List { sampleAttachment1, sampleAttachment2, sampleAttachment3 } } });
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List());
+
+ var actualLogo = await embeddedImageProvider.GetImage(input.Object, ImageType.Logo, CancellationToken.None);
+ Assert.NotNull(actualLogo);
+ Assert.True(actualLogo.HasImage);
+ Assert.Equal(targetPath1, actualLogo.Path);
+ Assert.Equal(ImageFormat.Png, actualLogo.Format);
+
+ var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None);
+ Assert.NotNull(actualBackdrop);
+ Assert.True(actualBackdrop.HasImage);
+ Assert.Equal(targetPath2, actualBackdrop.Path);
+ Assert.Equal(ImageFormat.Bmp, actualBackdrop.Format);
+
+ var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actualPrimary);
+ Assert.True(actualPrimary.HasImage);
+ Assert.Equal(targetPath3, actualPrimary.Path);
+ Assert.Equal(ImageFormat.Jpg, actualPrimary.Format);
+ }
+
+ [Fact]
+ public async void GetImage_EmbeddedDefault()
+ {
+ MediaStream sampleStream = new () { Type = MediaStreamType.EmbeddedImage, Index = 1 };
+ string targetPath = "path";
+
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream, 1, "jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath));
+ var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaSources(It.IsAny()))
+ .Returns(new List());
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List() { sampleStream });
+
+ var actual = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Fact]
+ public async void GetImage_EmbeddedSelection()
+ {
+ // primary is second stream to ensure it's not defaulting, backdrop is first
+ MediaStream sampleStream1 = new () { Type = MediaStreamType.EmbeddedImage, Index = 1, Comment = "backdrop" };
+ MediaStream sampleStream2 = new () { Type = MediaStreamType.EmbeddedImage, Index = 2, Comment = "cover" };
+ string targetPath1 = "path1.jpg";
+ string targetPath2 = "path2.jpg";
+
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream1, 1, "jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath1));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), sampleStream2, 2, "jpg", CancellationToken.None))
+ .Returns(Task.FromResult(targetPath2));
+ var embeddedImageProvider = GetEmbeddedImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaSources(It.IsAny()))
+ .Returns(new List());
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List { sampleStream1, sampleStream2 });
+
+ var actualPrimary = await embeddedImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actualPrimary);
+ Assert.True(actualPrimary.HasImage);
+ Assert.Equal(targetPath2, actualPrimary.Path);
+ Assert.Equal(ImageFormat.Jpg, actualPrimary.Format);
+
+ var actualBackdrop = await embeddedImageProvider.GetImage(input.Object, ImageType.Backdrop, CancellationToken.None);
+ Assert.NotNull(actualBackdrop);
+ Assert.True(actualBackdrop.HasImage);
+ Assert.Equal(targetPath1, actualBackdrop.Path);
+ Assert.Equal(ImageFormat.Jpg, actualBackdrop.Format);
+ }
+ }
+}
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
new file mode 100644
index 0000000000..9a5cd79bbf
--- /dev/null
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
@@ -0,0 +1,168 @@
+using System;
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using MediaBrowser.Controller.Entities.Movies;
+using MediaBrowser.Controller.MediaEncoding;
+using MediaBrowser.Model.Drawing;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Providers.MediaInfo;
+using Microsoft.Extensions.Logging.Abstractions;
+using Moq;
+using Xunit;
+
+namespace Jellyfin.Providers.Tests.MediaInfo
+{
+ public class VideoImageProviderTests
+ {
+ private VideoImageProvider GetVideoImageProvider(IMediaEncoder? mediaEncoder)
+ {
+ // strict to ensure this isn't accidentally used where a prepared mock is intended
+ mediaEncoder ??= new Mock(MockBehavior.Strict).Object;
+ return new VideoImageProvider(mediaEncoder, new NullLogger());
+ }
+
+ [Fact]
+ public async void GetImage_Empty_IsPlaceholder()
+ {
+ var videoImageProvider = GetVideoImageProvider(null);
+
+ var input = new Mock();
+ input.Object.IsPlaceHolder = true;
+
+ var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_Empty_NoDefaultVideoStream()
+ {
+ var videoImageProvider = GetVideoImageProvider(null);
+
+ var input = new Mock();
+
+ var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_Empty_DefaultSet_NoVideoStream()
+ {
+ var videoImageProvider = GetVideoImageProvider(null);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List());
+ // set a default index but don't put anything there (invalid input, but provider shouldn't break)
+ input.Object.DefaultVideoStreamIndex = 1;
+
+ var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.False(actual.HasImage);
+ }
+
+ [Fact]
+ public async void GetImage_Extract_DefaultStream()
+ {
+ MediaStream firstStream = new () { Type = MediaStreamType.Video, Index = 0 };
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 1 };
+ string targetPath = "path.jpg";
+
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), firstStream, It.IsAny(), It.IsAny(), CancellationToken.None))
+ .Returns(Task.FromResult("wrong stream called!"));
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None))
+ .Returns(Task.FromResult(targetPath));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetDefaultVideoStream())
+ .Returns(targetStream);
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List() { firstStream, targetStream });
+ input.Object.DefaultVideoStreamIndex = 1;
+
+ var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Fact]
+ public async void GetImage_Extract_FallbackToFirstVideoStream()
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+ string targetPath = "path.jpg";
+
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), targetStream, It.IsAny(), It.IsAny(), CancellationToken.None))
+ .Returns(Task.FromResult(targetPath));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List() { targetStream });
+ // default must be set, ensure a stream is still found if not pointed at a video
+ input.Object.DefaultVideoStreamIndex = 5;
+
+ var actual = await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+ Assert.NotNull(actual);
+ Assert.True(actual.HasImage);
+ Assert.Equal(targetPath, actual.Path);
+ Assert.Equal(ImageFormat.Jpg, actual.Format);
+ }
+
+ [Fact]
+ public async void GetImage_Time_Default()
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+
+ TimeSpan? actualTimeSpan = null;
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None))
+ .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan)
+ .Returns(Task.FromResult("path"));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List() { targetStream });
+ // default must be set
+ input.Object.DefaultVideoStreamIndex = 0;
+
+ // not testing return, just verifying what gets requested for time span
+ await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+
+ Assert.Equal(TimeSpan.FromSeconds(10), actualTimeSpan);
+ }
+
+ [Fact]
+ public async void GetImage_Time_Calculated()
+ {
+ MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+
+ TimeSpan? actualTimeSpan = null;
+ var mediaEncoder = new Mock(MockBehavior.Strict);
+ mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny(), CancellationToken.None))
+ .Callback((_, _, _, _, _, timeSpan, _) => actualTimeSpan = timeSpan)
+ .Returns(Task.FromResult("path"));
+ var videoImageProvider = GetVideoImageProvider(mediaEncoder.Object);
+
+ var input = new Mock();
+ input.Setup(movie => movie.GetMediaStreams())
+ .Returns(new List() { targetStream });
+ // default must be set
+ input.Object.DefaultVideoStreamIndex = 0;
+ input.Object.RunTimeTicks = 5000;
+
+ // not testing return, just verifying what gets requested for time span
+ await videoImageProvider.GetImage(input.Object, ImageType.Primary, CancellationToken.None);
+
+ Assert.Equal(TimeSpan.FromTicks(500), actualTimeSpan);
+ }
+ }
+}