Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Query media streams by type instead of filtering #6862

Merged
merged 5 commits into from
Nov 20, 2021
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
9 changes: 0 additions & 9 deletions MediaBrowser.Controller/Entities/Audio/Audio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -126,15 +126,6 @@ public override UnratedItem GetBlockUnratedType()
return base.GetBlockUnratedType();
}

public List<MediaStream> GetMediaStreams(MediaStreamType type)
{
return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
{
ItemId = Id,
Type = type
});
}

public SongInfo GetLookupInfo()
{
var info = GetItemLookupInfo<SongInfo>();
Expand Down
40 changes: 26 additions & 14 deletions MediaBrowser.Providers/MediaInfo/AudioImageProvider.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
#nullable disable

#pragma warning disable CA1002, CS1591

using System;
using System.Collections.Generic;
using System.Globalization;
Expand All @@ -13,46 +11,59 @@
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.IO;

namespace MediaBrowser.Providers.MediaInfo
{
/// <summary>
/// Uses ffmpeg to create video images.
/// Uses <see cref="IMediaEncoder"/> to extract embedded images.
/// </summary>
public class AudioImageProvider : IDynamicImageProvider
{
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly IServerConfigurationManager _config;
private readonly IFileSystem _fileSystem;

public AudioImageProvider(IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem)
/// <summary>
/// Initializes a new instance of the <see cref="AudioImageProvider"/> class.
/// </summary>
/// <param name="mediaSourceManager">The media source manager for fetching item streams.</param>
/// <param name="mediaEncoder">The media encoder for extracting embedded images.</param>
/// <param name="config">The server configuration manager for getting image paths.</param>
/// <param name="fileSystem">The filesystem.</param>
public AudioImageProvider(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, IServerConfigurationManager config, IFileSystem fileSystem)
{
_mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_config = config;
_fileSystem = fileSystem;
}

public string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images");
private string AudioImagesPath => Path.Combine(_config.ApplicationPaths.CachePath, "extracted-audio-images");

/// <inheritdoc />
public string Name => "Image Extractor";

/// <inheritdoc />
public IEnumerable<ImageType> GetSupportedImages(BaseItem item)
{
return new List<ImageType> { ImageType.Primary };
return new[] { ImageType.Primary };
}

/// <inheritdoc />
public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, CancellationToken cancellationToken)
{
var audio = (Audio)item;

var imageStreams =
audio.GetMediaStreams(MediaStreamType.EmbeddedImage)
.Where(i => i.Type == MediaStreamType.EmbeddedImage)
.ToList();
var imageStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery
{
ItemId = item.Id,
Type = MediaStreamType.EmbeddedImage
});

// Can't extract if we didn't find a video stream in the file
if (imageStreams.Count == 0)
Expand All @@ -63,7 +74,7 @@ public Task<DynamicImageResponse> GetImage(BaseItem item, ImageType type, Cancel
return GetImage((Audio)item, imageStreams, cancellationToken);
}

public async Task<DynamicImageResponse> GetImage(Audio item, List<MediaStream> imageStreams, CancellationToken cancellationToken)
private async Task<DynamicImageResponse> GetImage(Audio item, List<MediaStream> imageStreams, CancellationToken cancellationToken)
{
var path = GetAudioImagePath(item);

Expand All @@ -75,7 +86,7 @@ public async Task<DynamicImageResponse> GetImage(Audio item, List<MediaStream> i
imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ??
imageStreams.FirstOrDefault();

var imageStreamIndex = imageStream == null ? (int?)null : imageStream.Index;
var imageStreamIndex = imageStream?.Index;

var tempFile = await _mediaEncoder.ExtractAudioImage(item.Path, imageStreamIndex, cancellationToken).ConfigureAwait(false);

Expand Down Expand Up @@ -127,6 +138,7 @@ private string GetAudioImagePath(Audio item)
return Path.Join(AudioImagesPath, prefix, filename);
}

/// <inheritdoc />
public bool Supports(BaseItem item)
{
if (item.IsShortcut)
Expand Down
16 changes: 12 additions & 4 deletions MediaBrowser.Providers/MediaInfo/EmbeddedImageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
Expand Down Expand Up @@ -45,16 +47,19 @@ public class EmbeddedImageProvider : IDynamicImageProvider, IHasOrder
"logo",
};

private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger<EmbeddedImageProvider> _logger;

/// <summary>
/// Initializes a new instance of the <see cref="EmbeddedImageProvider"/> class.
/// </summary>
/// <param name="mediaSourceManager">The media source manager for fetching item streams and attachments.</param>
/// <param name="mediaEncoder">The media encoder for extracting attached/embedded images.</param>
/// <param name="logger">The logger.</param>
public EmbeddedImageProvider(IMediaEncoder mediaEncoder, ILogger<EmbeddedImageProvider> logger)
public EmbeddedImageProvider(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ILogger<EmbeddedImageProvider> logger)
{
_mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_logger = logger;
}
Expand Down Expand Up @@ -128,8 +133,7 @@ private async Task<DynamicImageResponse> GetEmbeddedImage(Video item, ImageType
}

// Try attachments first
var attachmentStream = item.GetMediaSources(false)
.SelectMany(source => source.MediaAttachments)
var attachmentStream = _mediaSourceManager.GetMediaAttachments(item.Id)
.FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName)
&& imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase)));

