diff options
Diffstat (limited to 'MediaBrowser.Controller')
41 files changed, 575 insertions, 171 deletions
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs index b263c173e..6acab13fe 100644 --- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs @@ -1,5 +1,4 @@ using System; -using System.Linq; using Jellyfin.Extensions; using MediaBrowser.Controller.Channels; using MediaBrowser.Controller.Configuration; diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs index ac20120d9..975218ad7 100644 --- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs +++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs @@ -1,4 +1,3 @@ -using System.Threading; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; diff --git a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs index dea1c2f32..2a7e6be0f 100644 --- a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs +++ b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs @@ -23,9 +23,12 @@ namespace MediaBrowser.Controller.ClientEvent { var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log"; var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName); - await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None); - await fileContents.CopyToAsync(fileStream).ConfigureAwait(false); - return fileName; + var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None); + await using (fileStream.ConfigureAwait(false)) + { + await fileContents.CopyToAsync(fileStream).ConfigureAwait(false); + return fileName; + } } } } diff --git a/MediaBrowser.Controller/Drawing/IImageEncoder.cs b/MediaBrowser.Controller/Drawing/IImageEncoder.cs index e5c8ebfaf..c7bfbdb53 100644 --- a/MediaBrowser.Controller/Drawing/IImageEncoder.cs +++ b/MediaBrowser.Controller/Drawing/IImageEncoder.cs @@ -81,5 +81,15 @@ namespace MediaBrowser.Controller.Drawing /// <param name="posters">The list of poster paths.</param> /// <param name="backdrops">The list of backdrop paths.</param> void CreateSplashscreen(IReadOnlyList<string> posters, IReadOnlyList<string> backdrops); + + /// <summary> + /// Creates a new trickplay tile image. + /// </summary> + /// <param name="options">The options to use when creating the image. Width and Height are a quantity of thumbnails in this case, not pixels.</param> + /// <param name="quality">The image encode quality.</param> + /// <param name="imgWidth">The width of a single trickplay thumbnail.</param> + /// <param name="imgHeight">Optional height of a single trickplay thumbnail, if it is known.</param> + /// <returns>Height of single decoded trickplay thumbnail.</returns> + int CreateTrickplayTile(ImageCollageOptions options, int quality, int imgWidth, int? imgHeight); } } diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs index cdc3d52b9..0d1e2a5a0 100644 --- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs +++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Threading.Tasks; using Jellyfin.Data.Entities; using MediaBrowser.Controller.Entities; @@ -74,14 +73,6 @@ namespace MediaBrowser.Controller.Drawing /// Processes the image. /// </summary> /// <param name="options">The options.</param> - /// <param name="toStream">To stream.</param> - /// <returns>Task.</returns> - Task ProcessImage(ImageProcessingOptions options, Stream toStream); - - /// <summary> - /// Processes the image. - /// </summary> - /// <param name="options">The options.</param> /// <returns>Task.</returns> Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options); @@ -97,7 +88,5 @@ namespace MediaBrowser.Controller.Drawing /// <param name="options">The options.</param> /// <param name="libraryName">The library name to draw onto the collage.</param> void CreateImageCollage(ImageCollageOptions options, string? libraryName); - - bool SupportsTransparency(string path); } } diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs index 7912c5e87..953cfe698 100644 --- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs +++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs @@ -119,7 +119,8 @@ namespace MediaBrowser.Controller.Drawing private bool IsFormatSupported(string originalImagePath) { var ext = Path.GetExtension(originalImagePath); - return SupportedOutputFormats.Any(outputFormat => string.Equals(ext, "." + outputFormat, StringComparison.OrdinalIgnoreCase)); + ext = ext.Replace(".jpeg", ".jpg", StringComparison.OrdinalIgnoreCase); + return SupportedOutputFormats.Any(outputFormat => string.Equals(ext, outputFormat.GetExtension(), StringComparison.OrdinalIgnoreCase)); } } } diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs index c7216a320..243d2f04f 100644 --- a/MediaBrowser.Controller/Entities/Audio/Audio.cs +++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs @@ -63,7 +63,7 @@ namespace MediaBrowser.Controller.Entities.Audio /// </summary> /// <value>The type of the media.</value> [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Audio; + public override MediaType MediaType => MediaType.Audio; public override double GetDefaultPrimaryImageAspectRatio() { diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index 9f3e8eec9..98485f9a8 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -422,7 +422,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The type of the media.</value> [JsonIgnore] - public virtual string MediaType => null; + public virtual MediaType MediaType => MediaType.Unknown; [JsonIgnore] public virtual string[] PhysicalLocations @@ -724,7 +724,7 @@ namespace MediaBrowser.Controller.Entities if (this is IHasCollectionType view) { - if (string.Equals(view.CollectionType, CollectionType.LiveTv, StringComparison.OrdinalIgnoreCase)) + if (view.CollectionType == CollectionType.LiveTv) { return true; } diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs index 615d236c7..dcd22a3b4 100644 --- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs +++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs @@ -95,10 +95,7 @@ namespace MediaBrowser.Controller.Entities } var p = destProps.Find(x => x.Name == sourceProp.Name); - if (p is not null) - { - p.SetValue(dest, v); - } + p?.SetValue(dest, v); } } diff --git a/MediaBrowser.Controller/Entities/BasePluginFolder.cs b/MediaBrowser.Controller/Entities/BasePluginFolder.cs index afafaf1c2..4bf21061c 100644 --- a/MediaBrowser.Controller/Entities/BasePluginFolder.cs +++ b/MediaBrowser.Controller/Entities/BasePluginFolder.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; namespace MediaBrowser.Controller.Entities { @@ -11,7 +12,7 @@ namespace MediaBrowser.Controller.Entities public abstract class BasePluginFolder : Folder, ICollectionFolder { [JsonIgnore] - public virtual string? CollectionType => null; + public virtual CollectionType? CollectionType => null; [JsonIgnore] public override bool SupportsInheritedParentImages => false; diff --git a/MediaBrowser.Controller/Entities/Book.cs b/MediaBrowser.Controller/Entities/Book.cs index 519dfa82a..66dea1084 100644 --- a/MediaBrowser.Controller/Entities/Book.cs +++ b/MediaBrowser.Controller/Entities/Book.cs @@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Entities } [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Book; + public override MediaType MediaType => MediaType.Book; public override bool SupportsPlayedStatus => true; diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs index f51162f9d..992bb19bb 100644 --- a/MediaBrowser.Controller/Entities/CollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs @@ -11,6 +11,7 @@ using System.Text.Json; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions.Json; using MediaBrowser.Controller.IO; using MediaBrowser.Controller.Library; @@ -69,7 +70,7 @@ namespace MediaBrowser.Controller.Entities [JsonIgnore] public override bool SupportsInheritedParentImages => false; - public string CollectionType { get; set; } + public CollectionType? CollectionType { get; set; } /// <summary> /// Gets the item's children. diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs index 44fe65103..e707eedbf 100644 --- a/MediaBrowser.Controller/Entities/Folder.cs +++ b/MediaBrowser.Controller/Entities/Folder.cs @@ -598,7 +598,7 @@ namespace MediaBrowser.Controller.Entities for (var i = 0; i < childrenCount; i++) { - await actionBlock.SendAsync(i).ConfigureAwait(false); + await actionBlock.SendAsync(i, cancellationToken).ConfigureAwait(false); } actionBlock.Complete(); diff --git a/MediaBrowser.Controller/Entities/ICollectionFolder.cs b/MediaBrowser.Controller/Entities/ICollectionFolder.cs index 89e494ebc..742691b00 100644 --- a/MediaBrowser.Controller/Entities/ICollectionFolder.cs +++ b/MediaBrowser.Controller/Entities/ICollectionFolder.cs @@ -3,6 +3,7 @@ #pragma warning disable CA1819, CS1591 using System; +using Jellyfin.Data.Enums; namespace MediaBrowser.Controller.Entities { @@ -27,6 +28,6 @@ namespace MediaBrowser.Controller.Entities public interface IHasCollectionType { - string CollectionType { get; } + CollectionType? CollectionType { get; } } } diff --git a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs index 14459624e..7a73f3eaf 100644 --- a/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs +++ b/MediaBrowser.Controller/Entities/IHasDisplayOrder.cs @@ -1,5 +1,7 @@ #nullable disable +using Jellyfin.Data.Enums; + namespace MediaBrowser.Controller.Entities { /// <summary> diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs index a51299284..555dd050c 100644 --- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs +++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs @@ -36,13 +36,13 @@ namespace MediaBrowser.Controller.Entities ImageTypes = Array.Empty<ImageType>(); IncludeItemTypes = Array.Empty<BaseItemKind>(); ItemIds = Array.Empty<Guid>(); - MediaTypes = Array.Empty<string>(); + MediaTypes = Array.Empty<MediaType>(); MinSimilarityScore = 20; OfficialRatings = Array.Empty<string>(); - OrderBy = Array.Empty<(string, SortOrder)>(); + OrderBy = Array.Empty<(ItemSortBy, SortOrder)>(); PersonIds = Array.Empty<Guid>(); PersonTypes = Array.Empty<string>(); - PresetViews = Array.Empty<string>(); + PresetViews = Array.Empty<CollectionType?>(); SeriesStatuses = Array.Empty<SeriesStatus>(); SourceTypes = Array.Empty<SourceType>(); StudioIds = Array.Empty<Guid>(); @@ -86,7 +86,7 @@ namespace MediaBrowser.Controller.Entities public bool? IncludeItemsByName { get; set; } - public string[] MediaTypes { get; set; } + public MediaType[] MediaTypes { get; set; } public BaseItemKind[] IncludeItemTypes { get; set; } @@ -248,7 +248,7 @@ namespace MediaBrowser.Controller.Entities public Guid[] TopParentIds { get; set; } - public string[] PresetViews { get; set; } + public CollectionType?[] PresetViews { get; set; } public TrailerType[] TrailerTypes { get; set; } @@ -284,7 +284,7 @@ namespace MediaBrowser.Controller.Entities public bool? HasChapterImages { get; set; } - public IReadOnlyList<(string OrderBy, SortOrder SortOrder)> OrderBy { get; set; } + public IReadOnlyList<(ItemSortBy OrderBy, SortOrder SortOrder)> OrderBy { get; set; } public DateTime? MinDateCreated { get; set; } diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 66210cb6c..d7ccfd8ae 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.Entities.Movies { public BoxSet() { - DisplayOrder = ItemSortBy.PremiereDate; + DisplayOrder = "PremiereDate"; } [JsonIgnore] @@ -116,13 +116,13 @@ namespace MediaBrowser.Controller.Entities.Movies { var children = base.GetChildren(user, includeLinkedChildren, query); - if (string.Equals(DisplayOrder, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(DisplayOrder, "SortName", StringComparison.OrdinalIgnoreCase)) { // Sort by name return LibraryManager.Sort(children, user, new[] { ItemSortBy.SortName }, SortOrder.Ascending).ToList(); } - if (string.Equals(DisplayOrder, ItemSortBy.PremiereDate, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(DisplayOrder, "PremiereDate", StringComparison.OrdinalIgnoreCase)) { // Sort by release date return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending).ToList(); @@ -136,7 +136,7 @@ namespace MediaBrowser.Controller.Entities.Movies { var children = base.GetRecursiveChildren(user, query); - if (string.Equals(DisplayOrder, ItemSortBy.PremiereDate, StringComparison.OrdinalIgnoreCase)) + if (string.Equals(DisplayOrder, "PremiereDate", StringComparison.OrdinalIgnoreCase)) { // Sort by release date return LibraryManager.Sort(children, user, new[] { ItemSortBy.ProductionYear, ItemSortBy.PremiereDate, ItemSortBy.SortName }, SortOrder.Ascending).ToList(); diff --git a/MediaBrowser.Controller/Entities/Photo.cs b/MediaBrowser.Controller/Entities/Photo.cs index ba6ce189a..cb9feacd3 100644 --- a/MediaBrowser.Controller/Entities/Photo.cs +++ b/MediaBrowser.Controller/Entities/Photo.cs @@ -3,6 +3,7 @@ #pragma warning disable CS1591 using System.Text.Json.Serialization; +using Jellyfin.Data.Enums; using MediaBrowser.Model.Drawing; namespace MediaBrowser.Controller.Entities @@ -13,7 +14,7 @@ namespace MediaBrowser.Controller.Entities public override bool SupportsLocalMetadata => false; [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Photo; + public override MediaType MediaType => MediaType.Photo; [JsonIgnore] public override Folder LatestItemsIndexContainer => AlbumEntity; diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs index 47432ee93..1f94cf767 100644 --- a/MediaBrowser.Controller/Entities/UserView.cs +++ b/MediaBrowser.Controller/Entities/UserView.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading.Tasks; using Jellyfin.Data.Entities; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.TV; using MediaBrowser.Model.Querying; @@ -16,21 +17,21 @@ namespace MediaBrowser.Controller.Entities { public class UserView : Folder, IHasCollectionType { - private static readonly string[] _viewTypesEligibleForGrouping = new string[] + private static readonly CollectionType?[] _viewTypesEligibleForGrouping = { - Model.Entities.CollectionType.Movies, - Model.Entities.CollectionType.TvShows, - string.Empty + Jellyfin.Data.Enums.CollectionType.Movies, + Jellyfin.Data.Enums.CollectionType.TvShows, + null }; - private static readonly string[] _originalFolderViewTypes = new string[] + private static readonly CollectionType?[] _originalFolderViewTypes = { - Model.Entities.CollectionType.Books, - Model.Entities.CollectionType.MusicVideos, - Model.Entities.CollectionType.HomeVideos, - Model.Entities.CollectionType.Photos, - Model.Entities.CollectionType.Music, - Model.Entities.CollectionType.BoxSets + Jellyfin.Data.Enums.CollectionType.Books, + Jellyfin.Data.Enums.CollectionType.MusicVideos, + Jellyfin.Data.Enums.CollectionType.HomeVideos, + Jellyfin.Data.Enums.CollectionType.Photos, + Jellyfin.Data.Enums.CollectionType.Music, + Jellyfin.Data.Enums.CollectionType.BoxSets }; public static ITVSeriesManager TVSeriesManager { get; set; } @@ -38,7 +39,7 @@ namespace MediaBrowser.Controller.Entities /// <summary> /// Gets or sets the view type. /// </summary> - public string ViewType { get; set; } + public CollectionType? ViewType { get; set; } /// <summary> /// Gets or sets the display parent id. @@ -52,7 +53,7 @@ namespace MediaBrowser.Controller.Entities /// <inheritdoc /> [JsonIgnore] - public string CollectionType => ViewType; + public CollectionType? CollectionType => ViewType; /// <inheritdoc /> [JsonIgnore] @@ -160,7 +161,7 @@ namespace MediaBrowser.Controller.Entities return true; } - return string.Equals(Model.Entities.CollectionType.Playlists, collectionFolder.CollectionType, StringComparison.OrdinalIgnoreCase); + return collectionFolder.CollectionType == Jellyfin.Data.Enums.CollectionType.Playlists; } public static bool IsEligibleForGrouping(Folder folder) @@ -169,14 +170,14 @@ namespace MediaBrowser.Controller.Entities && IsEligibleForGrouping(collectionFolder.CollectionType); } - public static bool IsEligibleForGrouping(string viewType) + public static bool IsEligibleForGrouping(CollectionType? viewType) { - return _viewTypesEligibleForGrouping.Contains(viewType ?? string.Empty, StringComparison.OrdinalIgnoreCase); + return _viewTypesEligibleForGrouping.Contains(viewType); } - public static bool EnableOriginalFolder(string viewType) + public static bool EnableOriginalFolder(CollectionType? viewType) { - return _originalFolderViewTypes.Contains(viewType ?? string.Empty, StringComparison.OrdinalIgnoreCase); + return _originalFolderViewTypes.Contains(viewType); } protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, Providers.MetadataRefreshOptions refreshOptions, Providers.IDirectoryService directoryService, System.Threading.CancellationToken cancellationToken) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index c276ab463..42431c832 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -42,7 +42,7 @@ namespace MediaBrowser.Controller.Entities _tvSeriesManager = tvSeriesManager; } - public QueryResult<BaseItem> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query) + public QueryResult<BaseItem> GetUserItems(Folder queryParent, Folder displayParent, CollectionType? viewType, InternalItemsQuery query) { var user = query.User; @@ -67,49 +67,49 @@ namespace MediaBrowser.Controller.Entities case CollectionType.Movies: return GetMovieFolders(queryParent, user, query); - case SpecialFolder.TvShowSeries: + case CollectionType.TvShowSeries: return GetTvSeries(queryParent, user, query); - case SpecialFolder.TvGenres: + case CollectionType.TvGenres: return GetTvGenres(queryParent, user, query); - case SpecialFolder.TvGenre: + case CollectionType.TvGenre: return GetTvGenreItems(queryParent, displayParent, user, query); - case SpecialFolder.TvResume: + case CollectionType.TvResume: return GetTvResume(queryParent, user, query); - case SpecialFolder.TvNextUp: + case CollectionType.TvNextUp: return GetTvNextUp(queryParent, query); - case SpecialFolder.TvLatest: + case CollectionType.TvLatest: return GetTvLatest(queryParent, user, query); - case SpecialFolder.MovieFavorites: + case CollectionType.MovieFavorites: return GetFavoriteMovies(queryParent, user, query); - case SpecialFolder.MovieLatest: + case CollectionType.MovieLatest: return GetMovieLatest(queryParent, user, query); - case SpecialFolder.MovieGenres: + case CollectionType.MovieGenres: return GetMovieGenres(queryParent, user, query); - case SpecialFolder.MovieGenre: + case CollectionType.MovieGenre: return GetMovieGenreItems(queryParent, displayParent, user, query); - case SpecialFolder.MovieResume: + case CollectionType.MovieResume: return GetMovieResume(queryParent, user, query); - case SpecialFolder.MovieMovies: + case CollectionType.MovieMovies: return GetMovieMovies(queryParent, user, query); - case SpecialFolder.MovieCollections: + case CollectionType.MovieCollections: return GetMovieCollections(user, query); - case SpecialFolder.TvFavoriteEpisodes: + case CollectionType.TvFavoriteEpisodes: return GetFavoriteEpisodes(queryParent, user, query); - case SpecialFolder.TvFavoriteSeries: + case CollectionType.TvFavoriteSeries: return GetFavoriteSeries(queryParent, user, query); default: @@ -146,12 +146,12 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem> { - GetUserView(SpecialFolder.MovieResume, "HeaderContinueWatching", "0", parent), - GetUserView(SpecialFolder.MovieLatest, "Latest", "1", parent), - GetUserView(SpecialFolder.MovieMovies, "Movies", "2", parent), - GetUserView(SpecialFolder.MovieCollections, "Collections", "3", parent), - GetUserView(SpecialFolder.MovieFavorites, "Favorites", "4", parent), - GetUserView(SpecialFolder.MovieGenres, "Genres", "5", parent) + GetUserView(CollectionType.MovieResume, "HeaderContinueWatching", "0", parent), + GetUserView(CollectionType.MovieLatest, "Latest", "1", parent), + GetUserView(CollectionType.MovieMovies, "Movies", "2", parent), + GetUserView(CollectionType.MovieCollections, "Collections", "3", parent), + GetUserView(CollectionType.MovieFavorites, "Favorites", "4", parent), + GetUserView(CollectionType.MovieGenres, "Genres", "5", parent) }; return GetResult(list, query); @@ -264,7 +264,7 @@ namespace MediaBrowser.Controller.Entities } }) .Where(i => i is not null) - .Select(i => GetUserViewWithName(SpecialFolder.MovieGenre, i.SortName, parent)); + .Select(i => GetUserViewWithName(CollectionType.MovieGenre, i.SortName, parent)); return GetResult(genres, query); } @@ -303,13 +303,13 @@ namespace MediaBrowser.Controller.Entities var list = new List<BaseItem> { - GetUserView(SpecialFolder.TvResume, "HeaderContinueWatching", "0", parent), - GetUserView(SpecialFolder.TvNextUp, "HeaderNextUp", "1", parent), - GetUserView(SpecialFolder.TvLatest, "Latest", "2", parent), - GetUserView(SpecialFolder.TvShowSeries, "Shows", "3", parent), - GetUserView(SpecialFolder.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent), - GetUserView(SpecialFolder.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent), - GetUserView(SpecialFolder.TvGenres, "Genres", "6", parent) + GetUserView(CollectionType.TvResume, "HeaderContinueWatching", "0", parent), + GetUserView(CollectionType.TvNextUp, "HeaderNextUp", "1", parent), + GetUserView(CollectionType.TvLatest, "Latest", "2", parent), + GetUserView(CollectionType.TvShowSeries, "Shows", "3", parent), + GetUserView(CollectionType.TvFavoriteSeries, "HeaderFavoriteShows", "4", parent), + GetUserView(CollectionType.TvFavoriteEpisodes, "HeaderFavoriteEpisodes", "5", parent), + GetUserView(CollectionType.TvGenres, "Genres", "6", parent) }; return GetResult(list, query); @@ -330,7 +330,7 @@ namespace MediaBrowser.Controller.Entities private QueryResult<BaseItem> GetTvNextUp(Folder parent, InternalItemsQuery query) { - var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows, string.Empty }); + var parentFolders = GetMediaFolders(parent, query.User, new[] { CollectionType.TvShows }); var result = _tvSeriesManager.GetNextUp( new NextUpQuery @@ -392,7 +392,7 @@ namespace MediaBrowser.Controller.Entities } }) .Where(i => i is not null) - .Select(i => GetUserViewWithName(SpecialFolder.TvGenre, i.SortName, parent)); + .Select(i => GetUserViewWithName(CollectionType.TvGenre, i.SortName, parent)); return GetResult(genres, query); } @@ -476,7 +476,7 @@ namespace MediaBrowser.Controller.Entities public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager) { - if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparison.OrdinalIgnoreCase)) + if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType)) { return false; } @@ -943,7 +943,7 @@ namespace MediaBrowser.Controller.Entities .Where(i => user.IsFolderGrouped(i.Id) && UserView.IsEligibleForGrouping(i)); } - private BaseItem[] GetMediaFolders(User user, IEnumerable<string> viewTypes) + private BaseItem[] GetMediaFolders(User user, IEnumerable<CollectionType> viewTypes) { if (user is null) { @@ -952,7 +952,7 @@ namespace MediaBrowser.Controller.Entities { var folder = i as ICollectionFolder; - return folder is not null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase); + return folder?.CollectionType is not null && viewTypes.Contains(folder.CollectionType.Value); }).ToArray(); } @@ -961,11 +961,11 @@ namespace MediaBrowser.Controller.Entities { var folder = i as ICollectionFolder; - return folder is not null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase); + return folder?.CollectionType is not null && viewTypes.Contains(folder.CollectionType.Value); }).ToArray(); } - private BaseItem[] GetMediaFolders(Folder parent, User user, IEnumerable<string> viewTypes) + private BaseItem[] GetMediaFolders(Folder parent, User user, IEnumerable<CollectionType> viewTypes) { if (parent is null || parent is UserView) { @@ -975,12 +975,12 @@ namespace MediaBrowser.Controller.Entities return new BaseItem[] { parent }; } - private UserView GetUserViewWithName(string type, string sortName, BaseItem parent) + private UserView GetUserViewWithName(CollectionType? type, string sortName, BaseItem parent) { - return _userViewManager.GetUserSubView(parent.Id, parent.Id.ToString("N", CultureInfo.InvariantCulture), type, sortName); + return _userViewManager.GetUserSubView(parent.Id, type, parent.Id.ToString("N", CultureInfo.InvariantCulture), sortName); } - private UserView GetUserView(string type, string localizationKey, string sortName, BaseItem parent) + private UserView GetUserView(CollectionType? type, string localizationKey, string sortName, BaseItem parent) { return _userViewManager.GetUserSubView(parent.Id, type, localizationKey, sortName); } diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index 9f685b7e2..be2eb4d28 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -9,6 +9,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using Jellyfin.Extensions; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.LiveTv; @@ -256,7 +257,7 @@ namespace MediaBrowser.Controller.Entities /// </summary> /// <value>The type of the media.</value> [JsonIgnore] - public override string MediaType => Model.Entities.MediaType.Video; + public override MediaType MediaType => MediaType.Video; public override List<string> GetUserDataKeys() { diff --git a/MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs b/MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs new file mode 100644 index 000000000..2742f21e3 --- /dev/null +++ b/MediaBrowser.Controller/Extensions/XmlReaderExtensions.cs @@ -0,0 +1,193 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Xml; +using Jellyfin.Data.Enums; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Extensions; + +/// <summary> +/// Provides extension methods for <see cref="XmlReader"/> to parse <see cref="BaseItem"/>'s. +/// </summary> +public static class XmlReaderExtensions +{ + /// <summary> + /// Reads a trimmed string from the current node. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <returns>The trimmed content.</returns> + public static string ReadNormalizedString(this XmlReader reader) + { + ArgumentNullException.ThrowIfNull(reader); + + return reader.ReadElementContentAsString().Trim(); + } + + /// <summary> + /// Reads an int from the current node. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <param name="value">The parsed <c>int</c>.</param> + /// <returns>A value indicating whether the parsing succeeded.</returns> + public static bool TryReadInt(this XmlReader reader, out int value) + { + ArgumentNullException.ThrowIfNull(reader); + + return int.TryParse(reader.ReadElementContentAsString(), CultureInfo.InvariantCulture, out value); + } + + /// <summary> + /// Parses a <see cref="DateTime"/> from the current node. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <param name="value">The parsed <see cref="DateTime"/>.</param> + /// <returns>A value indicating whether the parsing succeeded.</returns> + public static bool TryReadDateTime(this XmlReader reader, out DateTime value) + { + ArgumentNullException.ThrowIfNull(reader); + + return DateTime.TryParse( + reader.ReadElementContentAsString(), + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, + out value); + } + + /// <summary> + /// Parses a <see cref="DateTime"/> from the current node. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <param name="formatString">The date format string.</param> + /// <param name="value">The parsed <see cref="DateTime"/>.</param> + /// <returns>A value indicating whether the parsing succeeded.</returns> + public static bool TryReadDateTimeExact(this XmlReader reader, string formatString, out DateTime value) + { + ArgumentNullException.ThrowIfNull(reader); + ArgumentNullException.ThrowIfNull(formatString); + + return DateTime.TryParseExact( + reader.ReadElementContentAsString(), + formatString, + CultureInfo.InvariantCulture, + DateTimeStyles.AssumeUniversal | DateTimeStyles.AdjustToUniversal, + out value); + } + + /// <summary> + /// Parses a <see cref="PersonInfo"/> from the xml node. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <returns>A <see cref="PersonInfo"/>, or <c>null</c> if none is found.</returns> + public static PersonInfo? GetPersonFromXmlNode(this XmlReader reader) + { + ArgumentNullException.ThrowIfNull(reader); + + if (reader.IsEmptyElement) + { + reader.Read(); + return null; + } + + var name = string.Empty; + var type = PersonKind.Actor; // If type is not specified assume actor + var role = string.Empty; + int? sortOrder = null; + string? imageUrl = null; + + using var subtree = reader.ReadSubtree(); + subtree.MoveToContent(); + subtree.Read(); + + while (subtree is { EOF: false, ReadState: ReadState.Interactive }) + { + if (subtree.NodeType != XmlNodeType.Element) + { + subtree.Read(); + continue; + } + + switch (subtree.Name) + { + case "name": + case "Name": + name = subtree.ReadNormalizedString(); + break; + case "role": + case "Role": + role = subtree.ReadNormalizedString(); + break; + case "type": + case "Type": + Enum.TryParse(subtree.ReadElementContentAsString(), true, out type); + break; + case "order": + case "sortorder": + case "SortOrder": + if (subtree.TryReadInt(out var sortOrderVal)) + { + sortOrder = sortOrderVal; + } + + break; + case "thumb": + imageUrl = subtree.ReadNormalizedString(); + break; + default: + subtree.Skip(); + break; + } + } + + if (string.IsNullOrWhiteSpace(name)) + { + return null; + } + + return new PersonInfo + { + Name = name, + Role = role, + Type = type, + SortOrder = sortOrder, + ImageUrl = imageUrl + }; + } + + /// <summary> + /// Used to split names of comma or pipe delimited genres and people. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <returns>IEnumerable{System.String}.</returns> + public static IEnumerable<string> GetStringArray(this XmlReader reader) + { + ArgumentNullException.ThrowIfNull(reader); + var value = reader.ReadElementContentAsString(); + + // Only split by comma if there is no pipe in the string + // We have to be careful to not split names like Matthew, Jr. + var separator = !value.Contains('|', StringComparison.Ordinal) + && !value.Contains(';', StringComparison.Ordinal) + ? new[] { ',' } + : new[] { '|', ';' }; + + foreach (var part in value.Trim().Trim(separator).Split(separator)) + { + if (!string.IsNullOrWhiteSpace(part)) + { + yield return part.Trim(); + } + } + } + + /// <summary> + /// Parses a <see cref="PersonInfo"/> array from the xml node. + /// </summary> + /// <param name="reader">The <see cref="XmlReader"/>.</param> + /// <param name="personKind">The <see cref="PersonKind"/>.</param> + /// <returns>The <see cref="IEnumerable{PersonInfo}"/>.</returns> + public static IEnumerable<PersonInfo> GetPersonArray(this XmlReader reader, PersonKind personKind) + => reader.GetStringArray() + .Select(part => new PersonInfo { Name = part, Type = personKind }); +} diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs index 45ac5c3a8..e9c4d9e19 100644 --- a/MediaBrowser.Controller/IServerApplicationHost.cs +++ b/MediaBrowser.Controller/IServerApplicationHost.cs @@ -4,7 +4,6 @@ using System.Net; using MediaBrowser.Common; -using MediaBrowser.Model.System; using Microsoft.AspNetCore.Http; namespace MediaBrowser.Controller @@ -16,8 +15,6 @@ namespace MediaBrowser.Controller { bool CoreStartupHasCompleted { get; } - bool CanLaunchWebBrowser { get; } - /// <summary> /// Gets the HTTP server port. /// </summary> @@ -42,15 +39,6 @@ namespace MediaBrowser.Controller string FriendlyName { get; } /// <summary> - /// Gets the system info. - /// </summary> - /// <param name="request">The HTTP request.</param> - /// <returns>SystemInfo.</returns> - SystemInfo GetSystemInfo(HttpRequest request); - - PublicSystemInfo GetPublicSystemInfo(HttpRequest request); - - /// <summary> /// Gets a URL specific for the request. /// </summary> /// <param name="request">The <see cref="HttpRequest"/> instance.</param> diff --git a/MediaBrowser.Controller/ISystemManager.cs b/MediaBrowser.Controller/ISystemManager.cs new file mode 100644 index 000000000..ef3034d2f --- /dev/null +++ b/MediaBrowser.Controller/ISystemManager.cs @@ -0,0 +1,34 @@ +using MediaBrowser.Model.System; +using Microsoft.AspNetCore.Http; + +namespace MediaBrowser.Controller; + +/// <summary> +/// A service for managing the application instance. +/// </summary> +public interface ISystemManager +{ + /// <summary> + /// Gets the system info. + /// </summary> + /// <param name="request">The HTTP request.</param> + /// <returns>The <see cref="SystemInfo"/>.</returns> + SystemInfo GetSystemInfo(HttpRequest request); + + /// <summary> + /// Gets the public system info. + /// </summary> + /// <param name="request">The HTTP request.</param> + /// <returns>The <see cref="PublicSystemInfo"/>.</returns> + PublicSystemInfo GetPublicSystemInfo(HttpRequest request); + + /// <summary> + /// Starts the application restart process. + /// </summary> + void Restart(); + + /// <summary> + /// Starts the application shutdown process. + /// </summary> + void Shutdown(); +} diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index f34e3d68d..9ec22324f 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -79,7 +79,7 @@ namespace MediaBrowser.Controller.Library IDirectoryService directoryService, Folder parent, LibraryOptions libraryOptions, - string collectionType = null); + CollectionType? collectionType = null); /// <summary> /// Gets a Person. @@ -199,9 +199,9 @@ namespace MediaBrowser.Controller.Library /// <param name="sortBy">The sort by.</param> /// <param name="sortOrder">The sort order.</param> /// <returns>IEnumerable{BaseItem}.</returns> - IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder); + IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ItemSortBy> sortBy, SortOrder sortOrder); - IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy); + IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(ItemSortBy OrderBy, SortOrder SortOrder)> orderBy); /// <summary> /// Gets the user root folder. @@ -256,28 +256,28 @@ namespace MediaBrowser.Controller.Library /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> - string GetContentType(BaseItem item); + CollectionType? GetContentType(BaseItem item); /// <summary> /// Gets the type of the inherited content. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> - string GetInheritedContentType(BaseItem item); + CollectionType? GetInheritedContentType(BaseItem item); /// <summary> /// Gets the type of the configured content. /// </summary> /// <param name="item">The item.</param> /// <returns>System.String.</returns> - string GetConfiguredContentType(BaseItem item); + CollectionType? GetConfiguredContentType(BaseItem item); /// <summary> /// Gets the type of the configured content. /// </summary> /// <param name="path">The path.</param> /// <returns>System.String.</returns> - string GetConfiguredContentType(string path); + CollectionType? GetConfiguredContentType(string path); /// <summary> /// Normalizes the root path list. @@ -329,7 +329,7 @@ namespace MediaBrowser.Controller.Library User user, string name, Guid parentId, - string viewType, + CollectionType? viewType, string sortName); /// <summary> @@ -343,7 +343,7 @@ namespace MediaBrowser.Controller.Library UserView GetNamedView( User user, string name, - string viewType, + CollectionType? viewType, string sortName); /// <summary> @@ -355,7 +355,7 @@ namespace MediaBrowser.Controller.Library /// <returns>The named view.</returns> UserView GetNamedView( string name, - string viewType, + CollectionType viewType, string sortName); /// <summary> @@ -370,7 +370,7 @@ namespace MediaBrowser.Controller.Library UserView GetNamedView( string name, Guid parentId, - string viewType, + CollectionType? viewType, string sortName, string uniqueId); @@ -383,7 +383,7 @@ namespace MediaBrowser.Controller.Library /// <returns>The shadow view.</returns> UserView GetShadowView( BaseItem parent, - string viewType, + CollectionType? viewType, string sortName); /// <summary> diff --git a/MediaBrowser.Controller/Library/IUserViewManager.cs b/MediaBrowser.Controller/Library/IUserViewManager.cs index 055627d3e..a565dc88b 100644 --- a/MediaBrowser.Controller/Library/IUserViewManager.cs +++ b/MediaBrowser.Controller/Library/IUserViewManager.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Dto; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Library; @@ -28,7 +29,7 @@ namespace MediaBrowser.Controller.Library /// <param name="localizationKey">Localization key to use.</param> /// <param name="sortName">Sort to use.</param> /// <returns>User view.</returns> - UserView GetUserSubView(Guid parentId, string type, string localizationKey, string sortName); + UserView GetUserSubView(Guid parentId, CollectionType? type, string localizationKey, string sortName); /// <summary> /// Gets latest items. diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs index dcd0110fb..6202f92f5 100644 --- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs +++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Configuration; using MediaBrowser.Model.IO; @@ -120,7 +121,7 @@ namespace MediaBrowser.Controller.Library } } - public string CollectionType { get; set; } + public CollectionType? CollectionType { get; set; } public bool HasParent<T>() where T : Folder @@ -220,7 +221,7 @@ namespace MediaBrowser.Controller.Library return GetFileSystemEntryByName(name) is not null; } - public string GetCollectionType() + public CollectionType? GetCollectionType() { return CollectionType; } @@ -229,7 +230,7 @@ namespace MediaBrowser.Controller.Library /// Gets the configured content type for the path. /// </summary> /// <returns>The configured content type.</returns> - public string GetConfiguredContentType() + public CollectionType? GetConfiguredContentType() { return _libraryManager.GetConfiguredContentType(Path); } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs index f11e3c8f6..3c2cf8e3d 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs @@ -44,7 +44,7 @@ namespace MediaBrowser.Controller.LiveTv public override LocationType LocationType => LocationType.Remote; [JsonIgnore] - public override string MediaType => ChannelType == ChannelType.Radio ? Model.Entities.MediaType.Audio : Model.Entities.MediaType.Video; + public override MediaType MediaType => ChannelType == ChannelType.Radio ? MediaType.Audio : MediaType.Video; [JsonIgnore] public bool IsMovie { get; set; } diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs index c721fb778..05540d490 100644 --- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs +++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs @@ -20,7 +20,7 @@ namespace MediaBrowser.Controller.LiveTv { public class LiveTvProgram : BaseItem, IHasLookupInfo<ItemLookupInfo>, IHasStartDate, IHasProgramAttributes { - private static string EmbyServiceName = "Emby"; + private const string EmbyServiceName = "Emby"; public LiveTvProgram() { diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj index 69c0d26b6..f9468f6cd 100644 --- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj +++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj @@ -49,8 +49,12 @@ <AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder> </PropertyGroup> - <!-- Code Analyzers--> + <!-- Code Analyzers --> <ItemGroup Condition=" '$(Configuration)' == 'Debug' "> + <PackageReference Include="IDisposableAnalyzers"> + <PrivateAssets>all</PrivateAssets> + <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> + </PackageReference> <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers"> <PrivateAssets>all</PrivateAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets> diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs index 14417a5e4..6621ae284 100644 --- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs +++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs @@ -100,6 +100,13 @@ namespace MediaBrowser.Controller.MediaEncoding { "truehd", 6 }, }; + private static readonly string _defaultMjpegEncoder = "mjpeg"; + private static readonly Dictionary<string, string> _mjpegCodecMap = new(StringComparer.OrdinalIgnoreCase) + { + { "vaapi", _defaultMjpegEncoder + "_vaapi" }, + { "qsv", _defaultMjpegEncoder + "_qsv" } + }; + public static readonly string[] LosslessAudioCodecs = new string[] { "alac", @@ -167,6 +174,24 @@ namespace MediaBrowser.Controller.MediaEncoding return defaultEncoder; } + private string GetMjpegEncoder(EncodingJobInfo state, EncodingOptions encodingOptions) + { + if (state.VideoType == VideoType.VideoFile) + { + var hwType = encodingOptions.HardwareAccelerationType; + + if (!string.IsNullOrEmpty(hwType) + && encodingOptions.EnableHardwareEncoding + && _mjpegCodecMap.TryGetValue(hwType, out var preferredEncoder) + && _mediaEncoder.SupportsEncoder(preferredEncoder)) + { + return preferredEncoder; + } + } + + return _defaultMjpegEncoder; + } + private bool IsVaapiSupported(EncodingJobInfo state) { // vaapi will throw an error with this input @@ -300,6 +325,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)) { @@ -548,25 +578,25 @@ namespace MediaBrowser.Controller.MediaEncoding /// <returns>System.Nullable{VideoCodecs}.</returns> public string InferVideoCodec(string url) { - var ext = Path.GetExtension(url); + var ext = Path.GetExtension(url.AsSpan()); - if (string.Equals(ext, ".asf", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".asf", StringComparison.OrdinalIgnoreCase)) { return "wmv"; } - if (string.Equals(ext, ".webm", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".webm", StringComparison.OrdinalIgnoreCase)) { // TODO: this may not always mean VP8, as the codec ages return "vp8"; } - if (string.Equals(ext, ".ogg", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ogv", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".ogg", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ogv", StringComparison.OrdinalIgnoreCase)) { return "theora"; } - if (string.Equals(ext, ".m3u8", StringComparison.OrdinalIgnoreCase) || string.Equals(ext, ".ts", StringComparison.OrdinalIgnoreCase)) + if (ext.Equals(".m3u8", StringComparison.OrdinalIgnoreCase) || ext.Equals(".ts", StringComparison.OrdinalIgnoreCase)) { return "h264"; } @@ -745,12 +775,17 @@ namespace MediaBrowser.Controller.MediaEncoding private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string srcDeviceAlias, string alias) { alias ??= VaapiAlias; - renderNodePath = renderNodePath ?? "/dev/dri/renderD128"; - var driverOpts = string.IsNullOrEmpty(driver) - ? ":" + renderNodePath - : ":,driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver); + + // 'renderNodePath' has higher priority than 'kernelDriver' + var driverOpts = string.IsNullOrEmpty(renderNodePath) + ? (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver) + : renderNodePath; + + // 'driver' behaves similarly to env LIBVA_DRIVER_NAME + driverOpts += string.IsNullOrEmpty(driver) ? string.Empty : ",driver=" + driver; + var options = string.IsNullOrEmpty(srcDeviceAlias) - ? driverOpts + ? (string.IsNullOrEmpty(driverOpts) ? string.Empty : ":" + driverOpts) : "@" + srcDeviceAlias; return string.Format( @@ -872,14 +907,14 @@ namespace MediaBrowser.Controller.MediaEncoding if (_mediaEncoder.IsVaapiDeviceInteliHD) { - args.Append(GetVaapiDeviceArgs(null, "iHD", null, null, VaapiAlias)); + args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "iHD", null, null, VaapiAlias)); } else if (_mediaEncoder.IsVaapiDeviceInteli965) { // Only override i965 since it has lower priority than iHD in libva lookup. Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME", "i965"); Environment.SetEnvironmentVariable("LIBVA_DRIVER_NAME_JELLYFIN", "i965"); - args.Append(GetVaapiDeviceArgs(null, "i965", null, null, VaapiAlias)); + args.Append(GetVaapiDeviceArgs(options.VaapiDevice, "i965", null, null, VaapiAlias)); } var filterDevArgs = string.Empty; @@ -1080,10 +1115,10 @@ namespace MediaBrowser.Controller.MediaEncoding && state.SubtitleStream.IsExternal) { var subtitlePath = state.SubtitleStream.Path; - var subtitleExtension = Path.GetExtension(subtitlePath); + var subtitleExtension = Path.GetExtension(subtitlePath.AsSpan()); - if (string.Equals(subtitleExtension, ".sub", StringComparison.OrdinalIgnoreCase) - || string.Equals(subtitleExtension, ".sup", StringComparison.OrdinalIgnoreCase)) + if (subtitleExtension.Equals(".sub", StringComparison.OrdinalIgnoreCase) + || subtitleExtension.Equals(".sup", StringComparison.OrdinalIgnoreCase)) { var idxFile = Path.ChangeExtension(subtitlePath, ".idx"); if (File.Exists(idxFile)) @@ -1745,11 +1780,9 @@ namespace MediaBrowser.Controller.MediaEncoding // Values 0-3, 0 being highest quality but slower var profileScore = 0; - string crf; var qmin = "0"; var qmax = "50"; - - crf = "10"; + var crf = "10"; if (isVc1) { @@ -2947,7 +2980,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "scale=trunc(min(max(iw\\,ih*a)\\,min({0}\\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\\,ih)\\,min({0}/a\\,{1}))/2)*2", + @"scale=trunc(min(max(iw\,ih*a)\,min({0}\,{1}*a))/{2})*{2}:trunc(min(max(iw/a\,ih)\,min({0}/a\,{1}))/2)*2", maxWidthParam, maxHeightParam, scaleVal); @@ -2989,7 +3022,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2", + @"scale=trunc(min(max(iw\,ih*a)\,{0})/{1})*{1}:trunc(ow/a/2)*2", maxWidthParam, scaleVal); } @@ -3001,7 +3034,7 @@ namespace MediaBrowser.Controller.MediaEncoding return string.Format( CultureInfo.InvariantCulture, - "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})", + @"scale=trunc(oh*a/{1})*{1}:min(max(iw/a\,ih)\,{0})", maxHeightParam, scaleVal); } @@ -3021,19 +3054,19 @@ namespace MediaBrowser.Controller.MediaEncoding switch (threedFormat.Value) { case Video3DFormat.HalfSideBySide: - filter = "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; + filter = @"crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; // hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not. break; case Video3DFormat.FullSideBySide: - filter = "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; + filter = @"crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; // fsbs crop width in half,set the display aspect,crop out any black bars we may have made the scale width to requestedWidth. break; case Video3DFormat.HalfTopAndBottom: - filter = "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; + filter = @"crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; // htab crop height in half,scale to correct size, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth break; case Video3DFormat.FullTopAndBottom: - filter = "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; + filter = @"crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\,ih*dar):min(ih\,iw/dar):(iw-min(iw\,iw*sar))/2:(ih - min (ih\,ih/sar))/2,setsar=sar=1,scale={0}:trunc({0}/dar/2)*2"; // ftab crop height in half, set the display aspect,crop out any black bars we may have made the scale width to requestedWidth break; default: @@ -4919,6 +4952,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) { @@ -6040,7 +6082,7 @@ namespace MediaBrowser.Controller.MediaEncoding var format = string.Empty; var keyFrame = string.Empty; - if (string.Equals(Path.GetExtension(outputPath), ".mp4", StringComparison.OrdinalIgnoreCase) + if (Path.GetExtension(outputPath.AsSpan()).Equals(".mp4", StringComparison.OrdinalIgnoreCase) && state.BaseRequest.Context == EncodingContext.Streaming) { // Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs index 4114dea4f..c2cef4978 100644 --- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs +++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs @@ -4,8 +4,10 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using MediaBrowser.Model.Configuration; using MediaBrowser.Model.Dlna; using MediaBrowser.Model.Drawing; using MediaBrowser.Model.Dto; @@ -138,6 +140,36 @@ namespace MediaBrowser.Controller.MediaEncoding Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken); /// <summary> + /// Extracts the video images on interval. + /// </summary> + /// <param name="inputFile">Input file.</param> + /// <param name="container">Video container type.</param> + /// <param name="mediaSource">Media source information.</param> + /// <param name="imageStream">Media stream information.</param> + /// <param name="maxWidth">The maximum width.</param> + /// <param name="interval">The interval.</param> + /// <param name="allowHwAccel">Allow for hardware acceleration.</param> + /// <param name="threads">The input/output thread count for ffmpeg.</param> + /// <param name="qualityScale">The qscale value for ffmpeg.</param> + /// <param name="priority">The process priority for the ffmpeg process.</param> + /// <param name="encodingHelper">EncodingHelper instance.</param> + /// <param name="cancellationToken">The cancellation token.</param> + /// <returns>Directory where images where extracted. A given image made before another will always be named with a lower number.</returns> + Task<string> ExtractVideoImagesOnIntervalAccelerated( + string inputFile, + string container, + MediaSourceInfo mediaSource, + MediaStream imageStream, + int maxWidth, + TimeSpan interval, + bool allowHwAccel, + int? threads, + int? qualityScale, + ProcessPriorityClass? priority, + EncodingHelper encodingHelper, + CancellationToken cancellationToken); + + /// <summary> /// Gets the media info. /// </summary> /// <param name="request">The request.</param> diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs index e0942e490..0a706c307 100644 --- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs +++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs @@ -34,7 +34,7 @@ namespace MediaBrowser.Controller.Net /// <summary> /// The logger. /// </summary> - protected ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger; + protected readonly ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> Logger; protected BasePeriodicWebSocketListener(ILogger<BasePeriodicWebSocketListener<TReturnDataType, TStateType>> logger) { diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs index 498df5ab0..ca032e7f6 100644 --- a/MediaBrowser.Controller/Playlists/Playlist.cs +++ b/MediaBrowser.Controller/Playlists/Playlist.cs @@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.Playlists public override bool SupportsInheritedParentImages => false; [JsonIgnore] - public override bool SupportsPlayedStatus => string.Equals(MediaType, "Video", StringComparison.OrdinalIgnoreCase); + public override bool SupportsPlayedStatus => MediaType == Jellyfin.Data.Enums.MediaType.Video; [JsonIgnore] public override bool AlwaysScanInternalMetadataPath => true; @@ -80,10 +80,10 @@ namespace MediaBrowser.Controller.Playlists [JsonIgnore] public override bool IsPreSorted => true; - public string PlaylistMediaType { get; set; } + public MediaType PlaylistMediaType { get; set; } [JsonIgnore] - public override string MediaType => PlaylistMediaType; + public override MediaType MediaType => PlaylistMediaType; [JsonIgnore] private bool IsSharedItem @@ -107,9 +107,9 @@ namespace MediaBrowser.Controller.Playlists return System.IO.Path.HasExtension(path) && !Directory.Exists(path); } - public void SetMediaType(string value) + public void SetMediaType(MediaType? value) { - PlaylistMediaType = value; + PlaylistMediaType = value ?? MediaType.Unknown; } public override double GetDefaultPrimaryImageAspectRatio() @@ -167,7 +167,7 @@ namespace MediaBrowser.Controller.Playlists return base.GetChildren(user, true, query); } - public static List<BaseItem> GetPlaylistItems(string playlistMediaType, IEnumerable<BaseItem> inputItems, User user, DtoOptions options) + public static List<BaseItem> GetPlaylistItems(MediaType playlistMediaType, IEnumerable<BaseItem> inputItems, User user, DtoOptions options) { if (user is not null) { @@ -185,7 +185,7 @@ namespace MediaBrowser.Controller.Playlists return list; } - private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, string mediaType, DtoOptions options) + private static IEnumerable<BaseItem> GetPlaylistItems(BaseItem item, User user, MediaType mediaType, DtoOptions options) { if (item is MusicGenre musicGenre) { diff --git a/MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs b/MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs new file mode 100644 index 000000000..8b62f3808 --- /dev/null +++ b/MediaBrowser.Controller/Plugins/IPluginServiceRegistrator.cs @@ -0,0 +1,19 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace MediaBrowser.Controller.Plugins; + +/// <summary> +/// Defines the <see cref="IPluginServiceRegistrator" />. +/// </summary> +/// <remarks> +/// This interface is only used for service registration and requires a parameterless constructor. +/// </remarks> +public interface IPluginServiceRegistrator +{ + /// <summary> + /// Registers the plugin's services with the service collection. + /// </summary> + /// <param name="serviceCollection">The service collection.</param> + /// <param name="applicationHost">The server application host.</param> + void RegisterServices(IServiceCollection serviceCollection, IServerApplicationHost applicationHost); +} diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs index c4ad352a3..2f7ebb5cc 100644 --- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs +++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using Jellyfin.Data.Enums; namespace MediaBrowser.Controller.Providers { diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs index 16943f6aa..eb5069b06 100644 --- a/MediaBrowser.Controller/Providers/IProviderManager.cs +++ b/MediaBrowser.Controller/Providers/IProviderManager.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Jellyfin.Data.Events; diff --git a/MediaBrowser.Controller/Resolvers/IItemResolver.cs b/MediaBrowser.Controller/Resolvers/IItemResolver.cs index 282aa721e..0699734c4 100644 --- a/MediaBrowser.Controller/Resolvers/IItemResolver.cs +++ b/MediaBrowser.Controller/Resolvers/IItemResolver.cs @@ -1,6 +1,7 @@ #pragma warning disable CS1591 using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Library; using MediaBrowser.Controller.Providers; @@ -32,7 +33,7 @@ namespace MediaBrowser.Controller.Resolvers MultiItemResolverResult ResolveMultiple( Folder parent, List<FileSystemMetadata> files, - string collectionType, + CollectionType? collectionType, IDirectoryService directoryService); } diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs index 25bf23d61..3e30c8dc4 100644 --- a/MediaBrowser.Controller/Session/SessionInfo.cs +++ b/MediaBrowser.Controller/Session/SessionInfo.cs @@ -8,6 +8,7 @@ using System.Linq; using System.Text.Json.Serialization; using System.Threading; using System.Threading.Tasks; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; using MediaBrowser.Model.Dto; using MediaBrowser.Model.Session; @@ -60,13 +61,13 @@ namespace MediaBrowser.Controller.Session /// Gets the playable media types. /// </summary> /// <value>The playable media types.</value> - public IReadOnlyList<string> PlayableMediaTypes + public IReadOnlyList<MediaType> PlayableMediaTypes { get { if (Capabilities is null) { - return Array.Empty<string>(); + return Array.Empty<MediaType>(); } return Capabilities.PlayableMediaTypes; @@ -110,6 +111,12 @@ namespace MediaBrowser.Controller.Session public DateTime LastPlaybackCheckIn { get; set; } /// <summary> + /// Gets or sets the last paused date. + /// </summary> + /// <value>The last paused date.</value> + public DateTime? LastPausedDate { get; set; } + + /// <summary> /// Gets or sets the name of the device. /// </summary> /// <value>The name of the device.</value> diff --git a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs index 07fe1ea8a..96f8a2af5 100644 --- a/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs +++ b/MediaBrowser.Controller/Sorting/IBaseItemComparer.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using Jellyfin.Data.Enums; using MediaBrowser.Controller.Entities; namespace MediaBrowser.Controller.Sorting @@ -9,9 +10,8 @@ namespace MediaBrowser.Controller.Sorting public interface IBaseItemComparer : IComparer<BaseItem?> { /// <summary> - /// Gets the name. + /// Gets the comparer type. /// </summary> - /// <value>The name.</value> - string Name { get; } + ItemSortBy Type { get; } } } diff --git a/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs new file mode 100644 index 000000000..0c41f3023 --- /dev/null +++ b/MediaBrowser.Controller/Trickplay/ITrickplayManager.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Jellyfin.Data.Entities; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Configuration; + +namespace MediaBrowser.Controller.Trickplay; + +/// <summary> +/// Interface ITrickplayManager. +/// </summary> +public interface ITrickplayManager +{ + /// <summary> + /// Generates new trickplay images and metadata. + /// </summary> + /// <param name="video">The video.</param> + /// <param name="replace">Whether or not existing data should be replaced.</param> + /// <param name="cancellationToken">CancellationToken to use for operation.</param> + /// <returns>Task.</returns> + Task RefreshTrickplayDataAsync(Video video, bool replace, CancellationToken cancellationToken); + + /// <summary> + /// Creates trickplay tiles out of individual thumbnails. + /// </summary> + /// <param name="images">Ordered file paths of the thumbnails to be used.</param> + /// <param name="width">The width of a single thumbnail.</param> + /// <param name="options">The trickplay options.</param> + /// <param name="outputDir">The output directory.</param> + /// <returns>The associated trickplay information.</returns> + /// <remarks> + /// The output directory will be DELETED and replaced if it already exists. + /// </remarks> + TrickplayInfo CreateTiles(List<string> images, int width, TrickplayOptions options, string outputDir); + + /// <summary> + /// Get available trickplay resolutions and corresponding info. + /// </summary> + /// <param name="itemId">The item.</param> + /// <returns>Map of width resolutions to trickplay tiles info.</returns> + Task<Dictionary<int, TrickplayInfo>> GetTrickplayResolutions(Guid itemId); + + /// <summary> + /// Saves trickplay info. + /// </summary> + /// <param name="info">The trickplay info.</param> + /// <returns>Task.</returns> + Task SaveTrickplayInfo(TrickplayInfo info); + + /// <summary> + /// Gets all trickplay infos for all media streams of an item. + /// </summary> + /// <param name="item">The item.</param> + /// <returns>A map of media source id to a map of tile width to trickplay info.</returns> + Task<Dictionary<string, Dictionary<int, TrickplayInfo>>> GetTrickplayManifest(BaseItem item); + + /// <summary> + /// Gets the path to a trickplay tile image. + /// </summary> + /// <param name="item">The item.</param> + /// <param name="width">The width of a single thumbnail.</param> + /// <param name="index">The tile's index.</param> + /// <returns>The absolute path.</returns> + string GetTrickplayTilePath(BaseItem item, int width, int index); + + /// <summary> + /// Gets the trickplay HLS playlist. + /// </summary> + /// <param name="itemId">The item.</param> + /// <param name="width">The width of a single thumbnail.</param> + /// <param name="apiKey">Optional api key of the requesting user.</param> + /// <returns>The text content of the .m3u8 playlist.</returns> + Task<string?> GetHlsPlaylist(Guid itemId, int width, string? apiKey); +} |
