aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs18
-rw-r--r--MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs8
-rw-r--r--MediaBrowser.Controller/Channels/IChannelManager.cs10
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs3
-rw-r--r--MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs2
-rw-r--r--MediaBrowser.Controller/Dto/IDtoService.cs7
-rw-r--r--MediaBrowser.Controller/Entities/AggregateFolder.cs6
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs4
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs150
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs4
-rw-r--r--MediaBrowser.Controller/Entities/CollectionFolder.cs5
-rw-r--r--MediaBrowser.Controller/Entities/Extensions.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs96
-rw-r--r--MediaBrowser.Controller/Entities/IHasShares.cs11
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs8
-rw-r--r--MediaBrowser.Controller/Entities/ItemImageInfo.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs6
-rw-r--r--MediaBrowser.Controller/Entities/PeopleHelper.cs35
-rw-r--r--MediaBrowser.Controller/Entities/PersonInfo.cs9
-rw-r--r--MediaBrowser.Controller/Entities/Share.cs13
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs31
-rw-r--r--MediaBrowser.Controller/Entities/TV/Season.cs18
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs18
-rw-r--r--MediaBrowser.Controller/Entities/UserItemData.cs2
-rw-r--r--MediaBrowser.Controller/Entities/UserRootFolder.cs4
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs28
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs8
-rw-r--r--MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs21
-rw-r--r--MediaBrowser.Controller/IO/FileData.cs5
-rw-r--r--MediaBrowser.Controller/IServerApplicationPaths.cs18
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs10
-rw-r--r--MediaBrowser.Controller/Library/IUserManager.cs26
-rw-r--r--MediaBrowser.Controller/Library/ItemResolveArgs.cs54
-rw-r--r--MediaBrowser.Controller/Library/LibraryManagerExtensions.cs4
-rw-r--r--MediaBrowser.Controller/Library/NameExtensions.cs5
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs14
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs8
-rw-r--r--MediaBrowser.Controller/Lyrics/LyricMetadata.cs2
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj20
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs1271
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs33
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs26
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs8
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs12
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessage.cs28
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessageOfT.cs33
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/IInboundWebSocketMessage.cs10
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/IOutboundWebSocketMessage.cs10
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs9
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs23
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs23
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs23
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs23
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs14
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs14
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs14
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs25
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs23
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs24
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs9
-rw-r--r--MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs23
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationManager.cs43
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationService.cs34
-rw-r--r--MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs16
-rw-r--r--MediaBrowser.Controller/Notifications/UserNotification.cs25
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs2
-rw-r--r--MediaBrowser.Controller/Playlists/IPlaylistManager.cs14
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs17
-rw-r--r--MediaBrowser.Controller/Providers/EpisodeInfo.cs3
-rw-r--r--MediaBrowser.Controller/Providers/IHasOrder.cs9
-rw-r--r--MediaBrowser.Controller/Providers/IMetadataService.cs5
-rw-r--r--MediaBrowser.Controller/Providers/IProviderManager.cs20
-rw-r--r--MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs26
-rw-r--r--MediaBrowser.Controller/Providers/ImageRefreshOptions.cs3
-rw-r--r--MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs5
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs4
-rw-r--r--MediaBrowser.Controller/Resolvers/ItemResolver.cs2
-rw-r--r--MediaBrowser.Controller/Session/ISessionController.cs2
-rw-r--r--MediaBrowser.Controller/Session/ISessionManager.cs11
-rw-r--r--MediaBrowser.Controller/Session/SessionInfo.cs14
-rw-r--r--MediaBrowser.Controller/Subtitles/ISubtitleManager.cs9
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs8
-rw-r--r--MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs57
113 files changed, 2294 insertions, 1080 deletions
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index d273b54fc6..ed7c2c2c17 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -35,7 +35,7 @@ namespace MediaBrowser.Controller.BaseItemManager
public SemaphoreSlim MetadataRefreshThrottler { get; private set; }
/// <inheritdoc />
- public bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
+ public bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name)
{
if (baseItem is Channel)
{
@@ -49,19 +49,18 @@ namespace MediaBrowser.Controller.BaseItemManager
return !baseItem.EnableMediaSourceDisplay;
}
- var typeOptions = libraryOptions.GetTypeOptions(baseItem.GetType().Name);
- if (typeOptions != null)
+ if (libraryTypeOptions is not null)
{
- return typeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ return libraryTypeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase));
- return itemConfig == null || !itemConfig.DisabledMetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ return itemConfig is null || !itemConfig.DisabledMetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
/// <inheritdoc />
- public bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name)
+ public bool IsImageFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name)
{
if (baseItem is Channel)
{
@@ -75,15 +74,14 @@ namespace MediaBrowser.Controller.BaseItemManager
return !baseItem.EnableMediaSourceDisplay;
}
- var typeOptions = libraryOptions.GetTypeOptions(baseItem.GetType().Name);
- if (typeOptions != null)
+ if (libraryTypeOptions is not null)
{
- return typeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ return libraryTypeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, baseItem.GetType().Name, StringComparison.OrdinalIgnoreCase));
- return itemConfig == null || !itemConfig.DisabledImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
+ return itemConfig is null || !itemConfig.DisabledImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
/// <summary>
diff --git a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
index e18994214e..b07c80879d 100644
--- a/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/IBaseItemManager.cs
@@ -18,18 +18,18 @@ namespace MediaBrowser.Controller.BaseItemManager
/// Is metadata fetcher enabled.
/// </summary>
/// <param name="baseItem">The base item.</param>
- /// <param name="libraryOptions">The library options.</param>
+ /// <param name="libraryTypeOptions">The type options for <c>baseItem</c> from the library (if defined).</param>
/// <param name="name">The metadata fetcher name.</param>
/// <returns><c>true</c> if metadata fetcher is enabled, else false.</returns>
- bool IsMetadataFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
+ bool IsMetadataFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name);
/// <summary>
/// Is image fetcher enabled.
/// </summary>
/// <param name="baseItem">The base item.</param>
- /// <param name="libraryOptions">The library options.</param>
+ /// <param name="libraryTypeOptions">The type options for <c>baseItem</c> from the library (if defined).</param>
/// <param name="name">The image fetcher name.</param>
/// <returns><c>true</c> if image fetcher is enabled, else false.</returns>
- bool IsImageFetcherEnabled(BaseItem baseItem, LibraryOptions libraryOptions, string name);
+ bool IsImageFetcherEnabled(BaseItem baseItem, TypeOptions? libraryTypeOptions, string name);
}
}
diff --git a/MediaBrowser.Controller/Channels/IChannelManager.cs b/MediaBrowser.Controller/Channels/IChannelManager.cs
index 49be897ef3..8eb27888ab 100644
--- a/MediaBrowser.Controller/Channels/IChannelManager.cs
+++ b/MediaBrowser.Controller/Channels/IChannelManager.cs
@@ -16,12 +16,6 @@ namespace MediaBrowser.Controller.Channels
public interface IChannelManager
{
/// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="channels">The channels.</param>
- void AddParts(IEnumerable<IChannel> channels);
-
- /// <summary>
/// Gets the channel features.
/// </summary>
/// <param name="id">The identifier.</param>
@@ -52,14 +46,14 @@ namespace MediaBrowser.Controller.Channels
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The channels.</returns>
- QueryResult<Channel> GetChannelsInternal(ChannelQuery query);
+ Task<QueryResult<Channel>> GetChannelsInternalAsync(ChannelQuery query);
/// <summary>
/// Gets the channels.
/// </summary>
/// <param name="query">The query.</param>
/// <returns>The channels.</returns>
- QueryResult<BaseItemDto> GetChannels(ChannelQuery query);
+ Task<QueryResult<BaseItemDto>> GetChannelsAsync(ChannelQuery query);
/// <summary>
/// Gets the latest channel items.
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
index 11e6633011..7912c5e87e 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessingOptions.cs
@@ -42,8 +42,6 @@ namespace MediaBrowser.Controller.Drawing
public IReadOnlyCollection<ImageFormat> SupportedOutputFormats { get; set; }
- public bool AddPlayedIndicator { get; set; }
-
public int? UnplayedCount { get; set; }
public int? Blur { get; set; }
@@ -111,7 +109,6 @@ namespace MediaBrowser.Controller.Drawing
{
return (Quality >= 90) &&
IsFormatSupported(originalImagePath) &&
- !AddPlayedIndicator &&
PercentPlayed.Equals(0) &&
!UnplayedCount.HasValue &&
!Blur.HasValue &&
diff --git a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
index b036425ab3..62b70ce53c 100644
--- a/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
+++ b/MediaBrowser.Controller/Drawing/ImageProcessorExtensions.cs
@@ -18,7 +18,7 @@ namespace MediaBrowser.Controller.Drawing
{
var imageInfo = item.GetImageInfo(imageType, imageIndex);
- if (imageInfo == null)
+ if (imageInfo is null)
{
return null;
}
diff --git a/MediaBrowser.Controller/Dto/IDtoService.cs b/MediaBrowser.Controller/Dto/IDtoService.cs
index 89aafc84fb..22453f0f76 100644
--- a/MediaBrowser.Controller/Dto/IDtoService.cs
+++ b/MediaBrowser.Controller/Dto/IDtoService.cs
@@ -1,4 +1,3 @@
-#nullable disable
#pragma warning disable CA1002
using System.Collections.Generic;
@@ -28,7 +27,7 @@ namespace MediaBrowser.Controller.Dto
/// <param name="user">The user.</param>
/// <param name="owner">The owner.</param>
/// <returns>BaseItemDto.</returns>
- BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User user = null, BaseItem owner = null);
+ BaseItemDto GetBaseItemDto(BaseItem item, DtoOptions options, User? user = null, BaseItem? owner = null);
/// <summary>
/// Gets the base item dtos.
@@ -38,7 +37,7 @@ namespace MediaBrowser.Controller.Dto
/// <param name="user">The user.</param>
/// <param name="owner">The owner.</param>
/// <returns>The <see cref="IReadOnlyList{T}"/> of <see cref="BaseItemDto"/>.</returns>
- IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User user = null, BaseItem owner = null);
+ IReadOnlyList<BaseItemDto> GetBaseItemDtos(IReadOnlyList<BaseItem> items, DtoOptions options, User? user = null, BaseItem? owner = null);
/// <summary>
/// Gets the item by name dto.
@@ -48,6 +47,6 @@ namespace MediaBrowser.Controller.Dto
/// <param name="taggedItems">The list of tagged items.</param>
/// <param name="user">The user.</param>
/// <returns>The item dto.</returns>
- BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem> taggedItems, User user = null);
+ BaseItemDto GetItemByNameDto(BaseItem item, DtoOptions options, List<BaseItem>? taggedItems, User? user = null);
}
}
diff --git a/MediaBrowser.Controller/Entities/AggregateFolder.cs b/MediaBrowser.Controller/Entities/AggregateFolder.cs
index e671e5c711..d789033f16 100644
--- a/MediaBrowser.Controller/Entities/AggregateFolder.cs
+++ b/MediaBrowser.Controller/Entities/AggregateFolder.cs
@@ -67,14 +67,14 @@ namespace MediaBrowser.Controller.Entities
{
lock (_childIdsLock)
{
- if (_childrenIds == null || _childrenIds.Length == 0)
+ if (_childrenIds is null || _childrenIds.Length == 0)
{
var list = base.LoadChildren();
_childrenIds = list.Select(i => i.Id).ToArray();
return list;
}
- return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
+ return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i is not null).ToList();
}
}
@@ -120,7 +120,7 @@ namespace MediaBrowser.Controller.Entities
var path = ContainingFolderPath;
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
{
FileInfo = FileSystem.GetDirectoryInfo(path)
};
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 29f7bf92b7..c7216a3209 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -81,8 +81,8 @@ namespace MediaBrowser.Controller.Entities.Audio
/// <returns>System.String.</returns>
protected override string CreateSortName()
{
- return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty)
- + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + Name;
+ return (ParentIndexNumber is not null ? ParentIndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty)
+ + (IndexNumber is not null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + Name;
}
public override List<string> GetUserDataKeys()
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
index 6555de8554..2dbd513a17 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicAlbum.cs
@@ -138,7 +138,7 @@ namespace MediaBrowser.Controller.Entities.Audio
var artist = GetMusicArtist(new DtoOptions(false));
- if (artist != null)
+ if (artist is not null)
{
id.ArtistProviderIds = artist.ProviderIds;
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 7f5f9f74bd..1e868194e6 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -47,7 +47,7 @@ namespace MediaBrowser.Controller.Entities
/// The supported image extensions.
/// </summary>
public static readonly string[] SupportedImageExtensions
- = new[] { ".png", ".jpg", ".jpeg", ".tbn", ".gif" };
+ = new[] { ".png", ".jpg", ".jpeg", ".webp", ".tbn", ".gif" };
private static readonly List<string> _supportedExtensions = new List<string>(SupportedImageExtensions)
{
@@ -56,6 +56,7 @@ namespace MediaBrowser.Controller.Entities
".srt",
".vtt",
".sub",
+ ".sup",
".idx",
".txt",
".edl",
@@ -128,6 +129,13 @@ namespace MediaBrowser.Controller.Entities
public string Album { get; set; }
/// <summary>
+ /// Gets or sets the LUFS value.
+ /// </summary>
+ /// <value>The LUFS Value.</value>
+ [JsonIgnore]
+ public float LUFS { get; set; }
+
+ /// <summary>
/// Gets or sets the channel identifier.
/// </summary>
/// <value>The channel identifier.</value>
@@ -494,7 +502,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- if (_sortName == null)
+ if (_sortName is null)
{
if (!string.IsNullOrEmpty(ForcedSortName))
{
@@ -553,7 +561,7 @@ namespace MediaBrowser.Controller.Entities
public string OfficialRating { get; set; }
[JsonIgnore]
- public int InheritedParentalRatingValue { get; set; }
+ public int? InheritedParentalRatingValue { get; set; }
/// <summary>
/// Gets or sets the critic rating.
@@ -658,7 +666,7 @@ namespace MediaBrowser.Controller.Entities
}
var parent = DisplayParent;
- if (parent != null)
+ if (parent is not null)
{
return parent.OfficialRatingForComparison;
}
@@ -679,7 +687,7 @@ namespace MediaBrowser.Controller.Entities
}
var parent = DisplayParent;
- if (parent != null)
+ if (parent is not null)
{
return parent.CustomRatingForComparison;
}
@@ -800,16 +808,14 @@ namespace MediaBrowser.Controller.Entities
{
return allowed.Contains(ChannelId);
}
- else
- {
- var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
- foreach (var folder in collectionFolders)
+ var collectionFolders = LibraryManager.GetCollectionFolders(this, allCollectionFolders);
+
+ foreach (var folder in collectionFolders)
+ {
+ if (allowed.Contains(folder.Id))
{
- if (allowed.Contains(folder.Id))
- {
- return true;
- }
+ return true;
}
}
@@ -880,7 +886,7 @@ namespace MediaBrowser.Controller.Entities
/// <returns>System.String.</returns>
protected virtual string CreateSortName()
{
- if (Name == null)
+ if (Name is null)
{
return null; // some items may not have name filled in properly
}
@@ -892,16 +898,6 @@ namespace MediaBrowser.Controller.Entities
var sortable = Name.Trim().ToLowerInvariant();
- foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
- {
- sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
- }
-
- foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
- {
- sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
- }
-
foreach (var search in ConfigurationManager.Configuration.SortRemoveWords)
{
// Remove from beginning if a space follows
@@ -920,12 +916,22 @@ namespace MediaBrowser.Controller.Entities
}
}
+ foreach (var removeChar in ConfigurationManager.Configuration.SortRemoveCharacters)
+ {
+ sortable = sortable.Replace(removeChar, string.Empty, StringComparison.Ordinal);
+ }
+
+ foreach (var replaceChar in ConfigurationManager.Configuration.SortReplaceCharacters)
+ {
+ sortable = sortable.Replace(replaceChar, " ", StringComparison.Ordinal);
+ }
+
return ModifySortChunks(sortable);
}
- internal static string ModifySortChunks(string name)
+ internal static string ModifySortChunks(ReadOnlySpan<char> name)
{
- void AppendChunk(StringBuilder builder, bool isDigitChunk, ReadOnlySpan<char> chunk)
+ static void AppendChunk(StringBuilder builder, bool isDigitChunk, ReadOnlySpan<char> chunk)
{
if (isDigitChunk && chunk.Length < 10)
{
@@ -935,7 +941,7 @@ namespace MediaBrowser.Controller.Entities
builder.Append(chunk);
}
- if (name.Length == 0)
+ if (name.IsEmpty)
{
return string.Empty;
}
@@ -949,13 +955,13 @@ namespace MediaBrowser.Controller.Entities
var isDigit = char.IsDigit(name[i]);
if (isDigit != isDigitChunk)
{
- AppendChunk(builder, isDigitChunk, name.AsSpan(chunkStart, i - chunkStart));
+ AppendChunk(builder, isDigitChunk, name.Slice(chunkStart, i - chunkStart));
chunkStart = i;
isDigitChunk = isDigit;
}
}
- AppendChunk(builder, isDigitChunk, name.AsSpan(chunkStart));
+ AppendChunk(builder, isDigitChunk, name.Slice(chunkStart));
// logger.LogDebug("ModifySortChunks Start: {0} End: {1}", name, builder.ToString());
return builder.ToString().RemoveDiacritics();
@@ -976,7 +982,7 @@ namespace MediaBrowser.Controller.Entities
{
var parent = GetParent();
- while (parent != null)
+ while (parent is not null)
{
yield return parent;
@@ -1073,7 +1079,7 @@ namespace MediaBrowser.Controller.Entities
{
var stream = i.VideoStream;
- return stream == null || stream.Width == null ? 0 : stream.Width.Value;
+ return stream is null || stream.Width is null ? 0 : stream.Width.Value;
})
.ToList();
}
@@ -1114,7 +1120,7 @@ namespace MediaBrowser.Controller.Entities
}
var video = item as Video;
- if (video != null)
+ if (video is not null)
{
info.IsoType = video.IsoType;
info.VideoType = video.VideoType;
@@ -1153,7 +1159,7 @@ namespace MediaBrowser.Controller.Entities
info.SupportsDirectStream = MediaSourceManager.SupportsDirectStream(info.Path, info.Protocol);
}
- if (video != null && video.VideoType != VideoType.VideoFile)
+ if (video is not null && video.VideoType != VideoType.VideoFile)
{
info.SupportsDirectStream = false;
}
@@ -1330,7 +1336,7 @@ namespace MediaBrowser.Controller.Entities
public void SetParent(Folder parent)
{
- ParentId = parent == null ? Guid.Empty : parent.Id;
+ ParentId = parent is null ? Guid.Empty : parent.Id;
}
/// <summary>
@@ -1533,12 +1539,6 @@ namespace MediaBrowser.Controller.Entities
}
var maxAllowedRating = user.MaxParentalAgeRating;
-
- if (maxAllowedRating == null)
- {
- return true;
- }
-
var rating = CustomRatingForComparison;
if (string.IsNullOrEmpty(rating))
@@ -1548,12 +1548,13 @@ namespace MediaBrowser.Controller.Entities
if (string.IsNullOrEmpty(rating))
{
+ Logger.LogDebug("{0} has no parental rating set.", Name);
return !GetBlockUnratedValue(user);
}
var value = LocalizationManager.GetRatingLevel(rating);
- // Could not determine the integer value
+ // Could not determine rating level
if (!value.HasValue)
{
var isAllowed = !GetBlockUnratedValue(user);
@@ -1566,7 +1567,7 @@ namespace MediaBrowser.Controller.Entities
return isAllowed;
}
- return value.Value <= maxAllowedRating.Value;
+ return !maxAllowedRating.HasValue || value.Value <= maxAllowedRating.Value;
}
public int? GetInheritedParentalRatingValue()
@@ -1606,6 +1607,12 @@ namespace MediaBrowser.Controller.Entities
return false;
}
+ var allowedTagsPreference = user.GetPreference(PreferenceKind.AllowedTags);
+ if (allowedTagsPreference.Any() && !allowedTagsPreference.Any(i => Tags.Contains(i, StringComparison.OrdinalIgnoreCase)))
+ {
+ return false;
+ }
+
return true;
}
@@ -1620,10 +1627,10 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Gets the block unrated value.
+ /// Gets a bool indicating if access to the unrated item is blocked or not.
/// </summary>
/// <param name="user">The configuration.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ /// <returns><c>true</c> if blocked, <c>false</c> otherwise.</returns>
protected virtual bool GetBlockUnratedValue(User user)
{
// Don't block plain folders that are unrated. Let the media underneath get blocked
@@ -1692,7 +1699,7 @@ namespace MediaBrowser.Controller.Entities
var itemById = LibraryManager.GetItemById(info.ItemId.Value);
- if (itemById != null)
+ if (itemById is not null)
{
return itemById;
}
@@ -1701,7 +1708,7 @@ namespace MediaBrowser.Controller.Entities
var item = FindLinkedChild(info);
// If still null, log
- if (item == null)
+ if (item is null)
{
// Don't keep searching over and over
info.ItemId = Guid.Empty;
@@ -1725,7 +1732,7 @@ namespace MediaBrowser.Controller.Entities
var itemByPath = LibraryManager.FindByPath(path, null);
- if (itemByPath == null)
+ if (itemByPath is null)
{
Logger.LogWarning("Unable to find linked item at path {0}", info.Path);
}
@@ -1737,7 +1744,7 @@ namespace MediaBrowser.Controller.Entities
{
var item = LibraryManager.GetItemById(info.LibraryItemId);
- if (item == null)
+ if (item is null)
{
Logger.LogWarning("Unable to find linked item at path {0}", info.Path);
}
@@ -1755,10 +1762,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">Throws if name is null.</exception>
public void AddStudio(string name)
{
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
+ ArgumentException.ThrowIfNullOrEmpty(name);
var current = Studios;
@@ -1791,10 +1795,7 @@ namespace MediaBrowser.Controller.Entities
/// <exception cref="ArgumentNullException">Throwns if name is null.</exception>
public void AddGenre(string name)
{
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentNullException(nameof(name));
- }
+ ArgumentException.ThrowIfNullOrEmpty(name);
var genres = Genres;
if (!genres.Contains(name, StringComparison.OrdinalIgnoreCase))
@@ -1892,7 +1893,7 @@ namespace MediaBrowser.Controller.Entities
var existingImage = GetImageInfo(image.Type, index);
- if (existingImage == null)
+ if (existingImage is null)
{
AddImage(image);
}
@@ -1915,7 +1916,7 @@ namespace MediaBrowser.Controller.Entities
var image = GetImageInfo(type, index);
- if (image == null)
+ if (image is null)
{
AddImage(GetImageInfo(file, type));
}
@@ -1942,7 +1943,7 @@ namespace MediaBrowser.Controller.Entities
{
var info = GetImageInfo(type, index);
- if (info == null)
+ if (info is null)
{
// Nothing to do
return;
@@ -2035,7 +2036,7 @@ namespace MediaBrowser.Controller.Entities
{
var chapter = ItemRepository.GetChapter(this, imageIndex);
- if (chapter == null)
+ if (chapter is null)
{
return null;
}
@@ -2060,7 +2061,7 @@ namespace MediaBrowser.Controller.Entities
{
var artist = FindParent<MusicArtist>();
- if (artist != null)
+ if (artist is not null)
{
return artist.GetImages(imageType).ElementAtOrDefault(imageIndex);
}
@@ -2147,7 +2148,7 @@ namespace MediaBrowser.Controller.Entities
foreach (var newImage in images)
{
- if (newImage == null)
+ if (newImage is null)
{
throw new ArgumentException("null image found in list");
}
@@ -2155,7 +2156,7 @@ namespace MediaBrowser.Controller.Entities
var existing = existingImages
.Find(i => string.Equals(i.Path, newImage.FullName, StringComparison.OrdinalIgnoreCase));
- if (existing == null)
+ if (existing is null)
{
newImageList.Add(newImage);
}
@@ -2241,7 +2242,7 @@ namespace MediaBrowser.Controller.Entities
var info1 = GetImageInfo(type, index1);
var info2 = GetImageInfo(type, index2);
- if (info1 == null || info2 == null)
+ if (info1 is null || info2 is null)
{
// Nothing to do
return Task.CompletedTask;
@@ -2274,14 +2275,14 @@ namespace MediaBrowser.Controller.Entities
{
var userdata = UserDataManager.GetUserData(user, this);
- return userdata != null && userdata.Played;
+ return userdata is not null && userdata.Played;
}
public bool IsFavoriteOrLiked(User user)
{
var userdata = UserDataManager.GetUserData(user, this);
- return userdata != null && (userdata.IsFavorite || (userdata.Likes ?? false));
+ return userdata is not null && (userdata.IsFavorite || (userdata.Likes ?? false));
}
public virtual bool IsUnplayed(User user)
@@ -2290,7 +2291,7 @@ namespace MediaBrowser.Controller.Entities
var userdata = UserDataManager.GetUserData(user, this);
- return userdata == null || !userdata.Played;
+ return userdata is null || !userdata.Played;
}
ItemLookupInfo IHasLookupInfo<ItemLookupInfo>.GetLookupInfo()
@@ -2445,18 +2446,23 @@ namespace MediaBrowser.Controller.Entities
// Try to retrieve it from the db. If we don't find it, use the resolved version
var video = LibraryManager.GetItemById(id) as Video;
- if (video == null)
+ if (video is null)
{
video = LibraryManager.ResolvePath(FileSystem.GetFileSystemInfo(path)) as Video;
newOptions.ForceSave = true;
}
- if (video == null)
+ if (video is null)
{
return Task.FromResult(true);
}
+ if (video.OwnerId.Equals(default))
+ {
+ video.OwnerId = this.Id;
+ }
+
return RefreshMetadataForOwnedItem(video, copyTitleMetadata, newOptions, cancellationToken);
}
@@ -2511,7 +2517,7 @@ namespace MediaBrowser.Controller.Entities
var item = this;
- var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? 0;
+ var inheritedParentalRatingValue = item.GetInheritedParentalRatingValue() ?? null;
if (inheritedParentalRatingValue != item.InheritedParentalRatingValue)
{
item.InheritedParentalRatingValue = inheritedParentalRatingValue;
@@ -2565,7 +2571,7 @@ namespace MediaBrowser.Controller.Entities
{
return ExtraIds
.Select(LibraryManager.GetItemById)
- .Where(i => i != null)
+ .Where(i => i is not null)
.OrderBy(i => i.SortName);
}
@@ -2578,7 +2584,7 @@ namespace MediaBrowser.Controller.Entities
{
return ExtraIds
.Select(LibraryManager.GetItemById)
- .Where(i => i != null)
+ .Where(i => i is not null)
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value))
.OrderBy(i => i.SortName);
}
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index 948eb43754..615d236c73 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -89,13 +89,13 @@ namespace MediaBrowser.Controller.Entities
}
var v = sourceProp.GetValue(source);
- if (v == null)
+ if (v is null)
{
continue;
}
var p = destProps.Find(x => x.Name == sourceProp.Name);
- if (p != null)
+ if (p is not null)
{
p.SetValue(dest, v);
}
diff --git a/MediaBrowser.Controller/Entities/CollectionFolder.cs b/MediaBrowser.Controller/Entities/CollectionFolder.cs
index 7dc7f774d0..095b261c05 100644
--- a/MediaBrowser.Controller/Entities/CollectionFolder.cs
+++ b/MediaBrowser.Controller/Entities/CollectionFolder.cs
@@ -288,7 +288,7 @@ namespace MediaBrowser.Controller.Entities
{
var path = ContainingFolderPath;
- var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, directoryService)
+ var args = new ItemResolveArgs(ConfigurationManager.ApplicationPaths, LibraryManager)
{
FileInfo = FileSystem.GetDirectoryInfo(path),
Parent = GetParent() as Folder,
@@ -355,8 +355,7 @@ namespace MediaBrowser.Controller.Entities
return PhysicalLocations
.Where(i => !FileSystem.AreEqual(i, Path))
.SelectMany(i => GetPhysicalParents(i, rootChildren))
- .GroupBy(x => x.Id)
- .Select(x => x.First());
+ .DistinctBy(x => x.Id);
}
private IEnumerable<Folder> GetPhysicalParents(string path, List<Folder> rootChildren)
diff --git a/MediaBrowser.Controller/Entities/Extensions.cs b/MediaBrowser.Controller/Entities/Extensions.cs
index 1acb92fdc6..3005bee0a9 100644
--- a/MediaBrowser.Controller/Entities/Extensions.cs
+++ b/MediaBrowser.Controller/Entities/Extensions.cs
@@ -17,16 +17,11 @@ namespace MediaBrowser.Controller.Entities
/// <param name="url">Trailer URL.</param>
public static void AddTrailerUrl(this BaseItem item, string url)
{
- ArgumentNullException.ThrowIfNull(url);
-
- if (url.Length == 0)
- {
- throw new ArgumentException("String can't be empty", nameof(url));
- }
+ ArgumentException.ThrowIfNullOrEmpty(url);
var current = item.RemoteTrailers.FirstOrDefault(i => string.Equals(i.Url, url, StringComparison.OrdinalIgnoreCase));
- if (current == null)
+ if (current is null)
{
var mediaUrl = new MediaUrl
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 808f918104..84952295c4 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -483,7 +483,7 @@ namespace MediaBrowser.Controller.Entities
}
});
- if (container != null)
+ if (container is not null)
{
await RefreshAllMetadataForContainer(container, refreshOptions, innerProgress, cancellationToken).ConfigureAwait(false);
}
@@ -515,7 +515,7 @@ namespace MediaBrowser.Controller.Entities
async () =>
{
var series = container as Series;
- if (series != null)
+ if (series is not null)
{
await series.RefreshMetadata(refreshOptions, cancellationToken).ConfigureAwait(false);
}
@@ -529,7 +529,7 @@ namespace MediaBrowser.Controller.Entities
{
var container = child as IMetadataContainer;
- if (container != null)
+ if (container is not null)
{
await RefreshAllMetadataForContainer(container, refreshOptions, progress, cancellationToken).ConfigureAwait(false);
}
@@ -703,7 +703,7 @@ namespace MediaBrowser.Controller.Entities
IEnumerable<BaseItem> items;
Func<BaseItem, bool> filter = i => UserViewBuilder.Filter(i, user, query, UserDataManager, LibraryManager);
- if (query.User == null)
+ if (query.User is null)
{
items = GetRecursiveChildren(filter);
}
@@ -730,7 +730,7 @@ namespace MediaBrowser.Controller.Entities
return LibraryManager.GetItemsResult(query);
}
- private QueryResult<BaseItem> QueryWithPostFiltering2(InternalItemsQuery query)
+ protected QueryResult<BaseItem> QueryWithPostFiltering2(InternalItemsQuery query)
{
var startIndex = query.StartIndex;
var limit = query.Limit;
@@ -741,7 +741,7 @@ namespace MediaBrowser.Controller.Entities
IEnumerable<BaseItem> itemsList = LibraryManager.GetItemList(query);
var user = query.User;
- if (user != null)
+ if (user is not null)
{
// needed for boxsets
itemsList = itemsList.Where(i => i.IsVisibleStandalone(query.User));
@@ -961,7 +961,7 @@ namespace MediaBrowser.Controller.Entities
IEnumerable<BaseItem> items;
- if (query.User == null)
+ if (query.User is null)
{
items = Children.Where(filter);
}
@@ -984,7 +984,7 @@ namespace MediaBrowser.Controller.Entities
var user = query.User;
// Check recursive - don't substitute in plain folder views
- if (user != null)
+ if (user is not null)
{
items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
}
@@ -1069,7 +1069,7 @@ namespace MediaBrowser.Controller.Entities
if (!param.HasValue)
{
- if (user != null && !configurationManager.Configuration.EnableGroupingIntoCollections)
+ if (user is not null && !configurationManager.Configuration.EnableGroupingIntoCollections)
{
return false;
}
@@ -1272,7 +1272,7 @@ namespace MediaBrowser.Controller.Entities
{
ArgumentNullException.ThrowIfNull(user);
- return GetChildren(user, includeLinkedChildren, null);
+ return GetChildren(user, includeLinkedChildren, new InternalItemsQuery(user));
}
public virtual List<BaseItem> GetChildren(User user, bool includeLinkedChildren, InternalItemsQuery query)
@@ -1300,8 +1300,15 @@ namespace MediaBrowser.Controller.Entities
/// <summary>
/// Adds the children to list.
/// </summary>
- private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, InternalItemsQuery query)
+ private void AddChildren(User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, InternalItemsQuery query, HashSet<Folder> visitedFolders = null)
{
+ // Prevent infinite recursion of nested folders
+ visitedFolders ??= new HashSet<Folder>();
+ if (!visitedFolders.Add(this))
+ {
+ return;
+ }
+
// If Query.AlbumFolders is set, then enforce the format as per the db in that it permits sub-folders in music albums.
IEnumerable<BaseItem> children = null;
if ((query?.DisplayAlbumFolders ?? false) && (this is MusicAlbum))
@@ -1311,47 +1318,38 @@ namespace MediaBrowser.Controller.Entities
}
// If there are not sub-folders, proceed as normal.
- if (children == null)
+ if (children is null)
{
children = GetEligibleChildrenForRecursiveChildren(user);
}
- foreach (var child in children)
+ AddChildrenFromCollection(children, user, includeLinkedChildren, result, recursive, query, visitedFolders);
+
+ if (includeLinkedChildren)
{
- bool? isVisibleToUser = null;
+ AddChildrenFromCollection(GetLinkedChildren(user), user, includeLinkedChildren, result, recursive, query, visitedFolders);
+ }
+ }
- if (query == null || UserViewBuilder.FilterItem(child, query))
+ private void AddChildrenFromCollection(IEnumerable<BaseItem> children, User user, bool includeLinkedChildren, Dictionary<Guid, BaseItem> result, bool recursive, InternalItemsQuery query, HashSet<Folder> visitedFolders)
+ {
+ foreach (var child in children)
+ {
+ if (!child.IsVisible(user))
{
- isVisibleToUser = child.IsVisible(user);
-
- if (isVisibleToUser.Value)
- {
- result[child.Id] = child;
- }
+ continue;
}
- if (isVisibleToUser ?? child.IsVisible(user))
+ if (query is null || UserViewBuilder.FilterItem(child, query))
{
- if (recursive && child.IsFolder)
- {
- var folder = (Folder)child;
-
- folder.AddChildren(user, includeLinkedChildren, result, true, query);
- }
+ result[child.Id] = child;
}
- }
- if (includeLinkedChildren)
- {
- foreach (var child in GetLinkedChildren(user))
+ if (recursive && child.IsFolder)
{
- if (query == null || UserViewBuilder.FilterItem(child, query))
- {
- if (child.IsVisible(user))
- {
- result[child.Id] = child;
- }
- }
+ var folder = (Folder)child;
+
+ folder.AddChildren(user, includeLinkedChildren, result, true, query, visitedFolders);
}
}
}
@@ -1402,7 +1400,7 @@ namespace MediaBrowser.Controller.Entities
{
foreach (var child in Children)
{
- if (filter == null || filter(child))
+ if (filter is null || filter(child))
{
result[child.Id] = child;
}
@@ -1420,7 +1418,7 @@ namespace MediaBrowser.Controller.Entities
{
foreach (var child in GetLinkedChildren())
{
- if (filter == null || filter(child))
+ if (filter is null || filter(child))
{
result[child.Id] = child;
}
@@ -1441,7 +1439,7 @@ namespace MediaBrowser.Controller.Entities
{
var child = GetLinkedChild(i);
- if (child != null)
+ if (child is not null)
{
list.Add(child);
}
@@ -1467,7 +1465,7 @@ namespace MediaBrowser.Controller.Entities
var child = GetLinkedChild(i);
- if (child != null && child.Id.Equals(itemId))
+ if (child is not null && child.Id.Equals(itemId))
{
return true;
}
@@ -1478,7 +1476,7 @@ namespace MediaBrowser.Controller.Entities
public List<BaseItem> GetLinkedChildren(User user)
{
- if (!FilterLinkedChildrenPerUser || user == null)
+ if (!FilterLinkedChildrenPerUser || user is null)
{
return GetLinkedChildren();
}
@@ -1504,7 +1502,7 @@ namespace MediaBrowser.Controller.Entities
{
var child = GetLinkedChild(i);
- if (child == null)
+ if (child is null)
{
continue;
}
@@ -1547,7 +1545,7 @@ namespace MediaBrowser.Controller.Entities
{
return LinkedChildren
.Select(i => new Tuple<LinkedChild, BaseItem>(i, GetLinkedChild(i)))
- .Where(i => i.Item2 != null);
+ .Where(i => i.Item2 is not null);
}
protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, IReadOnlyList<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
@@ -1605,7 +1603,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
})
- .Where(i => i != null)
+ .Where(i => i is not null)
.ToList();
var currentShortcutLinks = LinkedChildren.Where(i => i.Type == LinkedChildType.Shortcut).ToList();
@@ -1662,7 +1660,7 @@ namespace MediaBrowser.Controller.Entities
{
// The querying doesn't support virtual unaired
var episode = item as Episode;
- if (episode != null && episode.IsUnaired)
+ if (episode is not null && episode.IsUnaired)
{
continue;
}
@@ -1719,7 +1717,7 @@ namespace MediaBrowser.Controller.Entities
return;
}
- if (itemDto != null)
+ if (itemDto is not null)
{
if (fields.ContainsField(ItemFields.RecursiveItemCount))
{
diff --git a/MediaBrowser.Controller/Entities/IHasShares.cs b/MediaBrowser.Controller/Entities/IHasShares.cs
deleted file mode 100644
index e6fa27703b..0000000000
--- a/MediaBrowser.Controller/Entities/IHasShares.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-#nullable disable
-
-#pragma warning disable CA1819, CS1591
-
-namespace MediaBrowser.Controller.Entities
-{
- public interface IHasShares
- {
- Share[] Shares { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 1bf5285382..a51299284b 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -26,6 +26,7 @@ namespace MediaBrowser.Controller.Entities
EnableTotalRecordCount = true;
ExcludeArtistIds = Array.Empty<Guid>();
ExcludeInheritedTags = Array.Empty<string>();
+ IncludeInheritedTags = Array.Empty<string>();
ExcludeItemIds = Array.Empty<Guid>();
ExcludeItemTypes = Array.Empty<BaseItemKind>();
ExcludeTags = Array.Empty<string>();
@@ -55,7 +56,7 @@ namespace MediaBrowser.Controller.Entities
public InternalItemsQuery(User? user)
: this()
{
- if (user != null)
+ if (user is not null)
{
SetUser(user);
}
@@ -95,6 +96,8 @@ namespace MediaBrowser.Controller.Entities
public string[] ExcludeInheritedTags { get; set; }
+ public string[] IncludeInheritedTags { get; set; }
+
public IReadOnlyList<string> Genres { get; set; }
public bool? IsSpecialSeason { get; set; }
@@ -316,7 +319,7 @@ namespace MediaBrowser.Controller.Entities
{
set
{
- if (value == null)
+ if (value is null)
{
ParentId = Guid.Empty;
ParentType = null;
@@ -368,6 +371,7 @@ namespace MediaBrowser.Controller.Entities
}
ExcludeInheritedTags = user.GetPreference(PreferenceKind.BlockedTags);
+ IncludeInheritedTags = user.GetPreference(PreferenceKind.AllowedTags);
User = user;
}
diff --git a/MediaBrowser.Controller/Entities/ItemImageInfo.cs b/MediaBrowser.Controller/Entities/ItemImageInfo.cs
index ea8555dbfe..0171af27cf 100644
--- a/MediaBrowser.Controller/Entities/ItemImageInfo.cs
+++ b/MediaBrowser.Controller/Entities/ItemImageInfo.cs
@@ -39,6 +39,6 @@ namespace MediaBrowser.Controller.Entities
public string BlurHash { get; set; }
[JsonIgnore]
- public bool IsLocalFile => Path == null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase);
+ public bool IsLocalFile => Path is null || !Path.StartsWith("http", StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index 882abc9272..66210cb6c4 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -104,7 +104,7 @@ namespace MediaBrowser.Controller.Entities.Movies
public override bool IsAuthorizedToDelete(User user, List<Folder> allCollectionFolders)
{
- return true;
+ return user.HasPermission(PermissionKind.IsAdministrator) || user.HasPermission(PermissionKind.EnableCollectionManagement);
}
public override bool IsSaveLocalMetadataEnabled()
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index 77e70f8fbd..81f6248fac 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -22,7 +22,7 @@ namespace MediaBrowser.Controller.Entities.Movies
/// <inheritdoc />
[JsonIgnore]
public IReadOnlyList<Guid> SpecialFeatureIds => GetExtras()
- .Where(extra => extra.ExtraType != null && extra is Video)
+ .Where(extra => extra.ExtraType is not null && extra is Video)
.Select(extra => extra.Id)
.ToArray();
@@ -33,9 +33,9 @@ namespace MediaBrowser.Controller.Entities.Movies
.ToArray();
/// <summary>
- /// Gets or sets the name of the TMDB collection.
+ /// Gets or sets the name of the TMDb collection.
/// </summary>
- /// <value>The name of the TMDB collection.</value>
+ /// <value>The name of the TMDb collection.</value>
public string TmdbCollectionName { get; set; }
[JsonIgnore]
diff --git a/MediaBrowser.Controller/Entities/PeopleHelper.cs b/MediaBrowser.Controller/Entities/PeopleHelper.cs
index 8571bfcea5..5292bd7727 100644
--- a/MediaBrowser.Controller/Entities/PeopleHelper.cs
+++ b/MediaBrowser.Controller/Entities/PeopleHelper.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities
@@ -12,48 +13,44 @@ namespace MediaBrowser.Controller.Entities
public static void AddPerson(List<PersonInfo> people, PersonInfo person)
{
ArgumentNullException.ThrowIfNull(person);
-
- if (string.IsNullOrEmpty(person.Name))
- {
- throw new ArgumentException("The person's name was empty or null.", nameof(person));
- }
+ ArgumentException.ThrowIfNullOrEmpty(person.Name);
// Normalize
if (string.Equals(person.Role, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
{
- person.Type = PersonType.GuestStar;
+ person.Type = PersonKind.GuestStar;
}
else if (string.Equals(person.Role, PersonType.Director, StringComparison.OrdinalIgnoreCase))
{
- person.Type = PersonType.Director;
+ person.Type = PersonKind.Director;
}
else if (string.Equals(person.Role, PersonType.Producer, StringComparison.OrdinalIgnoreCase))
{
- person.Type = PersonType.Producer;
+ person.Type = PersonKind.Producer;
}
else if (string.Equals(person.Role, PersonType.Writer, StringComparison.OrdinalIgnoreCase))
{
- person.Type = PersonType.Writer;
+ person.Type = PersonKind.Writer;
}
// If the type is GuestStar and there's already an Actor entry, then update it to avoid dupes
- if (string.Equals(person.Type, PersonType.GuestStar, StringComparison.OrdinalIgnoreCase))
+ if (person.Type == PersonKind.GuestStar)
{
- var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase));
+ var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && p.Type == PersonKind.Actor);
- if (existing != null)
+ if (existing is not null)
{
- existing.Type = PersonType.GuestStar;
+ existing.Type = PersonKind.GuestStar;
MergeExisting(existing, person);
return;
}
}
- if (string.Equals(person.Type, PersonType.Actor, StringComparison.OrdinalIgnoreCase))
+ if (person.Type == PersonKind.Actor)
{
// If the actor already exists without a role and we have one, fill it in
- var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type.Equals(PersonType.Actor, StringComparison.OrdinalIgnoreCase) || p.Type.Equals(PersonType.GuestStar, StringComparison.OrdinalIgnoreCase)));
- if (existing == null)
+ var existing = people.FirstOrDefault(p => p.Name.Equals(person.Name, StringComparison.OrdinalIgnoreCase) && (p.Type == PersonKind.Actor || p.Type == PersonKind.GuestStar));
+ if (existing is null)
{
// Wasn't there - add it
people.Add(person);
@@ -72,11 +69,11 @@ namespace MediaBrowser.Controller.Entities
else
{
var existing = people.FirstOrDefault(p =>
- string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase) &&
- string.Equals(p.Type, person.Type, StringComparison.OrdinalIgnoreCase));
+ string.Equals(p.Name, person.Name, StringComparison.OrdinalIgnoreCase)
+ && p.Type == person.Type);
// Check for dupes based on the combination of Name and Type
- if (existing == null)
+ if (existing is null)
{
people.Add(person);
}
diff --git a/MediaBrowser.Controller/Entities/PersonInfo.cs b/MediaBrowser.Controller/Entities/PersonInfo.cs
index 2b689ae7e2..3df0b0b785 100644
--- a/MediaBrowser.Controller/Entities/PersonInfo.cs
+++ b/MediaBrowser.Controller/Entities/PersonInfo.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
+using Jellyfin.Data.Enums;
using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities
@@ -36,7 +37,7 @@ namespace MediaBrowser.Controller.Entities
/// Gets or sets the type.
/// </summary>
/// <value>The type.</value>
- public string Type { get; set; }
+ public PersonKind Type { get; set; }
/// <summary>
/// Gets or sets the ascending sort order.
@@ -57,10 +58,6 @@ namespace MediaBrowser.Controller.Entities
return Name;
}
- public bool IsType(string type)
- {
- return string.Equals(Type, type, StringComparison.OrdinalIgnoreCase)
- || string.Equals(Role, type, StringComparison.OrdinalIgnoreCase);
- }
+ public bool IsType(PersonKind type) => Type == type || string.Equals(type.ToString(), Role, StringComparison.OrdinalIgnoreCase);
}
}
diff --git a/MediaBrowser.Controller/Entities/Share.cs b/MediaBrowser.Controller/Entities/Share.cs
deleted file mode 100644
index 64f446eef2..0000000000
--- a/MediaBrowser.Controller/Entities/Share.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-namespace MediaBrowser.Controller.Entities
-{
- public class Share
- {
- public string UserId { get; set; }
-
- public bool CanEdit { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 15b721fe63..597b4cecbc 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -136,7 +136,7 @@ namespace MediaBrowser.Controller.Entities.TV
public string FindSeriesSortName()
{
var series = Series;
- return series == null ? SeriesName : series.SortName;
+ return series is null ? SeriesName : series.SortName;
}
public override double GetDefaultPrimaryImageAspectRatio()
@@ -155,7 +155,7 @@ namespace MediaBrowser.Controller.Entities.TV
var list = base.GetUserDataKeys();
var series = Series;
- if (series != null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
+ if (series is not null && ParentIndexNumber.HasValue && IndexNumber.HasValue)
{
var seriesUserDataKeys = series.GetUserDataKeys();
var take = seriesUserDataKeys.Count;
@@ -181,14 +181,14 @@ namespace MediaBrowser.Controller.Entities.TV
public string FindSeriesPresentationUniqueKey()
{
var series = Series;
- return series == null ? null : series.PresentationUniqueKey;
+ return series is null ? null : series.PresentationUniqueKey;
}
public string FindSeasonName()
{
var season = Season;
- if (season == null)
+ if (season is null)
{
if (ParentIndexNumber.HasValue)
{
@@ -204,7 +204,7 @@ namespace MediaBrowser.Controller.Entities.TV
public string FindSeriesName()
{
var series = Series;
- return series == null ? SeriesName : series.Name;
+ return series is null ? SeriesName : series.Name;
}
public Guid FindSeasonId()
@@ -212,11 +212,11 @@ namespace MediaBrowser.Controller.Entities.TV
var season = FindParent<Season>();
// Episodes directly in series folder
- if (season == null)
+ if (season is null)
{
var series = Series;
- if (series != null && ParentIndexNumber.HasValue)
+ if (series is not null && ParentIndexNumber.HasValue)
{
var findNumber = ParentIndexNumber.Value;
@@ -226,7 +226,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
}
- return season == null ? Guid.Empty : season.Id;
+ return season is null ? Guid.Empty : season.Id;
}
/// <summary>
@@ -235,8 +235,8 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>System.String.</returns>
protected override string CreateSortName()
{
- return (ParentIndexNumber != null ? ParentIndexNumber.Value.ToString("000 - ", CultureInfo.InvariantCulture) : string.Empty)
- + (IndexNumber != null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + Name;
+ return (ParentIndexNumber is not null ? ParentIndexNumber.Value.ToString("000 - ", CultureInfo.InvariantCulture) : string.Empty)
+ + (IndexNumber is not null ? IndexNumber.Value.ToString("0000 - ", CultureInfo.InvariantCulture) : string.Empty) + Name;
}
/// <summary>
@@ -262,7 +262,7 @@ namespace MediaBrowser.Controller.Entities.TV
public Guid FindSeriesId()
{
var series = FindParent<Series>();
- return series == null ? Guid.Empty : series.Id;
+ return series is null ? Guid.Empty : series.Id;
}
public override IEnumerable<Guid> GetAncestorIds()
@@ -302,12 +302,17 @@ namespace MediaBrowser.Controller.Entities.TV
var series = Series;
- if (series != null)
+ if (series is not null)
{
id.SeriesProviderIds = series.ProviderIds;
id.SeriesDisplayOrder = series.DisplayOrder;
}
+ if (Season is not null)
+ {
+ id.SeasonProviderIds = Season.ProviderIds;
+ }
+
id.IsMissingEpisode = IsMissingEpisode;
id.IndexNumberEnd = IndexNumberEnd;
@@ -320,7 +325,7 @@ namespace MediaBrowser.Controller.Entities.TV
if (!IsLocked)
{
- if (SourceType == SourceType.Library)
+ if (SourceType == SourceType.Library || SourceType == SourceType.LiveTV)
{
try
{
diff --git a/MediaBrowser.Controller/Entities/TV/Season.cs b/MediaBrowser.Controller/Entities/TV/Season.cs
index 599d35da64..0a040a3c28 100644
--- a/MediaBrowser.Controller/Entities/TV/Season.cs
+++ b/MediaBrowser.Controller/Entities/TV/Season.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
var series = Series;
- if (series != null)
+ if (series is not null)
{
return series.Path;
}
@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.Entities.TV
public string FindSeriesSortName()
{
var series = Series;
- return series == null ? SeriesName : series.SortName;
+ return series is null ? SeriesName : series.SortName;
}
public override List<string> GetUserDataKeys()
@@ -101,7 +101,7 @@ namespace MediaBrowser.Controller.Entities.TV
var list = base.GetUserDataKeys();
var series = Series;
- if (series != null)
+ if (series is not null)
{
var newList = series.GetUserDataKeys();
var suffix = (IndexNumber ?? 0).ToString("000", CultureInfo.InvariantCulture);
@@ -129,7 +129,7 @@ namespace MediaBrowser.Controller.Entities.TV
if (IndexNumber.HasValue)
{
var series = Series;
- if (series != null)
+ if (series is not null)
{
return series.PresentationUniqueKey + "-" + (IndexNumber ?? 0).ToString("000", CultureInfo.InvariantCulture);
}
@@ -144,12 +144,12 @@ namespace MediaBrowser.Controller.Entities.TV
/// <returns>System.String.</returns>
protected override string CreateSortName()
{
- return IndexNumber != null ? IndexNumber.Value.ToString("0000", CultureInfo.InvariantCulture) : Name;
+ return IndexNumber is not null ? IndexNumber.Value.ToString("0000", CultureInfo.InvariantCulture) : Name;
}
protected override QueryResult<BaseItem> GetItemsInternal(InternalItemsQuery query)
{
- if (query.User == null)
+ if (query.User is null)
{
return base.GetItemsInternal(query);
}
@@ -208,13 +208,13 @@ namespace MediaBrowser.Controller.Entities.TV
public string FindSeriesPresentationUniqueKey()
{
var series = Series;
- return series == null ? null : series.PresentationUniqueKey;
+ return series is null ? null : series.PresentationUniqueKey;
}
public string FindSeriesName()
{
var series = Series;
- return series == null ? SeriesName : series.Name;
+ return series is null ? SeriesName : series.Name;
}
public Guid FindSeriesId()
@@ -233,7 +233,7 @@ namespace MediaBrowser.Controller.Entities.TV
var series = Series;
- if (series != null)
+ if (series is not null)
{
id.SeriesProviderIds = series.ProviderIds;
}
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index d66802a64f..a49c1609d9 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -28,6 +28,7 @@ namespace MediaBrowser.Controller.Entities.TV
public Series()
{
AirDays = Array.Empty<DayOfWeek>();
+ SeasonNames = new Dictionary<int, string>();
}
public DayOfWeek[] AirDays { get; set; }
@@ -35,6 +36,9 @@ namespace MediaBrowser.Controller.Entities.TV
public string AirTime { get; set; }
[JsonIgnore]
+ public Dictionary<int, string> SeasonNames { get; set; }
+
+ [JsonIgnore]
public override bool SupportsAddingToPlaylist => true;
[JsonIgnore]
@@ -218,7 +222,7 @@ namespace MediaBrowser.Controller.Entities.TV
query.IncludeItemTypes = new[] { BaseItemKind.Season };
query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
- if (user != null && !user.DisplayMissingEpisodes)
+ if (user is not null && !user.DisplayMissingEpisodes)
{
query.IsMissing = false;
}
@@ -266,7 +270,7 @@ namespace MediaBrowser.Controller.Entities.TV
DtoOptions = options
};
- if (user == null || !user.DisplayMissingEpisodes)
+ if (user is null || !user.DisplayMissingEpisodes)
{
query.IsMissing = false;
}
@@ -283,7 +287,7 @@ namespace MediaBrowser.Controller.Entities.TV
// This depends on settings for that series
// When this happens, remove the duplicate from season 0
- return allEpisodes.GroupBy(i => i.Id).Select(x => x.First()).Reverse();
+ return allEpisodes.DistinctBy(i => i.Id).Reverse();
}
public async Task RefreshAllMetadata(MetadataRefreshOptions refreshOptions, IProgress<double> progress, CancellationToken cancellationToken)
@@ -369,7 +373,7 @@ namespace MediaBrowser.Controller.Entities.TV
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options
};
- if (user != null)
+ if (user is not null)
{
if (!user.DisplayMissingEpisodes)
{
@@ -384,7 +388,7 @@ namespace MediaBrowser.Controller.Entities.TV
public List<BaseItem> GetSeasonEpisodes(Season parentSeason, User user, IEnumerable<BaseItem> allSeriesEpisodes, DtoOptions options)
{
- if (allSeriesEpisodes == null)
+ if (allSeriesEpisodes is null)
{
return GetSeasonEpisodes(parentSeason, user, options);
}
@@ -426,7 +430,7 @@ namespace MediaBrowser.Controller.Entities.TV
}
var season = episodeItem.Season;
- return season != null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
+ return season is not null && string.Equals(GetUniqueSeriesKey(season), seasonPresentationKey, StringComparison.OrdinalIgnoreCase);
});
}
@@ -448,7 +452,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
var episode = i;
- if (episode != null)
+ if (episode is not null)
{
var currentSeasonNumber = episode.AiredSeasonNumber;
diff --git a/MediaBrowser.Controller/Entities/UserItemData.cs b/MediaBrowser.Controller/Entities/UserItemData.cs
index 50ba9ef308..ecca440f05 100644
--- a/MediaBrowser.Controller/Entities/UserItemData.cs
+++ b/MediaBrowser.Controller/Entities/UserItemData.cs
@@ -105,7 +105,7 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- if (Rating != null)
+ if (Rating is not null)
{
return Rating >= MinLikeValue;
}
diff --git a/MediaBrowser.Controller/Entities/UserRootFolder.cs b/MediaBrowser.Controller/Entities/UserRootFolder.cs
index e547db5231..69743b926c 100644
--- a/MediaBrowser.Controller/Entities/UserRootFolder.cs
+++ b/MediaBrowser.Controller/Entities/UserRootFolder.cs
@@ -56,14 +56,14 @@ namespace MediaBrowser.Controller.Entities
{
lock (_childIdsLock)
{
- if (_childrenIds == null)
+ if (_childrenIds is null)
{
var list = base.LoadChildren();
_childrenIds = list.Select(i => i.Id).ToList();
return list;
}
- return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i != null).ToList();
+ return _childrenIds.Select(LibraryManager.GetItemById).Where(i => i is not null).ToList();
}
}
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index f467a60384..c276ab463a 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Entities
{
var user = query.User;
- // if (query.IncludeItemTypes != null &&
+ // if (query.IncludeItemTypes is not null &&
// query.IncludeItemTypes.Length == 1 &&
// string.Equals(query.IncludeItemTypes[0], "Playlist", StringComparison.OrdinalIgnoreCase))
// {
@@ -263,7 +263,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
})
- .Where(i => i != null)
+ .Where(i => i is not null)
.Select(i => GetUserViewWithName(SpecialFolder.MovieGenre, i.SortName, parent));
return GetResult(genres, query);
@@ -391,7 +391,7 @@ namespace MediaBrowser.Controller.Entities
return null;
}
})
- .Where(i => i != null)
+ .Where(i => i is not null)
.Select(i => GetUserViewWithName(SpecialFolder.TvGenre, i.SortName, parent));
return GetResult(genres, query);
@@ -559,7 +559,7 @@ namespace MediaBrowser.Controller.Entities
var val = query.Is3D.Value;
var video = item as Video;
- if (video == null || val != video.Video3DFormat.HasValue)
+ if (video is null || val != video.Video3DFormat.HasValue)
{
return false;
}
@@ -689,7 +689,7 @@ namespace MediaBrowser.Controller.Entities
var video = item as Video;
- if (video == null || val != video.HasSubtitles)
+ if (video is null || val != video.HasSubtitles)
{
return false;
}
@@ -776,7 +776,7 @@ namespace MediaBrowser.Controller.Entities
if (query.VideoTypes.Length > 0)
{
var video = item as Video;
- if (video == null || !query.VideoTypes.Contains(video.VideoType))
+ if (video is null || !query.VideoTypes.Contains(video.VideoType))
{
return false;
}
@@ -791,7 +791,7 @@ namespace MediaBrowser.Controller.Entities
if (query.StudioIds.Length > 0 && !query.StudioIds.Any(id =>
{
var studioItem = libraryManager.GetItemById(id);
- return studioItem != null && item.Studios.Contains(studioItem.Name, StringComparison.OrdinalIgnoreCase);
+ return studioItem is not null && item.Studios.Contains(studioItem.Name, StringComparison.OrdinalIgnoreCase);
}))
{
return false;
@@ -801,7 +801,7 @@ namespace MediaBrowser.Controller.Entities
if (query.GenreIds.Count > 0 && !query.GenreIds.Any(id =>
{
var genreItem = libraryManager.GetItemById(id);
- return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparison.OrdinalIgnoreCase);
+ return genreItem is not null && item.Genres.Contains(genreItem.Name, StringComparison.OrdinalIgnoreCase);
}))
{
return false;
@@ -913,7 +913,7 @@ namespace MediaBrowser.Controller.Entities
{
var episode = item as Episode;
- if (episode == null)
+ if (episode is null)
{
return false;
}
@@ -929,7 +929,7 @@ namespace MediaBrowser.Controller.Entities
private IEnumerable<BaseItem> GetMediaFolders(User user)
{
- if (user == null)
+ if (user is null)
{
return _libraryManager.RootFolder
.Children
@@ -945,14 +945,14 @@ namespace MediaBrowser.Controller.Entities
private BaseItem[] GetMediaFolders(User user, IEnumerable<string> viewTypes)
{
- if (user == null)
+ if (user is null)
{
return GetMediaFolders(null)
.Where(i =>
{
var folder = i as ICollectionFolder;
- return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
+ return folder is not null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}).ToArray();
}
@@ -961,13 +961,13 @@ namespace MediaBrowser.Controller.Entities
{
var folder = i as ICollectionFolder;
- return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
+ return folder is not null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}).ToArray();
}
private BaseItem[] GetMediaFolders(Folder parent, User user, IEnumerable<string> viewTypes)
{
- if (parent == null || parent is UserView)
+ if (parent is null || parent is UserView)
{
return GetMediaFolders(user, viewTypes);
}
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 5de2e0f50e..5b7abea10a 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -373,7 +373,7 @@ namespace MediaBrowser.Controller.Entities
{
return LinkedAlternateVersions
.Select(GetLinkedChild)
- .Where(i => i != null)
+ .Where(i => i is not null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
@@ -386,7 +386,7 @@ namespace MediaBrowser.Controller.Entities
{
return GetAdditionalPartIds()
.Select(i => LibraryManager.GetItemById(i))
- .Where(i => i != null)
+ .Where(i => i is not null)
.OfType<Video>()
.OrderBy(i => i.SortName);
}
@@ -469,7 +469,7 @@ namespace MediaBrowser.Controller.Entities
var localAlternates = GetLocalAlternateVersionIds()
.Select(i => LibraryManager.GetItemById(i))
- .Where(i => i != null);
+ .Where(i => i is not null);
foreach (var item in localAlternates)
{
@@ -542,7 +542,7 @@ namespace MediaBrowser.Controller.Entities
return i.Item1 is Video video ? video.GetLocalAlternateVersionIds() : Enumerable.Empty<Guid>();
})
.Select(LibraryManager.GetItemById)
- .Where(i => i != null)
+ .Where(i => i is not null)
.ToList();
list.AddRange(localAlternates.Select(i => (i, MediaSourceType.Default)));
diff --git a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
index 5a71102617..6c58064ce9 100644
--- a/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
+++ b/MediaBrowser.Controller/Extensions/ConfigurationExtensions.cs
@@ -60,6 +60,11 @@ namespace MediaBrowser.Controller.Extensions
public const string UnixSocketPermissionsKey = "kestrel:socketPermissions";
/// <summary>
+ /// The cache size of the SQL database, see cache_size.
+ /// </summary>
+ public const string SqliteCacheSizeKey = "sqlite:cacheSize";
+
+ /// <summary>
/// Gets a value indicating whether the application should host static web content from the <see cref="IConfiguration"/>.
/// </summary>
/// <param name="configuration">The configuration to retrieve the value from.</param>
@@ -73,7 +78,7 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The FFmpeg probe size option.</returns>
- public static string GetFFmpegProbeSize(this IConfiguration configuration)
+ public static string? GetFFmpegProbeSize(this IConfiguration configuration)
=> configuration[FfmpegProbeSizeKey];
/// <summary>
@@ -81,7 +86,7 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The FFmpeg analyze duration option.</returns>
- public static string GetFFmpegAnalyzeDuration(this IConfiguration configuration)
+ public static string? GetFFmpegAnalyzeDuration(this IConfiguration configuration)
=> configuration[FfmpegAnalyzeDurationKey];
/// <summary>
@@ -105,7 +110,7 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The unix socket path.</returns>
- public static string GetUnixSocketPath(this IConfiguration configuration)
+ public static string? GetUnixSocketPath(this IConfiguration configuration)
=> configuration[UnixSocketPathKey];
/// <summary>
@@ -113,7 +118,15 @@ namespace MediaBrowser.Controller.Extensions
/// </summary>
/// <param name="configuration">The configuration to read the setting from.</param>
/// <returns>The unix socket permissions.</returns>
- public static string GetUnixSocketPermissions(this IConfiguration configuration)
+ public static string? GetUnixSocketPermissions(this IConfiguration configuration)
=> configuration[UnixSocketPermissionsKey];
+
+ /// <summary>
+ /// Gets the cache_size from the <see cref="IConfiguration" />.
+ /// </summary>
+ /// <param name="configuration">The configuration to read the setting from.</param>
+ /// <returns>The sqlite cache size.</returns>
+ public static int? GetSqliteCacheSize(this IConfiguration configuration)
+ => configuration.GetValue<int?>(SqliteCacheSizeKey);
}
}
diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs
index 71feae536c..9f7a93ae2c 100644
--- a/MediaBrowser.Controller/IO/FileData.cs
+++ b/MediaBrowser.Controller/IO/FileData.cs
@@ -35,10 +35,7 @@ namespace MediaBrowser.Controller.IO
int flattenFolderDepth = 0,
bool resolveShortcuts = true)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentNullException(nameof(path));
- }
+ ArgumentException.ThrowIfNullOrEmpty(path);
ArgumentNullException.ThrowIfNull(args);
diff --git a/MediaBrowser.Controller/IServerApplicationPaths.cs b/MediaBrowser.Controller/IServerApplicationPaths.cs
index 1890dbb360..608286cd8a 100644
--- a/MediaBrowser.Controller/IServerApplicationPaths.cs
+++ b/MediaBrowser.Controller/IServerApplicationPaths.cs
@@ -51,24 +51,6 @@ namespace MediaBrowser.Controller
string YearPath { get; }
/// <summary>
- /// Gets the path to the General IBN directory.
- /// </summary>
- /// <value>The general path.</value>
- string GeneralPath { get; }
-
- /// <summary>
- /// Gets the path to the Ratings IBN directory.
- /// </summary>
- /// <value>The ratings path.</value>
- string RatingsPath { get; }
-
- /// <summary>
- /// Gets the media info images path.
- /// </summary>
- /// <value>The media info images path.</value>
- string MediaInfoImagesPath { get; }
-
- /// <summary>
/// Gets the path to the user configuration directory.
/// </summary>
/// <value>The user configuration directory path.</value>
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index 5905c25a57..f34e3d68d9 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -429,10 +429,16 @@ namespace MediaBrowser.Controller.Library
/// Gets the collection folders.
/// </summary>
/// <param name="item">The item.</param>
- /// <returns>IEnumerable&lt;Folder&gt;.</returns>
+ /// <returns>The folders that contain the item.</returns>
List<Folder> GetCollectionFolders(BaseItem item);
- List<Folder> GetCollectionFolders(BaseItem item, List<Folder> allUserRootChildren);
+ /// <summary>
+ /// Gets the collection folders.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="allUserRootChildren">The root folders to consider.</param>
+ /// <returns>The folders that contain the item.</returns>
+ List<Folder> GetCollectionFolders(BaseItem item, IEnumerable<Folder> allUserRootChildren);
LibraryOptions GetLibraryOptions(BaseItem item);
diff --git a/MediaBrowser.Controller/Library/IUserManager.cs b/MediaBrowser.Controller/Library/IUserManager.cs
index 993e3e18f9..6d6a532dba 100644
--- a/MediaBrowser.Controller/Library/IUserManager.cs
+++ b/MediaBrowser.Controller/Library/IUserManager.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -47,14 +45,14 @@ namespace MediaBrowser.Controller.Library
/// <param name="id">The id.</param>
/// <returns>The user with the specified Id, or <c>null</c> if the user doesn't exist.</returns>
/// <exception cref="ArgumentException"><c>id</c> is an empty Guid.</exception>
- User GetUserById(Guid id);
+ User? GetUserById(Guid id);
/// <summary>
/// Gets the name of the user by.
/// </summary>
/// <param name="name">The name.</param>
/// <returns>User.</returns>
- User GetUserByName(string name);
+ User? GetUserByName(string name);
/// <summary>
/// Renames the user.
@@ -99,13 +97,6 @@ namespace MediaBrowser.Controller.Library
Task ResetPassword(User user);
/// <summary>
- /// Resets the easy password.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns>Task.</returns>
- Task ResetEasyPassword(User user);
-
- /// <summary>
/// Changes the password.
/// </summary>
/// <param name="user">The user.</param>
@@ -114,21 +105,12 @@ namespace MediaBrowser.Controller.Library
Task ChangePassword(User user, string newPassword);
/// <summary>
- /// Changes the easy password.
- /// </summary>
- /// <param name="user">The user.</param>
- /// <param name="newPassword">New password to use.</param>
- /// <param name="newPasswordSha1">Hash of new password.</param>
- /// <returns>Task.</returns>
- Task ChangeEasyPassword(User user, string newPassword, string newPasswordSha1);
-
- /// <summary>
/// Gets the user dto.
/// </summary>
/// <param name="user">The user.</param>
/// <param name="remoteEndPoint">The remote end point.</param>
/// <returns>UserDto.</returns>
- UserDto GetUserDto(User user, string remoteEndPoint = null);
+ UserDto GetUserDto(User user, string? remoteEndPoint = null);
/// <summary>
/// Authenticates the user.
@@ -139,7 +121,7 @@ namespace MediaBrowser.Controller.Library
/// <param name="remoteEndPoint">Remove endpoint to use.</param>
/// <param name="isUserSession">Specifies if a user session.</param>
/// <returns>User wrapped in awaitable task.</returns>
- Task<User> AuthenticateUser(string username, string password, string passwordSha1, string remoteEndPoint, bool isUserSession);
+ Task<User?> AuthenticateUser(string username, string password, string passwordSha1, string remoteEndPoint, bool isUserSession);
/// <summary>
/// Starts the forgot password process.
diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
index 91d162b411..c701021679 100644
--- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
@@ -1,12 +1,11 @@
#nullable disable
-#pragma warning disable CA1721, CA1819, CS1591
+#pragma warning disable CS1591
using System;
using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Controller.Entities;
-using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.IO;
@@ -23,22 +22,20 @@ namespace MediaBrowser.Controller.Library
/// </summary>
private readonly IServerApplicationPaths _appPaths;
+ private readonly ILibraryManager _libraryManager;
private LibraryOptions _libraryOptions;
/// <summary>
/// Initializes a new instance of the <see cref="ItemResolveArgs" /> class.
/// </summary>
/// <param name="appPaths">The app paths.</param>
- /// <param name="directoryService">The directory service.</param>
- public ItemResolveArgs(IServerApplicationPaths appPaths, IDirectoryService directoryService)
+ /// <param name="libraryManager">The library manager.</param>
+ public ItemResolveArgs(IServerApplicationPaths appPaths, ILibraryManager libraryManager)
{
_appPaths = appPaths;
- DirectoryService = directoryService;
+ _libraryManager = libraryManager;
}
- // TODO remove dependencies as properties, they should be injected where it makes sense
- public IDirectoryService DirectoryService { get; }
-
/// <summary>
/// Gets or sets the file system children.
/// </summary>
@@ -47,7 +44,7 @@ namespace MediaBrowser.Controller.Library
public LibraryOptions LibraryOptions
{
- get => _libraryOptions ??= Parent == null ? new LibraryOptions() : BaseItem.LibraryManager.GetLibraryOptions(Parent);
+ get => _libraryOptions ??= Parent is null ? new LibraryOptions() : _libraryManager.GetLibraryOptions(Parent);
set => _libraryOptions = value;
}
@@ -119,7 +116,7 @@ namespace MediaBrowser.Controller.Library
get
{
var paths = string.IsNullOrEmpty(Path) ? Array.Empty<string>() : new[] { Path };
- return AdditionalLocations == null ? paths : paths.Concat(AdditionalLocations).ToArray();
+ return AdditionalLocations is null ? paths : paths.Concat(AdditionalLocations).ToArray();
}
}
@@ -130,13 +127,13 @@ namespace MediaBrowser.Controller.Library
{
var parent = Parent;
- if (parent != null)
+ if (parent is not null)
{
var item = parent as T;
// Just in case the user decided to nest episodes.
// Not officially supported but in some cases we can handle it.
- if (item == null)
+ if (item is null)
{
var parents = parent.GetParents();
foreach (var currentParent in parents)
@@ -148,7 +145,7 @@ namespace MediaBrowser.Controller.Library
}
}
- return item != null;
+ return item is not null;
}
return false;
@@ -171,10 +168,7 @@ namespace MediaBrowser.Controller.Library
/// <exception cref="ArgumentNullException"><paramref name="path"/> is <c>null</c> or empty.</exception>
public void AddAdditionalLocation(string path)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentException("The path was empty or null.", nameof(path));
- }
+ ArgumentException.ThrowIfNullOrEmpty(path);
AdditionalLocations ??= new List<string>();
AdditionalLocations.Add(path);
@@ -190,10 +184,7 @@ namespace MediaBrowser.Controller.Library
/// <exception cref="ArgumentNullException"><paramref name="name"/> is <c>null</c> or empty.</exception>
public FileSystemMetadata GetFileSystemEntryByName(string name)
{
- if (string.IsNullOrEmpty(name))
- {
- throw new ArgumentException("The name was empty or null.", nameof(name));
- }
+ ArgumentException.ThrowIfNullOrEmpty(name);
return GetFileSystemEntryByPath(System.IO.Path.Combine(Path, name));
}
@@ -206,10 +197,7 @@ namespace MediaBrowser.Controller.Library
/// <exception cref="ArgumentNullException">Throws if path is invalid.</exception>
public FileSystemMetadata GetFileSystemEntryByPath(string path)
{
- if (string.IsNullOrEmpty(path))
- {
- throw new ArgumentException("The path was empty or null.", nameof(path));
- }
+ ArgumentException.ThrowIfNullOrEmpty(path);
foreach (var file in FileSystemChildren)
{
@@ -240,21 +228,15 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the configured content type for the path.
/// </summary>
- /// <remarks>
- /// This is subject to future refactoring as it relies on a static property in BaseItem.
- /// </remarks>
/// <returns>The configured content type.</returns>
public string GetConfiguredContentType()
{
- return BaseItem.LibraryManager.GetConfiguredContentType(Path);
+ return _libraryManager.GetConfiguredContentType(Path);
}
/// <summary>
/// Gets the file system children that do not hit the ignore file check.
/// </summary>
- /// <remarks>
- /// This is subject to future refactoring as it relies on a static property in BaseItem.
- /// </remarks>
/// <returns>The file system children that are not ignored.</returns>
public IEnumerable<FileSystemMetadata> GetActualFileSystemChildren()
{
@@ -262,7 +244,7 @@ namespace MediaBrowser.Controller.Library
for (var i = 0; i < numberOfChildren; i++)
{
var child = FileSystemChildren[i];
- if (BaseItem.LibraryManager.IgnoreFile(child, Parent))
+ if (_libraryManager.IgnoreFile(child, Parent))
{
continue;
}
@@ -287,14 +269,14 @@ namespace MediaBrowser.Controller.Library
/// <returns><c>true</c> if the arguments are the same, <c>false</c> otherwise.</returns>
protected bool Equals(ItemResolveArgs args)
{
- if (args != null)
+ if (args is not null)
{
- if (args.Path == null && Path == null)
+ if (args.Path is null && Path is null)
{
return true;
}
- return args.Path != null && BaseItem.FileSystem.AreEqual(args.Path, Path);
+ return args.Path is not null && BaseItem.FileSystem.AreEqual(args.Path, Path);
}
return false;
diff --git a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs
index 7bc8fa5abd..6d2c3c3d29 100644
--- a/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs
+++ b/MediaBrowser.Controller/Library/LibraryManagerExtensions.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
@@ -9,7 +7,7 @@ namespace MediaBrowser.Controller.Library
{
public static class LibraryManagerExtensions
{
- public static BaseItem GetItemById(this ILibraryManager manager, string id)
+ public static BaseItem? GetItemById(this ILibraryManager manager, string id)
{
return manager.GetItemById(new Guid(id));
}
diff --git a/MediaBrowser.Controller/Library/NameExtensions.cs b/MediaBrowser.Controller/Library/NameExtensions.cs
index 9d78b8b6c8..ee37fb2dc2 100644
--- a/MediaBrowser.Controller/Library/NameExtensions.cs
+++ b/MediaBrowser.Controller/Library/NameExtensions.cs
@@ -10,12 +10,11 @@ namespace MediaBrowser.Controller.Library
public static class NameExtensions
{
public static IEnumerable<string> DistinctNames(this IEnumerable<string> names)
- => names.GroupBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase)
- .Select(x => x.First());
+ => names.DistinctBy(RemoveDiacritics, StringComparer.OrdinalIgnoreCase);
private static string RemoveDiacritics(string? name)
{
- if (name == null)
+ if (name is null)
{
return string.Empty;
}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index 46bdca3027..3b6a16dee3 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -97,7 +97,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="query">The query.</param>
/// <param name="options">The options.</param>
/// <returns>A recording.</returns>
- QueryResult<BaseItemDto> GetRecordings(RecordingQuery query, DtoOptions options);
+ Task<QueryResult<BaseItemDto>> GetRecordingsAsync(RecordingQuery query, DtoOptions options);
/// <summary>
/// Gets the timers.
@@ -308,6 +308,6 @@ namespace MediaBrowser.Controller.LiveTv
void AddInfoToRecordingDto(BaseItem item, BaseItemDto dto, ActiveRecordingInfo activeRecordingInfo, User user = null);
- List<BaseItem> GetRecordingFolders(User user);
+ Task<BaseItem[]> GetRecordingFoldersAsync(User user);
}
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
index 335222da96..f11e3c8f68 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -5,6 +5,7 @@
using System;
using System.Collections.Generic;
using System.Globalization;
+using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -105,12 +106,9 @@ namespace MediaBrowser.Controller.LiveTv
protected override string CreateSortName()
{
- if (!string.IsNullOrEmpty(Number))
+ if (double.TryParse(Number, CultureInfo.InvariantCulture, out double number))
{
- if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out double number))
- {
- return string.Format(CultureInfo.InvariantCulture, "{0:00000.0}", number) + "-" + (Name ?? string.Empty);
- }
+ return string.Format(CultureInfo.InvariantCulture, "{0:00000.0}", number) + "-" + (Name ?? string.Empty);
}
return (Number ?? string.Empty) + "-" + (Name ?? string.Empty);
@@ -122,9 +120,7 @@ namespace MediaBrowser.Controller.LiveTv
}
public IEnumerable<BaseItem> GetTaggedItems()
- {
- return new List<BaseItem>();
- }
+ => Enumerable.Empty<BaseItem>();
public override List<MediaSourceInfo> GetMediaSources(bool enablePathSubstitution)
{
@@ -139,7 +135,7 @@ namespace MediaBrowser.Controller.LiveTv
Path = Path,
RunTimeTicks = RunTimeTicks,
Type = MediaSourceType.Placeholder,
- IsInfiniteStream = RunTimeTicks == null
+ IsInfiniteStream = RunTimeTicks is null
};
list.Add(info);
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 6c4a5ea177..c721fb7785 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -197,10 +197,8 @@ namespace MediaBrowser.Controller.LiveTv
{
return 2.0 / 3;
}
- else
- {
- return 16.0 / 9;
- }
+
+ return 16.0 / 9;
}
public override string GetClientTypeName()
@@ -246,7 +244,7 @@ namespace MediaBrowser.Controller.LiveTv
var listings = GetListingsProviderInfo();
- if (listings != null)
+ if (listings is not null)
{
if (!string.IsNullOrEmpty(listings.MoviePrefix) && name.StartsWith(listings.MoviePrefix, StringComparison.OrdinalIgnoreCase))
{
diff --git a/MediaBrowser.Controller/Lyrics/LyricMetadata.cs b/MediaBrowser.Controller/Lyrics/LyricMetadata.cs
index 6091ede52a..c4f0334892 100644
--- a/MediaBrowser.Controller/Lyrics/LyricMetadata.cs
+++ b/MediaBrowser.Controller/Lyrics/LyricMetadata.cs
@@ -1,5 +1,3 @@
-using System;
-
namespace MediaBrowser.Controller.Lyrics;
/// <summary>
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index d4e025a43e..69c0d26b6b 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -14,14 +14,14 @@
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
- <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ <CodeAnalysisTreatWarningsAsErrors>false</CodeAnalysisTreatWarningsAsErrors>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="6.0.0" />
- <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="All" />
- <PackageReference Include="System.Threading.Tasks.Dataflow" Version="6.0.0" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" />
+ <PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
+ <PackageReference Include="Microsoft.SourceLink.GitHub" PrivateAssets="All" />
+ <PackageReference Include="System.Threading.Tasks.Dataflow" />
</ItemGroup>
<ItemGroup>
@@ -35,7 +35,7 @@
</ItemGroup>
<PropertyGroup>
- <TargetFramework>net6.0</TargetFramework>
+ <TargetFramework>net7.0</TargetFramework>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
@@ -51,13 +51,13 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers" Version="3.3.3">
+ <PackageReference Include="Microsoft.CodeAnalysis.BannedApiAnalyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
- <PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.435" PrivateAssets="All" />
- <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
+ <PackageReference Include="SerilogAnalyzer" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" PrivateAssets="All" />
+ <PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" PrivateAssets="All" />
</ItemGroup>
</Project>
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index 0ddb3be426..af540ecb89 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -38,7 +38,14 @@ namespace MediaBrowser.Controller.MediaEncoding
private readonly ISubtitleEncoder _subtitleEncoder;
private readonly IConfiguration _config;
private readonly Version _minKernelVersionAmdVkFmtModifier = new Version(5, 15);
- private readonly Version _minKernelVersioni915Hang = new Version(5, 18);
+ // i915 hang was fixed by linux 6.2 (3f882f2)
+ private readonly Version _minKerneli915Hang = new Version(5, 18);
+ private readonly Version _maxKerneli915Hang = new Version(6, 1, 3);
+ private readonly Version _minFixedKernel60i915Hang = new Version(6, 0, 18);
+
+ private readonly Version _minFFmpegImplictHwaccel = new Version(6, 0);
+ private readonly Version _minFFmpegHwaUnsafeOutput = new Version(6, 0);
+ private readonly Version _minFFmpegOclCuTonemapMode = new Version(5, 1, 3);
private static readonly string[] _videoProfilesH264 = new[]
{
@@ -58,6 +65,41 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main10"
};
+ private static readonly HashSet<string> _mp4ContainerNames = new(StringComparer.OrdinalIgnoreCase)
+ {
+ "mp4",
+ "m4a",
+ "m4p",
+ "m4b",
+ "m4r",
+ "m4v",
+ };
+
+ // Set max transcoding channels for encoders that can't handle more than a set amount of channels
+ // AAC, FLAC, ALAC, libopus, libvorbis encoders all support at least 8 channels
+ private static readonly Dictionary<string, int> _audioTranscodeChannelLookup = new(StringComparer.OrdinalIgnoreCase)
+ {
+ { "wmav2", 2 },
+ { "libmp3lame", 2 },
+ { "libfdk_aac", 6 },
+ { "aac_at", 6 },
+ { "ac3", 6 },
+ { "eac3", 6 },
+ { "dca", 6 },
+ { "mlp", 6 },
+ { "truehd", 6 },
+ };
+
+ public static readonly string[] LosslessAudioCodecs = new string[]
+ {
+ "alac",
+ "ape",
+ "flac",
+ "mlp",
+ "truehd",
+ "wavpack"
+ };
+
public EncodingHelper(
IApplicationPaths appPaths,
IMediaEncoder mediaEncoder,
@@ -97,14 +139,10 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(hwType)
&& encodingOptions.EnableHardwareEncoding
- && codecMap.ContainsKey(hwType))
+ && codecMap.TryGetValue(hwType, out var preferredEncoder)
+ && _mediaEncoder.SupportsEncoder(preferredEncoder))
{
- var preferredEncoder = codecMap[hwType];
-
- if (_mediaEncoder.SupportsEncoder(preferredEncoder))
- {
- return preferredEncoder;
- }
+ return preferredEncoder;
}
}
@@ -125,7 +163,8 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVaapiFullSupported()
{
- return _mediaEncoder.SupportsHwaccel("vaapi")
+ return _mediaEncoder.SupportsHwaccel("drm")
+ && _mediaEncoder.SupportsHwaccel("vaapi")
&& _mediaEncoder.SupportsFilter("scale_vaapi")
&& _mediaEncoder.SupportsFilter("deinterlace_vaapi")
&& _mediaEncoder.SupportsFilter("tonemap_vaapi")
@@ -162,7 +201,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null
+ if (state.VideoStream is null
|| !options.EnableTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
@@ -189,7 +228,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVulkanHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null)
+ if (state.VideoStream is null)
{
return false;
}
@@ -202,7 +241,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- if (state.VideoStream == null
+ if (state.VideoStream is null
|| !options.EnableVppTonemapping
|| GetVideoColorBitDepth(state) != 10)
{
@@ -520,7 +559,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
}
- else if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
{
return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
}
@@ -530,9 +570,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetInputPathArgument(EncodingJobInfo state)
{
- var mediaPath = state.MediaPath ?? string.Empty;
-
- return _mediaEncoder.GetInputArgument(mediaPath, state.MediaSource);
+ return state.MediaSource.VideoType switch
+ {
+ VideoType.Dvd => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistVobFiles(state.MediaPath, null).ToList(), state.MediaSource),
+ VideoType.BluRay => _mediaEncoder.GetInputArgument(_mediaEncoder.GetPrimaryPlaylistM2tsFiles(state.MediaPath).ToList(), state.MediaSource),
+ _ => _mediaEncoder.GetInputArgument(state.MediaPath, state.MediaSource)
+ };
}
/// <summary>
@@ -546,6 +589,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(codec, "aac", StringComparison.OrdinalIgnoreCase))
{
+ // Use Apple's aac encoder if available as it provides best audio quality
+ if (_mediaEncoder.SupportsEncoder("aac_at"))
+ {
+ return "aac_at";
+ }
+
// Use libfdk_aac for better audio quality if using custom build of FFmpeg which has fdk_aac support
if (_mediaEncoder.SupportsEncoder("libfdk_aac"))
{
@@ -580,6 +629,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return "flac";
}
+ if (string.Equals(codec, "dts", StringComparison.OrdinalIgnoreCase))
+ {
+ return "dca";
+ }
+
return codec.ToLowerInvariant();
}
@@ -605,6 +659,26 @@ namespace MediaBrowser.Controller.MediaEncoding
deviceIndex);
}
+ private string GetVulkanDeviceArgs(int deviceIndex, string deviceName, string srcDeviceAlias, string alias)
+ {
+ alias ??= VulkanAlias;
+ deviceIndex = deviceIndex >= 0
+ ? deviceIndex
+ : 0;
+ var vendorOpts = string.IsNullOrEmpty(deviceName)
+ ? ":" + deviceIndex
+ : ":" + "\"" + deviceName + "\"";
+ var options = string.IsNullOrEmpty(srcDeviceAlias)
+ ? vendorOpts
+ : "@" + srcDeviceAlias;
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device vulkan={0}{1}",
+ alias,
+ options);
+ }
+
private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias)
{
alias ??= OpenclAlias;
@@ -640,28 +714,43 @@ namespace MediaBrowser.Controller.MediaEncoding
options);
}
- private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string alias)
+ private string GetVaapiDeviceArgs(string renderNodePath, string driver, string kernelDriver, string srcDeviceAlias, string alias)
{
alias ??= VaapiAlias;
renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
- var options = string.IsNullOrEmpty(driver)
- ? renderNodePath
- : ",driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver);
+ var driverOpts = string.IsNullOrEmpty(driver)
+ ? ":" + renderNodePath
+ : ":,driver=" + driver + (string.IsNullOrEmpty(kernelDriver) ? string.Empty : ",kernel_driver=" + kernelDriver);
+ var options = string.IsNullOrEmpty(srcDeviceAlias)
+ ? driverOpts
+ : "@" + srcDeviceAlias;
return string.Format(
CultureInfo.InvariantCulture,
- " -init_hw_device vaapi={0}:{1}",
+ " -init_hw_device vaapi={0}{1}",
alias,
options);
}
+ private string GetDrmDeviceArgs(string renderNodePath, string alias)
+ {
+ alias ??= DrmAlias;
+ renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device drm={0}:{1}",
+ alias,
+ renderNodePath);
+ }
+
private string GetQsvDeviceArgs(string alias)
{
var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
if (OperatingSystem.IsLinux())
{
// derive qsv from vaapi device
- return GetVaapiDeviceArgs(null, "iHD", "i915", VaapiAlias) + arg + "@" + VaapiAlias;
+ return GetVaapiDeviceArgs(null, "iHD", "i915", null, VaapiAlias) + arg + "@" + VaapiAlias;
}
if (OperatingSystem.IsWindows())
@@ -682,9 +771,12 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
{
- if (state.SubtitleStream != null
+ // DVBSUB and DVDSUB use the fixed canvas size 720x576
+ if (state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
- && !state.SubtitleStream.IsTextSubtitleStream)
+ && !state.SubtitleStream.IsTextSubtitleStream
+ && !string.Equals(state.SubtitleStream.Codec, "DVBSUB", StringComparison.OrdinalIgnoreCase)
+ && !string.Equals(state.SubtitleStream.Codec, "DVDSUB", StringComparison.OrdinalIgnoreCase))
{
var inW = state.VideoStream?.Width;
var inH = state.VideoStream?.Height;
@@ -752,47 +844,57 @@ namespace MediaBrowser.Controller.MediaEncoding
if (_mediaEncoder.IsVaapiDeviceInteliHD)
{
- args.Append(GetVaapiDeviceArgs(null, "iHD", null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(null, "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, VaapiAlias));
- }
- else
- {
- args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, VaapiAlias));
+ args.Append(GetVaapiDeviceArgs(null, "i965", null, null, VaapiAlias));
}
- var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
+ var filterDevArgs = string.Empty;
+ var doOclTonemap = isHwTonemapAvailable && IsOpenclFullSupported();
- if (isHwTonemapAvailable && IsOpenclFullSupported())
+ if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
{
- if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
+ if (doOclTonemap && !isVaapiDecoder)
{
- if (!isVaapiDecoder)
- {
- args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
- filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
- }
+ args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
+ }
+ }
+ else if (_mediaEncoder.IsVaapiDeviceAmd)
+ {
+ if (IsVulkanFullSupported()
+ && _mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
+ && Environment.OSVersion.Version >= _minKernelVersionAmdVkFmtModifier)
+ {
+ args.Append(GetDrmDeviceArgs(options.VaapiDevice, DrmAlias));
+ args.Append(GetVaapiDeviceArgs(null, null, null, DrmAlias, VaapiAlias));
+ args.Append(GetVulkanDeviceArgs(0, null, DrmAlias, VulkanAlias));
+
+ // libplacebo wants an explicitly set vulkan filter device.
+ filterDevArgs = GetFilterHwDeviceArgs(VulkanAlias);
}
- else if (_mediaEncoder.IsVaapiDeviceAmd)
+ else
{
- if (!IsVulkanFullSupported()
- || !_mediaEncoder.IsVaapiDeviceSupportVulkanFmtModifier
- || Environment.OSVersion.Version < _minKernelVersionAmdVkFmtModifier)
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, null, VaapiAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
+
+ if (doOclTonemap)
{
+ // ROCm/ROCr OpenCL runtime
args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
}
- else
- {
- args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
- filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
- }
+ }
+ else if (doOclTonemap)
+ {
+ args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
args.Append(filterDevArgs);
@@ -928,18 +1030,30 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(canvasArgs);
}
- arg.Append(" -i ")
- .Append(GetInputPathArgument(state));
+ if (state.MediaSource.VideoType == VideoType.Dvd || state.MediaSource.VideoType == VideoType.BluRay)
+ {
+ var tmpConcatPath = Path.Join(options.TranscodingTempPath, state.MediaSource.Id + ".concat");
+ _mediaEncoder.GenerateConcatConfig(state.MediaSource, tmpConcatPath);
+ arg.Append(" -f concat -safe 0 -i ")
+ .Append(tmpConcatPath);
+ }
+ else
+ {
+ arg.Append(" -i ")
+ .Append(GetInputPathArgument(state));
+ }
// sub2video for external graphical subtitles
- if (state.SubtitleStream != null
+ if (state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& !state.SubtitleStream.IsTextSubtitleStream
&& state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
+ var subtitleExtension = Path.GetExtension(subtitlePath);
- if (string.Equals(Path.GetExtension(subtitlePath), ".sub", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(subtitleExtension, ".sub", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(subtitleExtension, ".sup", StringComparison.OrdinalIgnoreCase))
{
var idxFile = Path.ChangeExtension(subtitlePath, ".idx");
if (File.Exists(idxFile))
@@ -963,7 +1077,7 @@ namespace MediaBrowser.Controller.MediaEncoding
arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
}
- if (state.AudioStream != null && state.AudioStream.IsExternal)
+ if (state.AudioStream is not null && state.AudioStream.IsExternal)
{
// Also seek the external audio stream.
var seekAudioParam = GetFastSeekCommandLineParameter(state, options, segmentContainer);
@@ -977,7 +1091,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// Disable auto inserted SW scaler for HW decoders in case of changed resolution.
var isSwDecoder = string.IsNullOrEmpty(GetHardwareVideoDecoder(state, options));
- if (!isSwDecoder)
+ if (!isSwDecoder && _mediaEncoder.EncoderVersion >= new Version(4, 4))
{
arg.Append(" -autoscale 0");
}
@@ -1021,19 +1135,19 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return "-bsf:v h264_mp4toannexb";
}
- else if (IsH265(stream))
+
+ if (IsH265(stream))
{
return "-bsf:v hevc_mp4toannexb";
}
- else if (IsAAC(stream))
+
+ if (IsAAC(stream))
{
// Convert adts header(mpegts) to asc header(mp4).
return "-bsf:a aac_adtstoasc";
}
- else
- {
- return null;
- }
+
+ return null;
}
public static string GetAudioBitStreamArguments(EncodingJobInfo state, string segmentContainer, string mediaSourceContainer)
@@ -1066,7 +1180,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetVideoBitrateParam(EncodingJobInfo state, string videoCodec)
{
- if (state.OutputVideoBitrate == null)
+ if (state.OutputVideoBitrate is null)
{
return string.Empty;
}
@@ -1111,10 +1225,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return FormattableString.Invariant($" -rc_mode CBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
- else
- {
- return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
- }
+
+ return FormattableString.Invariant($" -rc_mode VBR -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
return FormattableString.Invariant($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
@@ -1122,7 +1234,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
- if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out double requestLevel))
{
if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
@@ -1176,24 +1288,6 @@ namespace MediaBrowser.Controller.MediaEncoding
":fontsdir='{0}'",
_mediaEncoder.EscapeSubtitleFilterPath(fontPath));
- // TODO
- // var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf");
- // string fallbackFontParam = string.Empty;
-
- // if (!File.Exists(fallbackFontPath))
- // {
- // _fileSystem.CreateDirectory(_fileSystem.GetDirectoryName(fallbackFontPath));
- // using (var stream = _assemblyInfo.GetManifestResourceStream(GetType(), GetType().Namespace + ".DroidSansFallback.ttf"))
- // {
- // using (var fileStream = new FileStream(fallbackFontPath, FileMode.Create, FileAccess.Write, FileShare.Read))
- // {
- // stream.CopyTo(fileStream);
- // }
- // }
- // }
-
- // fallbackFontParam = string.Format(CultureInfo.InvariantCulture, ":force_style='FontName=Droid Sans Fallback':fontsdir='{0}'", _mediaEncoder.EscapeSubtitleFilterPath(_fileSystem.GetDirectoryName(fallbackFontPath)));
-
if (state.SubtitleStream.IsExternal)
{
var charsetParam = string.Empty;
@@ -1221,7 +1315,6 @@ namespace MediaBrowser.Controller.MediaEncoding
alphaParam,
sub2videoParam,
fontParam,
- // fallbackFontParam,
setPtsParam);
}
@@ -1250,7 +1343,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var maxrate = request.MaxFramerate;
- if (maxrate.HasValue && state.VideoStream != null)
+ if (maxrate.HasValue && state.VideoStream is not null)
{
var contentRate = state.VideoStream.AverageFrameRate ?? state.VideoStream.RealFrameRate;
@@ -1336,13 +1429,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var param = string.Empty;
// Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
- // https://01.org/linuxgraphics/downloads/firmware
+ // https://01.org/group/43/downloads/firmware
// https://wiki.archlinux.org/title/intel_graphics#Enable_GuC_/_HuC_firmware_loading
// Intel Low Power Encoding can save unnecessary CPU-GPU synchronization,
// which will reduce overhead in performance intensive tasks such as 4k transcoding and tonemapping.
var intelLowPowerHwEncoding = false;
- // Workaround for linux 5.18+ i915 hang at cost of performance.
+ // Workaround for linux 5.18 to 6.1.3 i915 hang at cost of performance.
// https://github.com/intel/media-driver/issues/1456
var enableWaFori915Hang = false;
@@ -1361,18 +1454,25 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
{
- if (OperatingSystem.IsLinux() && Environment.OSVersion.Version >= _minKernelVersioni915Hang)
+ if (OperatingSystem.IsLinux())
{
- var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
- var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
- || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
- var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
- && IsVaapiSupported(state)
- && IsOpenclFullSupported()
- && !IsVaapiVppTonemapAvailable(state, encodingOptions)
- && IsHwTonemapAvailable(state, encodingOptions);
+ var ver = Environment.OSVersion.Version;
+ var isFixedKernel60 = ver.Major == 6 && ver.Minor == 0 && ver >= _minFixedKernel60i915Hang;
+ var isUnaffectedKernel = ver < _minKerneli915Hang || ver > _maxKerneli915Hang;
- enableWaFori915Hang = isIntelDecoder && doOclTonemap;
+ if (!(isUnaffectedKernel || isFixedKernel60))
+ {
+ var vidDecoder = GetHardwareVideoDecoder(state, encodingOptions) ?? string.Empty;
+ var isIntelDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase)
+ || vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var doOclTonemap = _mediaEncoder.SupportsHwaccel("qsv")
+ && IsVaapiSupported(state)
+ && IsOpenclFullSupported()
+ && !IsVaapiVppTonemapAvailable(state, encodingOptions)
+ && IsHwTonemapAvailable(state, encodingOptions);
+
+ enableWaFori915Hang = isIntelDecoder && doOclTonemap;
+ }
}
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
@@ -1463,11 +1563,11 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -preset p7";
break;
- case "slow":
+ case "slower":
param += " -preset p6";
break;
- case "slower":
+ case "slow":
param += " -preset p5";
break;
@@ -1500,8 +1600,8 @@ namespace MediaBrowser.Controller.MediaEncoding
switch (encodingOptions.EncoderPreset)
{
case "veryslow":
- case "slow":
case "slower":
+ case "slow":
param += " -quality quality";
break;
@@ -1717,7 +1817,7 @@ namespace MediaBrowser.Controller.MediaEncoding
else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
{
// hevc_qsv use -level 51 instead of -level 153.
- if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out double hevcLevel))
{
param += " -level " + (hevcLevel / 3);
}
@@ -1896,8 +1996,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// If a specific level was requested, the source must match or be less than
var level = state.GetRequestedLevel(videoStream.Codec);
- if (!string.IsNullOrEmpty(level)
- && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out var requestLevel))
{
if (!videoStream.Level.HasValue)
{
@@ -1974,9 +2073,9 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- // Video bitrate must fall within requested value
+ // Audio bitrate must fall within requested value
if (request.AudioBitRate.HasValue
- && audioStream.BitDepth.HasValue
+ && audioStream.BitRate.HasValue
&& audioStream.BitRate.Value > request.AudioBitRate.Value)
{
return false;
@@ -1989,7 +2088,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var bitrate = request.VideoBitRate;
- if (videoStream != null)
+ if (videoStream is not null)
{
var isUpscaling = request.Height.HasValue
&& videoStream.Height.HasValue
@@ -2040,14 +2139,20 @@ namespace MediaBrowser.Controller.MediaEncoding
private static double GetVideoBitrateScaleFactor(string codec)
{
+ // hevc & vp9 - 40% more efficient than h.264
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
{
return .6;
}
+ // av1 - 50% more efficient than h.264
+ if (string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
+ {
+ return .5;
+ }
+
return 1;
}
@@ -2055,7 +2160,9 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var inputScaleFactor = GetVideoBitrateScaleFactor(inputVideoCodec);
var outputScaleFactor = GetVideoBitrateScaleFactor(outputVideoCodec);
- var scaleFactor = outputScaleFactor / inputScaleFactor;
+
+ // Don't scale the real bitrate lower than the requested bitrate
+ var scaleFactor = Math.Max(outputScaleFactor / inputScaleFactor, 1);
if (bitrate <= 500000)
{
@@ -2077,56 +2184,96 @@ namespace MediaBrowser.Controller.MediaEncoding
return Convert.ToInt32(scaleFactor * bitrate);
}
- public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream)
+ public int? GetAudioBitrateParam(BaseEncodingJobOptions request, MediaStream audioStream, int? outputAudioChannels)
{
- return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream);
+ return GetAudioBitrateParam(request.AudioBitRate, request.AudioCodec, audioStream, outputAudioChannels);
}
- public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream)
+ public int? GetAudioBitrateParam(int? audioBitRate, string audioCodec, MediaStream audioStream, int? outputAudioChannels)
{
- if (audioStream == null)
+ if (audioStream is null)
{
return null;
}
- if (audioBitRate.HasValue && string.IsNullOrEmpty(audioCodec))
+ var inputChannels = audioStream.Channels ?? 0;
+ var outputChannels = outputAudioChannels ?? 0;
+ var bitrate = audioBitRate ?? int.MaxValue;
+
+ if (string.IsNullOrEmpty(audioCodec)
+ || string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
{
- return Math.Min(384000, audioBitRate.Value);
+ return (inputChannels, outputChannels) switch
+ {
+ (>= 6, >= 6 or 0) => Math.Min(640000, bitrate),
+ (> 0, > 0) => Math.Min(outputChannels * 128000, bitrate),
+ (> 0, _) => Math.Min(inputChannels * 128000, bitrate),
+ (_, _) => Math.Min(384000, bitrate)
+ };
}
- if (audioBitRate.HasValue && !string.IsNullOrEmpty(audioCodec))
+ if (string.Equals(audioCodec, "dts", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(audioCodec, "dca", StringComparison.OrdinalIgnoreCase))
{
- if (string.Equals(audioCodec, "aac", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "mp3", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "opus", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "vorbis", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "ac3", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "eac3", StringComparison.OrdinalIgnoreCase))
+ return (inputChannels, outputChannels) switch
{
- if ((audioStream.Channels ?? 0) >= 6)
- {
- return Math.Min(640000, audioBitRate.Value);
- }
+ (>= 6, >= 6 or 0) => Math.Min(768000, bitrate),
+ (> 0, > 0) => Math.Min(outputChannels * 136000, bitrate),
+ (> 0, _) => Math.Min(inputChannels * 136000, bitrate),
+ (_, _) => Math.Min(672000, bitrate)
+ };
+ }
- return Math.Min(384000, audioBitRate.Value);
- }
+ // Empty bitrate area is not allow on iOS
+ // Default audio bitrate to 128K per channel if we don't have codec specific defaults
+ // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
+ return 128000 * (outputAudioChannels ?? audioStream.Channels ?? 2);
+ }
- if (string.Equals(audioCodec, "flac", StringComparison.OrdinalIgnoreCase)
- || string.Equals(audioCodec, "alac", StringComparison.OrdinalIgnoreCase))
+ public string GetAudioVbrModeParam(string encoder, int bitratePerChannel)
+ {
+ if (string.Equals(encoder, "libfdk_aac", StringComparison.OrdinalIgnoreCase))
+ {
+ return " -vbr:a " + bitratePerChannel switch
{
- if ((audioStream.Channels ?? 0) >= 6)
- {
- return Math.Min(3584000, audioBitRate.Value);
- }
+ < 32000 => "1",
+ < 48000 => "2",
+ < 64000 => "3",
+ < 96000 => "4",
+ _ => "5"
+ };
+ }
- return Math.Min(1536000, audioBitRate.Value);
- }
+ if (string.Equals(encoder, "libmp3lame", StringComparison.OrdinalIgnoreCase))
+ {
+ return " -qscale:a " + bitratePerChannel switch
+ {
+ < 48000 => "8",
+ < 64000 => "6",
+ < 88000 => "4",
+ < 112000 => "2",
+ _ => "0"
+ };
}
- // Empty bitrate area is not allow on iOS
- // Default audio bitrate to 128K if it is not being requested
- // https://ffmpeg.org/ffmpeg-codecs.html#toc-Codec-Options
- return 128000;
+ if (string.Equals(encoder, "libvorbis", StringComparison.OrdinalIgnoreCase))
+ {
+ return " -qscale:a " + bitratePerChannel switch
+ {
+ < 40000 => "0",
+ < 56000 => "2",
+ < 80000 => "4",
+ < 112000 => "6",
+ _ => "8"
+ };
+ }
+
+ return null;
}
public string GetAudioFilterParam(EncodingJobInfo state, EncodingOptions encodingOptions)
@@ -2135,19 +2282,34 @@ namespace MediaBrowser.Controller.MediaEncoding
var filters = new List<string>();
- // Boost volume to 200% when downsampling from 6ch to 2ch
if (channels.HasValue
- && channels.Value <= 2
- && state.AudioStream != null
+ && channels.Value == 2
+ && state.AudioStream is not null
&& state.AudioStream.Channels.HasValue
- && state.AudioStream.Channels.Value > 5
- && !encodingOptions.DownMixAudioBoost.Equals(1))
+ && state.AudioStream.Channels.Value > 5)
{
- filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
+ switch (encodingOptions.DownMixStereoAlgorithm)
+ {
+ case DownMixStereoAlgorithms.Dave750:
+ filters.Add("volume=4.25");
+ filters.Add("pan=stereo|c0=0.5*c2+0.707*c0+0.707*c4+0.5*c3|c1=0.5*c2+0.707*c1+0.707*c5+0.5*c3");
+ break;
+ case DownMixStereoAlgorithms.NightmodeDialogue:
+ filters.Add("pan=stereo|c0=c2+0.30*c0+0.30*c4|c1=c2+0.30*c1+0.30*c5");
+ break;
+ case DownMixStereoAlgorithms.None:
+ default:
+ if (!encodingOptions.DownMixAudioBoost.Equals(1))
+ {
+ filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
+ }
+
+ break;
+ }
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
- if (state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps)
+ if (state.SubtitleStream is not null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode && !isCopyingTimestamps)
{
var seconds = TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds;
@@ -2175,94 +2337,55 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <returns>System.Nullable{System.Int32}.</returns>
public int? GetNumAudioChannelsParam(EncodingJobInfo state, MediaStream audioStream, string outputAudioCodec)
{
- if (audioStream == null)
+ if (audioStream is null)
{
return null;
}
var request = state.BaseRequest;
- var inputChannels = audioStream.Channels;
+ var codec = outputAudioCodec ?? string.Empty;
- if (inputChannels <= 0)
- {
- inputChannels = null;
- }
+ int? resultChannels = state.GetRequestedAudioChannels(codec);
- var codec = outputAudioCodec ?? string.Empty;
+ var inputChannels = audioStream.Channels;
- int? transcoderChannelLimit;
- if (codec.IndexOf("wma", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // wmav2 currently only supports two channel output
- transcoderChannelLimit = 2;
- }
- else if (codec.IndexOf("mp3", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // libmp3lame currently only supports two channel output
- transcoderChannelLimit = 2;
- }
- else if (codec.IndexOf("aac", StringComparison.OrdinalIgnoreCase) != -1)
- {
- // aac is able to handle 8ch(7.1 layout)
- transcoderChannelLimit = 8;
- }
- else
+ if (inputChannels > 0)
{
- // If we don't have any media info then limit it to 6 to prevent encoding errors due to asking for too many channels
- transcoderChannelLimit = 6;
+ resultChannels = inputChannels < resultChannels ? inputChannels : resultChannels ?? inputChannels;
}
var isTranscodingAudio = !IsCopyCodec(codec);
- int? resultChannels = state.GetRequestedAudioChannels(codec);
if (isTranscodingAudio)
{
- resultChannels = GetMinValue(request.TranscodingMaxAudioChannels, resultChannels);
- }
+ var audioEncoder = GetAudioEncoder(state);
+ if (!_audioTranscodeChannelLookup.TryGetValue(audioEncoder, out var transcoderChannelLimit))
+ {
+ // Set default max transcoding channels to 8 to prevent encoding errors due to asking for too many channels.
+ transcoderChannelLimit = 8;
+ }
- if (inputChannels.HasValue)
- {
- resultChannels = resultChannels.HasValue
- ? Math.Min(resultChannels.Value, inputChannels.Value)
- : inputChannels.Value;
- }
+ // Set resultChannels to minimum between resultChannels, TranscodingMaxAudioChannels, transcoderChannelLimit
+ resultChannels = transcoderChannelLimit < resultChannels ? transcoderChannelLimit : resultChannels ?? transcoderChannelLimit;
- if (isTranscodingAudio && transcoderChannelLimit.HasValue)
- {
- resultChannels = resultChannels.HasValue
- ? Math.Min(resultChannels.Value, transcoderChannelLimit.Value)
- : transcoderChannelLimit.Value;
- }
+ if (request.TranscodingMaxAudioChannels < resultChannels)
+ {
+ resultChannels = request.TranscodingMaxAudioChannels;
+ }
- // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
- // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
- if (isTranscodingAudio
- && state.TranscodingType != TranscodingJobType.Progressive
- && resultChannels.HasValue
- && ((resultChannels.Value > 2 && resultChannels.Value < 6) || resultChannels.Value == 7))
- {
- resultChannels = 2;
+ // Avoid transcoding to audio channels other than 1ch, 2ch, 6ch (5.1 layout) and 8ch (7.1 layout).
+ // https://developer.apple.com/documentation/http_live_streaming/hls_authoring_specification_for_apple_devices
+ if (state.TranscodingType != TranscodingJobType.Progressive
+ && ((resultChannels > 2 && resultChannels < 6) || resultChannels == 7))
+ {
+ resultChannels = 2;
+ }
}
return resultChannels;
}
- private int? GetMinValue(int? val1, int? val2)
- {
- if (!val1.HasValue)
- {
- return val2;
- }
-
- if (!val2.HasValue)
- {
- return val1;
- }
-
- return Math.Min(val1.Value, val2.Value);
- }
-
/// <summary>
/// Enforces the resolution limit.
/// </summary>
@@ -2328,26 +2451,26 @@ namespace MediaBrowser.Controller.MediaEncoding
// If we don't have known media info
// If input is video, use -sn to drop subtitles
// Otherwise just return empty
- if (state.VideoStream == null && state.AudioStream == null)
+ if (state.VideoStream is null && state.AudioStream is null)
{
return state.IsInputVideo ? "-sn" : string.Empty;
}
// We have media info, but we don't know the stream index
- if (state.VideoStream != null && state.VideoStream.Index == -1)
+ if (state.VideoStream is not null && state.VideoStream.Index == -1)
{
return "-sn";
}
// We have media info, but we don't know the stream index
- if (state.AudioStream != null && state.AudioStream.Index == -1)
+ if (state.AudioStream is not null && state.AudioStream.Index == -1)
{
return state.IsInputVideo ? "-sn" : string.Empty;
}
var args = string.Empty;
- if (state.VideoStream != null)
+ if (state.VideoStream is not null)
{
int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
@@ -2362,12 +2485,12 @@ namespace MediaBrowser.Controller.MediaEncoding
args += "-vn";
}
- if (state.AudioStream != null)
+ if (state.AudioStream is not null)
{
int audioStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.AudioStream);
if (state.AudioStream.IsExternal)
{
- bool hasExternalGraphicsSubs = state.SubtitleStream != null
+ bool hasExternalGraphicsSubs = state.SubtitleStream is not null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
&& state.SubtitleStream.IsExternal
&& !state.SubtitleStream.IsTextSubtitleStream;
@@ -2393,7 +2516,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var subtitleMethod = state.SubtitleDeliveryMethod;
- if (state.SubtitleStream == null || subtitleMethod == SubtitleDeliveryMethod.Hls)
+ if (state.SubtitleStream is null || subtitleMethod == SubtitleDeliveryMethod.Hls)
{
args += " -map -0:s";
}
@@ -2420,6 +2543,30 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets the negative map args by filters.
+ /// </summary>
+ /// <param name="state">The state.</param>
+ /// <param name="videoProcessFilters">The videoProcessFilters.</param>
+ /// <returns>System.String.</returns>
+ public string GetNegativeMapArgsByFilters(EncodingJobInfo state, string videoProcessFilters)
+ {
+ string args = string.Empty;
+
+ // http://ffmpeg.org/ffmpeg-all.html#toc-Complex-filtergraphs-1
+ if (state.VideoStream != null && videoProcessFilters.Contains("-filter_complex", StringComparison.Ordinal))
+ {
+ int videoStreamIndex = FindIndex(state.MediaSource.MediaStreams, state.VideoStream);
+
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ "-map -0:{0} ",
+ videoStreamIndex);
+ }
+
+ return args;
+ }
+
+ /// <summary>
/// Determines which stream will be used for playback.
/// </summary>
/// <param name="allStream">All stream.</param>
@@ -2435,7 +2582,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var stream = streams.FirstOrDefault(s => s.Index == desiredIndex.Value);
- if (stream != null)
+ if (stream is not null)
{
return stream;
}
@@ -2480,8 +2627,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (outputWidth > maximumWidth || outputHeight > maximumHeight)
{
- var scaleW = (double)maximumWidth / (double)outputWidth;
- var scaleH = (double)maximumHeight / (double)outputHeight;
+ var scaleW = (double)maximumWidth / outputWidth;
+ var scaleH = (double)maximumHeight / outputHeight;
var scale = Math.Min(scaleW, scaleH);
outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale));
outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale));
@@ -2628,79 +2775,76 @@ namespace MediaBrowser.Controller.MediaEncoding
widthParam,
heightParam);
}
- else
- {
- return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
- }
+
+ return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
}
// If Max dimensions were supplied, for width selects lowest even number between input width and width req size and selects lowest even number from in width*display aspect and requested size
- else if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
+
+ if (requestedMaxWidth.HasValue && requestedMaxHeight.HasValue)
{
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
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",
- maxWidthParam,
- maxHeightParam,
- scaleVal);
+ 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",
+ maxWidthParam,
+ maxHeightParam,
+ scaleVal);
}
// If a fixed width was requested
- else if (requestedWidth.HasValue)
+ if (requestedWidth.HasValue)
{
if (threedFormat.HasValue)
{
// This method can handle 0 being passed in for the requested height
return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
}
- else
- {
- var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
- return string.Format(
- CultureInfo.InvariantCulture,
- "scale={0}:trunc(ow/a/2)*2",
- widthParam);
- }
+ var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale={0}:trunc(ow/a/2)*2",
+ widthParam);
}
// If a fixed height was requested
- else if (requestedHeight.HasValue)
+ if (requestedHeight.HasValue)
{
var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/{1})*{1}:{0}",
- heightParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(oh*a/{1})*{1}:{0}",
+ heightParam,
+ scaleVal);
}
// If a max width was requested
- else if (requestedMaxWidth.HasValue)
+ if (requestedMaxWidth.HasValue)
{
var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
- maxWidthParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(min(max(iw\\,ih*a)\\,{0})/{1})*{1}:trunc(ow/a/2)*2",
+ maxWidthParam,
+ scaleVal);
}
// If a max height was requested
- else if (requestedMaxHeight.HasValue)
+ if (requestedMaxHeight.HasValue)
{
var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
return string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
- maxHeightParam,
- scaleVal);
+ CultureInfo.InvariantCulture,
+ "scale=trunc(oh*a/{1})*{1}:min(max(iw/a\\,ih)\\,{0})",
+ maxHeightParam,
+ scaleVal);
}
return string.Empty;
@@ -2739,7 +2883,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
// default
- if (filter == null)
+ if (filter is null)
{
if (requestedHeight > 0)
{
@@ -2774,22 +2918,32 @@ namespace MediaBrowser.Controller.MediaEncoding
"yadif_cuda={0}:-1:0",
doubleRateDeint ? "1" : "0");
}
- else if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
+
+ if (hwDeintSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
{
return string.Format(
CultureInfo.InvariantCulture,
"deinterlace_vaapi=rate={0}",
doubleRateDeint ? "field" : "frame");
}
- else if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
+
+ if (hwDeintSuffix.Contains("qsv", StringComparison.OrdinalIgnoreCase))
{
return "deinterlace_qsv=mode=2";
}
+ if (hwDeintSuffix.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_videotoolbox={0}:-1:0",
+ doubleRateDeint ? "1" : "0");
+ }
+
return string.Empty;
}
- public static string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
+ public string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
{
if (string.IsNullOrEmpty(hwTonemapSuffix))
{
@@ -2801,7 +2955,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (string.Equals(hwTonemapSuffix, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- args = "tonemap_vaapi=format={0}:p=bt709:t=bt709:m=bt709,procamp_vaapi=b={1}:c={2}:extra_hw_frames=16";
+ args = "procamp_vaapi=b={2}:c={3}," + args + ":extra_hw_frames=32";
+
return string.Format(
CultureInfo.InvariantCulture,
args,
@@ -2809,7 +2964,8 @@ namespace MediaBrowser.Controller.MediaEncoding
options.VppTonemappingBrightness,
options.VppTonemappingContrast);
}
- else if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(hwTonemapSuffix, "vulkan", StringComparison.OrdinalIgnoreCase))
{
args = "libplacebo=format={1}:tonemapping={2}:color_primaries=bt709:color_trc=bt709:colorspace=bt709:peak_detect=0:upscaler=none:downscaler=none";
@@ -2822,7 +2978,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
algorithm = "bt.2390";
}
-
else if (string.Equals(options.TonemappingAlgorithm, "none", StringComparison.OrdinalIgnoreCase))
{
algorithm = "clip";
@@ -2832,14 +2987,24 @@ namespace MediaBrowser.Controller.MediaEncoding
{
args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709:tonemap={2}:peak={3}:desat={4}";
+ if (string.Equals(options.TonemappingMode, "max", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(options.TonemappingMode, "rgb", StringComparison.OrdinalIgnoreCase))
+ {
+ if (_mediaEncoder.EncoderVersion >= _minFFmpegOclCuTonemapMode)
+ {
+ args += ":tonemap_mode={5}";
+ }
+ }
+
if (options.TonemappingParam != 0)
{
- args += ":param={5}";
+ args += ":param={6}";
}
- if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options.TonemappingRange, "tv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(options.TonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
{
- args += ":range={6}";
+ args += ":range={7}";
}
}
@@ -2851,10 +3016,80 @@ namespace MediaBrowser.Controller.MediaEncoding
algorithm,
options.TonemappingPeak,
options.TonemappingDesat,
+ options.TonemappingMode,
options.TonemappingParam,
options.TonemappingRange);
}
+ public string GetLibplaceboFilter(
+ EncodingOptions options,
+ string videoFormat,
+ bool doTonemap,
+ int? videoWidth,
+ int? videoHeight,
+ int? requestedWidth,
+ int? requestedHeight,
+ int? requestedMaxWidth,
+ int? requestedMaxHeight)
+ {
+ var (outWidth, outHeight) = GetFixedOutputSize(
+ videoWidth,
+ videoHeight,
+ requestedWidth,
+ requestedHeight,
+ requestedMaxWidth,
+ requestedMaxHeight);
+
+ var isFormatFixed = !string.IsNullOrEmpty(videoFormat);
+ var isSizeFixed = !videoWidth.HasValue
+ || outWidth.Value != videoWidth.Value
+ || !videoHeight.HasValue
+ || outHeight.Value != videoHeight.Value;
+
+ var sizeArg = isSizeFixed ? (":w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
+ var formatArg = isFormatFixed ? (":format=" + videoFormat) : string.Empty;
+ var tonemapArg = string.Empty;
+
+ if (doTonemap)
+ {
+ var algorithm = options.TonemappingAlgorithm;
+ var mode = options.TonemappingMode;
+ var range = options.TonemappingRange;
+
+ if (string.Equals(algorithm, "bt2390", StringComparison.OrdinalIgnoreCase))
+ {
+ algorithm = "bt.2390";
+ }
+ else if (string.Equals(algorithm, "none", StringComparison.OrdinalIgnoreCase))
+ {
+ algorithm = "clip";
+ }
+
+ tonemapArg = ":tonemapping=" + algorithm;
+
+ if (string.Equals(mode, "max", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(mode, "rgb", StringComparison.OrdinalIgnoreCase))
+ {
+ tonemapArg += ":tonemapping_mode=" + mode;
+ }
+
+ tonemapArg += ":peak_detect=0:color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+
+ if (string.Equals(range, "tv", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(range, "pc", StringComparison.OrdinalIgnoreCase))
+ {
+ tonemapArg += ":range=" + range;
+ }
+ }
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "libplacebo=upscaler=none:downscaler=none{0}{1}{2}",
+ sizeArg,
+ formatArg,
+ tonemapArg);
+ }
+
/// <summary>
/// Gets the parameter of software filter chain.
/// </summary>
@@ -2884,7 +3119,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -2931,8 +3166,8 @@ namespace MediaBrowser.Controller.MediaEncoding
}
else if (hasGraphicalSubs)
{
- // [0:s]scale=expr
- var subSwScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ // [0:s]scale=s=1280x720
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -2999,7 +3234,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doCuTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3118,9 +3353,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3189,7 +3422,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3257,7 +3490,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl.
- var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap";
+ var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap=mode=read";
mainFilters.Add(hwTransferFilter);
mainFilters.Add("format=nv12");
}
@@ -3320,9 +3553,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3411,7 +3642,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3502,7 +3733,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl.
// qsv hwmap is not fully implemented for the time being.
- mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload");
+ mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
mainFilters.Add("format=nv12");
}
@@ -3574,9 +3805,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
}
@@ -3614,7 +3843,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3662,6 +3891,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ // allocate extra pool sizes for vaapi vpp
+ if (!string.IsNullOrEmpty(hwScaleFilter) && isVaapiDecoder)
+ {
+ hwScaleFilter += ":extra_hw_frames=24";
+ }
+
// hw scale
mainFilters.Add(hwScaleFilter);
}
@@ -3708,7 +3944,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl/vaapi.
// qsv hwmap is not fully implemented for the time being.
- mainFilters.Add(isHwmapUsable ? "hwmap" : "hwdownload");
+ mainFilters.Add(isHwmapUsable ? "hwmap=mode=read" : "hwdownload");
mainFilters.Add("format=nv12");
}
@@ -3785,9 +4021,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
}
@@ -3891,7 +4125,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doTonemap = doVaVppTonemap || doOclTonemap;
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -3939,6 +4173,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var outFormat = doTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ // allocate extra pool sizes for vaapi vpp
+ if (!string.IsNullOrEmpty(hwScaleFilter))
+ {
+ hwScaleFilter += ":extra_hw_frames=24";
+ }
+
// hw scale
mainFilters.Add(hwScaleFilter);
}
@@ -3980,7 +4221,7 @@ namespace MediaBrowser.Controller.MediaEncoding
// OUTPUT nv12 surface(memory)
// prefer hwmap to hwdownload on opencl/vaapi.
- mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
+ mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap=mode=read");
mainFilters.Add("format=nv12");
}
@@ -4046,9 +4287,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4080,14 +4319,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
var isSwEncoder = !isVaapiEncoder;
- var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
var doVkTonemap = IsVulkanHwTonemapAvailable(state, options);
var doDeintH2645 = doDeintH264 || doDeintHevc;
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
var hasAssSubs = hasSubs
@@ -4109,95 +4347,81 @@ namespace MediaBrowser.Controller.MediaEncoding
mainFilters.Add(swDeintFilter);
}
- var outFormat = doVkTonemap ? "yuv420p10le" : "nv12";
- var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
- // sw scale
- mainFilters.Add(swScaleFilter);
- mainFilters.Add("format=" + outFormat);
-
- // keep video at memory except vk tonemap,
- // since the overhead caused by hwupload >>> using sw filter.
- // sw => hw
- if (doVkTonemap)
+ if (doVkTonemap || hasSubs)
+ {
+ // sw => hw
+ mainFilters.Add("hwupload=derive_device=vulkan");
+ mainFilters.Add("format=vulkan");
+ }
+ else
{
- mainFilters.Add("hwupload=derive_device=vulkan:extra_hw_frames=16");
+ // sw scale
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ mainFilters.Add(swScaleFilter);
+ mainFilters.Add("format=nv12");
}
}
else if (isVaapiDecoder)
{
// INPUT vaapi surface(vram)
- // hw deint
- if (doDeintH2645)
+ if (doVkTonemap || hasSubs)
{
- var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
- mainFilters.Add(deintFilter);
+ // map from vaapi to vulkan/drm via interop (Vega/gfx9+).
+ mainFilters.Add("hwmap=derive_device=vulkan");
+ mainFilters.Add("format=vulkan");
}
-
- var outFormat = doVkTonemap ? string.Empty : (hasSubs && isVaInVaOut ? "bgra" : "nv12");
- var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
-
- // allocate extra pool sizes for overlay_vulkan
- if (!string.IsNullOrEmpty(hwScaleFilter) && isVaInVaOut && hasSubs)
+ else
{
- hwScaleFilter += ":extra_hw_frames=32";
- }
-
- // hw scale
- mainFilters.Add(hwScaleFilter);
- }
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ mainFilters.Add(deintFilter);
+ }
- if ((isVaapiDecoder && doVkTonemap) || (isVaInVaOut && (doVkTonemap || hasSubs)))
- {
- // map from vaapi to vulkan via vaapi-vulkan interop (Vega/gfx9+).
- mainFilters.Add("hwmap=derive_device=vulkan");
+ // hw scale
+ var hwScaleFilter = GetHwScaleFilter("vaapi", "nv12", inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ mainFilters.Add(hwScaleFilter);
+ }
}
- // vk tonemap
- if (doVkTonemap)
+ // vk libplacebo
+ if (doVkTonemap || hasSubs)
{
- var outFormat = isVaInVaOut && hasSubs ? "bgra" : "nv12";
- var tonemapFilter = GetHwTonemapFilter(options, "vulkan", outFormat);
- mainFilters.Add(tonemapFilter);
+ var libplaceboFilter = GetLibplaceboFilter(options, "bgra", doVkTonemap, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ mainFilters.Add(libplaceboFilter);
}
- if (doVkTonemap && isVaInVaOut && !hasSubs)
+ if (doVkTonemap && !hasSubs)
{
- // OUTPUT vaapi(nv12/bgra) surface(vram)
- // reverse-mapping via vaapi-vulkan interop.
- mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
+ // OUTPUT vaapi(nv12) surface(vram)
+ // map from vulkan/drm to vaapi via interop (Vega/gfx9+).
+ mainFilters.Add("hwmap=derive_device=drm");
+ mainFilters.Add("format=drm_prime");
+ mainFilters.Add("hwmap=derive_device=vaapi");
mainFilters.Add("format=vaapi");
- }
- var memoryOutput = false;
- var isUploadForVkTonemap = isSwDecoder && doVkTonemap;
- if ((isVaapiDecoder && isSwEncoder) || isUploadForVkTonemap)
- {
- memoryOutput = true;
-
- // OUTPUT nv12 surface(memory)
- mainFilters.Add("hwdownload");
- mainFilters.Add("format=nv12");
- }
+ // clear the surf->meta_offset and output nv12
+ mainFilters.Add("scale_vaapi=format=nv12");
- // OUTPUT nv12 surface(memory)
- if (isSwDecoder && isVaapiEncoder)
- {
- memoryOutput = true;
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ mainFilters.Add(deintFilter);
+ }
}
- if (memoryOutput)
+ if (!hasSubs)
{
- // text subtitles
- if (hasTextSubs)
+ // OUTPUT nv12 surface(memory)
+ if (isSwEncoder && (doVkTonemap || isVaapiDecoder))
{
- var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
- mainFilters.Add(textSubtitlesFilter);
+ mainFilters.Add("hwdownload");
+ mainFilters.Add("format=nv12");
}
- }
- if (memoryOutput && isVaapiEncoder)
- {
- if (!hasGraphicalSubs)
+ if (isSwDecoder && isVaapiEncoder && !doVkTonemap)
{
mainFilters.Add("hwupload_vaapi");
}
@@ -4206,52 +4430,53 @@ namespace MediaBrowser.Controller.MediaEncoding
/* Make sub and overlay filters for subtitle stream */
var subFilters = new List<string>();
var overlayFilters = new List<string>();
- if (isVaInVaOut)
+ if (hasSubs)
{
- if (hasSubs)
+ if (hasGraphicalSubs)
{
- if (hasGraphicalSubs)
- {
- // scale=s=1280x720,format=bgra,hwupload
- var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
- subFilters.Add("format=bgra");
- }
- else if (hasTextSubs)
- {
- var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
- var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
- subFilters.Add(alphaSrcFilter);
- subFilters.Add("format=bgra");
- subFilters.Add(subTextSubtitlesFilter);
- }
-
- subFilters.Add("hwupload=derive_device=vulkan:extra_hw_frames=16");
+ // scale=s=1280x720,format=bgra,hwupload
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ subFilters.Add("format=bgra");
+ }
+ else if (hasTextSubs)
+ {
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, reqMaxH, hasAssSubs ? 10 : 5);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
+ }
- overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
+ subFilters.Add("hwupload=derive_device=vulkan");
+ subFilters.Add("format=vulkan");
- // explicitly sync using libplacebo.
- overlayFilters.Add("libplacebo=format=nv12:upscaler=none:downscaler=none");
+ overlayFilters.Add("overlay_vulkan=eof_action=endall:shortest=1:repeatlast=0");
- // OUTPUT vaapi(nv12/bgra) surface(vram)
- // reverse-mapping via vaapi-vulkan interop.
- overlayFilters.Add("hwmap=derive_device=vaapi:reverse=1");
- overlayFilters.Add("format=vaapi");
+ if (isSwEncoder)
+ {
+ // OUTPUT nv12 surface(memory)
+ overlayFilters.Add("scale_vulkan=format=nv12");
+ overlayFilters.Add("hwdownload");
+ overlayFilters.Add("format=nv12");
}
- }
- else if (memoryOutput)
- {
- if (hasGraphicalSubs)
+ else if (isVaapiEncoder)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
- subFilters.Add(subSwScaleFilter);
- overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+ // OUTPUT vaapi(nv12) surface(vram)
+ // map from vulkan/drm to vaapi via interop (Vega/gfx9+).
+ overlayFilters.Add("hwmap=derive_device=drm");
+ overlayFilters.Add("format=drm_prime");
+ overlayFilters.Add("hwmap=derive_device=vaapi");
+ overlayFilters.Add("format=vaapi");
- if (isVaapiEncoder)
+ // clear the surf->meta_offset and output nv12
+ overlayFilters.Add("scale_vaapi=format=nv12");
+
+ // hw deint
+ if (doDeintH2645)
{
- overlayFilters.Add("hwupload_vaapi");
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ overlayFilters.Add(deintFilter);
}
}
}
@@ -4286,7 +4511,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var doDeintH2645 = doDeintH264 || doDeintHevc;
var doOclTonemap = IsHwTonemapAvailable(state, options);
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -4332,6 +4557,13 @@ namespace MediaBrowser.Controller.MediaEncoding
outFormat = doOclTonemap ? string.Empty : "nv12";
var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ // allocate extra pool sizes for vaapi vpp
+ if (!string.IsNullOrEmpty(hwScaleFilter))
+ {
+ hwScaleFilter += ":extra_hw_frames=24";
+ }
+
// hw scale
mainFilters.Add(hwScaleFilter);
}
@@ -4420,9 +4652,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (hasGraphicalSubs)
{
- var subSwScaleFilter = isSwDecoder
- ? GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH)
- : GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
subFilters.Add(subSwScaleFilter);
overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
@@ -4437,6 +4667,75 @@ namespace MediaBrowser.Controller.MediaEncoding
}
/// <summary>
+ /// Gets the parameter of Apple VideoToolBox filter chain.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="vidEncoder">Video encoder to use.</param>
+ /// <returns>The tuple contains three lists: main, sub and overlay filters.</returns>
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAppleVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
+
+ if (!options.EnableHardwareEncoding)
+ {
+ return swFilterChain;
+ }
+
+ if (_mediaEncoder.EncoderVersion.CompareTo(new Version("5.0.0")) < 0)
+ {
+ // All features used here requires ffmpeg 5.0 or later, fallback to software filters if using an old ffmpeg
+ return swFilterChain;
+ }
+
+ var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
+ var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+ var inW = state.VideoStream?.Width;
+ var inH = state.VideoStream?.Height;
+ var reqW = state.BaseRequest.Width;
+ var reqH = state.BaseRequest.Height;
+ var reqMaxW = state.BaseRequest.MaxWidth;
+ var reqMaxH = state.BaseRequest.MaxHeight;
+ var threeDFormat = state.MediaSource.Video3DFormat;
+ var newfilters = new List<string>();
+ var noOverlay = swFilterChain.OverlayFilters.Count == 0;
+ var supportsHwDeint = _mediaEncoder.SupportsFilter("yadif_videotoolbox");
+ // fallback to software filters if we are using filters not supported by hardware yet.
+ var useHardwareFilters = noOverlay && (!doDeintH2645 || supportsHwDeint);
+
+ if (!useHardwareFilters)
+ {
+ return swFilterChain;
+ }
+
+ // ffmpeg cannot use videotoolbox to scale
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ newfilters.Add(swScaleFilter);
+
+ // hwupload on videotoolbox encoders can automatically convert AVFrame into its CVPixelBuffer equivalent
+ // videotoolbox will automatically convert the CVPixelBuffer to a pixel format the encoder supports, so we don't have to set a pixel format explicitly here
+ // This will reduce CPU usage significantly on UHD videos with 10 bit colors because we bypassed the ffmpeg pixel format conversion
+ newfilters.Add("hwupload");
+
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "videotoolbox");
+ newfilters.Add(deintFilter);
+ }
+
+ return (newfilters, swFilterChain.SubFilters, swFilterChain.OverlayFilters);
+ }
+
+ /// <summary>
/// Gets the parameter of video processing filters.
/// </summary>
/// <param name="state">Encoding state.</param>
@@ -4449,12 +4748,12 @@ namespace MediaBrowser.Controller.MediaEncoding
string outputVideoCodec)
{
var videoStream = state.VideoStream;
- if (videoStream == null)
+ if (videoStream is null)
{
return string.Empty;
}
- var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasSubs = state.SubtitleStream is not null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
@@ -4478,6 +4777,10 @@ namespace MediaBrowser.Controller.MediaEncoding
{
(mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec);
}
+ else if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetAppleVidFilterChain(state, options, outputVideoCodec);
+ }
else
{
(mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
@@ -4504,7 +4807,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (overlayFilters?.Count > 0
&& subFilters?.Count > 0
- && state.SubtitleStream != null)
+ && state.SubtitleStream is not null)
{
// overlay graphical/text subtitles
var subStr = string.Format(
@@ -4591,32 +4894,33 @@ namespace MediaBrowser.Controller.MediaEncoding
public static int GetVideoColorBitDepth(EncodingJobInfo state)
{
var videoStream = state.VideoStream;
- if (videoStream != null)
+ if (videoStream is not null)
{
if (videoStream.BitDepth.HasValue)
{
return videoStream.BitDepth.Value;
}
- else if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(videoStream.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuvj420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
{
return 8;
}
- else if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(videoStream.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
{
return 10;
}
- else if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
+
+ if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
{
return 12;
}
- else
- {
- return 8;
- }
+
+ return 8;
}
return 0;
@@ -4632,13 +4936,13 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var videoStream = state.VideoStream;
var mediaSource = state.MediaSource;
- if (videoStream == null || mediaSource == null)
+ if (videoStream is null || mediaSource is null)
{
return null;
}
// HWA decoders can handle both video files and video folders.
- var videoType = mediaSource.VideoType;
+ var videoType = state.VideoType;
if (videoType != VideoType.VideoFile
&& videoType != VideoType.Iso
&& videoType != VideoType.Dvd
@@ -4779,8 +5083,18 @@ namespace MediaBrowser.Controller.MediaEncoding
var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparison.OrdinalIgnoreCase);
+ var ffmpegVersion = _mediaEncoder.EncoderVersion;
+
// Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
- var isAv1 = string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
+ var isAv1 = ffmpegVersion < _minFFmpegImplictHwaccel
+ && string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
+
+ // Allow profile mismatch if decoding H.264 baseline with d3d11va and vaapi hwaccels.
+ var profileMismatch = string.Equals(videoCodec, "h264", StringComparison.OrdinalIgnoreCase)
+ && string.Equals(state.VideoStream?.Profile, "baseline", StringComparison.OrdinalIgnoreCase);
+
+ // Disable the extra internal copy in nvdec. We already handle it in filter chain.
+ var nvdecNoInternalCopy = ffmpegVersion >= _minFFmpegHwaUnsafeOutput;
if (bitDepth == 10 && isCodecAvailable)
{
@@ -4806,14 +5120,16 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isVaapiSupported && isCodecAvailable)
{
- return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
if (isD3d11Supported && isCodecAvailable)
{
// set -threads 3 to intel d3d11va decoder explicitly. Lower threads may result in dead lock.
// on newer devices such as Xe, the larger the init_pool_size, the longer the initialization time for opencl to derive from d3d11.
- return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + " -threads 3" + (isAv1 ? " -c:v av1" : string.Empty);
}
}
else
@@ -4833,13 +5149,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (options.EnableEnhancedNvdecDecoder)
{
// set -threads 1 to nvdec decoder explicitly since it doesn't implement threading support.
- return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty);
- }
- else
- {
- // cuvid decoder doesn't have threading issue.
- return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty);
+ return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty)
+ + (nvdecNoInternalCopy ? " -hwaccel_flags +unsafe_output" : string.Empty) + " -threads 1" + (isAv1 ? " -c:v av1" : string.Empty);
}
+
+ // cuvid decoder doesn't have threading issue.
+ return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty);
}
}
@@ -4848,7 +5163,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (isD3d11Supported && isCodecAvailable)
{
- return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
}
@@ -4857,9 +5173,11 @@ namespace MediaBrowser.Controller.MediaEncoding
&& isVaapiSupported
&& isCodecAvailable)
{
- return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty)
+ + (profileMismatch ? " -hwaccel_flags +allow_profile_mismatch" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
+ // Apple videotoolbox
if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)
&& isVideotoolboxSupported
&& isCodecAvailable)
@@ -5194,7 +5512,8 @@ namespace MediaBrowser.Controller.MediaEncoding
// Automatically set thread count
return mustSetThreadCount ? Math.Max(Environment.ProcessorCount - 1, 1) : 0;
}
- else if (threads >= Environment.ProcessorCount)
+
+ if (threads >= Environment.ProcessorCount)
{
return Environment.ProcessorCount;
}
@@ -5205,7 +5524,7 @@ namespace MediaBrowser.Controller.MediaEncoding
#nullable disable
public void TryStreamCopy(EncodingJobInfo state)
{
- if (state.VideoStream != null && CanStreamCopyVideo(state, state.VideoStream))
+ if (state.VideoStream is not null && CanStreamCopyVideo(state, state.VideoStream))
{
state.OutputVideoCodec = "copy";
}
@@ -5214,13 +5533,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var user = state.User;
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
- if (user != null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
+ if (user is not null && !user.HasPermission(PermissionKind.EnableVideoPlaybackTranscoding))
{
state.OutputVideoCodec = "copy";
}
}
- if (state.AudioStream != null
+ if (state.AudioStream is not null
&& CanStreamCopyAudio(state, state.AudioStream, state.SupportedAudioCodecs))
{
state.OutputAudioCodec = "copy";
@@ -5230,7 +5549,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var user = state.User;
// If the user doesn't have access to transcoding, then force stream copy, regardless of whether it will be compatible or not
- if (user != null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
+ if (user is not null && !user.HasPermission(PermissionKind.EnableAudioPlaybackTranscoding))
{
state.OutputAudioCodec = "copy";
}
@@ -5427,7 +5746,7 @@ namespace MediaBrowser.Controller.MediaEncoding
state.SubtitleDeliveryMethod = videoRequest.SubtitleMethod;
state.AudioStream = GetMediaStream(mediaStreams, videoRequest.AudioStreamIndex, MediaStreamType.Audio);
- if (state.SubtitleStream != null && !state.SubtitleStream.IsExternal)
+ if (state.SubtitleStream is not null && !state.SubtitleStream.IsExternal)
{
state.InternalSubtitleStreamOffset = mediaStreams.Where(i => i.Type == MediaStreamType.Subtitle && !i.IsExternal).ToList().IndexOf(state.SubtitleStream);
}
@@ -5445,7 +5764,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var request = state.BaseRequest;
var supportedAudioCodecs = state.SupportedAudioCodecs;
- if (request != null && supportedAudioCodecs != null && supportedAudioCodecs.Length > 0)
+ if (request is not null && supportedAudioCodecs is not null && supportedAudioCodecs.Length > 0)
{
var supportedAudioCodecsList = supportedAudioCodecs.ToList();
@@ -5458,7 +5777,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var supportedVideoCodecs = state.SupportedVideoCodecs;
- if (request != null && supportedVideoCodecs != null && supportedVideoCodecs.Length > 0)
+ if (request is not null && supportedVideoCodecs is not null && supportedVideoCodecs.Length > 0)
{
var supportedVideoCodecsList = supportedVideoCodecs.ToList();
@@ -5478,15 +5797,23 @@ namespace MediaBrowser.Controller.MediaEncoding
return;
}
- var inputChannels = audioStream == null ? 6 : audioStream.Channels ?? 6;
+ var inputChannels = audioStream is null ? 6 : audioStream.Channels ?? 6;
+ var shiftAudioCodecs = new List<string>();
if (inputChannels >= 6)
{
- return;
+ // DTS and TrueHD are not supported by HLS
+ // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
+ shiftAudioCodecs.Add("dca");
+ shiftAudioCodecs.Add("truehd");
+ }
+ else
+ {
+ // Transcoding to 2ch ac3 or eac3 almost always causes a playback failure
+ // Keep them in the supported codecs list, but shift them to the end of the list so that if transcoding happens, another codec is used
+ shiftAudioCodecs.Add("ac3");
+ shiftAudioCodecs.Add("eac3");
}
- // Transcoding to 2ch ac3 almost always causes a playback failure
- // Keep it in the supported codecs list, but shift it to the end of the list so that if transcoding happens, another codec is used
- var shiftAudioCodecs = new[] { "ac3", "eac3" };
if (audioCodecs.All(i => shiftAudioCodecs.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return;
@@ -5530,7 +5857,7 @@ namespace MediaBrowser.Controller.MediaEncoding
private void NormalizeSubtitleEmbed(EncodingJobInfo state)
{
- if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
+ if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
{
return;
}
@@ -5545,7 +5872,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetSubtitleEmbedArguments(EncodingJobInfo state)
{
- if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
+ if (state.SubtitleStream is null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
{
return string.Empty;
}
@@ -5577,7 +5904,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.BaseRequest.Context == EncodingContext.Streaming)
{
// Comparison: https://github.com/jansmolders86/mediacenterjs/blob/master/lib/transcoding/desktop.js
- format = " -f mp4 -movflags frag_keyframe+empty_moov";
+ format = " -f mp4 -movflags frag_keyframe+empty_moov+delay_moov";
}
var threads = GetNumberOfThreads(state, encodingOptions, videoCodec);
@@ -5626,7 +5953,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (IsCopyCodec(videoCodec))
{
- if (state.VideoStream != null
+ if (state.VideoStream is not null
&& string.Equals(state.OutputContainer, "ts", StringComparison.OrdinalIgnoreCase)
&& !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
@@ -5656,14 +5983,16 @@ namespace MediaBrowser.Controller.MediaEncoding
args += keyFrameArg;
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasGraphicalSubs = state.SubtitleStream is not null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
var hasCopyTs = false;
// video processing filters.
var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
- args += videoProcessParam;
+ var negativeMapArgs = GetNegativeMapArgsByFilters(state, videoProcessParam);
+
+ args = negativeMapArgs + args + videoProcessParam;
hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
@@ -5676,7 +6005,7 @@ namespace MediaBrowser.Controller.MediaEncoding
args += " -avoid_negative_ts disabled";
- if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
+ if (!(state.SubtitleStream is not null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
{
args += " -start_at_zero";
}
@@ -5703,7 +6032,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string GetProgressiveVideoAudioArguments(EncodingJobInfo state, EncodingOptions encodingOptions)
{
// If the video doesn't have an audio stream, return a default.
- if (state.AudioStream == null && state.VideoStream != null)
+ if (state.AudioStream is null && state.VideoStream is not null)
{
return string.Empty;
}
@@ -5718,19 +6047,25 @@ namespace MediaBrowser.Controller.MediaEncoding
return args;
}
- // Add the number of audio channels
var channels = state.OutputAudioChannels;
- if (channels.HasValue)
+ if (channels.HasValue && ((channels.Value != 2 && state.AudioStream.Channels <= 5) || encodingOptions.DownMixStereoAlgorithm == DownMixStereoAlgorithms.None))
{
args += " -ac " + channels.Value;
}
var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
+ if (bitrate.HasValue && !LosslessAudioCodecs.Contains(codec, StringComparison.OrdinalIgnoreCase))
{
- args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
+ var vbrParam = GetAudioVbrModeParam(codec, bitrate.Value / (channels ?? 2));
+ if (encodingOptions.EnableAudioVbr && vbrParam is not null)
+ {
+ args += vbrParam;
+ }
+ else
+ {
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
+ }
}
if (state.OutputAudioSampleRate.HasValue)
@@ -5748,18 +6083,33 @@ namespace MediaBrowser.Controller.MediaEncoding
var audioTranscodeParams = new List<string>();
var bitrate = state.OutputAudioBitrate;
+ var channels = state.OutputAudioChannels;
+ var outputCodec = state.OutputAudioCodec;
- if (bitrate.HasValue)
+ if (bitrate.HasValue && !LosslessAudioCodecs.Contains(outputCodec, StringComparison.OrdinalIgnoreCase))
{
- audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
+ var vbrParam = GetAudioVbrModeParam(GetAudioEncoder(state), bitrate.Value / (channels ?? 2));
+ if (encodingOptions.EnableAudioVbr && vbrParam is not null)
+ {
+ audioTranscodeParams.Add(vbrParam);
+ }
+ else
+ {
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
+ }
}
- if (state.OutputAudioChannels.HasValue)
+ if (channels.HasValue)
{
audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
- if (!string.Equals(state.OutputAudioCodec, "opus", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(outputCodec))
+ {
+ audioTranscodeParams.Add("-acodec " + GetAudioEncoder(state));
+ }
+
+ if (!string.Equals(outputCodec, "opus", StringComparison.OrdinalIgnoreCase))
{
// opus only supports specific sampling rates
var sampleRate = state.OutputAudioSampleRate;
@@ -5778,6 +6128,13 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
+ // Copy the movflags from GetProgressiveVideoFullCommandLine
+ // See #9248 and the associated PR for why this is needed
+ if (_mp4ContainerNames.Contains(state.OutputContainer))
+ {
+ audioTranscodeParams.Add("-movflags empty_moov+delay_moov");
+ }
+
var threads = GetNumberOfThreads(state, encodingOptions, null);
var inputModifier = GetInputModifier(state, encodingOptions, null);
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index c9625cf1da..a6b5416601 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -40,7 +40,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (!_transcodeReasons.HasValue)
{
- if (BaseRequest.TranscodeReasons == null)
+ if (BaseRequest.TranscodeReasons is null)
{
_transcodeReasons = 0;
return 0;
@@ -147,7 +147,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
+ if (VideoStream is not null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
@@ -174,7 +174,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (VideoStream != null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
+ if (VideoStream is not null && VideoStream.Width.HasValue && VideoStream.Height.HasValue)
{
var size = new ImageDimensions(VideoStream.Width.Value, VideoStream.Height.Value);
@@ -204,7 +204,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (BaseRequest.Static
|| EncodingHelper.IsCopyCodec(OutputAudioCodec))
{
- if (AudioStream != null)
+ if (AudioStream is not null)
{
return AudioStream.SampleRate;
}
@@ -227,7 +227,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (BaseRequest.Static
|| EncodingHelper.IsCopyCodec(OutputAudioCodec))
{
- if (AudioStream != null)
+ if (AudioStream is not null)
{
return AudioStream.BitDepth;
}
@@ -250,8 +250,7 @@ namespace MediaBrowser.Controller.MediaEncoding
}
var level = GetRequestedLevel(ActualOutputVideoCodec);
- if (!string.IsNullOrEmpty(level)
- && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ if (double.TryParse(level, CultureInfo.InvariantCulture, out var result))
{
return result;
}
@@ -305,7 +304,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (BaseRequest.Static
|| EncodingHelper.IsCopyCodec(OutputVideoCodec))
{
- return VideoStream == null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
+ return VideoStream is null ? null : (VideoStream.AverageFrameRate ?? VideoStream.RealFrameRate);
}
return BaseRequest.MaxFramerate ?? BaseRequest.Framerate;
@@ -419,7 +418,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (VideoStream == null)
+ if (VideoStream is null)
{
return null;
}
@@ -437,7 +436,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
get
{
- if (AudioStream == null)
+ if (AudioStream is null)
{
return null;
}
@@ -556,7 +555,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public bool DeInterlace(string videoCodec, bool forceDeinterlaceIfSourceIsInterlaced)
{
var videoStream = VideoStream;
- var isInputInterlaced = videoStream != null && videoStream.IsInterlaced;
+ var isInputInterlaced = videoStream is not null && videoStream.IsInterlaced;
if (!isInputInterlaced)
{
@@ -645,8 +644,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "maxrefframes");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
{
return result;
}
@@ -665,8 +663,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "videobitdepth");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
{
return result;
}
@@ -685,8 +682,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiobitdepth");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
{
return result;
}
@@ -700,8 +696,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(codec))
{
var value = BaseRequest.GetOption(codec, "audiochannels");
- if (!string.IsNullOrEmpty(value)
- && int.TryParse(value, NumberStyles.Any, CultureInfo.InvariantCulture, out var result))
+ if (int.TryParse(value, CultureInfo.InvariantCulture, out var result))
{
return result;
}
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 52c57b906e..f830b9f298 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.MediaEncoding
Version EncoderVersion { get; }
/// <summary>
- /// Whether p key pausing is supported.
+ /// Gets a value indicating whether p key pausing is supported.
/// </summary>
/// <value><c>true</c> if p key pausing is supported, <c>false</c> otherwise.</value>
bool IsPkeyPauseSupported { get; }
@@ -154,6 +154,14 @@ namespace MediaBrowser.Controller.MediaEncoding
string GetInputArgument(string inputFile, MediaSourceInfo mediaSource);
/// <summary>
+ /// Gets the input argument.
+ /// </summary>
+ /// <param name="inputFiles">The input files.</param>
+ /// <param name="mediaSource">The mediaSource.</param>
+ /// <returns>System.String.</returns>
+ string GetInputArgument(IReadOnlyList<string> inputFiles, MediaSourceInfo mediaSource);
+
+ /// <summary>
/// Gets the input argument for an external subtitle file.
/// </summary>
/// <param name="inputFile">The input file.</param>
@@ -194,6 +202,20 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="path">The to the .vob files.</param>
/// <param name="titleNumber">The title number to start with.</param>
/// <returns>A playlist.</returns>
- IEnumerable<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber);
+ IReadOnlyList<string> GetPrimaryPlaylistVobFiles(string path, uint? titleNumber);
+
+ /// <summary>
+ /// Gets the primary playlist of .m2ts files.
+ /// </summary>
+ /// <param name="path">The to the .m2ts files.</param>
+ /// <returns>A playlist.</returns>
+ IReadOnlyList<string> GetPrimaryPlaylistM2tsFiles(string path);
+
+ /// <summary>
+ /// Generates a FFmpeg concat config for the source.
+ /// </summary>
+ /// <param name="source">The <see cref="MediaSourceInfo"/>.</param>
+ /// <param name="concatFilePath">The path the config should be written to.</param>
+ void GenerateConcatConfig(MediaSourceInfo source, string concatFilePath);
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index d8475f12ae..3b34af4e96 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -86,7 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var rate = parts[i + 1];
- if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
+ if (float.TryParse(rate, CultureInfo.InvariantCulture, out var val))
{
framerate = val;
}
@@ -95,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var rate = part.Split('=', 2)[^1];
- if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
+ if (float.TryParse(rate, CultureInfo.InvariantCulture, out var val))
{
framerate = val;
}
@@ -127,7 +127,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (scale.HasValue)
{
- if (long.TryParse(size, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
+ if (long.TryParse(size, CultureInfo.InvariantCulture, out var val))
{
bytesTranscoded = val * scale.Value;
}
@@ -146,7 +146,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (scale.HasValue)
{
- if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
+ if (float.TryParse(rate, CultureInfo.InvariantCulture, out var val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 647de50030..0524999c79 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -166,7 +166,7 @@ namespace MediaBrowser.Controller.Net
var data = await GetDataToSend().ConfigureAwait(false);
- if (data != null)
+ if (data is not null)
{
await connection.SendAsync(
new WebSocketMessage<TReturnDataType>
@@ -204,7 +204,7 @@ namespace MediaBrowser.Controller.Net
{
var connection = _activeConnections.FirstOrDefault(c => c.Item1 == message.Connection);
- if (connection != null)
+ if (connection is not null)
{
DisposeConnection(connection);
}
@@ -227,9 +227,15 @@ namespace MediaBrowser.Controller.Net
connection.Item2.Cancel();
connection.Item2.Dispose();
}
- catch (ObjectDisposedException)
+ catch (ObjectDisposedException ex)
{
// TODO Investigate and properly fix.
+ Logger.LogError(ex, "Object Disposed");
+ }
+ catch (Exception ex)
+ {
+ // TODO Investigate and properly fix.
+ Logger.LogError(ex, "Error disposing websocket");
}
lock (_activeConnections)
diff --git a/MediaBrowser.Controller/Net/WebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessage.cs
new file mode 100644
index 0000000000..c02bcd70b6
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessage.cs
@@ -0,0 +1,28 @@
+using System;
+using System.Text.Json.Serialization;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net;
+
+/// <summary>
+/// Websocket message without data.
+/// </summary>
+public abstract class WebSocketMessage
+{
+ /// <summary>
+ /// Gets or sets the type of the message.
+ /// TODO make this abstract and get only.
+ /// </summary>
+ public virtual SessionMessageType MessageType { get; set; }
+
+ /// <summary>
+ /// Gets or sets the message id.
+ /// </summary>
+ public Guid MessageId { get; set; }
+
+ /// <summary>
+ /// Gets or sets the server id.
+ /// </summary>
+ [JsonIgnore]
+ public string? ServerId { get; set; }
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs b/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs
new file mode 100644
index 0000000000..7c35c8010d
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessageOfT.cs
@@ -0,0 +1,33 @@
+#pragma warning disable SA1649 // File name must equal class name.
+
+namespace MediaBrowser.Controller.Net;
+
+/// <summary>
+/// Class WebSocketMessage.
+/// </summary>
+/// <typeparam name="T">The type of the data.</typeparam>
+// TODO make this abstract, remove empty ctor.
+public class WebSocketMessage<T> : WebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WebSocketMessage{T}"/> class.
+ /// </summary>
+ public WebSocketMessage()
+ {
+ }
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="WebSocketMessage{T}"/> class.
+ /// </summary>
+ /// <param name="data">The data to send.</param>
+ protected WebSocketMessage(T data)
+ {
+ Data = data;
+ }
+
+ /// <summary>
+ /// Gets or sets the data.
+ /// </summary>
+ // TODO make this set only.
+ public T? Data { get; set; }
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/IInboundWebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/IInboundWebSocketMessage.cs
new file mode 100644
index 0000000000..c3cf9955ad
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/IInboundWebSocketMessage.cs
@@ -0,0 +1,10 @@
+#pragma warning disable CA1040
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
+
+/// <summary>
+/// Interface representing that the websocket message is inbound.
+/// </summary>
+public interface IInboundWebSocketMessage
+{
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/IOutboundWebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/IOutboundWebSocketMessage.cs
new file mode 100644
index 0000000000..c74a254a68
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/IOutboundWebSocketMessage.cs
@@ -0,0 +1,10 @@
+#pragma warning disable CA1040
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
+
+/// <summary>
+/// Interface representing that the websocket message is outbound.
+/// </summary>
+public interface IOutboundWebSocketMessage
+{
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs
new file mode 100644
index 0000000000..b9f71b9225
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStartMessage.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Activity log entry start message.
+/// </summary>
+public class ActivityLogEntryStartMessage : WebSocketMessage<IReadOnlyCollection<ActivityLogEntry>>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActivityLogEntryStartMessage"/> class.
+ /// </summary>
+ /// <param name="data">Collection of activity log entries.</param>
+ public ActivityLogEntryStartMessage(IReadOnlyCollection<ActivityLogEntry> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ActivityLogEntryStart)]
+ public override SessionMessageType MessageType => SessionMessageType.ActivityLogEntryStart;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs
new file mode 100644
index 0000000000..eac129b20a
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ActivityLogEntryStopMessage.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Activity log entry stop message.
+/// </summary>
+public class ActivityLogEntryStopMessage : WebSocketMessage<IReadOnlyCollection<ActivityLogEntry>>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActivityLogEntryStopMessage"/> class.
+ /// </summary>
+ /// <param name="data">Collection of activity log entries.</param>
+ public ActivityLogEntryStopMessage(IReadOnlyCollection<ActivityLogEntry> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ActivityLogEntryStop)]
+ public override SessionMessageType MessageType => SessionMessageType.ActivityLogEntryStop;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs
new file mode 100644
index 0000000000..dd2a7145e3
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStartMessage.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Tasks;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Scheduled tasks info start message.
+/// </summary>
+public class ScheduledTasksInfoStartMessage : WebSocketMessage<IReadOnlyCollection<TaskInfo>>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScheduledTasksInfoStartMessage"/> class.
+ /// </summary>
+ /// <param name="data">Collection of task info.</param>
+ public ScheduledTasksInfoStartMessage(IReadOnlyCollection<TaskInfo> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ScheduledTasksInfoStart)]
+ public override SessionMessageType MessageType => SessionMessageType.ScheduledTasksInfoStart;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs
new file mode 100644
index 0000000000..84e1f01667
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/ScheduledTasksInfoStopMessage.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Tasks;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Scheduled tasks info stop message.
+/// </summary>
+public class ScheduledTasksInfoStopMessage : WebSocketMessage<IReadOnlyCollection<TaskInfo>>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScheduledTasksInfoStopMessage"/> class.
+ /// </summary>
+ /// <param name="data">Collection of task info.</param>
+ public ScheduledTasksInfoStopMessage(IReadOnlyCollection<TaskInfo> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ScheduledTasksInfoStop)]
+ public override SessionMessageType MessageType => SessionMessageType.ScheduledTasksInfoStop;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs
new file mode 100644
index 0000000000..e35a5dc3ad
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStartMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Sessions start message.
+/// </summary>
+public class SessionsStartMessage : WebSocketMessage<SessionInfo>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SessionsStartMessage"/> class.
+ /// </summary>
+ /// <param name="data">Session info.</param>
+ public SessionsStartMessage(SessionInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SessionsStart)]
+ public override SessionMessageType MessageType => SessionMessageType.SessionsStart;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs
new file mode 100644
index 0000000000..7e3582d640
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Inbound/SessionsStopMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Inbound;
+
+/// <summary>
+/// Sessions stop message.
+/// </summary>
+public class SessionsStopMessage : WebSocketMessage<SessionInfo>, IInboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SessionsStopMessage"/> class.
+ /// </summary>
+ /// <param name="data">Session info.</param>
+ public SessionsStopMessage(SessionInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SessionsStop)]
+ public override SessionMessageType MessageType => SessionMessageType.SessionsStop;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs
new file mode 100644
index 0000000000..20ca888e11
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/InboundWebSocketMessage.cs
@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
+
+/// <summary>
+/// Class representing the list of outbound websocket message types.
+/// Only used in openapi generation.
+/// </summary>
+public class InboundWebSocketMessage : WebSocketMessage
+{
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs
new file mode 100644
index 0000000000..5650ee4bbe
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ActivityLogEntryMessage.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Activity;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Activity log created message.
+/// </summary>
+public class ActivityLogEntryMessage : WebSocketMessage<IReadOnlyList<ActivityLogEntry>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ActivityLogEntryMessage"/> class.
+ /// </summary>
+ /// <param name="data">List of activity log entries.</param>
+ public ActivityLogEntryMessage(IReadOnlyList<ActivityLogEntry> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ActivityLogEntry)]
+ public override SessionMessageType MessageType => SessionMessageType.ActivityLogEntry;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs
new file mode 100644
index 0000000000..94ade5e817
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ForceKeepAliveMessage.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Force keep alive websocket messages.
+/// </summary>
+public class ForceKeepAliveMessage : WebSocketMessage<int>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ForceKeepAliveMessage"/> class.
+ /// </summary>
+ /// <param name="data">The timeout in seconds.</param>
+ public ForceKeepAliveMessage(int data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ForceKeepAlive)]
+ public override SessionMessageType MessageType => SessionMessageType.ForceKeepAlive;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs
new file mode 100644
index 0000000000..6c71e73f9d
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/GeneralCommandMessage.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// General command websocket message.
+/// </summary>
+public class GeneralCommandMessage : WebSocketMessage<GeneralCommand>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GeneralCommandMessage"/> class.
+ /// </summary>
+ /// <param name="data">The general command.</param>
+ public GeneralCommandMessage(GeneralCommand data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.GeneralCommand)]
+ public override SessionMessageType MessageType => SessionMessageType.GeneralCommand;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs
new file mode 100644
index 0000000000..6432ae8efb
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/LibraryChangedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Entities;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Library changed message.
+/// </summary>
+public class LibraryChangedMessage : WebSocketMessage<LibraryUpdateInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="LibraryChangedMessage"/> class.
+ /// </summary>
+ /// <param name="data">The library update info.</param>
+ public LibraryChangedMessage(LibraryUpdateInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.LibraryChanged)]
+ public override SessionMessageType MessageType => SessionMessageType.LibraryChanged;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs
new file mode 100644
index 0000000000..7f943bda10
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlayMessage.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Play command websocket message.
+/// </summary>
+public class PlayMessage : WebSocketMessage<PlayRequest>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlayMessage"/> class.
+ /// </summary>
+ /// <param name="data">The play request.</param>
+ public PlayMessage(PlayRequest data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.Play)]
+ public override SessionMessageType MessageType => SessionMessageType.Play;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs
new file mode 100644
index 0000000000..804ccb37d6
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PlaystateMessage.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Playstate message.
+/// </summary>
+public class PlaystateMessage : WebSocketMessage<PlaystateRequest>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PlaystateMessage"/> class.
+ /// </summary>
+ /// <param name="data">Playstate request data.</param>
+ public PlaystateMessage(PlaystateRequest data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.Playstate)]
+ public override SessionMessageType MessageType => SessionMessageType.Playstate;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs
new file mode 100644
index 0000000000..3d7dc5c937
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCancelledMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Plugin installation cancelled message.
+/// </summary>
+public class PluginInstallationCancelledMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PluginInstallationCancelledMessage"/> class.
+ /// </summary>
+ /// <param name="data">Installation info.</param>
+ public PluginInstallationCancelledMessage(InstallationInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.PackageInstallationCancelled)]
+ public override SessionMessageType MessageType => SessionMessageType.PackageInstallationCancelled;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs
new file mode 100644
index 0000000000..81268007fd
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationCompletedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Plugin installation completed message.
+/// </summary>
+public class PluginInstallationCompletedMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PluginInstallationCompletedMessage"/> class.
+ /// </summary>
+ /// <param name="data">Installation info.</param>
+ public PluginInstallationCompletedMessage(InstallationInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.PackageInstallationCompleted)]
+ public override SessionMessageType MessageType => SessionMessageType.PackageInstallationCompleted;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs
new file mode 100644
index 0000000000..9177f12938
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallationFailedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Plugin installation failed message.
+/// </summary>
+public class PluginInstallationFailedMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PluginInstallationFailedMessage"/> class.
+ /// </summary>
+ /// <param name="data">Installation info.</param>
+ public PluginInstallationFailedMessage(InstallationInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.PackageInstallationFailed)]
+ public override SessionMessageType MessageType => SessionMessageType.PackageInstallationFailed;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs
new file mode 100644
index 0000000000..e371440a0f
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginInstallingMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Updates;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Package installing message.
+/// </summary>
+public class PluginInstallingMessage : WebSocketMessage<InstallationInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PluginInstallingMessage"/> class.
+ /// </summary>
+ /// <param name="data">Installation info.</param>
+ public PluginInstallingMessage(InstallationInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.PackageInstalling)]
+ public override SessionMessageType MessageType => SessionMessageType.PackageInstalling;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs
new file mode 100644
index 0000000000..b2994fc956
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/PluginUninstalledMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Plugins;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Plugin uninstalled message.
+/// </summary>
+public class PluginUninstalledMessage : WebSocketMessage<PluginInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="PluginUninstalledMessage"/> class.
+ /// </summary>
+ /// <param name="data">Plugin info.</param>
+ public PluginUninstalledMessage(PluginInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.PackageUninstalled)]
+ public override SessionMessageType MessageType => SessionMessageType.PackageUninstalled;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs
new file mode 100644
index 0000000000..42dbc30295
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RefreshProgressMessage.cs
@@ -0,0 +1,24 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Refresh progress message.
+/// </summary>
+public class RefreshProgressMessage : WebSocketMessage<Dictionary<string, string>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="RefreshProgressMessage"/> class.
+ /// </summary>
+ /// <param name="data">Refresh progress data.</param>
+ public RefreshProgressMessage(Dictionary<string, string> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.RefreshProgress)]
+ public override SessionMessageType MessageType => SessionMessageType.RefreshProgress;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs
new file mode 100644
index 0000000000..3f3d9e4c84
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/RestartRequiredMessage.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Restart required.
+/// </summary>
+public class RestartRequiredMessage : WebSocketMessage, IOutboundWebSocketMessage
+{
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.RestartRequired)]
+ public override SessionMessageType MessageType => SessionMessageType.RestartRequired;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs
new file mode 100644
index 0000000000..d69662b004
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTaskEndedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Tasks;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Scheduled task ended message.
+/// </summary>
+public class ScheduledTaskEndedMessage : WebSocketMessage<TaskResult>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScheduledTaskEndedMessage"/> class.
+ /// </summary>
+ /// <param name="data">Task result.</param>
+ public ScheduledTaskEndedMessage(TaskResult data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ScheduledTaskEnded)]
+ public override SessionMessageType MessageType => SessionMessageType.ScheduledTaskEnded;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs
new file mode 100644
index 0000000000..41a05b0de2
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ScheduledTasksInfoMessage.cs
@@ -0,0 +1,25 @@
+using System.Collections.Generic;
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.Tasks;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Scheduled tasks info message.
+/// </summary>
+public class ScheduledTasksInfoMessage : WebSocketMessage<IReadOnlyList<TaskInfo>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ScheduledTasksInfoMessage"/> class.
+ /// </summary>
+ /// <param name="data">List of task infos.</param>
+ public ScheduledTasksInfoMessage(IReadOnlyList<TaskInfo> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ScheduledTasksInfo)]
+ public override SessionMessageType MessageType => SessionMessageType.ScheduledTasksInfo;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs
new file mode 100644
index 0000000000..d4950b8b67
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCancelledMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Series timer cancelled message.
+/// </summary>
+public class SeriesTimerCancelledMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeriesTimerCancelledMessage"/> class.
+ /// </summary>
+ /// <param name="data">The timer event info.</param>
+ public SeriesTimerCancelledMessage(TimerEventInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SeriesTimerCancelled)]
+ public override SessionMessageType MessageType => SessionMessageType.SeriesTimerCancelled;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs
new file mode 100644
index 0000000000..091c10be6d
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SeriesTimerCreatedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Series timer created message.
+/// </summary>
+public class SeriesTimerCreatedMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SeriesTimerCreatedMessage"/> class.
+ /// </summary>
+ /// <param name="data">timer event info.</param>
+ public SeriesTimerCreatedMessage(TimerEventInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SeriesTimerCreated)]
+ public override SessionMessageType MessageType => SessionMessageType.SeriesTimerCreated;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs
new file mode 100644
index 0000000000..a465d8b008
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerRestartingMessage.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Server restarting down message.
+/// </summary>
+public class ServerRestartingMessage : WebSocketMessage, IOutboundWebSocketMessage
+{
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ServerRestarting)]
+ public override SessionMessageType MessageType => SessionMessageType.ServerRestarting;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs
new file mode 100644
index 0000000000..0b998a5239
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/ServerShuttingDownMessage.cs
@@ -0,0 +1,14 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Server shutting down message.
+/// </summary>
+public class ServerShuttingDownMessage : WebSocketMessage, IOutboundWebSocketMessage
+{
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.ServerShuttingDown)]
+ public override SessionMessageType MessageType => SessionMessageType.ServerShuttingDown;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
new file mode 100644
index 0000000000..4c91e0bca2
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SessionsMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.Session;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Sessions message.
+/// </summary>
+public class SessionsMessage : WebSocketMessage<SessionInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SessionsMessage"/> class.
+ /// </summary>
+ /// <param name="data">Session info.</param>
+ public SessionsMessage(SessionInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.Sessions)]
+ public override SessionMessageType MessageType => SessionMessageType.Sessions;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs
new file mode 100644
index 0000000000..17a0fc66e5
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayCommandMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Sync play command.
+/// </summary>
+public class SyncPlayCommandMessage : WebSocketMessage<SendCommand>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayCommandMessage"/> class.
+ /// </summary>
+ /// <param name="data">The send command.</param>
+ public SyncPlayCommandMessage(SendCommand data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SyncPlayCommand)]
+ public override SessionMessageType MessageType => SessionMessageType.SyncPlayCommand;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs
new file mode 100644
index 0000000000..d145d0e01a
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Untyped sync play command.
+/// </summary>
+public class SyncPlayGroupUpdateCommandMessage : WebSocketMessage<GroupUpdate>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandMessage"/> class.
+ /// </summary>
+ /// <param name="data">The send command.</param>
+ public SyncPlayGroupUpdateCommandMessage(GroupUpdate data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
+ public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs
new file mode 100644
index 0000000000..668392c66b
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupInfoMessage.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Sync play group update command with group info.
+/// GroupUpdateTypes: GroupJoined.
+/// </summary>
+public class SyncPlayGroupUpdateCommandOfGroupInfoMessage : WebSocketMessage<GroupUpdate<GroupInfoDto>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfGroupInfoMessage"/> class.
+ /// </summary>
+ /// <param name="data">The group info.</param>
+ public SyncPlayGroupUpdateCommandOfGroupInfoMessage(GroupUpdate<GroupInfoDto> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
+ public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs
new file mode 100644
index 0000000000..ec8c3344f4
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Sync play group update command with group state update.
+/// GroupUpdateTypes: StateUpdate.
+/// </summary>
+public class SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage : WebSocketMessage<GroupUpdate<GroupStateUpdate>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage"/> class.
+ /// </summary>
+ /// <param name="data">The group info.</param>
+ public SyncPlayGroupUpdateCommandOfGroupStateUpdateMessage(GroupUpdate<GroupStateUpdate> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
+ public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs
new file mode 100644
index 0000000000..465363f143
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Sync play group update command with play queue update.
+/// GroupUpdateTypes: PlayQueue.
+/// </summary>
+public class SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage : WebSocketMessage<GroupUpdate<PlayQueueUpdate>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage"/> class.
+ /// </summary>
+ /// <param name="data">The play queue update.</param>
+ public SyncPlayGroupUpdateCommandOfPlayQueueUpdateMessage(GroupUpdate<PlayQueueUpdate> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
+ public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs
new file mode 100644
index 0000000000..b87e9bf715
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/SyncPlayGroupUpdateCommandOfStringMessage.cs
@@ -0,0 +1,25 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+using MediaBrowser.Model.SyncPlay;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Sync play group update command with string.
+/// GroupUpdateTypes: GroupDoesNotExist (error), LibraryAccessDenied (error), NotInGroup (error), GroupLeft (groupId), UserJoined (username), UserLeft (username).
+/// </summary>
+public class SyncPlayGroupUpdateCommandOfStringMessage : WebSocketMessage<GroupUpdate<string>>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="SyncPlayGroupUpdateCommandOfStringMessage"/> class.
+ /// </summary>
+ /// <param name="data">The send command.</param>
+ public SyncPlayGroupUpdateCommandOfStringMessage(GroupUpdate<string> data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.SyncPlayGroupUpdate)]
+ public override SessionMessageType MessageType => SessionMessageType.SyncPlayGroupUpdate;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs
new file mode 100644
index 0000000000..0e70549ef8
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCancelledMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Timer cancelled message.
+/// </summary>
+public class TimerCancelledMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TimerCancelledMessage"/> class.
+ /// </summary>
+ /// <param name="data">Timer event info.</param>
+ public TimerCancelledMessage(TimerEventInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.TimerCancelled)]
+ public override SessionMessageType MessageType => SessionMessageType.TimerCancelled;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs
new file mode 100644
index 0000000000..295b3081ce
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/TimerCreatedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Controller.LiveTv;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// Timer created message.
+/// </summary>
+public class TimerCreatedMessage : WebSocketMessage<TimerEventInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="TimerCreatedMessage"/> class.
+ /// </summary>
+ /// <param name="data">Timer event info.</param>
+ public TimerCreatedMessage(TimerEventInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.TimerCreated)]
+ public override SessionMessageType MessageType => SessionMessageType.TimerCreated;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs
new file mode 100644
index 0000000000..b60769540d
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDataChangedMessage.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// User data changed message.
+/// </summary>
+public class UserDataChangedMessage : WebSocketMessage<UserDataChangeInfo>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserDataChangedMessage"/> class.
+ /// </summary>
+ /// <param name="data">The data change info.</param>
+ public UserDataChangedMessage(UserDataChangeInfo data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.UserDataChanged)]
+ public override SessionMessageType MessageType => SessionMessageType.UserDataChanged;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs
new file mode 100644
index 0000000000..6d527be7f2
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserDeletedMessage.cs
@@ -0,0 +1,24 @@
+using System;
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// User deleted message.
+/// </summary>
+public class UserDeletedMessage : WebSocketMessage<Guid>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserDeletedMessage"/> class.
+ /// </summary>
+ /// <param name="data">The user id.</param>
+ public UserDeletedMessage(Guid data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.UserDeleted)]
+ public override SessionMessageType MessageType => SessionMessageType.UserDeleted;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs
new file mode 100644
index 0000000000..99e9a1f911
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Outbound/UserUpdatedMessage.cs
@@ -0,0 +1,24 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Dto;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Outbound;
+
+/// <summary>
+/// User updated message.
+/// </summary>
+public class UserUpdatedMessage : WebSocketMessage<UserDto>, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="UserUpdatedMessage"/> class.
+ /// </summary>
+ /// <param name="data">The user dto.</param>
+ public UserUpdatedMessage(UserDto data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.UserUpdated)]
+ public override SessionMessageType MessageType => SessionMessageType.UserUpdated;
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs
new file mode 100644
index 0000000000..dba3c8392b
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/OutboundWebSocketMessage.cs
@@ -0,0 +1,9 @@
+namespace MediaBrowser.Controller.Net.WebSocketMessages;
+
+/// <summary>
+/// Class representing the list of outbound websocket message types.
+/// Only used in openapi generation.
+/// </summary>
+public class OutboundWebSocketMessage : WebSocketMessage
+{
+}
diff --git a/MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs b/MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs
new file mode 100644
index 0000000000..7f636212ca
--- /dev/null
+++ b/MediaBrowser.Controller/Net/WebSocketMessages/Shared/KeepAliveMessage.cs
@@ -0,0 +1,23 @@
+using System.ComponentModel;
+using MediaBrowser.Model.Session;
+
+namespace MediaBrowser.Controller.Net.WebSocketMessages.Shared;
+
+/// <summary>
+/// Keep alive websocket messages.
+/// </summary>
+public class KeepAliveMessage : WebSocketMessage<int>, IInboundWebSocketMessage, IOutboundWebSocketMessage
+{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="KeepAliveMessage"/> class.
+ /// </summary>
+ /// <param name="data">The seconds to keep alive for.</param>
+ public KeepAliveMessage(int data)
+ : base(data)
+ {
+ }
+
+ /// <inheritdoc />
+ [DefaultValue(SessionMessageType.KeepAlive)]
+ public override SessionMessageType MessageType => SessionMessageType.KeepAlive;
+}
diff --git a/MediaBrowser.Controller/Notifications/INotificationManager.cs b/MediaBrowser.Controller/Notifications/INotificationManager.cs
deleted file mode 100644
index 7caba1097a..0000000000
--- a/MediaBrowser.Controller/Notifications/INotificationManager.cs
+++ /dev/null
@@ -1,43 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using System.Threading;
-using System.Threading.Tasks;
-using MediaBrowser.Controller.Entities;
-using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Notifications;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public interface INotificationManager
- {
- /// <summary>
- /// Sends the notification.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendNotification(NotificationRequest request, CancellationToken cancellationToken);
-
- Task SendNotification(NotificationRequest request, BaseItem? relatedItem, CancellationToken cancellationToken);
-
- /// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="services">The services.</param>
- /// <param name="notificationTypeFactories">The notification type factories.</param>
- void AddParts(IEnumerable<INotificationService> services, IEnumerable<INotificationTypeFactory> notificationTypeFactories);
-
- /// <summary>
- /// Gets the notification types.
- /// </summary>
- /// <returns>IEnumerable{NotificationTypeInfo}.</returns>
- List<NotificationTypeInfo> GetNotificationTypes();
-
- /// <summary>
- /// Gets the notification services.
- /// </summary>
- /// <returns>IEnumerable{NotificationServiceInfo}.</returns>
- IEnumerable<NameIdPair> GetNotificationServices();
- }
-}
diff --git a/MediaBrowser.Controller/Notifications/INotificationService.cs b/MediaBrowser.Controller/Notifications/INotificationService.cs
deleted file mode 100644
index 535c08795b..0000000000
--- a/MediaBrowser.Controller/Notifications/INotificationService.cs
+++ /dev/null
@@ -1,34 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Data.Entities;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public interface INotificationService
- {
- /// <summary>
- /// Gets the name.
- /// </summary>
- /// <value>The name.</value>
- string Name { get; }
-
- /// <summary>
- /// Sends the notification.
- /// </summary>
- /// <param name="request">The request.</param>
- /// <param name="cancellationToken">The cancellation token.</param>
- /// <returns>Task.</returns>
- Task SendNotification(UserNotification request, CancellationToken cancellationToken);
-
- /// <summary>
- /// Determines whether [is enabled for user] [the specified user identifier].
- /// </summary>
- /// <param name="user">The user.</param>
- /// <returns><c>true</c> if [is enabled for user] [the specified user identifier]; otherwise, <c>false</c>.</returns>
- bool IsEnabledForUser(User user);
- }
-}
diff --git a/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs b/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs
deleted file mode 100644
index 52a3e120b5..0000000000
--- a/MediaBrowser.Controller/Notifications/INotificationTypeFactory.cs
+++ /dev/null
@@ -1,16 +0,0 @@
-#pragma warning disable CS1591
-
-using System.Collections.Generic;
-using MediaBrowser.Model.Notifications;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public interface INotificationTypeFactory
- {
- /// <summary>
- /// Gets the notification types.
- /// </summary>
- /// <returns>IEnumerable{NotificationTypeInfo}.</returns>
- IEnumerable<NotificationTypeInfo> GetNotificationTypes();
- }
-}
diff --git a/MediaBrowser.Controller/Notifications/UserNotification.cs b/MediaBrowser.Controller/Notifications/UserNotification.cs
deleted file mode 100644
index 4be0e09ae7..0000000000
--- a/MediaBrowser.Controller/Notifications/UserNotification.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-#nullable disable
-
-#pragma warning disable CS1591
-
-using System;
-using Jellyfin.Data.Entities;
-using MediaBrowser.Model.Notifications;
-
-namespace MediaBrowser.Controller.Notifications
-{
- public class UserNotification
- {
- public string Name { get; set; }
-
- public string Description { get; set; }
-
- public string Url { get; set; }
-
- public NotificationLevel Level { get; set; }
-
- public DateTime Date { get; set; }
-
- public User User { get; set; }
- }
-}
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index 24f7b5cd36..2c52b2b45e 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.Controller.Persistence
/// </summary>
/// <param name="items">The items.</param>
/// <param name="cancellationToken">The cancellation token.</param>
- void SaveItems(IEnumerable<BaseItem> items, CancellationToken cancellationToken);
+ void SaveItems(IReadOnlyList<BaseItem> items, CancellationToken cancellationToken);
void SaveImages(BaseItem item);
diff --git a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
index f6c5920709..d1a51c2cf6 100644
--- a/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
+++ b/MediaBrowser.Controller/Playlists/IPlaylistManager.cs
@@ -56,5 +56,19 @@ namespace MediaBrowser.Controller.Playlists
/// <param name="newIndex">The new index.</param>
/// <returns>Task.</returns>
Task MoveItemAsync(string playlistId, string entryId, int newIndex);
+
+ /// <summary>
+ /// Removed all playlists of a user.
+ /// If the playlist is shared, ownership is transferred.
+ /// </summary>
+ /// <param name="userId">The user id.</param>
+ /// <returns>Task.</returns>
+ Task RemovePlaylistsAsync(Guid userId);
+
+ /// <summary>
+ /// Saves a playlist.
+ /// </summary>
+ /// <param name="item">The playlist.</param>
+ void SavePlaylistFile(Playlist item);
}
}
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 7ae9ff7463..498df5ab06 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -15,6 +15,7 @@ using MediaBrowser.Controller.Dto;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Providers;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Playlists
@@ -33,10 +34,13 @@ namespace MediaBrowser.Controller.Playlists
public Playlist()
{
Shares = Array.Empty<Share>();
+ OpenAccess = false;
}
public Guid OwnerUserId { get; set; }
+ public bool OpenAccess { get; set; }
+
public Share[] Shares { get; set; }
[JsonIgnore]
@@ -165,7 +169,7 @@ namespace MediaBrowser.Controller.Playlists
public static List<BaseItem> GetPlaylistItems(string playlistMediaType, IEnumerable<BaseItem> inputItems, User user, DtoOptions options)
{
- if (user != null)
+ if (user is not null)
{
inputItems = inputItems.Where(i => i.IsVisible(user));
}
@@ -232,7 +236,13 @@ namespace MediaBrowser.Controller.Playlists
return base.IsVisible(user);
}
- if (user.Id.Equals(OwnerUserId))
+ if (OpenAccess)
+ {
+ return true;
+ }
+
+ var userId = user.Id;
+ if (userId.Equals(OwnerUserId))
{
return true;
}
@@ -240,10 +250,9 @@ namespace MediaBrowser.Controller.Playlists
var shares = Shares;
if (shares.Length == 0)
{
- return base.IsVisible(user);
+ return false;
}
- var userId = user.Id;
return shares.Any(share => Guid.TryParse(share.UserId, out var id) && id.Equals(userId));
}
diff --git a/MediaBrowser.Controller/Providers/EpisodeInfo.cs b/MediaBrowser.Controller/Providers/EpisodeInfo.cs
index b59a037384..c4ad352a3b 100644
--- a/MediaBrowser.Controller/Providers/EpisodeInfo.cs
+++ b/MediaBrowser.Controller/Providers/EpisodeInfo.cs
@@ -12,10 +12,13 @@ namespace MediaBrowser.Controller.Providers
public EpisodeInfo()
{
SeriesProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
+ SeasonProviderIds = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
}
public Dictionary<string, string> SeriesProviderIds { get; set; }
+ public Dictionary<string, string> SeasonProviderIds { get; set; }
+
public int? IndexNumberEnd { get; set; }
public bool IsMissingEpisode { get; set; }
diff --git a/MediaBrowser.Controller/Providers/IHasOrder.cs b/MediaBrowser.Controller/Providers/IHasOrder.cs
index 9fde0e6958..77b0407a20 100644
--- a/MediaBrowser.Controller/Providers/IHasOrder.cs
+++ b/MediaBrowser.Controller/Providers/IHasOrder.cs
@@ -1,9 +1,14 @@
-#pragma warning disable CS1591
-
namespace MediaBrowser.Controller.Providers
{
+ /// <summary>
+ /// Interface IHasOrder.
+ /// </summary>
public interface IHasOrder
{
+ /// <summary>
+ /// Gets the order.
+ /// </summary>
+ /// <value>The order.</value>
int Order { get; }
}
}
diff --git a/MediaBrowser.Controller/Providers/IMetadataService.cs b/MediaBrowser.Controller/Providers/IMetadataService.cs
index 05fbb18ee4..f0f1d18624 100644
--- a/MediaBrowser.Controller/Providers/IMetadataService.cs
+++ b/MediaBrowser.Controller/Providers/IMetadataService.cs
@@ -23,6 +23,11 @@ namespace MediaBrowser.Controller.Providers
/// <returns><c>true</c> if this instance can refresh the specified item.</returns>
bool CanRefresh(BaseItem item);
+ /// <summary>
+ /// Determines whether this instance primarily targets the specified type.
+ /// </summary>
+ /// <param name="type">The type.</param>
+ /// <returns><c>true</c> if this instance primarily targets the specified type.</returns>
bool CanRefreshPrimary(Type type);
/// <summary>
diff --git a/MediaBrowser.Controller/Providers/IProviderManager.cs b/MediaBrowser.Controller/Providers/IProviderManager.cs
index 44bc4a50cb..7e0a69586c 100644
--- a/MediaBrowser.Controller/Providers/IProviderManager.cs
+++ b/MediaBrowser.Controller/Providers/IProviderManager.cs
@@ -132,6 +132,24 @@ namespace MediaBrowser.Controller.Providers
IEnumerable<ImageProviderInfo> GetRemoteImageProviderInfo(BaseItem item);
/// <summary>
+ /// Gets the image providers for the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="refreshOptions">The image refresh options.</param>
+ /// <returns>The image providers for the item.</returns>
+ IEnumerable<IImageProvider> GetImageProviders(BaseItem item, ImageRefreshOptions refreshOptions);
+
+ /// <summary>
+ /// Gets the metadata providers for the provided item.
+ /// </summary>
+ /// <param name="item">The item.</param>
+ /// <param name="libraryOptions">The library options.</param>
+ /// <typeparam name="T">The type of metadata provider.</typeparam>
+ /// <returns>The metadata providers.</returns>
+ IEnumerable<IMetadataProvider<T>> GetMetadataProviders<T>(BaseItem item, LibraryOptions libraryOptions)
+ where T : BaseItem;
+
+ /// <summary>
/// Gets all metadata plugins.
/// </summary>
/// <returns>IEnumerable{MetadataPlugin}.</returns>
@@ -198,7 +216,7 @@ namespace MediaBrowser.Controller.Providers
/// <returns>Task{HttpResponseInfo}.</returns>
Task<HttpResponseMessage> GetSearchImage(string providerName, string url, CancellationToken cancellationToken);
- Dictionary<Guid, Guid> GetRefreshQueue();
+ HashSet<Guid> GetRefreshQueue();
void OnRefreshStart(BaseItem item);
diff --git a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs
index f146decb60..9ad0c26b0c 100644
--- a/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs
+++ b/MediaBrowser.Controller/Providers/IRemoteMetadataProvider.cs
@@ -1,5 +1,3 @@
-#pragma warning disable CS1591
-
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
@@ -8,20 +6,44 @@ using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Providers
{
+ /// <summary>
+ /// Interface IRemoteMetadataProvider.
+ /// </summary>
public interface IRemoteMetadataProvider : IMetadataProvider
{
}
+ /// <summary>
+ /// Interface IRemoteMetadataProvider.
+ /// </summary>
+ /// <typeparam name="TItemType">The type of <see cref="BaseItem" />.</typeparam>
+ /// <typeparam name="TLookupInfoType">The type of <see cref="ItemLookupInfo" />.</typeparam>
public interface IRemoteMetadataProvider<TItemType, in TLookupInfoType> : IMetadataProvider<TItemType>, IRemoteMetadataProvider, IRemoteSearchProvider<TLookupInfoType>
where TItemType : BaseItem, IHasLookupInfo<TLookupInfoType>
where TLookupInfoType : ItemLookupInfo, new()
{
+ /// <summary>
+ /// Gets the metadata for a specific LookupInfoType.
+ /// </summary>
+ /// <param name="info">The LookupInfoType to get metadata for.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <returns>A task returning a MetadataResult for the specific LookupInfoType.</returns>
Task<MetadataResult<TItemType>> GetMetadata(TLookupInfoType info, CancellationToken cancellationToken);
}
+ /// <summary>
+ /// Interface IRemoteMetadataProvider.
+ /// </summary>
+ /// <typeparam name="TLookupInfoType">The type of <see cref="ItemLookupInfo" />.</typeparam>
public interface IRemoteSearchProvider<in TLookupInfoType> : IRemoteSearchProvider
where TLookupInfoType : ItemLookupInfo
{
+ /// <summary>
+ /// Gets the list of <see cref="RemoteSearchResult"/> for a specific LookupInfoType.
+ /// </summary>
+ /// <param name="searchInfo">The LookupInfoType to search for.</param>
+ /// <param name="cancellationToken">The <see cref="CancellationToken"/>.</param>
+ /// <returns>A task returning RemoteSearchResults for the searchInfo.</returns>
Task<IEnumerable<RemoteSearchResult>> GetSearchResults(TLookupInfoType searchInfo, CancellationToken cancellationToken);
}
}
diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
index fd73ed5f80..05b4d43a5a 100644
--- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
@@ -1,6 +1,7 @@
#pragma warning disable CA1819, CS1591
using System;
+using System.Collections.Generic;
using System.Linq;
using MediaBrowser.Model.Entities;
@@ -23,7 +24,7 @@ namespace MediaBrowser.Controller.Providers
public bool ReplaceAllImages { get; set; }
- public ImageType[] ReplaceImages { get; set; }
+ public IReadOnlyList<ImageType> ReplaceImages { get; set; }
public bool IsAutomated { get; set; }
diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
index a38bbaf695..9e91a8bcd7 100644
--- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
@@ -26,13 +26,14 @@ namespace MediaBrowser.Controller.Providers
ReplaceAllMetadata = copy.ReplaceAllMetadata;
EnableRemoteContentProbe = copy.EnableRemoteContentProbe;
+ IsAutomated = copy.IsAutomated;
ImageRefreshMode = copy.ImageRefreshMode;
ReplaceAllImages = copy.ReplaceAllImages;
ReplaceImages = copy.ReplaceImages;
SearchResult = copy.SearchResult;
RemoveOldMetadata = copy.RemoveOldMetadata;
- if (copy.RefreshPaths != null && copy.RefreshPaths.Length > 0)
+ if (copy.RefreshPaths is not null && copy.RefreshPaths.Length > 0)
{
RefreshPaths ??= Array.Empty<string>();
@@ -58,7 +59,7 @@ namespace MediaBrowser.Controller.Providers
public bool RefreshItem(BaseItem item)
{
- if (RefreshPaths != null && RefreshPaths.Length > 0)
+ if (RefreshPaths is not null && RefreshPaths.Length > 0)
{
return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 58a0fa2a9a..952dd48703 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -59,7 +59,7 @@ namespace MediaBrowser.Controller.Providers
/// </summary>
public void ResetPeople()
{
- if (People == null)
+ if (People is null)
{
People = new List<PersonInfo>();
}
@@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.Providers
}
}
- if (userData == null)
+ if (userData is null)
{
userData = new UserItemData()
{
diff --git a/MediaBrowser.Controller/Resolvers/ItemResolver.cs b/MediaBrowser.Controller/Resolvers/ItemResolver.cs
index e7bf013fa2..a6da8384e8 100644
--- a/MediaBrowser.Controller/Resolvers/ItemResolver.cs
+++ b/MediaBrowser.Controller/Resolvers/ItemResolver.cs
@@ -46,7 +46,7 @@ namespace MediaBrowser.Controller.Resolvers
{
var item = Resolve(args);
- if (item != null)
+ if (item is not null)
{
SetInitialItemValues(item, args);
}
diff --git a/MediaBrowser.Controller/Session/ISessionController.cs b/MediaBrowser.Controller/Session/ISessionController.cs
index b38ee11462..c8b29aa1f3 100644
--- a/MediaBrowser.Controller/Session/ISessionController.cs
+++ b/MediaBrowser.Controller/Session/ISessionController.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Controller/Session/ISessionManager.cs b/MediaBrowser.Controller/Session/ISessionManager.cs
index b16399598c..0c4719a0e5 100644
--- a/MediaBrowser.Controller/Session/ISessionManager.cs
+++ b/MediaBrowser.Controller/Session/ISessionManager.cs
@@ -7,7 +7,6 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Jellyfin.Data.Entities.Security;
-using Jellyfin.Data.Events;
using MediaBrowser.Controller.Authentication;
using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Session;
@@ -58,16 +57,6 @@ namespace MediaBrowser.Controller.Session
event EventHandler<SessionEventArgs> CapabilitiesChanged;
/// <summary>
- /// Occurs when [authentication failed].
- /// </summary>
- event EventHandler<GenericEventArgs<AuthenticationRequest>> AuthenticationFailed;
-
- /// <summary>
- /// Occurs when [authentication succeeded].
- /// </summary>
- event EventHandler<GenericEventArgs<AuthenticationResult>> AuthenticationSucceeded;
-
- /// <summary>
/// Gets the sessions.
/// </summary>
/// <value>The sessions.</value>
diff --git a/MediaBrowser.Controller/Session/SessionInfo.cs b/MediaBrowser.Controller/Session/SessionInfo.cs
index b4520ae48f..25bf23d61b 100644
--- a/MediaBrowser.Controller/Session/SessionInfo.cs
+++ b/MediaBrowser.Controller/Session/SessionInfo.cs
@@ -64,7 +64,7 @@ namespace MediaBrowser.Controller.Session
{
get
{
- if (Capabilities == null)
+ if (Capabilities is null)
{
return Array.Empty<string>();
}
@@ -182,7 +182,7 @@ namespace MediaBrowser.Controller.Session
{
get
{
- if (Capabilities == null || !Capabilities.SupportsMediaControl)
+ if (Capabilities is null || !Capabilities.SupportsMediaControl)
{
return false;
}
@@ -204,7 +204,7 @@ namespace MediaBrowser.Controller.Session
{
get
{
- if (Capabilities == null || !Capabilities.SupportsMediaControl)
+ if (Capabilities is null || !Capabilities.SupportsMediaControl)
{
return false;
}
@@ -239,7 +239,7 @@ namespace MediaBrowser.Controller.Session
/// </summary>
/// <value>The supported commands.</value>
public IReadOnlyList<GeneralCommandType> SupportedCommands
- => Capabilities == null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
+ => Capabilities is null ? Array.Empty<GeneralCommandType>() : Capabilities.SupportedCommands;
public Tuple<ISessionController, bool> EnsureController<T>(Func<SessionInfo, ISessionController> factory)
{
@@ -296,7 +296,7 @@ namespace MediaBrowser.Controller.Session
{
_lastProgressInfo = progressInfo;
- if (_progressTimer == null)
+ if (_progressTimer is null)
{
_progressTimer = new Timer(OnProgressTimerCallback, null, 1000, 1000);
}
@@ -315,7 +315,7 @@ namespace MediaBrowser.Controller.Session
}
var progressInfo = _lastProgressInfo;
- if (progressInfo == null)
+ if (progressInfo is null)
{
return;
}
@@ -357,7 +357,7 @@ namespace MediaBrowser.Controller.Session
{
lock (_progressLock)
{
- if (_progressTimer != null)
+ if (_progressTimer is not null)
{
_progressTimer.Dispose();
_progressTimer = null;
diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
index 52aa44024d..fcfc18a644 100644
--- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
+++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
@@ -1,9 +1,6 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
-using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Controller.Entities;
@@ -20,12 +17,6 @@ namespace MediaBrowser.Controller.Subtitles
event EventHandler<SubtitleDownloadFailureEventArgs> SubtitleDownloadFailure;
/// <summary>
- /// Adds the parts.
- /// </summary>
- /// <param name="subtitleProviders">The subtitle providers.</param>
- void AddParts(IEnumerable<ISubtitleProvider> subtitleProviders);
-
- /// <summary>
/// Searches the subtitles.
/// </summary>
/// <param name="video">The video.</param>
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
index 2164945560..dcc06db1ed 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/WaitingGroupState.cs
@@ -533,11 +533,9 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
_logger.LogWarning("Session {SessionId} is seeking to wrong position, correcting.", session.Id);
return;
}
- else
- {
- // Session is ready.
- context.SetBuffering(session, false);
- }
+
+ // Session is ready.
+ context.SetBuffering(session, false);
if (!context.IsBuffering())
{
diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
index 3a7685f347..c0a168192e 100644
--- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
+++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
@@ -23,13 +23,13 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// The sorted playlist.
/// </summary>
/// <value>The sorted playlist, or play queue of the group.</value>
- private List<QueueItem> _sortedPlaylist = new List<QueueItem>();
+ private List<SyncPlayQueueItem> _sortedPlaylist = new List<SyncPlayQueueItem>();
/// <summary>
/// The shuffled playlist.
/// </summary>
/// <value>The shuffled playlist, or play queue of the group.</value>
- private List<QueueItem> _shuffledPlaylist = new List<QueueItem>();
+ private List<SyncPlayQueueItem> _shuffledPlaylist = new List<SyncPlayQueueItem>();
/// <summary>
/// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
@@ -76,7 +76,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// Gets the current playlist considering the shuffle mode.
/// </summary>
/// <returns>The playlist.</returns>
- public IReadOnlyList<QueueItem> GetPlaylist()
+ public IReadOnlyList<SyncPlayQueueItem> GetPlaylist()
{
return GetPlaylistInternal();
}
@@ -93,7 +93,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
_sortedPlaylist = CreateQueueItemsFromArray(items);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
- _shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
+ _shuffledPlaylist = new List<SyncPlayQueueItem>(_sortedPlaylist);
_shuffledPlaylist.Shuffle();
}
@@ -125,14 +125,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (PlayingItemIndex == NoPlayingItemIndex)
{
- _shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
+ _shuffledPlaylist = new List<SyncPlayQueueItem>(_sortedPlaylist);
_shuffledPlaylist.Shuffle();
}
else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
{
// First time shuffle.
var playingItem = _sortedPlaylist[PlayingItemIndex];
- _shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
+ _shuffledPlaylist = new List<SyncPlayQueueItem>(_sortedPlaylist);
_shuffledPlaylist.RemoveAt(PlayingItemIndex);
_shuffledPlaylist.Shuffle();
_shuffledPlaylist.Insert(0, playingItem);
@@ -180,7 +180,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
_shuffledPlaylist.Clear();
LastChange = DateTime.UtcNow;
- if (!clearPlayingItem && playingItem != null)
+ if (!clearPlayingItem && playingItem is not null)
{
_sortedPlaylist.Add(playingItem);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
@@ -298,7 +298,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
LastChange = DateTime.UtcNow;
- if (playingItem != null)
+ if (playingItem is not null)
{
if (playlistItemIds.Contains(playingItem.PlaylistItemId))
{
@@ -313,17 +313,13 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
return true;
}
- else
- {
- // Restoring playing item.
- SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
- return false;
- }
- }
- else
- {
+
+ // Restoring playing item.
+ SetPlayingItemByPlaylistId(playingItem.PlaylistItemId);
return false;
}
+
+ return false;
}
/// <summary>
@@ -411,7 +407,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// Gets the next item in the playlist considering repeat mode and shuffle mode.
/// </summary>
/// <returns>The next item in the playlist.</returns>
- public QueueItem GetNextItemPlaylistId()
+ public SyncPlayQueueItem GetNextItemPlaylistId()
{
int newIndex;
var playlist = GetPlaylistInternal();
@@ -506,12 +502,12 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// Creates a list from the array of items. Each item is given an unique playlist identifier.
/// </summary>
/// <returns>The list of queue items.</returns>
- private List<QueueItem> CreateQueueItemsFromArray(IReadOnlyList<Guid> items)
+ private List<SyncPlayQueueItem> CreateQueueItemsFromArray(IReadOnlyList<Guid> items)
{
- var list = new List<QueueItem>();
+ var list = new List<SyncPlayQueueItem>();
foreach (var item in items)
{
- var queueItem = new QueueItem(item);
+ var queueItem = new SyncPlayQueueItem(item);
list.Add(queueItem);
}
@@ -522,36 +518,33 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// Gets the current playlist considering the shuffle mode.
/// </summary>
/// <returns>The playlist.</returns>
- private List<QueueItem> GetPlaylistInternal()
+ private List<SyncPlayQueueItem> GetPlaylistInternal()
{
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
return _shuffledPlaylist;
}
- else
- {
- return _sortedPlaylist;
- }
+
+ return _sortedPlaylist;
}
/// <summary>
/// Gets the current playing item, depending on the shuffle mode.
/// </summary>
/// <returns>The playing item.</returns>
- private QueueItem GetPlayingItem()
+ private SyncPlayQueueItem GetPlayingItem()
{
if (PlayingItemIndex == NoPlayingItemIndex)
{
return null;
}
- else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
+
+ if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
return _shuffledPlaylist[PlayingItemIndex];
}
- else
- {
- return _sortedPlaylist[PlayingItemIndex];
- }
+
+ return _sortedPlaylist[PlayingItemIndex];
}
}
}