Expand All @@ -139,7 +143,11 @@ private async Task<DynamicImageResponse> GetEmbeddedImage(Video item, ImageType
}

// Fall back to EmbeddedImage streams
var imageStreams = item.GetMediaStreams().FindAll(i => i.Type == MediaStreamType.EmbeddedImage);
var imageStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery
{
ItemId = item.Id,
Type = MediaStreamType.EmbeddedImage
});

if (imageStreams.Count == 0)
{
Expand Down
16 changes: 12 additions & 4 deletions MediaBrowser.Providers/MediaInfo/VideoImageProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
Expand All @@ -19,16 +21,19 @@ namespace MediaBrowser.Providers.MediaInfo
/// </summary>
public class VideoImageProvider : IDynamicImageProvider, IHasOrder
{
private readonly IMediaSourceManager _mediaSourceManager;
private readonly IMediaEncoder _mediaEncoder;
private readonly ILogger<VideoImageProvider> _logger;

/// <summary>
/// Initializes a new instance of the <see cref="VideoImageProvider"/> class.
/// </summary>
/// <param name="mediaSourceManager">The media source manager for fetching item streams.</param>
/// <param name="mediaEncoder">The media encoder for capturing images.</param>
/// <param name="logger">The logger.</param>
public VideoImageProvider(IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger)
public VideoImageProvider(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ILogger<VideoImageProvider> logger)
{
_mediaSourceManager = mediaSourceManager;
_mediaEncoder = mediaEncoder;
_logger = logger;
}
Expand Down Expand Up @@ -78,12 +83,15 @@ private async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationT

// If we know the duration, grab it from 10% into the video. Otherwise just 10 seconds in.
// Always use 10 seconds for dvd because our duration could be out of whack
var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks.HasValue &&
item.RunTimeTicks.Value > 0
var imageOffset = item.VideoType != VideoType.Dvd && item.RunTimeTicks is > 0
1337joe marked this conversation as resolved.
Show resolved Hide resolved
? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10)
: TimeSpan.FromSeconds(10);

var videoStream = item.GetDefaultVideoStream() ?? item.GetMediaStreams().FirstOrDefault(i => i.Type == MediaStreamType.Video);
var defaultQuery = new MediaStreamQuery { ItemId = item.Id, Index = item.DefaultVideoStreamIndex };
var videoQuery = new MediaStreamQuery { ItemId = item.Id, Type = MediaStreamType.Video };

var videoStream = _mediaSourceManager.GetMediaStreams(defaultQuery).FirstOrDefault()
?? _mediaSourceManager.GetMediaStreams(videoQuery).FirstOrDefault();
1337joe marked this conversation as resolved.
Show resolved Hide resolved

if (videoStream == null)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.MediaEncoding;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
Expand All @@ -29,17 +31,18 @@ public class EmbeddedImageProviderTests
public void GetSupportedImages_AnyBaseItem_ReturnsExpected(Type type, params ImageType[] expected)
{
BaseItem item = (BaseItem)Activator.CreateInstance(type)!;
var embeddedImageProvider = new EmbeddedImageProvider(Mock.Of<IMediaEncoder>(), new NullLogger<EmbeddedImageProvider>());
var embeddedImageProvider = new EmbeddedImageProvider(Mock.Of<IMediaSourceManager>(), Mock.Of<IMediaEncoder>(), new NullLogger<EmbeddedImageProvider>());
var actual = embeddedImageProvider.GetSupportedImages(item);
Assert.Equal(expected.OrderBy(i => i.ToString()), actual.OrderBy(i => i.ToString()));
}

[Fact]
public async void GetImage_NoStreams_ReturnsNoImage()
{
var embeddedImageProvider = new EmbeddedImageProvider(null, new NullLogger<EmbeddedImageProvider>());
var input = new Movie();

var input = GetMovie(new List<MediaAttachment>(), new List<MediaStream>());
var mediaSourceManager = GetMediaSourceManager(input, new List<MediaAttachment>(), new List<MediaStream>());
var embeddedImageProvider = new EmbeddedImageProvider(mediaSourceManager, null, new NullLogger<EmbeddedImageProvider>());

var actual = await embeddedImageProvider.GetImage(input, ImageType.Primary, CancellationToken.None);
Assert.NotNull(actual);
Expand Down Expand Up @@ -67,12 +70,13 @@ public async void GetImage_Attachment_ReturnsCorrectSelection(string filename, s
});
}

