using System; using System.Collections.Generic; using System.Linq; 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; using MediaBrowser.Model.Entities; using MediaBrowser.Model.MediaInfo; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.MediaInfo { /// /// Uses to create still images from the main video. /// public class VideoImageProvider : IDynamicImageProvider, IHasOrder { private readonly IMediaSourceManager _mediaSourceManager; private readonly IMediaEncoder _mediaEncoder; private readonly ILogger _logger; /// /// Initializes a new instance of the class. /// /// The media source manager for fetching item streams. /// The media encoder for capturing images. /// The logger. public VideoImageProvider(IMediaSourceManager mediaSourceManager, IMediaEncoder mediaEncoder, ILogger logger) { _mediaSourceManager = mediaSourceManager; _mediaEncoder = mediaEncoder; _logger = logger; } /// public string Name => "Screen Grabber"; /// // Make sure this comes after internet image providers public int Order => 100; /// public IEnumerable GetSupportedImages(BaseItem item) { return new[] { ImageType.Primary }; } /// public Task 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 }); } // Can't extract if we didn't find a video stream 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 GetVideoImage(video, cancellationToken); } private async Task GetVideoImage(Video item, CancellationToken cancellationToken) { MediaSourceInfo mediaSource = new MediaSourceInfo { VideoType = item.VideoType, IsoType = item.IsoType, Protocol = item.PathProtocol ?? MediaProtocol.File, }; // 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 > 0 ? TimeSpan.FromTicks(item.RunTimeTicks.Value / 10) : TimeSpan.FromSeconds(10); var query = new MediaStreamQuery { ItemId = item.Id, Index = item.DefaultVideoStreamIndex }; var videoStream = _mediaSourceManager.GetMediaStreams(query).FirstOrDefault(); if (videoStream is null) { query.Type = MediaStreamType.Video; query.Index = null; videoStream = _mediaSourceManager.GetMediaStreams(query).FirstOrDefault(); } if (videoStream is 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 { Format = ImageFormat.Jpg, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File }; } /// public bool Supports(BaseItem item) { if (item.IsShortcut) { return false; } if (!item.IsFileProtocol) { return false; } return item is Video video && !video.IsPlaceHolder && video.IsCompleteMedia; } } }