public async Task <DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) { var protocol = item.PathProtocol ?? MediaProtocol.File; var inputPath = item.Path; var mediaStreams = item.GetMediaStreams(); var imageStreams = mediaStreams .Where(i => i.Type == MediaStreamType.EmbeddedImage) .ToList(); var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(); string extractedImagePath; if (imageStream != null) { MediaSourceInfo mediaSource = new MediaSourceInfo { VideoType = item.VideoType, IsoType = item.IsoType, Protocol = item.PathProtocol.Value, }; extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, imageStream, imageStream.Index, cancellationToken).ConfigureAwait(false); } else { // 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 ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); var mediaSource = new MediaSourceInfo { VideoType = item.VideoType, IsoType = item.IsoType, Protocol = item.PathProtocol.Value, }; extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, mediaSource, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); } return(new DynamicImageResponse { Format = ImageFormat.Jpg, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File }); }
public async Task <DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) { var isoMount = await MountIsoIfNeeded(item, cancellationToken).ConfigureAwait(false); try { // 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 ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); InputType type; var inputPath = MediaEncoderHelpers.GetInputArgument(item.Path, item.LocationType == LocationType.Remote, item.VideoType, item.IsoType, isoMount, item.PlayableStreamFileNames, out type); var stream = await _mediaEncoder.ExtractVideoImage(inputPath, type, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); return(new DynamicImageResponse { Format = ImageFormat.Jpg, HasImage = true, Stream = stream }); } finally { if (isoMount != null) { isoMount.Dispose(); } } }
private async Task <DynamicImageResponse> 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 == null) { query.Type = MediaStreamType.Video; query.Index = null; videoStream = _mediaSourceManager.GetMediaStreams(query).FirstOrDefault(); } 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 { Format = ImageFormat.Jpg, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File }); }
public async Task<DynamicImageResponse> GetVideoImage(Video item, CancellationToken cancellationToken) { var protocol = item.PathProtocol ?? MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, item.Path, null, item.GetPlayableStreamFileNames(_mediaEncoder)); var mediaStreams = item.GetMediaStreams(); var imageStreams = mediaStreams .Where(i => i.Type == MediaStreamType.EmbeddedImage) .ToList(); var imageStream = imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("front", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(i => (i.Comment ?? string.Empty).IndexOf("cover", StringComparison.OrdinalIgnoreCase) != -1) ?? imageStreams.FirstOrDefault(); string extractedImagePath; if (imageStream != null) { // Instead of using the raw stream index, we need to use nth video/embedded image stream var videoIndex = -1; foreach (var mediaStream in mediaStreams) { if (mediaStream.Type == MediaStreamType.Video || mediaStream.Type == MediaStreamType.EmbeddedImage) { videoIndex++; } if (mediaStream == imageStream) { break; } } extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, imageStream, videoIndex, cancellationToken).ConfigureAwait(false); } else { // 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 ? TimeSpan.FromTicks(Convert.ToInt64(item.RunTimeTicks.Value * .1)) : TimeSpan.FromSeconds(10); var videoStream = mediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Video); extractedImagePath = await _mediaEncoder.ExtractVideoImage(inputPath, item.Container, protocol, videoStream, item.Video3DFormat, imageOffset, cancellationToken).ConfigureAwait(false); } return new DynamicImageResponse { Format = ImageFormat.Jpg, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File }; }
public async Task <bool> RefreshChapterImages(Video video, IDirectoryService directoryService, List <ChapterInfo> chapters, bool extractImages, bool saveChapters, CancellationToken cancellationToken) { if (!IsEligibleForChapterImageExtraction(video)) { extractImages = false; } var success = true; var changesMade = false; var runtimeTicks = video.RunTimeTicks ?? 0; var currentImages = GetSavedChapterImages(video, directoryService); foreach (var chapter in chapters) { if (chapter.StartPositionTicks >= runtimeTicks) { _logger.LogInformation("Stopping chapter extraction for {0} because a chapter was found with a position greater than the runtime.", video.Name); break; } var path = GetChapterImagePath(video, chapter.StartPositionTicks); if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) { if (extractImages) { cancellationToken.ThrowIfCancellationRequested(); try { // Add some time for the first chapter to make sure we don't end up with a black image var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); var protocol = MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, Array.Empty <string>()); _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(path)); var container = video.Container; var tempFile = await _encoder.ExtractVideoImage(inputPath, container, protocol, video.GetDefaultVideoStream(), video.Video3DFormat, time, cancellationToken).ConfigureAwait(false); _fileSystem.CopyFile(tempFile, path, true); try { _fileSystem.DeleteFile(tempFile); } catch { } chapter.ImagePath = path; chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); changesMade = true; } catch (Exception ex) { _logger.LogError(ex, "Error extracting chapter images for {0}", string.Join(",", video.Path)); success = false; break; } } else if (!string.IsNullOrEmpty(chapter.ImagePath)) { chapter.ImagePath = null; changesMade = true; } } else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase)) { chapter.ImagePath = path; chapter.ImageDateModified = _fileSystem.GetLastWriteTimeUtc(path); changesMade = true; } } if (saveChapters && changesMade) { _chapterManager.SaveChapters(video.Id.ToString(), chapters); } DeleteDeadImages(currentImages, chapters); return(success); }
public async Task <bool> RefreshChapterImages(ChapterImageRefreshOptions options, CancellationToken cancellationToken) { var extractImages = options.ExtractImages; var video = options.Video; var chapters = options.Chapters; var saveChapters = options.SaveChapters; if (!IsEligibleForChapterImageExtraction(video)) { extractImages = false; } var success = true; var changesMade = false; var runtimeTicks = video.RunTimeTicks ?? 0; var currentImages = GetSavedChapterImages(video); foreach (var chapter in chapters) { if (chapter.StartPositionTicks >= runtimeTicks) { _logger.Info("Stopping chapter extraction for {0} because a chapter was found with a position greater than the runtime.", video.Name); break; } var path = GetChapterImagePath(video, chapter.StartPositionTicks); if (!currentImages.Contains(path, StringComparer.OrdinalIgnoreCase)) { if (extractImages) { if (video.VideoType == VideoType.HdDvd || video.VideoType == VideoType.Iso || video.VideoType == VideoType.BluRay) { continue; } // Add some time for the first chapter to make sure we don't end up with a black image var time = chapter.StartPositionTicks == 0 ? TimeSpan.FromTicks(Math.Min(FirstChapterTicks, video.RunTimeTicks ?? 0)) : TimeSpan.FromTicks(chapter.StartPositionTicks); var protocol = MediaProtocol.File; var inputPath = MediaEncoderHelpers.GetInputArgument(_fileSystem, video.Path, protocol, null, video.PlayableStreamFileNames); try { _fileSystem.CreateDirectory(Path.GetDirectoryName(path)); using (var stream = await _encoder.ExtractVideoImage(inputPath, protocol, video.Video3DFormat, time, cancellationToken).ConfigureAwait(false)) { using (var fileStream = _fileSystem.GetFileStream(path, FileMode.Create, FileAccess.Write, FileShare.Read, true)) { await stream.CopyToAsync(fileStream).ConfigureAwait(false); } } chapter.ImagePath = path; changesMade = true; } catch (Exception ex) { _logger.ErrorException("Error extracting chapter images for {0}", ex, string.Join(",", inputPath)); success = false; break; } } else if (!string.IsNullOrEmpty(chapter.ImagePath)) { chapter.ImagePath = null; changesMade = true; } } else if (!string.Equals(path, chapter.ImagePath, StringComparison.OrdinalIgnoreCase)) { chapter.ImagePath = path; changesMade = true; } } if (saveChapters && changesMade) { await _chapterManager.SaveChapters(video.Id.ToString(), chapters, cancellationToken).ConfigureAwait(false); } DeleteDeadImages(currentImages, chapters); return(success); }
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, _ => Array.Empty <string>() }; if (imageFileNames.Length == 0) { _logger.LogWarning("Attempted to load unexpected image type: {Type}", type); return(new DynamicImageResponse { HasImage = false }); } // Try attachments first var attachmentStream = _mediaSourceManager.GetMediaAttachments(item.Id) .FirstOrDefault(attachment => !string.IsNullOrEmpty(attachment.FileName) && imageFileNames.Any(name => attachment.FileName.Contains(name, StringComparison.OrdinalIgnoreCase))); if (attachmentStream != null) { return(await ExtractAttachment(item, attachmentStream, mediaSource, cancellationToken)); } // Fall back to EmbeddedImage streams var imageStreams = _mediaSourceManager.GetMediaStreams(new MediaStreamQuery { ItemId = item.Id, 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 }); } } var format = imageStream.Codec switch { "mjpeg" => ImageFormat.Jpg, "png" => ImageFormat.Png, "gif" => ImageFormat.Gif, _ => ImageFormat.Jpg }; string extractedImagePath = await _mediaEncoder.ExtractVideoImage(item.Path, item.Container, mediaSource, imageStream, imageStream.Index, format, cancellationToken) .ConfigureAwait(false); return(new DynamicImageResponse { Format = format, HasImage = true, Path = extractedImagePath, Protocol = MediaProtocol.File }); }