From 8bb44b85d7008ee167f2260934a1f325abb06e2d Mon Sep 17 00:00:00 2001 From: herby2212 <12448284+herby2212@users.noreply.github.com> Date: Mon, 1 May 2023 16:24:15 +0200 Subject: close inactive sessions after 10 minutes --- MediaBrowser.Controller/Session/SessionInfo.cs | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 25bf23d61..172d79a59 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -109,6 +109,12 @@ namespace MediaBrowser.Controller.Session /// The last playback check in. public DateTime LastPlaybackCheckIn { get; set; } + /// + /// Gets or sets the last paused date. + /// + /// The last paused date. + public DateTime? LastPausedDate { get; set; } + /// /// Gets or sets the name of the device. /// -- cgit v1.2.3 From ca7d1a13000ad948eebbfdeb40542312f3e37d3e Mon Sep 17 00:00:00 2001 From: nicknsy <20588554+nicknsy@users.noreply.github.com> Date: Wed, 22 Feb 2023 00:08:35 -0800 Subject: Trickplay generation, manager, storage --- Emby.Server.Implementations/ApplicationHost.cs | 3 + .../Data/SqliteItemRepository.cs | 123 +++++++ Emby.Server.Implementations/Dto/DtoService.cs | 5 + .../MediaEncoding/EncodingHelper.cs | 35 ++ .../MediaEncoding/IMediaEncoder.cs | 27 ++ .../Persistence/IItemRepository.cs | 21 ++ .../Trickplay/ITrickplayManager.cs | 54 +++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 173 ++++++++++ .../Configuration/EncodingOptions.cs | 12 + MediaBrowser.Model/Dto/BaseItemDto.cs | 6 + MediaBrowser.Model/Entities/TrickplayTilesInfo.cs | 50 +++ MediaBrowser.Model/Querying/ItemFields.cs | 5 + .../MediaBrowser.Providers.csproj | 1 + .../Trickplay/TrickplayImagesTask.cs | 116 +++++++ .../Trickplay/TrickplayManager.cs | 363 +++++++++++++++++++++ .../Trickplay/TrickplayProvider.cs | 109 +++++++ 16 files changed, 1103 insertions(+) create mode 100644 MediaBrowser.Controller/Trickplay/ITrickplayManager.cs create mode 100644 MediaBrowser.Model/Entities/TrickplayTilesInfo.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayManager.cs create mode 100644 MediaBrowser.Providers/Trickplay/TrickplayProvider.cs (limited to 'MediaBrowser.Controller') diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs index 7969577bc..1e0bb0cd6 100644 --- a/Emby.Server.Implementations/ApplicationHost.cs +++ b/Emby.Server.Implementations/ApplicationHost.cs @@ -78,6 +78,7 @@ using MediaBrowser.Controller.Session; using MediaBrowser.Controller.Sorting; using MediaBrowser.Controller.Subtitles; using MediaBrowser.Controller.SyncPlay; +using MediaBrowser.Controller.Trickplay; using MediaBrowser.Controller.TV; using MediaBrowser.LocalMetadata.Savers; using MediaBrowser.MediaEncoding.BdInfo; @@ -96,6 +97,7 @@ using MediaBrowser.Providers.Lyric; using MediaBrowser.Providers.Manager; using MediaBrowser.Providers.Plugins.Tmdb; using MediaBrowser.Providers.Subtitles; +using MediaBrowser.Providers.Trickplay; using MediaBrowser.XbmcMetadata.Providers; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; @@ -591,6 +593,7 @@ namespace Emby.Server.Implementations serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); serviceCollection.AddSingleton(); diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs index ca8f605a0..8ec24522b 100644 --- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs +++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs @@ -48,6 +48,7 @@ namespace Emby.Server.Implementations.Data { private const string FromText = " from TypedBaseItems A"; private const string ChaptersTableName = "Chapters2"; + private const string TrickplayTableName = "Trickplay"; private const string SaveItemCommandText = @"replace into TypedBaseItems @@ -383,6 +384,8 @@ namespace Emby.Server.Implementations.Data "create table if not exists " + ChaptersTableName + " (ItemId GUID, ChapterIndex INT NOT NULL, StartPositionTicks BIGINT NOT NULL, Name TEXT, ImagePath TEXT, PRIMARY KEY (ItemId, ChapterIndex))", + "create table if not exists " + TrickplayTableName + " (ItemId GUID, Width INT NOT NULL, Height INT NOT NULL, TileWidth INT NOT NULL, TileHeight INT NOT NULL, TileCount INT NOT NULL, Interval INT NOT NULL, Bandwidth INT NOT NULL, PRIMARY KEY (ItemId, Width))", + CreateMediaStreamsTableCommand, CreateMediaAttachmentsTableCommand, @@ -2135,6 +2138,126 @@ namespace Emby.Server.Implementations.Data } } + /// + public Dictionary GetTilesResolutions(Guid itemId) + { + CheckDisposed(); + + var tilesResolutions = new Dictionary(); + using (var connection = GetConnection(true)) + { + using (var statement = PrepareStatement(connection, "select Width,Height,TileWidth,TileHeight,TileCount,Interval,Bandwidth from " + TrickplayTableName + " where ItemId = @ItemId order by Width asc")) + { + statement.TryBind("@ItemId", itemId); + + foreach (var row in statement.ExecuteQuery()) + { + TrickplayTilesInfo tilesInfo = GetTrickplayTilesInfo(row); + tilesResolutions[tilesInfo.Width] = tilesInfo; + } + } + } + + return tilesResolutions; + } + + /// + public void SaveTilesInfo(Guid itemId, TrickplayTilesInfo tilesInfo) + { + CheckDisposed(); + + ArgumentNullException.ThrowIfNull(tilesInfo); + + var idBlob = itemId.ToByteArray(); + using (var connection = GetConnection(false)) + { + connection.RunInTransaction( + db => + { + // Delete old tiles info + db.Execute("delete from " + TrickplayTableName + " where ItemId=@ItemId and Width=@Width", idBlob, tilesInfo.Width); + db.Execute( + "insert into " + TrickplayTableName + " values (@ItemId, @Width, @Height, @TileWidth, @TileHeight, @TileCount, @Interval, @Bandwidth)", + idBlob, + tilesInfo.Width, + tilesInfo.Height, + tilesInfo.TileWidth, + tilesInfo.TileHeight, + tilesInfo.TileCount, + tilesInfo.Interval, + tilesInfo.Bandwidth); + }, + TransactionMode); + } + } + + /// + public Dictionary> GetTrickplayManifest(BaseItem item) + { + CheckDisposed(); + + var trickplayManifest = new Dictionary>(); + foreach (var mediaSource in item.GetMediaSources(false)) + { + var mediaSourceId = Guid.Parse(mediaSource.Id); + var tilesResolutions = GetTilesResolutions(mediaSourceId); + + if (tilesResolutions.Count > 0) + { + trickplayManifest[mediaSourceId] = tilesResolutions; + } + } + + return trickplayManifest; + } + + /// + /// Gets the trickplay tiles info. + /// + /// The reader. + /// TrickplayTilesInfo. + private TrickplayTilesInfo GetTrickplayTilesInfo(IReadOnlyList reader) + { + var tilesInfo = new TrickplayTilesInfo(); + + if (reader.TryGetInt32(0, out var width)) + { + tilesInfo.Width = width; + } + + if (reader.TryGetInt32(1, out var height)) + { + tilesInfo.Height = height; + } + + if (reader.TryGetInt32(2, out var tileWidth)) + { + tilesInfo.TileWidth = tileWidth; + } + + if (reader.TryGetInt32(3, out var tileHeight)) + { + tilesInfo.TileHeight = tileHeight; + } + + if (reader.TryGetInt32(4, out var tileCount)) + { + tilesInfo.TileCount = tileCount; + } + + if (reader.TryGetInt32(5, out var interval)) + { + tilesInfo.Interval = interval; + } + + if (reader.TryGetInt32(6, out var bandwidth)) + { + tilesInfo.Bandwidth = bandwidth; + } + + return tilesInfo; + } + private static bool EnableJoinUserData(InternalItemsQuery query) { if (query.User is null) diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs index 7a6ed2cb8..10352b6ff 100644 --- a/Emby.Server.Implementations/Dto/DtoService.cs +++ b/Emby.Server.Implementations/Dto/DtoService.cs @@ -1058,6 +1058,11 @@ namespace Emby.Server.Implementations.Dto dto.Chapters = _itemRepo.GetChapters(item); } + if (options.ContainsField(ItemFields.Trickplay)) + { + dto.Trickplay = _itemRepo.GetTrickplayManifest(item); + } + if (video.ExtraType.HasValue) { dto.ExtraType = video.ExtraType.Value.ToString(); diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index b155d674d..0889a90f4 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -149,6 +149,36 @@ namespace MediaBrowser.Controller.MediaEncoding return defaultEncoder; } + private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) + { + var defaultEncoder = "mjpeg"; + + if (state.VideoType == VideoType.VideoFile) + { + var hwType = encodingOptions.HardwareAccelerationType; + + var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + { "vaapi", defaultEncoder + "_vaapi" }, + { "qsv", defaultEncoder + "_qsv" } + }; + + if (!string.IsNullOrEmpty(hwType) + && encodingOptions.EnableHardwareEncoding + && codecMap.ContainsKey(hwType)) + { + var preferredEncoder = codecMap[hwType]; + + if (_mediaEncoder.SupportsEncoder(preferredEncoder)) + { + return preferredEncoder; + } + } + } + + return defaultEncoder; + } + private bool IsVaapiSupported(EncodingJobInfo state) { // vaapi will throw an error with this input @@ -277,6 +307,11 @@ namespace MediaBrowser.Controller.MediaEncoding return GetH264Encoder(state, encodingOptions); } + if (string.Equals(codec, "mjpeg", StringComparison.OrdinalIgnoreCase)) + { + return GetMjpegEncoder(state, encodingOptions); + } + if (string.Equals(codec, "vp8", StringComparison.OrdinalIgnoreCase) || string.Equals(codec, "vpx", StringComparison.OrdinalIgnoreCase)) { diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index f830b9f29..aa9faa936 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; @@ -137,6 +138,32 @@ namespace MediaBrowser.Controller.MediaEncoding /// Location of video image. Task ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken); + /// + /// Extracts the video images on interval. + /// + /// Input file. + /// Video container type. + /// Media source information. + /// Media stream information. + /// The interval. + /// The maximum width. + /// Allow for hardware acceleration. + /// Allow for hardware encoding. allowHwAccel must also be true. + /// EncodingHelper instance. + /// The cancellation token. + /// Directory where images where extracted. A given image made before another will always be named with a lower number. + Task ExtractVideoImagesOnIntervalAccelerated( + string inputFile, + string container, + MediaSourceInfo mediaSource, + MediaStream imageStream, + TimeSpan interval, + int maxWidth, + bool allowHwAccel, + bool allowHwEncode, + EncodingHelper encodingHelper, + CancellationToken cancellationToken); + /// /// Gets the media info. /// diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs index 2c52b2b45..11eb4932c 100644 --- a/MediaBrowser.Controller/Persistence/IItemRepository.cs +++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs @@ -61,6 +61,27 @@ namespace MediaBrowser.Controller.Persistence /// The list of chapters to save. void SaveChapters(Guid id, IReadOnlyList chapters); + /// + /// Get available trickplay resolutions and corresponding info. + /// + /// The item. + /// Map of width resolutions to trickplay tiles info. + Dictionary GetTilesResolutions(Guid itemId); + + /// + /// Saves trickplay tiles info. + /// + /// The item. + /// The trickplay tiles info. + void SaveTilesInfo(Guid itemId, TrickplayTilesInfo tilesInfo); + + /// + /// Gets trickplay data for an item. + /// + /// The item. + /// A map of media source id to a map of tile width to tile info. + Dictionary> GetTrickplayManifest(BaseItem item); + /// /// Gets the media streams. /// diff --git a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs new file mode 100644 index 000000000..bae458f98 --- /dev/null +++ b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Entities; + +namespace MediaBrowser.Controller.Trickplay +{ + /// + /// Interface ITrickplayManager. + /// + public interface ITrickplayManager + { + /// + /// Generate or replace trickplay data. + /// + /// The video. + /// Whether or not existing data should be replaced. + /// CancellationToken to use for operation. + /// Task. + Task RefreshTrickplayData(Video video, bool replace, CancellationToken cancellationToken); + + /// + /// Get available trickplay resolutions and corresponding info. + /// + /// The item. + /// Map of width resolutions to trickplay tiles info. + Dictionary GetTilesResolutions(Guid itemId); + + /// + /// Saves trickplay tiles info. + /// + /// The item. + /// The trickplay tiles info. + void SaveTilesInfo(Guid itemId, TrickplayTilesInfo tilesInfo); + + /// + /// Gets the trickplay manifest. + /// + /// The item. + /// A map of media source id to a map of tile width to tile info. + Dictionary> GetTrickplayManifest(BaseItem item); + + /// + /// Gets the path to a trickplay tiles image. + /// + /// The item. + /// The width of a single tile. + /// The tile grid's index. + /// The absolute path. + string GetTrickplayTilePath(BaseItem item, int width, int index); + } +} diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4e63d205c..7f8ec03fa 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -21,6 +21,7 @@ using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Extensions; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.MediaEncoding.Probing; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; @@ -28,8 +29,10 @@ using MediaBrowser.Model.Entities; using MediaBrowser.Model.Globalization; using MediaBrowser.Model.IO; using MediaBrowser.Model.MediaInfo; +using Microsoft.AspNetCore.Components.Forms; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; +using static Nikse.SubtitleEdit.Core.Common.IfoParser; namespace MediaBrowser.MediaEncoding.Encoder { @@ -775,6 +778,176 @@ namespace MediaBrowser.MediaEncoding.Encoder } /// + public Task ExtractVideoImagesOnIntervalAccelerated( + string inputFile, + string container, + MediaSourceInfo mediaSource, + MediaStream imageStream, + TimeSpan interval, + int maxWidth, + bool allowHwAccel, + bool allowHwEncode, + EncodingHelper encodingHelper, + CancellationToken cancellationToken) + { + var options = allowHwAccel ? _configurationManager.GetEncodingOptions() : new EncodingOptions(); + + // A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin. + // Additionally, we must set a few fields without defaults to prevent null pointer exceptions. + if (!allowHwAccel) + { + options.EnableHardwareEncoding = false; + options.HardwareAccelerationType = string.Empty; + options.EnableTonemapping = false; + } + + var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth }; + var jobState = new EncodingJobInfo(TranscodingJobType.Progressive) + { + IsVideoRequest = true, // must be true for InputVideoHwaccelArgs to return non-empty value + MediaSource = mediaSource, + VideoStream = imageStream, + BaseRequest = baseRequest, // GetVideoProcessingFilterParam errors if null + MediaPath = inputFile, + OutputVideoCodec = "mjpeg" + }; + var vidEncoder = options.AllowMjpegEncoding ? encodingHelper.GetVideoEncoder(jobState, options) : jobState.OutputVideoCodec; + + // Get input and filter arguments + var inputArg = encodingHelper.GetInputArgument(jobState, options, container).Trim(); + if (string.IsNullOrWhiteSpace(inputArg)) + { + throw new InvalidOperationException("EncodingHelper returned empty input arguments."); + } + + if (!allowHwAccel) + { + inputArg = "-threads " + _threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled + } + + var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, jobState.OutputVideoCodec).Trim(); + if (string.IsNullOrWhiteSpace(filterParam) || filterParam.IndexOf("\"", StringComparison.Ordinal) == -1) + { + throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters."); + } + + return ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, interval, vidEncoder, _threads, cancellationToken); + } + + private async Task ExtractVideoImagesOnIntervalInternal( + string inputArg, + string filterParam, + TimeSpan interval, + string vidEncoder, + int outputThreads, + CancellationToken cancellationToken) + { + if (string.IsNullOrWhiteSpace(inputArg)) + { + throw new InvalidOperationException("Empty or invalid input argument."); + } + + // Output arguments + string fps = "fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); + if (string.IsNullOrWhiteSpace(filterParam)) + { + filterParam = "-vf \"" + fps + "\""; + } + else + { + filterParam = filterParam.Insert(filterParam.IndexOf("\"", StringComparison.Ordinal) + 1, fps + ","); + } + + var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); + Directory.CreateDirectory(targetDirectory); + var outputPath = Path.Combine(targetDirectory, "%08d.jpg"); + + // Final command arguments + var args = string.Format( + CultureInfo.InvariantCulture, + "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} -f {4} \"{5}\"", + inputArg, + filterParam, + outputThreads, + vidEncoder, + "image2", + outputPath); + + // Start ffmpeg process + var process = new Process + { + StartInfo = new ProcessStartInfo + { + CreateNoWindow = true, + UseShellExecute = false, + FileName = _ffmpegPath, + Arguments = args, + WindowStyle = ProcessWindowStyle.Hidden, + ErrorDialog = false, + }, + EnableRaisingEvents = true + }; + + var processDescription = string.Format(CultureInfo.InvariantCulture, "{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); + _logger.LogDebug("{ProcessDescription}", processDescription); + + using (var processWrapper = new ProcessWrapper(process, this)) + { + bool ranToCompletion = false; + + await _thumbnailResourcePool.WaitAsync(cancellationToken).ConfigureAwait(false); + try + { + StartProcess(processWrapper); + + // Need to give ffmpeg enough time to make all the thumbnails, which could be a while, + // but we still need to detect if the process hangs. + // Making the assumption that as long as new jpegs are showing up, everything is good. + + bool isResponsive = true; + int lastCount = 0; + + while (isResponsive) + { + if (await process.WaitForExitAsync(TimeSpan.FromSeconds(30)).ConfigureAwait(false)) + { + ranToCompletion = true; + break; + } + + cancellationToken.ThrowIfCancellationRequested(); + + var jpegCount = _fileSystem.GetFilePaths(targetDirectory) + .Count(i => string.Equals(Path.GetExtension(i), ".jpg", StringComparison.OrdinalIgnoreCase)); + + isResponsive = jpegCount > lastCount; + lastCount = jpegCount; + } + + if (!ranToCompletion) + { + _logger.LogInformation("Killing ffmpeg extraction process due to inactivity."); + StopProcess(processWrapper, 1000); + } + } + finally + { + _thumbnailResourcePool.Release(); + } + + var exitCode = ranToCompletion ? processWrapper.ExitCode ?? 0 : -1; + + if (exitCode == -1) + { + _logger.LogError("ffmpeg image extraction failed for {ProcessDescription}", processDescription); + + throw new FfmpegException(string.Format(CultureInfo.InvariantCulture, "ffmpeg image extraction failed for {0}", processDescription)); + } + + return targetDirectory; + } + } + public string GetTimeParameter(long ticks) { var time = TimeSpan.FromTicks(ticks); diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs index a53be0fee..e1d9e00b7 100644 --- a/MediaBrowser.Model/Configuration/EncodingOptions.cs +++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs @@ -48,7 +48,9 @@ public class EncodingOptions EnableIntelLowPowerH264HwEncoder = false; EnableIntelLowPowerHevcHwEncoder = false; EnableHardwareEncoding = true; + EnableTrickplayHwAccel = false; AllowHevcEncoding = false; + AllowMjpegEncoding = false; EnableSubtitleExtraction = true; AllowOnDemandMetadataBasedKeyframeExtractionForExtensions = new[] { "mkv" }; HardwareDecodingCodecs = new string[] { "h264", "vc1" }; @@ -244,11 +246,21 @@ public class EncodingOptions /// public bool EnableHardwareEncoding { get; set; } + /// + /// Gets or sets a value indicating whether hardware acceleration is enabled for trickplay generation. + /// + public bool EnableTrickplayHwAccel { get; set; } + /// /// Gets or sets a value indicating whether HEVC encoding is enabled. /// public bool AllowHevcEncoding { get; set; } + /// + /// Gets or sets a value indicating whether MJPEG encoding is enabled. + /// + public bool AllowMjpegEncoding { get; set; } + /// /// Gets or sets a value indicating whether subtitle extraction is enabled. /// diff --git a/MediaBrowser.Model/Dto/BaseItemDto.cs b/MediaBrowser.Model/Dto/BaseItemDto.cs index 8fab1ca6d..ab424c6f5 100644 --- a/MediaBrowser.Model/Dto/BaseItemDto.cs +++ b/MediaBrowser.Model/Dto/BaseItemDto.cs @@ -568,6 +568,12 @@ namespace MediaBrowser.Model.Dto /// The chapters. public List Chapters { get; set; } + /// + /// Gets or sets the trickplay manifest. + /// + /// The trickplay manifest. + public Dictionary> Trickplay { get; set; } + /// /// Gets or sets the type of the location. /// diff --git a/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs b/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs new file mode 100644 index 000000000..84b6b0322 --- /dev/null +++ b/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs @@ -0,0 +1,50 @@ +namespace MediaBrowser.Model.Entities +{ + /// + /// Class TrickplayTilesInfo. + /// + public class TrickplayTilesInfo + { + /// + /// Gets or sets width of an individual tile. + /// + /// The width. + public int Width { get; set; } + + /// + /// Gets or sets height of an individual tile. + /// + /// The height. + public int Height { get; set; } + + /// + /// Gets or sets amount of tiles per row. + /// + /// The tile grid's width. + public int TileWidth { get; set; } + + /// + /// Gets or sets amount of tiles per column. + /// + /// The tile grid's height. + public int TileHeight { get; set; } + + /// + /// Gets or sets total amount of non-black tiles. + /// + /// The tile count. + public int TileCount { get; set; } + + /// + /// Gets or sets interval in milliseconds between each trickplay tile. + /// + /// The interval. + public int Interval { get; set; } + + /// + /// Gets or sets peak bandwith usage in bits per second. + /// + /// The bandwidth. + public int Bandwidth { get; set; } + } +} diff --git a/MediaBrowser.Model/Querying/ItemFields.cs b/MediaBrowser.Model/Querying/ItemFields.cs index 6fa1d778a..242a1c6e9 100644 --- a/MediaBrowser.Model/Querying/ItemFields.cs +++ b/MediaBrowser.Model/Querying/ItemFields.cs @@ -34,6 +34,11 @@ namespace MediaBrowser.Model.Querying /// Chapters, + /// + /// The trickplay manifest. + /// + Trickplay, + ChildCount, /// diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj index 6a40833d7..c836c8ed5 100644 --- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj +++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj @@ -22,6 +22,7 @@ + diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs new file mode 100644 index 000000000..3d1450a90 --- /dev/null +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -0,0 +1,116 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Controller.Library; +using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Entities; +using MediaBrowser.Model.Globalization; +using MediaBrowser.Model.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace MediaBrowser.Providers.Trickplay +{ + /// + /// Class TrickplayImagesTask. + /// + public class TrickplayImagesTask : IScheduledTask + { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly IServerConfigurationManager _configurationManager; + private readonly ITrickplayManager _trickplayManager; + + /// + /// Initializes a new instance of the class. + /// + /// The logger. + /// The library manager. + /// The localization manager. + /// The configuration manager. + /// The trickplay manager. + public TrickplayImagesTask( + ILogger logger, + ILibraryManager libraryManager, + ILocalizationManager localization, + IServerConfigurationManager configurationManager, + ITrickplayManager trickplayManager) + { + _libraryManager = libraryManager; + _logger = logger; + _localization = localization; + _configurationManager = configurationManager; + _trickplayManager = trickplayManager; + } + + /// + public string Name => _localization.GetLocalizedString("TaskRefreshTrickplayImages"); + + /// + public string Description => _localization.GetLocalizedString("TaskRefreshTrickplayImagesDescription"); + + /// + public string Key => "RefreshTrickplayImages"; + + /// + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + + /// + public IEnumerable GetDefaultTriggers() + { + return new[] + { + new TaskTriggerInfo + { + Type = TaskTriggerInfo.TriggerDaily, + TimeOfDayTicks = TimeSpan.FromHours(3).Ticks, + MaxRuntimeTicks = TimeSpan.FromHours(5).Ticks + } + }; + } + + /// + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + // TODO: libraryoptions dont run on libraries with trickplay disabled + var items = _libraryManager.GetItemList(new InternalItemsQuery + { + MediaTypes = new[] { MediaType.Video }, + IsVirtualItem = false, + IsFolder = false, + Recursive = false + }).OfType public bool EnableHardwareEncoding { get; set; } - /// - /// Gets or sets a value indicating whether hardware acceleration is enabled for trickplay generation. - /// - public bool EnableTrickplayHwAccel { get; set; } - /// /// Gets or sets a value indicating whether HEVC encoding is enabled. /// diff --git a/MediaBrowser.Model/Configuration/LibraryOptions.cs b/MediaBrowser.Model/Configuration/LibraryOptions.cs index df6829946..7718f822b 100644 --- a/MediaBrowser.Model/Configuration/LibraryOptions.cs +++ b/MediaBrowser.Model/Configuration/LibraryOptions.cs @@ -36,6 +36,10 @@ namespace MediaBrowser.Model.Configuration public bool ExtractChapterImagesDuringLibraryScan { get; set; } + public bool EnableTrickplayImageExtraction { get; set; } + + public bool ExtractTrickplayImagesDuringLibraryScan { get; set; } + public MediaPathInfo[] PathInfos { get; set; } public bool SaveLocalMetadata { get; set; } diff --git a/MediaBrowser.Providers/Trickplay/TrickplayManager.cs b/MediaBrowser.Providers/Trickplay/TrickplayManager.cs index 62180804f..4b4514897 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayManager.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayManager.cs @@ -6,6 +6,7 @@ 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.Trickplay; @@ -26,6 +27,7 @@ namespace MediaBrowser.Providers.Trickplay private readonly IMediaEncoder _mediaEncoder; private readonly IFileSystem _fileSystem; private readonly EncodingHelper _encodingHelper; + private readonly ILibraryManager _libraryManager; private static readonly SemaphoreSlim _resourcePool = new(1, 1); @@ -37,18 +39,21 @@ namespace MediaBrowser.Providers.Trickplay /// The media encoder. /// The file systen. /// The encoding helper. + /// The library manager. public TrickplayManager( ILogger logger, IItemRepository itemRepo, IMediaEncoder mediaEncoder, IFileSystem fileSystem, - EncodingHelper encodingHelper) + EncodingHelper encodingHelper, + ILibraryManager libraryManager) { _logger = logger; _itemRepo = itemRepo; _mediaEncoder = mediaEncoder; _fileSystem = fileSystem; _encodingHelper = encodingHelper; + _libraryManager = libraryManager; } /// @@ -287,11 +292,15 @@ namespace MediaBrowser.Providers.Trickplay return false; } - /* TODO config options + if (!video.RunTimeTicks.HasValue || video.RunTimeTicks.Value < TimeSpan.FromMilliseconds(interval).Ticks) + { + return false; + } + var libraryOptions = _libraryManager.GetLibraryOptions(video); if (libraryOptions is not null) { - if (!libraryOptions.EnableChapterImageExtraction) + if (!libraryOptions.EnableTrickplayImageExtraction) { return false; } @@ -300,12 +309,6 @@ namespace MediaBrowser.Providers.Trickplay { return false; } - */ - - if (!video.RunTimeTicks.HasValue || video.RunTimeTicks.Value < TimeSpan.FromMilliseconds(interval).Ticks) - { - return false; - } // Can't extract images if there are no video streams return video.GetMediaStreams().Count > 0; diff --git a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs index be66dea8a..2b3879ca3 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs @@ -27,6 +27,7 @@ namespace MediaBrowser.Providers.Trickplay private readonly ILogger _logger; private readonly IServerConfigurationManager _configurationManager; private readonly ITrickplayManager _trickplayManager; + private readonly ILibraryManager _libraryManager; /// /// Initializes a new instance of the class. @@ -34,21 +35,24 @@ namespace MediaBrowser.Providers.Trickplay /// The logger. /// The configuration manager. /// The trickplay manager. + /// The library manager. public TrickplayProvider( ILogger logger, IServerConfigurationManager configurationManager, - ITrickplayManager trickplayManager) + ITrickplayManager trickplayManager, + ILibraryManager libraryManager) { _logger = logger; _configurationManager = configurationManager; _trickplayManager = trickplayManager; + _libraryManager = libraryManager; } /// - public string Name => "Trickplay Preview"; + public string Name => "Trickplay Provider"; /// - public int Order => 1000; + public int Order => 100; /// public bool HasChanged(BaseItem item, IDirectoryService directoryService) @@ -95,11 +99,24 @@ namespace MediaBrowser.Providers.Trickplay return FetchInternal(item, options, cancellationToken); } - private async Task FetchInternal(Video item, MetadataRefreshOptions options, CancellationToken cancellationToken) + private async Task FetchInternal(Video video, MetadataRefreshOptions options, CancellationToken cancellationToken) { - // TODO: implement all config options --> + var libraryOptions = _libraryManager.GetLibraryOptions(video); + bool? enableDuringScan = libraryOptions?.ExtractTrickplayImagesDuringLibraryScan; + bool replace = options.ReplaceAllImages; + + if (options.IsAutomated && !enableDuringScan.GetValueOrDefault(false)) + { + _logger.LogDebug("exit refresh: automated - {0} enable scan - {1}", options.IsAutomated, enableDuringScan.GetValueOrDefault(false)); + return ItemUpdateType.None; + } + // TODO: this is always blocking for metadata collection, make non-blocking option - await _trickplayManager.RefreshTrickplayData(item, options.ReplaceAllImages, cancellationToken).ConfigureAwait(false); + if (true) + { + _logger.LogDebug("called refresh"); + await _trickplayManager.RefreshTrickplayData(video, replace, cancellationToken).ConfigureAwait(false); + } // The core doesn't need to trigger any save operations over this return ItemUpdateType.None; -- cgit v1.2.3 From 16ea7baad4030074e291159f3b35ebb009872544 Mon Sep 17 00:00:00 2001 From: nicknsy <20588554+nicknsy@users.noreply.github.com> Date: Thu, 23 Feb 2023 16:10:18 -0800 Subject: Stay consistent with patch branch --- MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs | 1 - 1 file changed, 1 deletion(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs index 004c16ba2..9e91a8bcd 100644 --- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs +++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs @@ -25,7 +25,6 @@ namespace MediaBrowser.Controller.Providers ForceSave = copy.ForceSave; ReplaceAllMetadata = copy.ReplaceAllMetadata; EnableRemoteContentProbe = copy.EnableRemoteContentProbe; - IsAutomated = copy.IsAutomated; IsAutomated = copy.IsAutomated; ImageRefreshMode = copy.ImageRefreshMode; -- cgit v1.2.3 From 6744e712d3a4fd6f800e5499c90b247787e48cb6 Mon Sep 17 00:00:00 2001 From: nicknsy <20588554+nicknsy@users.noreply.github.com> Date: Sat, 25 Feb 2023 15:59:46 -0800 Subject: Use config values --- .../MediaEncoding/IMediaEncoder.cs | 13 ++++-- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 37 +++++++++++----- .../Configuration/ServerConfiguration.cs | 2 + .../Trickplay/TrickplayImagesTask.cs | 12 ++++-- .../Trickplay/TrickplayManager.cs | 49 +++++++++++++++------- .../Trickplay/TrickplayProvider.cs | 16 ++++--- 6 files changed, 89 insertions(+), 40 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index aa9faa936..f5e3d03cb 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; using MediaBrowser.Model.Configuration; @@ -145,10 +146,12 @@ namespace MediaBrowser.Controller.MediaEncoding /// Video container type. /// Media source information. /// Media stream information. - /// The interval. /// The maximum width. + /// The interval. /// Allow for hardware acceleration. - /// Allow for hardware encoding. allowHwAccel must also be true. + /// The input/output thread count for ffmpeg. + /// The qscale value for ffmpeg. + /// The process priority for the ffmpeg process. /// EncodingHelper instance. /// The cancellation token. /// Directory where images where extracted. A given image made before another will always be named with a lower number. @@ -157,10 +160,12 @@ namespace MediaBrowser.Controller.MediaEncoding string container, MediaSourceInfo mediaSource, MediaStream imageStream, - TimeSpan interval, int maxWidth, + TimeSpan interval, bool allowHwAccel, - bool allowHwEncode, + int? threads, + int? qualityScale, + ProcessPriorityClass? priority, EncodingHelper encodingHelper, CancellationToken cancellationToken); diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 9b58f83b4..11f42c3f9 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -783,14 +783,17 @@ namespace MediaBrowser.MediaEncoding.Encoder string container, MediaSourceInfo mediaSource, MediaStream imageStream, - TimeSpan interval, int maxWidth, + TimeSpan interval, bool allowHwAccel, - bool allowHwEncode, + int? threads, + int? qualityScale, + ProcessPriorityClass? priority, EncodingHelper encodingHelper, CancellationToken cancellationToken) { var options = allowHwAccel ? _configurationManager.GetEncodingOptions() : new EncodingOptions(); + threads = threads ?? _threads; // A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin. // Additionally, we must set a few fields without defaults to prevent null pointer exceptions. @@ -822,7 +825,7 @@ namespace MediaBrowser.MediaEncoding.Encoder if (!allowHwAccel) { - inputArg = "-threads " + _threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled + inputArg = "-threads " + threads + " " + inputArg; // HW accel args set a different input thread count, only set if disabled } var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, jobState.OutputVideoCodec).Trim(); @@ -831,7 +834,7 @@ namespace MediaBrowser.MediaEncoding.Encoder throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters."); } - return ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, interval, vidEncoder, _threads, cancellationToken); + return ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, interval, vidEncoder, threads, qualityScale, priority, cancellationToken); } private async Task ExtractVideoImagesOnIntervalInternal( @@ -839,7 +842,9 @@ namespace MediaBrowser.MediaEncoding.Encoder string filterParam, TimeSpan interval, string vidEncoder, - int outputThreads, + int? outputThreads, + int? qualityScale, + ProcessPriorityClass? priority, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(inputArg)) @@ -857,10 +862,6 @@ namespace MediaBrowser.MediaEncoding.Encoder { filterParam = filterParam.Insert(filterParam.IndexOf("\"", StringComparison.Ordinal) + 1, fps + ","); } - else - { - filterParam += fps + ","; - } var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(targetDirectory); @@ -869,11 +870,12 @@ namespace MediaBrowser.MediaEncoding.Encoder // Final command arguments var args = string.Format( CultureInfo.InvariantCulture, - "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} -f {4} \"{5}\"", + "-loglevel error {0} -an -sn {1} -threads {2} -c:v {3} {4}-f {5} \"{6}\"", inputArg, filterParam, - outputThreads, + outputThreads.GetValueOrDefault(_threads), vidEncoder, + qualityScale.HasValue ? "-qscale:v " + qualityScale.Value.ToString(CultureInfo.InvariantCulture) + " " : string.Empty, "image2", outputPath); @@ -904,6 +906,19 @@ namespace MediaBrowser.MediaEncoding.Encoder { StartProcess(processWrapper); + // Set process priority + if (priority.HasValue) + { + try + { + processWrapper.Process.PriorityClass = priority.Value; + } + catch (Exception ex) + { + _logger.LogDebug(ex, "Unable to set process priority to {Priority} for {Description}", priority.Value, processDescription); + } + } + // Need to give ffmpeg enough time to make all the thumbnails, which could be a while, // but we still need to detect if the process hangs. // Making the assumption that as long as new jpegs are showing up, everything is good. diff --git a/MediaBrowser.Model/Configuration/ServerConfiguration.cs b/MediaBrowser.Model/Configuration/ServerConfiguration.cs index 78a310f0b..097eff295 100644 --- a/MediaBrowser.Model/Configuration/ServerConfiguration.cs +++ b/MediaBrowser.Model/Configuration/ServerConfiguration.cs @@ -264,5 +264,7 @@ namespace MediaBrowser.Model.Configuration /// /// The limit for parallel image encoding. public int ParallelImageEncodingLimit { get; set; } + + public TrickplayOptions TrickplayOptions { get; set; } = new TrickplayOptions(); } } diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs index 87ac145d7..a364926c0 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -22,7 +22,6 @@ namespace MediaBrowser.Providers.Trickplay private readonly ILogger _logger; private readonly ILibraryManager _libraryManager; private readonly ILocalizationManager _localization; - private readonly IServerConfigurationManager _configurationManager; private readonly ITrickplayManager _trickplayManager; /// @@ -31,19 +30,16 @@ namespace MediaBrowser.Providers.Trickplay /// The logger. /// The library manager. /// The localization manager. - /// The configuration manager. /// The trickplay manager. public TrickplayImagesTask( ILogger logger, ILibraryManager libraryManager, ILocalizationManager localization, - IServerConfigurationManager configurationManager, ITrickplayManager trickplayManager) { _libraryManager = libraryManager; _logger = logger; _localization = localization; - _configurationManager = configurationManager; _trickplayManager = trickplayManager; } @@ -77,6 +73,14 @@ namespace MediaBrowser.Providers.Trickplay public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) { // TODO: libraryoptions dont run on libraries with trickplay disabled + /* will this still get all sub-items? should recursive be true? + * from chapterimagestask + * DtoOptions = new DtoOptions(false) + { + EnableImages = false + }, + SourceTypes = new SourceType[] { SourceType.Library }, + */ var items = _libraryManager.GetItemList(new InternalItemsQuery { MediaTypes = new[] { MediaType.Video }, diff --git a/MediaBrowser.Providers/Trickplay/TrickplayManager.cs b/MediaBrowser.Providers/Trickplay/TrickplayManager.cs index cb916dfdb..ed2c11281 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayManager.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayManager.cs @@ -5,11 +5,13 @@ using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Controller.Configuration; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.MediaEncoding; using MediaBrowser.Controller.Persistence; using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Entities; using MediaBrowser.Model.IO; using Microsoft.Extensions.Logging; @@ -28,6 +30,7 @@ namespace MediaBrowser.Providers.Trickplay private readonly IFileSystem _fileSystem; private readonly EncodingHelper _encodingHelper; private readonly ILibraryManager _libraryManager; + private readonly IServerConfigurationManager _config; private static readonly SemaphoreSlim _resourcePool = new(1, 1); @@ -40,13 +43,15 @@ namespace MediaBrowser.Providers.Trickplay /// The file systen. /// The encoding helper. /// The library manager. + /// The server configuration manager. public TrickplayManager( ILogger logger, IItemRepository itemRepo, IMediaEncoder mediaEncoder, IFileSystem fileSystem, EncodingHelper encodingHelper, - ILibraryManager libraryManager) + ILibraryManager libraryManager, + IServerConfigurationManager config) { _logger = logger; _itemRepo = itemRepo; @@ -54,6 +59,7 @@ namespace MediaBrowser.Providers.Trickplay _fileSystem = fileSystem; _encodingHelper = encodingHelper; _libraryManager = libraryManager; + _config = config; } /// @@ -61,16 +67,27 @@ namespace MediaBrowser.Providers.Trickplay { _logger.LogDebug("Trickplay refresh for {ItemId} (replace existing: {Replace})", video.Id, replace); - foreach (var width in new int[] { 320 } /*todo conf*/) + var options = _config.Configuration.TrickplayOptions; + foreach (var width in options.WidthResolutions) { cancellationToken.ThrowIfCancellationRequested(); - await RefreshTrickplayData(video, replace, width, 10000/*todo conf*/, 10/*todo conf*/, 10/*todo conf*/, true/*todo conf*/, true/*todo conf*/, cancellationToken).ConfigureAwait(false); + await RefreshTrickplayDataInternal( + video, + replace, + width, + options, + cancellationToken).ConfigureAwait(false); } } - private async Task RefreshTrickplayData(Video video, bool replace, int width, int interval, int tileWidth, int tileHeight, bool doHwAccel, bool doHwEncode, CancellationToken cancellationToken) + private async Task RefreshTrickplayDataInternal( + Video video, + bool replace, + int width, + TrickplayOptions options, + CancellationToken cancellationToken) { - if (!CanGenerateTrickplay(video, interval)) + if (!CanGenerateTrickplay(video, options.Interval)) { return; } @@ -108,10 +125,12 @@ namespace MediaBrowser.Providers.Trickplay container, mediaSource, mediaStream, - TimeSpan.FromMilliseconds(interval), width, - doHwAccel, - doHwEncode, + TimeSpan.FromMilliseconds(options.Interval), + options.EnableHwAcceleration, + options.ProcessThreads, + options.Qscale, + options.ProcessPriority, _encodingHelper, cancellationToken).ConfigureAwait(false); @@ -127,7 +146,7 @@ namespace MediaBrowser.Providers.Trickplay // Create tiles var tilesTempDir = Path.Combine(imgTempDir, Guid.NewGuid().ToString("N")); - var tilesInfo = CreateTiles(images, width, interval, tileWidth, tileHeight, 100/* todo _config.JpegQuality*/, tilesTempDir, outputDir); + var tilesInfo = CreateTiles(images, width, options, tilesTempDir, outputDir); // Save tiles info try @@ -166,7 +185,7 @@ namespace MediaBrowser.Providers.Trickplay } } - private TrickplayTilesInfo CreateTiles(List images, int width, int interval, int tileWidth, int tileHeight, int quality, string workDir, string outputDir) + private TrickplayTilesInfo CreateTiles(List images, int width, TrickplayOptions options, string workDir, string outputDir) { if (images.Count == 0) { @@ -178,9 +197,9 @@ namespace MediaBrowser.Providers.Trickplay var tilesInfo = new TrickplayTilesInfo { Width = width, - Interval = interval, - TileWidth = tileWidth, - TileHeight = tileHeight, + Interval = options.Interval, + TileWidth = options.TileWidth, + TileHeight = options.TileHeight, TileCount = 0, Bandwidth = 0 }; @@ -244,7 +263,7 @@ namespace MediaBrowser.Providers.Trickplay var tileGridPath = Path.Combine(workDir, $"{imgNo}.jpg"); using (var stream = File.OpenWrite(tileGridPath)) { - tileGrid.Encode(stream, SKEncodedImageFormat.Jpeg, quality); + tileGrid.Encode(stream, SKEncodedImageFormat.Jpeg, options.JpegQuality); } var bitrate = (int)Math.Ceiling((decimal)new FileInfo(tileGridPath).Length * 8 / tilesInfo.TileWidth / tilesInfo.TileHeight / (tilesInfo.Interval / 1000)); @@ -351,7 +370,7 @@ namespace MediaBrowser.Providers.Trickplay { Directory.Move(source, destination); } - catch (System.IO.IOException) + catch (IOException) { // Cross device move requires a copy Directory.CreateDirectory(destination); diff --git a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs index e4bd9e3c2..e29646725 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayProvider.cs @@ -7,6 +7,7 @@ using MediaBrowser.Controller.Entities.TV; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; using MediaBrowser.Controller.Trickplay; +using MediaBrowser.Model.Configuration; using Microsoft.Extensions.Logging; namespace MediaBrowser.Providers.Trickplay @@ -25,7 +26,7 @@ namespace MediaBrowser.Providers.Trickplay IForcedProvider { private readonly ILogger _logger; - private readonly IServerConfigurationManager _configurationManager; + private readonly IServerConfigurationManager _config; private readonly ITrickplayManager _trickplayManager; private readonly ILibraryManager _libraryManager; @@ -33,17 +34,17 @@ namespace MediaBrowser.Providers.Trickplay /// Initializes a new instance of the class. /// /// The logger. - /// The configuration manager. + /// The configuration manager. /// The trickplay manager. /// The library manager. public TrickplayProvider( ILogger logger, - IServerConfigurationManager configurationManager, + IServerConfigurationManager config, ITrickplayManager trickplayManager, ILibraryManager libraryManager) { _logger = logger; - _configurationManager = configurationManager; + _config = config; _trickplayManager = trickplayManager; _libraryManager = libraryManager; } @@ -110,11 +111,14 @@ namespace MediaBrowser.Providers.Trickplay return ItemUpdateType.None; } - // TODO: this is always blocking for metadata collection, make non-blocking option - if (true) + if (_config.Configuration.TrickplayOptions.ScanBehavior == TrickplayScanBehavior.Blocking) { await _trickplayManager.RefreshTrickplayData(video, replace, cancellationToken).ConfigureAwait(false); } + else + { + _ = _trickplayManager.RefreshTrickplayData(video, replace, cancellationToken).ConfigureAwait(false); + } // The core doesn't need to trigger any save operations over this return ItemUpdateType.None; -- cgit v1.2.3 From dd8ef08592830236b31307e2424b491e974f024a Mon Sep 17 00:00:00 2001 From: Nick <20588554+nicknsy@users.noreply.github.com> Date: Wed, 29 Mar 2023 16:43:17 -0700 Subject: Move fps filter to GetVideoProcessingFilterParam --- .../MediaEncoding/EncodingHelper.cs | 9 +++++++++ MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 19 ++++--------------- 2 files changed, 13 insertions(+), 15 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 0889a90f4..bcdf2934a 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -4806,6 +4806,15 @@ namespace MediaBrowser.Controller.MediaEncoding subFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); overlayFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter)); + var framerate = GetFramerateParam(state); + if (framerate.HasValue) + { + mainFilters.Insert(0, string.Format( + CultureInfo.InvariantCulture, + "fps={0}", + framerate.Value)); + } + var mainStr = string.Empty; if (mainFilters?.Count > 0) { diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 11f42c3f9..4692bf504 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -804,7 +804,7 @@ namespace MediaBrowser.MediaEncoding.Encoder options.EnableTonemapping = false; } - var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth }; + var baseRequest = new BaseEncodingJobOptions { MaxWidth = maxWidth, MaxFramerate = (float)(1.0 / interval.TotalSeconds) }; var jobState = new EncodingJobInfo(TranscodingJobType.Progressive) { IsVideoRequest = true, // must be true for InputVideoHwaccelArgs to return non-empty value @@ -829,18 +829,17 @@ namespace MediaBrowser.MediaEncoding.Encoder } var filterParam = encodingHelper.GetVideoProcessingFilterParam(jobState, options, jobState.OutputVideoCodec).Trim(); - if (string.IsNullOrWhiteSpace(filterParam) || filterParam.IndexOf("\"", StringComparison.Ordinal) == -1) + if (string.IsNullOrWhiteSpace(filterParam)) { throw new InvalidOperationException("EncodingHelper returned empty or invalid filter parameters."); } - return ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, interval, vidEncoder, threads, qualityScale, priority, cancellationToken); + return ExtractVideoImagesOnIntervalInternal(inputArg, filterParam, vidEncoder, threads, qualityScale, priority, cancellationToken); } private async Task ExtractVideoImagesOnIntervalInternal( string inputArg, string filterParam, - TimeSpan interval, string vidEncoder, int? outputThreads, int? qualityScale, @@ -853,16 +852,6 @@ namespace MediaBrowser.MediaEncoding.Encoder } // Output arguments - string fps = "fps=1/" + interval.TotalSeconds.ToString(CultureInfo.InvariantCulture); - if (string.IsNullOrWhiteSpace(filterParam)) - { - filterParam = "-vf \"" + fps + "\""; - } - else if (filterParam.IndexOf("\"", StringComparison.Ordinal) != -1) - { - filterParam = filterParam.Insert(filterParam.IndexOf("\"", StringComparison.Ordinal) + 1, fps + ","); - } - var targetDirectory = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid().ToString("N")); Directory.CreateDirectory(targetDirectory); var outputPath = Path.Combine(targetDirectory, "%08d.jpg"); @@ -895,7 +884,7 @@ namespace MediaBrowser.MediaEncoding.Encoder }; var processDescription = string.Format(CultureInfo.InvariantCulture, "{0} {1}", process.StartInfo.FileName, process.StartInfo.Arguments); - _logger.LogDebug("{ProcessDescription}", processDescription); + _logger.LogInformation("Trickplay generation: {ProcessDescription}", processDescription); using (var processWrapper = new ProcessWrapper(process, this)) { -- cgit v1.2.3 From 33770322282326304b4b8073f583d6fed2354c0b Mon Sep 17 00:00:00 2001 From: Nick <20588554+nicknsy@users.noreply.github.com> Date: Mon, 1 May 2023 12:51:05 -0700 Subject: crobibero styling, format, code suggestions --- .../MediaEncoding/EncodingHelper.cs | 27 +- .../Trickplay/ITrickplayManager.cs | 77 ++- MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs | 2 +- .../Configuration/TrickplayOptions.cs | 109 ++-- .../Configuration/TrickplayScanBehavior.cs | 25 +- MediaBrowser.Model/Entities/TrickplayTilesInfo.cs | 79 ++- .../Trickplay/TrickplayImagesTask.cs | 147 +++--- .../Trickplay/TrickplayManager.cs | 569 ++++++++++----------- .../Trickplay/TrickplayProvider.cs | 181 ++++--- 9 files changed, 602 insertions(+), 614 deletions(-) (limited to 'MediaBrowser.Controller') diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index bcdf2934a..01b6e31e9 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -90,6 +90,13 @@ namespace MediaBrowser.Controller.MediaEncoding { "truehd", 6 }, }; + private static readonly string _defaultMjpegEncoder = "mjpeg"; + private static readonly Dictionary _mjpegCodecMap = new(StringComparer.OrdinalIgnoreCase) + { + { "vaapi", _defaultMjpegEncoder + "_vaapi" }, + { "qsv", _defaultMjpegEncoder + "_qsv" } + }; + public static readonly string[] LosslessAudioCodecs = new string[] { "alac", @@ -151,32 +158,20 @@ namespace MediaBrowser.Controller.MediaEncoding private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) { - var defaultEncoder = "mjpeg"; - if (state.VideoType == VideoType.VideoFile) { var hwType = encodingOptions.HardwareAccelerationType; - var codecMap = new Dictionary(StringComparer.OrdinalIgnoreCase) - { - { "vaapi", defaultEncoder + "_vaapi" }, - { "qsv", defaultEncoder + "_qsv" } - }; - if (!string.IsNullOrEmpty(hwType) && encodingOptions.EnableHardwareEncoding - && codecMap.ContainsKey(hwType)) + && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder) + && _mediaEncoder.SupportsEncoder(preferredEncoder)) { - var preferredEncoder = codecMap[hwType]; - - if (_mediaEncoder.SupportsEncoder(preferredEncoder)) - { - return preferredEncoder; - } + return preferredEncoder; } } - return defaultEncoder; + return _defaultMjpegEncoder; } private bool IsVaapiSupported(EncodingJobInfo state) diff --git a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs index bae458f98..8e82c57d4 100644 --- a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs +++ b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs @@ -5,50 +5,49 @@ using System.Threading.Tasks; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Entities; -namespace MediaBrowser.Controller.Trickplay +namespace MediaBrowser.Controller.Trickplay; + +/// +/// Interface ITrickplayManager. +/// +public interface ITrickplayManager { /// - /// Interface ITrickplayManager. + /// Generate or replace trickplay data. /// - public interface ITrickplayManager - { - /// - /// Generate or replace trickplay data. - /// - /// The video. - /// Whether or not existing data should be replaced. - /// CancellationToken to use for operation. - /// Task. - Task RefreshTrickplayData(Video video, bool replace, CancellationToken cancellationToken); + /// The video. + /// Whether or not existing data should be replaced. + /// CancellationToken to use for operation. + /// Task. + Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken); - /// - /// Get available trickplay resolutions and corresponding info. - /// - /// The item. - /// Map of width resolutions to trickplay tiles info. - Dictionary GetTilesResolutions(Guid itemId); + /// + /// Get available trickplay resolutions and corresponding info. + /// + /// The item. + /// Map of width resolutions to trickplay tiles info. + Dictionary GetTilesResolutions(Guid itemId); - /// - /// Saves trickplay tiles info. - /// - /// The item. - /// The trickplay tiles info. - void SaveTilesInfo(Guid itemId, TrickplayTilesInfo tilesInfo); + /// + /// Saves trickplay tiles info. + /// + /// The item. + /// The trickplay tiles info. + void SaveTilesInfo(Guid itemId, TrickplayTilesInfo tilesInfo); - /// - /// Gets the trickplay manifest. - /// - /// The item. - /// A map of media source id to a map of tile width to tile info. - Dictionary> GetTrickplayManifest(BaseItem item); + /// + /// Gets the trickplay manifest. + /// + /// The item. + /// A map of media source id to a map of tile width to tile info. + Dictionary> GetTrickplayManifest(BaseItem item); - /// - /// Gets the path to a trickplay tiles image. - /// - /// The item. - /// The width of a single tile. - /// The tile grid's index. - /// The absolute path. - string GetTrickplayTilePath(BaseItem item, int width, int index); - } + /// + /// Gets the path to a trickplay tiles image. + /// + /// The item. + /// The width of a single tile. + /// The tile grid's index. + /// The absolute path. + string GetTrickplayTilePath(BaseItem item, int width, int index); } diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs index 4692bf504..000831fe2 100644 --- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs +++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs @@ -793,7 +793,7 @@ namespace MediaBrowser.MediaEncoding.Encoder CancellationToken cancellationToken) { var options = allowHwAccel ? _configurationManager.GetEncodingOptions() : new EncodingOptions(); - threads = threads ?? _threads; + threads ??= _threads; // A new EncodingOptions instance must be used as to not disable HW acceleration for all of Jellyfin. // Additionally, we must set a few fields without defaults to prevent null pointer exceptions. diff --git a/MediaBrowser.Model/Configuration/TrickplayOptions.cs b/MediaBrowser.Model/Configuration/TrickplayOptions.cs index d89e5f590..1fff1a5ed 100644 --- a/MediaBrowser.Model/Configuration/TrickplayOptions.cs +++ b/MediaBrowser.Model/Configuration/TrickplayOptions.cs @@ -1,61 +1,60 @@ using System.Collections.Generic; using System.Diagnostics; -namespace MediaBrowser.Model.Configuration +namespace MediaBrowser.Model.Configuration; + +/// +/// Class TrickplayOptions. +/// +public class TrickplayOptions { /// - /// Class TrickplayOptions. - /// - public class TrickplayOptions - { - /// - /// Gets or sets a value indicating whether or not to use HW acceleration. - /// - public bool EnableHwAcceleration { get; set; } = false; - - /// - /// Gets or sets the behavior used by trickplay provider on library scan/update. - /// - public TrickplayScanBehavior ScanBehavior { get; set; } = TrickplayScanBehavior.NonBlocking; - - /// - /// Gets or sets the process priority for the ffmpeg process. - /// - public ProcessPriorityClass ProcessPriority { get; set; } = ProcessPriorityClass.BelowNormal; - - /// - /// Gets or sets the interval, in ms, between each new trickplay image. - /// - public int Interval { get; set; } = 10000; - - /// - /// Gets or sets the target width resolutions, in px, to generates preview images for. - /// - public int[] WidthResolutions { get; set; } = new[] { 320 }; - - /// - /// Gets or sets number of tile images to allow in X dimension. - /// - public int TileWidth { get; set; } = 10; - - /// - /// Gets or sets number of tile images to allow in Y dimension. - /// - public int TileHeight { get; set; } = 10; - - /// - /// Gets or sets the ffmpeg output quality level. - /// - public int Qscale { get; set; } = 4; - - /// - /// Gets or sets the jpeg quality to use for image tiles. - /// - public int JpegQuality { get; set; } = 90; - - /// - /// Gets or sets the number of threads to be used by ffmpeg. - /// - public int ProcessThreads { get; set; } = 0; - } + /// Gets or sets a value indicating whether or not to use HW acceleration. + /// + public bool EnableHwAcceleration { get; set; } = false; + + /// + /// Gets or sets the behavior used by trickplay provider on library scan/update. + /// + public TrickplayScanBehavior ScanBehavior { get; set; } = TrickplayScanBehavior.NonBlocking; + + /// + /// Gets or sets the process priority for the ffmpeg process. + /// + public ProcessPriorityClass ProcessPriority { get; set; } = ProcessPriorityClass.BelowNormal; + + /// + /// Gets or sets the interval, in ms, between each new trickplay image. + /// + public int Interval { get; set; } = 10000; + + /// + /// Gets or sets the target width resolutions, in px, to generates preview images for. + /// + public int[] WidthResolutions { get; set; } = new[] { 320 }; + + /// + /// Gets or sets number of tile images to allow in X dimension. + /// + public int TileWidth { get; set; } = 10; + + /// + /// Gets or sets number of tile images to allow in Y dimension. + /// + public int TileHeight { get; set; } = 10; + + /// + /// Gets or sets the ffmpeg output quality level. + /// + public int Qscale { get; set; } = 4; + + /// + /// Gets or sets the jpeg quality to use for image tiles. + /// + public int JpegQuality { get; set; } = 90; + + /// + /// Gets or sets the number of threads to be used by ffmpeg. + /// + public int ProcessThreads { get; set; } = 0; } diff --git a/MediaBrowser.Model/Configuration/TrickplayScanBehavior.cs b/MediaBrowser.Model/Configuration/TrickplayScanBehavior.cs index 799794176..d0db53218 100644 --- a/MediaBrowser.Model/Configuration/TrickplayScanBehavior.cs +++ b/MediaBrowser.Model/Configuration/TrickplayScanBehavior.cs @@ -1,18 +1,17 @@ -namespace MediaBrowser.Model.Configuration +namespace MediaBrowser.Model.Configuration; + +/// +/// Enum TrickplayScanBehavior. +/// +public enum TrickplayScanBehavior { /// - /// Enum TrickplayScanBehavior. + /// Starts generation, only return once complete. /// - public enum TrickplayScanBehavior - { - /// - /// Starts generation, only return once complete. - /// - Blocking, + Blocking, - /// - /// Start generation, return immediately. - /// - NonBlocking - } + /// + /// Start generation, return immediately. + /// + NonBlocking } diff --git a/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs b/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs index 84b6b0322..86d37787f 100644 --- a/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs +++ b/MediaBrowser.Model/Entities/TrickplayTilesInfo.cs @@ -1,50 +1,49 @@ -namespace MediaBrowser.Model.Entities +namespace MediaBrowser.Model.Entities; + +/// +/// Class TrickplayTilesInfo. +/// +public class TrickplayTilesInfo { /// - /// Class TrickplayTilesInfo. + /// Gets or sets width of an individual tile. /// - public class TrickplayTilesInfo - { - /// - /// Gets or sets width of an individual tile. - /// - /// The width. - public int Width { get; set; } + /// The width. + public int Width { get; set; } - /// - /// Gets or sets height of an individual tile. - /// - /// The height. - public int Height { get; set; } + /// + /// Gets or sets height of an individual tile. + /// + /// The height. + public int Height { get; set; } - /// - /// Gets or sets amount of tiles per row. - /// - /// The tile grid's width. - public int TileWidth { get; set; } + /// + /// Gets or sets amount of tiles per row. + /// + /// The tile grid's width. + public int TileWidth { get; set; } - /// - /// Gets or sets amount of tiles per column. - /// - /// The tile grid's height. - public int TileHeight { get; set; } + /// + /// Gets or sets amount of tiles per column. + /// + /// The tile grid's height. + public int TileHeight { get; set; } - /// - /// Gets or sets total amount of non-black tiles. - /// - /// The tile count. - public int TileCount { get; set; } + /// + /// Gets or sets total amount of non-black tiles. + /// + /// The tile count. + public int TileCount { get; set; } - /// - /// Gets or sets interval in milliseconds between each trickplay tile. - /// - /// The interval. - public int Interval { get; set; } + /// + /// Gets or sets interval in milliseconds between each trickplay tile. + /// + /// The interval. + public int Interval { get; set; } - /// - /// Gets or sets peak bandwith usage in bits per second. - /// - /// The bandwidth. - public int Bandwidth { get; set; } - } + /// + /// Gets or sets peak bandwith usage in bits per second. + /// + /// The bandwidth. + public int Bandwidth { get; set; } } diff --git a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs index 8d0d9d5a3..f32557cd1 100644 --- a/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs +++ b/MediaBrowser.Providers/Trickplay/TrickplayImagesTask.cs @@ -12,98 +12,97 @@ using MediaBrowser.Model.Globalization; using MediaBrowser.Model.Tasks; using Microsoft.Extensions.Logging; -namespace MediaBrowser.Providers.Trickplay +namespace MediaBrowser.Providers.Trickplay; + +/// +/// Class TrickplayImagesTask. +/// +public class TrickplayImagesTask : IScheduledTask { + private readonly ILogger _logger; + private readonly ILibraryManager _libraryManager; + private readonly ILocalizationManager _localization; + private readonly ITrickplayManager _trickplayManager; + /// - /// Class TrickplayImagesTask. + /// Initializes a new instance of the class. /// - public class TrickplayImagesTask : IScheduledTask + /// The logger. + /// The library manager. + /// The localization manager. + /// The trickplay manager. + public TrickplayImagesTask( + ILogger logger, + ILibraryManager libraryManager, + ILocalizationManager localization, + ITrickplayManager trickplayManager) { - private readonly ILogger _logger; - private readonly ILibraryManager _libraryManager; - private readonly ILocalizationManager _localization; - private readonly ITrickplayManager _trickplayManager; - - /// - /// Initializes a new instance of the class. - /// - /// The logger. - /// The library manager. - /// The localization manager. - /// The trickplay manager. - public TrickplayImagesTask( - ILogger logger, - ILibraryManager libraryManager, - ILocalizationManager localization, - ITrickplayManager trickplayManager) - { - _libraryManager = libraryManager; - _logger = logger; - _localization = localization; - _trickplayManager = trickplayManager; - } + _libraryManager = libraryManager; + _logger = logger; + _localization = localization; + _trickplayManager = trickplayManager; + } - /// - public string Name => _localization.GetLocalizedString("TaskRefreshTrickplayImages"); + /// + public string Name => _localization.GetLocalizedString("TaskRefreshTrickplayImages"); - /// - public string Description => _localization.GetLocalizedString("TaskRefreshTrickplayImagesDescription"); + /// + public string Description => _localization.GetLocalizedString("TaskRefreshTrickplayImagesDescription"); - /// - public string Key => "RefreshTrickplayImages"; + /// + public string Key => "RefreshTrickplayImages"; - /// - public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); + /// + public string Category => _localization.GetLocalizedString("TasksLibraryCategory"); - /// - public IEnumerable GetDefaultTriggers() + /// + public IEnumerable GetDefaultTriggers() + { + return new[] { - return new[] + new TaskTriggerInfo { - new TaskTriggerInfo - { - Type = TaskTriggerInfo.TriggerDaily, - TimeOfDayTicks = TimeSpan.FromHours(3).Ticks - } - }; - } + Type = TaskTriggerInfo.TriggerDaily, + TimeOfDayTicks = TimeSpan.FromHours(3).Ticks + } + }; + } - /// - public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + /// + public async Task ExecuteAsync(IProgress progress, CancellationToken cancellationToken) + { + var items = _libraryManager.GetItemList(new InternalItemsQuery { - var items = _libraryManager.GetItemList(new InternalItemsQuery - { - MediaTypes = new[] { MediaType.Video }, - IsVirtualItem = false, - IsFolder = false, - Recursive = true - }).OfType