var input = new Movie();

var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<int>(), It.IsAny<ImageFormat>(), It.IsAny<CancellationToken>()))
.Returns<string, string, MediaSourceInfo, MediaStream, int, ImageFormat, CancellationToken>((_, _, _, _, index, ext, _) => Task.FromResult(pathPrefix + index + "." + ext));
var embeddedImageProvider = new EmbeddedImageProvider(mediaEncoder.Object, new NullLogger<EmbeddedImageProvider>());

var input = GetMovie(attachments, new List<MediaStream>());
var mediaSourceManager = GetMediaSourceManager(input, attachments, new List<MediaStream>());
var embeddedImageProvider = new EmbeddedImageProvider(mediaSourceManager, mediaEncoder.Object, new NullLogger<EmbeddedImageProvider>());

var actual = await embeddedImageProvider.GetImage(input, type, CancellationToken.None);
Assert.NotNull(actual);
Expand Down Expand Up @@ -112,6 +116,8 @@ public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string
});
}

var input = new Movie();

var pathPrefix = "path";
var mediaEncoder = new Mock<IMediaEncoder>(MockBehavior.Strict);
mediaEncoder.Setup(encoder => encoder.ExtractVideoImage(It.IsAny<string>(), It.IsAny<string>(), It.IsAny<MediaSourceInfo>(), It.IsAny<MediaStream>(), It.IsAny<int>(), It.IsAny<ImageFormat>(), It.IsAny<CancellationToken>()))
Expand All @@ -120,9 +126,8 @@ public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string
Assert.Equal(streams[index - 1], stream);
return Task.FromResult(pathPrefix + index + "." + ext);
});
var embeddedImageProvider = new EmbeddedImageProvider(mediaEncoder.Object, new NullLogger<EmbeddedImageProvider>());

var input = GetMovie(new List<MediaAttachment>(), streams);
var mediaSourceManager = GetMediaSourceManager(input, new List<MediaAttachment>(), streams);
var embeddedImageProvider = new EmbeddedImageProvider(mediaSourceManager, mediaEncoder.Object, new NullLogger<EmbeddedImageProvider>());

var actual = await embeddedImageProvider.GetImage(input, type, CancellationToken.None);
Assert.NotNull(actual);
Expand All @@ -138,19 +143,14 @@ public async void GetImage_Embedded_ReturnsCorrectSelection(string label, string
}
}

private static Movie GetMovie(List<MediaAttachment> mediaAttachments, List<MediaStream> mediaStreams)
private static IMediaSourceManager GetMediaSourceManager(BaseItem item, List<MediaAttachment> mediaAttachments, List<MediaStream> mediaStreams)
{
// Mocking IMediaSourceManager GetMediaAttachments and GetMediaStreams instead of mocking Movie works, but
// has concurrency problems between this and VideoImageProviderTests due to BaseItem.MediaSourceManager
// being static
var movie = new Mock<Movie>();

movie.Setup(item => item.GetMediaSources(It.IsAny<bool>()))
.Returns(new List<MediaSourceInfo> { new () { MediaAttachments = mediaAttachments } } );
movie.Setup(item => item.GetMediaStreams())
var mediaSourceManager = new Mock<IMediaSourceManager>(MockBehavior.Strict);
mediaSourceManager.Setup(i => i.GetMediaAttachments(item.Id))
.Returns(mediaAttachments);
mediaSourceManager.Setup(i => i.GetMediaStreams(It.Is<MediaStreamQuery>(q => q.ItemId == item.Id && q.Type == MediaStreamType.EmbeddedImage)))
.Returns(mediaStreams);

return movie.Object;
return mediaSourceManager.Object;
}
}
}