aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller
diff options
context:
space:
mode:
Diffstat (limited to 'MediaBrowser.Controller')
-rw-r--r--MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs14
-rw-r--r--MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs2
-rw-r--r--MediaBrowser.Controller/Channels/IHasFolderAttributes.cs2
-rw-r--r--MediaBrowser.Controller/Channels/ISupportsDelete.cs2
-rw-r--r--MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs2
-rw-r--r--MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs31
-rw-r--r--MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs23
-rw-r--r--MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/Dlna/IDlnaManager.cs3
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs19
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicArtist.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/MusicGenre.cs3
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs484
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs13
-rw-r--r--MediaBrowser.Controller/Entities/Folder.cs14
-rw-r--r--MediaBrowser.Controller/Entities/Genre.cs10
-rw-r--r--MediaBrowser.Controller/Entities/IHasScreenshots.cs9
-rw-r--r--MediaBrowser.Controller/Entities/IHasShares.cs2
-rw-r--r--MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs4
-rw-r--r--MediaBrowser.Controller/Entities/IHasTrailers.cs68
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs16
-rw-r--r--MediaBrowser.Controller/Entities/LinkedChildComparer.cs2
-rw-r--r--MediaBrowser.Controller/Entities/LinkedChildType.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Movies/BoxSet.cs12
-rw-r--r--MediaBrowser.Controller/Entities/Movies/Movie.cs71
-rw-r--r--MediaBrowser.Controller/Entities/TV/Episode.cs14
-rw-r--r--MediaBrowser.Controller/Entities/TV/Series.cs22
-rw-r--r--MediaBrowser.Controller/Entities/TagExtensions.cs3
-rw-r--r--MediaBrowser.Controller/Entities/UserView.cs7
-rw-r--r--MediaBrowser.Controller/Entities/UserViewBuilder.cs73
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs32
-rw-r--r--MediaBrowser.Controller/Entities/Year.cs4
-rw-r--r--MediaBrowser.Controller/IO/FileData.cs4
-rw-r--r--MediaBrowser.Controller/IServerApplicationHost.cs29
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs60
-rw-r--r--MediaBrowser.Controller/Library/IMediaSourceManager.cs7
-rw-r--r--MediaBrowser.Controller/Library/ItemResolveArgs.cs35
-rw-r--r--MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs4
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvChannel.cs8
-rw-r--r--MediaBrowser.Controller/LiveTv/LiveTvProgram.cs11
-rw-r--r--MediaBrowser.Controller/LiveTv/TimerInfo.cs12
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj21
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs4197
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs18
-rw-r--r--MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs12
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs60
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs13
-rw-r--r--MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs2
-rw-r--r--MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs3
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketConnection.cs6
-rw-r--r--MediaBrowser.Controller/Net/IWebSocketListener.cs4
-rw-r--r--MediaBrowser.Controller/Net/WebSocketListenerState.cs2
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs12
-rw-r--r--MediaBrowser.Controller/Playlists/Playlist.cs4
-rw-r--r--MediaBrowser.Controller/Providers/DirectoryService.cs10
-rw-r--r--MediaBrowser.Controller/Providers/IExternalId.cs4
-rw-r--r--MediaBrowser.Controller/Providers/ImageRefreshOptions.cs2
-rw-r--r--MediaBrowser.Controller/Providers/ItemInfo.cs2
-rw-r--r--MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs3
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs6
-rw-r--r--MediaBrowser.Controller/Providers/RefreshPriority.cs2
-rw-r--r--MediaBrowser.Controller/Subtitles/ISubtitleManager.cs2
-rw-r--r--MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs2
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs6
-rw-r--r--MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs6
-rw-r--r--MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs124
70 files changed, 3150 insertions, 2518 deletions
diff --git a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
index abfdb41d8..d273b54fc 100644
--- a/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
+++ b/MediaBrowser.Controller/BaseItemManager/BaseItemManager.cs
@@ -55,12 +55,7 @@ namespace MediaBrowser.Controller.BaseItemManager
return typeOptions.MetadataFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
- if (!libraryOptions.EnableInternetProviders)
- {
- return false;
- }
-
- var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, 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);
}
@@ -86,12 +81,7 @@ namespace MediaBrowser.Controller.BaseItemManager
return typeOptions.ImageFetchers.Contains(name.AsSpan(), StringComparison.OrdinalIgnoreCase);
}
- if (!libraryOptions.EnableInternetProviders)
- {
- return false;
- }
-
- var itemConfig = _serverConfigurationManager.Configuration.MetadataOptions.FirstOrDefault(i => string.Equals(i.ItemType, GetType().Name, 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);
}
diff --git a/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs
index 6f0761e64..e02f42fa4 100644
--- a/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs
+++ b/MediaBrowser.Controller/Channels/ChannelLatestMediaSearch.cs
@@ -8,4 +8,4 @@ namespace MediaBrowser.Controller.Channels
{
public string UserId { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs
index 64af8496c..6c92785d2 100644
--- a/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs
+++ b/MediaBrowser.Controller/Channels/IHasFolderAttributes.cs
@@ -6,4 +6,4 @@ namespace MediaBrowser.Controller.Channels
{
string[] Attributes { get; }
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Channels/ISupportsDelete.cs b/MediaBrowser.Controller/Channels/ISupportsDelete.cs
index 204054374..30798a4b2 100644
--- a/MediaBrowser.Controller/Channels/ISupportsDelete.cs
+++ b/MediaBrowser.Controller/Channels/ISupportsDelete.cs
@@ -12,4 +12,4 @@ namespace MediaBrowser.Controller.Channels
Task DeleteItem(string id, CancellationToken cancellationToken);
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs
index dbba7cba2..8ad93387e 100644
--- a/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs
+++ b/MediaBrowser.Controller/Channels/ISupportsLatestMedia.cs
@@ -18,4 +18,4 @@ namespace MediaBrowser.Controller.Channels
/// <returns>The latest media.</returns>
Task<IEnumerable<ChannelItemInfo>> GetLatestMedia(ChannelLatestMediaSearch request, CancellationToken cancellationToken);
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
new file mode 100644
index 000000000..dea1c2f32
--- /dev/null
+++ b/MediaBrowser.Controller/ClientEvent/ClientEventLogger.cs
@@ -0,0 +1,31 @@
+using System;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.ClientEvent
+{
+ /// <inheritdoc />
+ public class ClientEventLogger : IClientEventLogger
+ {
+ private readonly IServerApplicationPaths _applicationPaths;
+
+ /// <summary>
+ /// Initializes a new instance of the <see cref="ClientEventLogger"/> class.
+ /// </summary>
+ /// <param name="applicationPaths">Instance of the <see cref="IServerApplicationPaths"/> interface.</param>
+ public ClientEventLogger(IServerApplicationPaths applicationPaths)
+ {
+ _applicationPaths = applicationPaths;
+ }
+
+ /// <inheritdoc />
+ public async Task<string> WriteDocumentAsync(string clientName, string clientVersion, Stream fileContents)
+ {
+ var fileName = $"upload_{clientName}_{clientVersion}_{DateTime.UtcNow:yyyyMMddHHmmss}_{Guid.NewGuid():N}.log";
+ var logFilePath = Path.Combine(_applicationPaths.LogDirectoryPath, fileName);
+ await using var fileStream = new FileStream(logFilePath, FileMode.CreateNew, FileAccess.Write, FileShare.None);
+ await fileContents.CopyToAsync(fileStream).ConfigureAwait(false);
+ return fileName;
+ }
+ }
+}
diff --git a/MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs b/MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs
new file mode 100644
index 000000000..ad8a1bd24
--- /dev/null
+++ b/MediaBrowser.Controller/ClientEvent/IClientEventLogger.cs
@@ -0,0 +1,23 @@
+using System.IO;
+using System.Threading.Tasks;
+
+namespace MediaBrowser.Controller.ClientEvent
+{
+ /// <summary>
+ /// The client event logger.
+ /// </summary>
+ public interface IClientEventLogger
+ {
+ /// <summary>
+ /// Writes a file to the log directory.
+ /// </summary>
+ /// <param name="clientName">The client name writing the document.</param>
+ /// <param name="clientVersion">The client version writing the document.</param>
+ /// <param name="fileContents">The file contents to write.</param>
+ /// <returns>The created file name.</returns>
+ Task<string> WriteDocumentAsync(
+ string clientName,
+ string clientVersion,
+ Stream fileContents);
+ }
+}
diff --git a/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs
index 82b3a4977..1797d15ea 100644
--- a/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs
+++ b/MediaBrowser.Controller/Collections/CollectionCreatedEventArgs.cs
@@ -21,4 +21,4 @@ namespace MediaBrowser.Controller.Collections
/// <value>The options.</value>
public CollectionCreationOptions Options { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Dlna/IDlnaManager.cs b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
index cc0a107a8..06da5ea09 100644
--- a/MediaBrowser.Controller/Dlna/IDlnaManager.cs
+++ b/MediaBrowser.Controller/Dlna/IDlnaManager.cs
@@ -37,8 +37,9 @@ namespace MediaBrowser.Controller.Dlna
/// <summary>
/// Updates the profile.
/// </summary>
+ /// <param name="profileId">The profile id.</param>
/// <param name="profile">The profile.</param>
- void UpdateProfile(DeviceProfile profile);
+ void UpdateProfile(string profileId, DeviceProfile profile);
/// <summary>
/// Deletes the profile.
diff --git a/MediaBrowser.Controller/Drawing/IImageProcessor.cs b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
index 7ca0e851b..03882a0b9 100644
--- a/MediaBrowser.Controller/Drawing/IImageProcessor.cs
+++ b/MediaBrowser.Controller/Drawing/IImageProcessor.cs
@@ -75,7 +75,7 @@ namespace MediaBrowser.Controller.Drawing
/// </summary>
/// <param name="options">The options.</param>
/// <returns>Task.</returns>
- Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options);
+ Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options);
/// <summary>
/// Gets the supported image output formats.
diff --git a/MediaBrowser.Controller/Entities/Audio/Audio.cs b/MediaBrowser.Controller/Entities/Audio/Audio.cs
index 536668e50..29f7bf92b 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -8,10 +8,8 @@ using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Dto;
-using MediaBrowser.Model.Entities;
namespace MediaBrowser.Controller.Entities.Audio
{
@@ -126,15 +124,6 @@ namespace MediaBrowser.Controller.Entities.Audio
return base.GetBlockUnratedType();
}
- public List<MediaStream> GetMediaStreams(MediaStreamType type)
- {
- return MediaSourceManager.GetMediaStreams(new MediaStreamQuery
- {
- ItemId = Id,
- Type = type
- });
- }
-
public SongInfo GetLookupInfo()
{
var info = GetItemLookupInfo<SongInfo>();
@@ -146,11 +135,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return info;
}
- protected override List<Tuple<BaseItem, MediaSourceType>> GetAllItemsForMediaSources()
- {
- var list = new List<Tuple<BaseItem, MediaSourceType>>();
- list.Add(new Tuple<BaseItem, MediaSourceType>(this, MediaSourceType.Default));
- return list;
- }
+ protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
+ => new[] { ((BaseItem)this, MediaSourceType.Default) };
}
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
index f30f8ce7f..11b95b94b 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicArtist.cs
@@ -88,7 +88,7 @@ namespace MediaBrowser.Controller.Entities.Audio
{
if (query.IncludeItemTypes.Length == 0)
{
- query.IncludeItemTypes = new[] { nameof(Audio), nameof(MusicVideo), nameof(MusicAlbum) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Audio, BaseItemKind.MusicVideo, BaseItemKind.MusicAlbum };
query.ArtistIds = new[] { Id };
}
diff --git a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
index dc6fcc55a..73a25232e 100644
--- a/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
+++ b/MediaBrowser.Controller/Entities/Audio/MusicGenre.cs
@@ -6,6 +6,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
+using Jellyfin.Data.Enums;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities.Audio
@@ -66,7 +67,7 @@ namespace MediaBrowser.Controller.Entities.Audio
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
{
query.GenreIds = new[] { Id };
- query.IncludeItemTypes = new[] { nameof(MusicVideo), nameof(Audio), nameof(MusicAlbum), nameof(MusicArtist) };
+ query.IncludeItemTypes = new[] { BaseItemKind.MusicVideo, BaseItemKind.Audio, BaseItemKind.MusicAlbum, BaseItemKind.MusicArtist };
return LibraryManager.GetItemList(query);
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 1237268d7..915971adc 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.IO;
using System.Linq;
using System.Text;
using System.Text.Json.Serialization;
@@ -23,7 +22,6 @@ using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Configuration;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Globalization;
@@ -41,13 +39,9 @@ namespace MediaBrowser.Controller.Entities
/// </summary>
public abstract class BaseItem : IHasProviderIds, IHasLookupInfo<ItemLookupInfo>, IEquatable<BaseItem>
{
- /// <summary>
- /// The trailer folder name.
- /// </summary>
- public const string TrailersFolderName = "trailers";
- public const string ThemeSongsFolderName = "theme-music";
+ private BaseItemKind? _baseItemKind;
+
public const string ThemeSongFileName = "theme";
- public const string ThemeVideosFolderName = "backdrops";
/// <summary>
/// The supported image extensions.
@@ -84,26 +78,7 @@ namespace MediaBrowser.Controller.Entities
Model.Entities.ExtraType.Scene
};
- public static readonly char[] SlugReplaceChars = { '?', '/', '&' };
-
- /// <summary>
- /// The supported extra folder names and types. See <see cref="Emby.Naming.Common.NamingOptions" />.
- /// </summary>
- public static readonly Dictionary<string, ExtraType> AllExtrasTypesFolderNames = new Dictionary<string, ExtraType>(StringComparer.OrdinalIgnoreCase)
- {
- ["extras"] = MediaBrowser.Model.Entities.ExtraType.Unknown,
- ["behind the scenes"] = MediaBrowser.Model.Entities.ExtraType.BehindTheScenes,
- ["deleted scenes"] = MediaBrowser.Model.Entities.ExtraType.DeletedScene,
- ["interviews"] = MediaBrowser.Model.Entities.ExtraType.Interview,
- ["scenes"] = MediaBrowser.Model.Entities.ExtraType.Scene,
- ["samples"] = MediaBrowser.Model.Entities.ExtraType.Sample,
- ["shorts"] = MediaBrowser.Model.Entities.ExtraType.Clip,
- ["featurettes"] = MediaBrowser.Model.Entities.ExtraType.Clip
- };
-
private string _sortName;
- private Guid[] _themeSongIds;
- private Guid[] _themeVideoIds;
private string _forcedSortName;
@@ -125,40 +100,6 @@ namespace MediaBrowser.Controller.Entities
}
[JsonIgnore]
- public Guid[] ThemeSongIds
- {
- get
- {
- return _themeSongIds ??= GetExtras()
- .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeSong)
- .Select(song => song.Id)
- .ToArray();
- }
-
- private set
- {
- _themeSongIds = value;
- }
- }
-
- [JsonIgnore]
- public Guid[] ThemeVideoIds
- {
- get
- {
- return _themeVideoIds ??= GetExtras()
- .Where(extra => extra.ExtraType == Model.Entities.ExtraType.ThemeVideo)
- .Select(song => song.Id)
- .ToArray();
- }
-
- private set
- {
- _themeVideoIds = value;
- }
- }
-
- [JsonIgnore]
public string PreferredMetadataCountryCode { get; set; }
[JsonIgnore]
@@ -335,13 +276,6 @@ namespace MediaBrowser.Controller.Entities
[JsonIgnore]
public string ExternalSeriesId { get; set; }
- /// <summary>
- /// Gets or sets the etag.
- /// </summary>
- /// <value>The etag.</value>
- [JsonIgnore]
- public string ExternalEtag { get; set; }
-
[JsonIgnore]
public virtual bool IsHidden => false;
@@ -354,11 +288,6 @@ namespace MediaBrowser.Controller.Entities
{
get
{
- // if (IsOffline)
- // {
- // return LocationType.Offline;
- // }
-
var path = Path;
if (string.IsNullOrEmpty(path))
{
@@ -391,7 +320,7 @@ namespace MediaBrowser.Controller.Entities
}
[JsonIgnore]
- public bool IsFileProtocol => IsPathProtocol(MediaProtocol.File);
+ public bool IsFileProtocol => PathProtocol == MediaProtocol.File;
[JsonIgnore]
public bool HasPathProtocol => PathProtocol.HasValue;
@@ -583,14 +512,7 @@ namespace MediaBrowser.Controller.Entities
}
[JsonIgnore]
- public virtual Guid DisplayParentId
- {
- get
- {
- var parentId = ParentId;
- return parentId;
- }
- }
+ public virtual Guid DisplayParentId => ParentId;
[JsonIgnore]
public BaseItem DisplayParent
@@ -853,13 +775,6 @@ namespace MediaBrowser.Controller.Entities
return Id.ToString("N", CultureInfo.InvariantCulture);
}
- public bool IsPathProtocol(MediaProtocol protocol)
- {
- var current = PathProtocol;
-
- return current.HasValue && current.Value == protocol;
- }
-
private List<Tuple<StringBuilder, bool>> GetSortChunks(string s1)
{
var list = new List<Tuple<StringBuilder, bool>>();
@@ -987,7 +902,7 @@ namespace MediaBrowser.Controller.Entities
ReadOnlySpan<char> idString = Id.ToString("N", CultureInfo.InvariantCulture);
- return System.IO.Path.Join(basePath, "library", idString.Slice(0, 2), idString);
+ return System.IO.Path.Join(basePath, "library", idString[..2], idString);
}
/// <summary>
@@ -1154,7 +1069,7 @@ namespace MediaBrowser.Controller.Entities
}
var list = GetAllItemsForMediaSources();
- var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item1, i.Item2)).ToList();
+ var result = list.Select(i => GetVersionInfo(enablePathSubstitution, i.Item, i.MediaSourceType)).ToList();
if (IsActiveRecording())
{
@@ -1182,9 +1097,9 @@ namespace MediaBrowser.Controller.Entities
.ToList();
}
- protected virtual List<Tuple<BaseItem, MediaSourceType>> GetAllItemsForMediaSources()
+ protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
{
- return new List<Tuple<BaseItem, MediaSourceType>>();
+ return Enumerable.Empty<(BaseItem, MediaSourceType)>();
}
private MediaSourceInfo GetVersionInfo(bool enablePathSubstitution, BaseItem item, MediaSourceType type)
@@ -1302,8 +1217,7 @@ namespace MediaBrowser.Controller.Entities
terms.Add(item.Name);
}
- var video = item as Video;
- if (video != null)
+ if (item is Video video)
{
if (video.Video3DFormat.HasValue)
{
@@ -1338,99 +1252,7 @@ namespace MediaBrowser.Controller.Entities
}
}
- return string.Join('/', terms.ToArray());
- }
-
- /// <summary>
- /// Loads the theme songs.
- /// </summary>
- /// <returns>List{Audio.Audio}.</returns>
- private static Audio.Audio[] LoadThemeSongs(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
- {
- var files = fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => string.Equals(i.Name, ThemeSongsFolderName, StringComparison.OrdinalIgnoreCase))
- .SelectMany(i => FileSystem.GetFiles(i.FullName))
- .ToList();
-
- // Support plex/xbmc convention
- files.AddRange(fileSystemChildren
- .Where(i => !i.IsDirectory && System.IO.Path.GetFileNameWithoutExtension(i.FullName.AsSpan()).Equals(ThemeSongFileName, StringComparison.OrdinalIgnoreCase)));
-
- return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
- .OfType<Audio.Audio>()
- .Select(audio =>
- {
- // Try to retrieve it from the db. If we don't find it, use the resolved version
- var dbItem = LibraryManager.GetItemById(audio.Id) as Audio.Audio;
-
- if (dbItem != null)
- {
- audio = dbItem;
- }
- else
- {
- // item is new
- audio.ExtraType = MediaBrowser.Model.Entities.ExtraType.ThemeSong;
- }
-
- return audio;
-
- // Sort them so that the list can be easily compared for changes
- }).OrderBy(i => i.Path).ToArray();
- }
-
- /// <summary>
- /// Loads the video backdrops.
- /// </summary>
- /// <returns>List{Video}.</returns>
- private static Video[] LoadThemeVideos(IEnumerable<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
- {
- var files = fileSystemChildren.Where(i => i.IsDirectory)
- .Where(i => string.Equals(i.Name, ThemeVideosFolderName, StringComparison.OrdinalIgnoreCase))
- .SelectMany(i => FileSystem.GetFiles(i.FullName));
-
- return LibraryManager.ResolvePaths(files, directoryService, null, new LibraryOptions())
- .OfType<Video>()
- .Select(item =>
- {
- // Try to retrieve it from the db. If we don't find it, use the resolved version
-
- if (LibraryManager.GetItemById(item.Id) is Video dbItem)
- {
- item = dbItem;
- }
- else
- {
- // item is new
- item.ExtraType = Model.Entities.ExtraType.ThemeVideo;
- }
-
- return item;
-
- // Sort them so that the list can be easily compared for changes
- }).OrderBy(i => i.Path).ToArray();
- }
-
- protected virtual BaseItem[] LoadExtras(List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService)
- {
- return fileSystemChildren
- .Where(child => child.IsDirectory && AllExtrasTypesFolderNames.ContainsKey(child.Name))
- .SelectMany(folder => LibraryManager
- .ResolvePaths(FileSystem.GetFiles(folder.FullName), directoryService, null, new LibraryOptions())
- .OfType<Video>()
- .Select(video =>
- {
- // Try to retrieve it from the db. If we don't find it, use the resolved version
- if (LibraryManager.GetItemById(video.Id) is Video dbItem)
- {
- video = dbItem;
- }
-
- video.ExtraType = AllExtrasTypesFolderNames[folder.Name];
- return video;
- })
- .OrderBy(video => video.Path)) // Sort them so that the list can be easily compared for changes
- .ToArray();
+ return string.Join('/', terms);
}
public Task RefreshMetadata(CancellationToken cancellationToken)
@@ -1462,21 +1284,16 @@ namespace MediaBrowser.Controller.Entities
{
try
{
- var files = IsFileProtocol ?
- GetFileSystemChildren(options.DirectoryService).ToList() :
- new List<FileSystemMetadata>();
-
- var ownedItemsChanged = await RefreshedOwnedItems(options, files, cancellationToken).ConfigureAwait(false);
- await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh
-
- if (ownedItemsChanged)
+ if (IsFileProtocol)
{
- requiresSave = true;
+ requiresSave = await RefreshedOwnedItems(options, GetFileSystemChildren(options.DirectoryService).ToList(), cancellationToken).ConfigureAwait(false);
}
+
+ await LibraryManager.UpdateImagesAsync(this).ConfigureAwait(false); // ensure all image properties in DB are fresh
}
catch (Exception ex)
{
- Logger.LogError(ex, "Error refreshing owned items for {path}", Path ?? Name);
+ Logger.LogError(ex, "Error refreshing owned items for {Path}", Path ?? Name);
}
}
@@ -1548,36 +1365,12 @@ namespace MediaBrowser.Controller.Entities
/// <returns><c>true</c> if any items have changed, else <c>false</c>.</returns>
protected virtual async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
- var themeSongsChanged = false;
-
- var themeVideosChanged = false;
-
- var extrasChanged = false;
-
- var localTrailersChanged = false;
-
- if (IsFileProtocol && SupportsOwnedItems)
+ if (!IsFileProtocol || !SupportsOwnedItems || IsInMixedFolder || this is ICollectionFolder or UserRootFolder or AggregateFolder || this.GetType() == typeof(Folder))
{
- if (SupportsThemeMedia)
- {
- if (!IsInMixedFolder)
- {
- themeSongsChanged = await RefreshThemeSongs(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
-
- themeVideosChanged = await RefreshThemeVideos(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
-
- extrasChanged = await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
- }
- }
-
- var hasTrailers = this as IHasTrailers;
- if (hasTrailers != null)
- {
- localTrailersChanged = await RefreshLocalTrailers(hasTrailers, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
- }
+ return false;
}
- return themeSongsChanged || themeVideosChanged || extrasChanged || localTrailersChanged;
+ return await RefreshExtras(this, options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
}
protected virtual FileSystemMetadata[] GetFileSystemChildren(IDirectoryService directoryService)
@@ -1587,136 +1380,24 @@ namespace MediaBrowser.Controller.Entities
return directoryService.GetFileSystemEntries(path);
}
- private async Task<bool> RefreshLocalTrailers(IHasTrailers item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
- {
- var newItems = LibraryManager.FindTrailers(this, fileSystemChildren, options.DirectoryService);
-
- var newItemIds = newItems.Select(i => i.Id);
-
- var itemsChanged = !item.LocalTrailerIds.SequenceEqual(newItemIds);
- var ownerId = item.Id;
-
- var tasks = newItems.Select(i =>
- {
- var subOptions = new MetadataRefreshOptions(options);
-
- if (i.ExtraType != Model.Entities.ExtraType.Trailer ||
- i.OwnerId != ownerId ||
- !i.ParentId.Equals(Guid.Empty))
- {
- i.ExtraType = Model.Entities.ExtraType.Trailer;
- i.OwnerId = ownerId;
- i.ParentId = Guid.Empty;
- subOptions.ForceSave = true;
- }
-
- return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
- });
-
- await Task.WhenAll(tasks).ConfigureAwait(false);
-
- item.LocalTrailerIds = newItemIds.ToArray();
-
- return itemsChanged;
- }
-
private async Task<bool> RefreshExtras(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
{
- var extras = LoadExtras(fileSystemChildren, options.DirectoryService);
- var themeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
- var themeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
- var newExtras = new BaseItem[extras.Length + themeVideos.Length + themeSongs.Length];
- extras.CopyTo(newExtras, 0);
- themeVideos.CopyTo(newExtras, extras.Length);
- themeSongs.CopyTo(newExtras, extras.Length + themeVideos.Length);
-
- var newExtraIds = newExtras.Select(i => i.Id).ToArray();
-
+ var extras = LibraryManager.FindExtras(item, fileSystemChildren, options.DirectoryService).ToArray();
+ var newExtraIds = extras.Select(i => i.Id).ToArray();
var extrasChanged = !item.ExtraIds.SequenceEqual(newExtraIds);
- if (extrasChanged)
+ if (!extrasChanged && !options.ReplaceAllMetadata && options.MetadataRefreshMode != MetadataRefreshMode.FullRefresh)
{
- var ownerId = item.Id;
-
- var tasks = newExtras.Select(i =>
- {
- var subOptions = new MetadataRefreshOptions(options);
- if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
- {
- i.OwnerId = ownerId;
- i.ParentId = Guid.Empty;
- subOptions.ForceSave = true;
- }
-
- return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
- });
-
- await Task.WhenAll(tasks).ConfigureAwait(false);
-
- item.ExtraIds = newExtraIds;
+ return false;
}
- return extrasChanged;
- }
-
- private async Task<bool> RefreshThemeVideos(BaseItem item, MetadataRefreshOptions options, IEnumerable<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
- {
- var newThemeVideos = LoadThemeVideos(fileSystemChildren, options.DirectoryService);
-
- var newThemeVideoIds = newThemeVideos.Select(i => i.Id).ToArray();
-
- var themeVideosChanged = !item.ThemeVideoIds.SequenceEqual(newThemeVideoIds);
-
- var ownerId = item.Id;
-
- var tasks = newThemeVideos.Select(i =>
- {
- var subOptions = new MetadataRefreshOptions(options);
-
- if (!i.ExtraType.HasValue ||
- i.ExtraType.Value != Model.Entities.ExtraType.ThemeVideo ||
- i.OwnerId != ownerId ||
- !i.ParentId.Equals(Guid.Empty))
- {
- i.ExtraType = Model.Entities.ExtraType.ThemeVideo;
- i.OwnerId = ownerId;
- i.ParentId = Guid.Empty;
- subOptions.ForceSave = true;
- }
-
- return RefreshMetadataForOwnedItem(i, true, subOptions, cancellationToken);
- });
-
- await Task.WhenAll(tasks).ConfigureAwait(false);
-
- // They are expected to be sorted by SortName
- item.ThemeVideoIds = newThemeVideos.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
-
- return themeVideosChanged;
- }
-
- /// <summary>
- /// Refreshes the theme songs.
- /// </summary>
- private async Task<bool> RefreshThemeSongs(BaseItem item, MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
- {
- var newThemeSongs = LoadThemeSongs(fileSystemChildren, options.DirectoryService);
- var newThemeSongIds = newThemeSongs.Select(i => i.Id).ToArray();
-
- var themeSongsChanged = !item.ThemeSongIds.SequenceEqual(newThemeSongIds);
-
var ownerId = item.Id;
- var tasks = newThemeSongs.Select(i =>
+ var tasks = extras.Select(i =>
{
var subOptions = new MetadataRefreshOptions(options);
-
- if (!i.ExtraType.HasValue ||
- i.ExtraType.Value != Model.Entities.ExtraType.ThemeSong ||
- i.OwnerId != ownerId ||
- !i.ParentId.Equals(Guid.Empty))
+ if (i.OwnerId != ownerId || i.ParentId != Guid.Empty)
{
- i.ExtraType = Model.Entities.ExtraType.ThemeSong;
i.OwnerId = ownerId;
i.ParentId = Guid.Empty;
subOptions.ForceSave = true;
@@ -1727,10 +1408,9 @@ namespace MediaBrowser.Controller.Entities
await Task.WhenAll(tasks).ConfigureAwait(false);
- // They are expected to be sorted by SortName
- item.ThemeSongIds = newThemeSongs.OrderBy(i => i.SortName).Select(i => i.Id).ToArray();
+ item.ExtraIds = newExtraIds;
- return themeSongsChanged;
+ return true;
}
public string GetPresentationUniqueKey()
@@ -1963,7 +1643,7 @@ namespace MediaBrowser.Controller.Entities
private bool IsVisibleViaTags(User user)
{
- if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparer.OrdinalIgnoreCase)))
+ if (user.GetPreference(PreferenceKind.BlockedTags).Any(i => Tags.Contains(i, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
@@ -2042,7 +1722,7 @@ namespace MediaBrowser.Controller.Entities
public BaseItemKind GetBaseItemKind()
{
- return Enum.Parse<BaseItemKind>(GetClientTypeName());
+ return _baseItemKind ??= Enum.Parse<BaseItemKind>(GetClientTypeName());
}
/// <summary>
@@ -2132,7 +1812,7 @@ namespace MediaBrowser.Controller.Entities
var current = Studios;
- if (!current.Contains(name, StringComparer.OrdinalIgnoreCase))
+ if (!current.Contains(name, StringComparison.OrdinalIgnoreCase))
{
int curLen = current.Length;
if (curLen == 0)
@@ -2167,7 +1847,7 @@ namespace MediaBrowser.Controller.Entities
}
var genres = Genres;
- if (!genres.Contains(name, StringComparer.OrdinalIgnoreCase))
+ if (!genres.Contains(name, StringComparison.OrdinalIgnoreCase))
{
var list = genres.ToList();
list.Add(name);
@@ -2268,7 +1948,11 @@ namespace MediaBrowser.Controller.Entities
var existingImage = GetImageInfo(image.Type, index);
- if (existingImage != null)
+ if (existingImage == null)
+ {
+ AddImage(image);
+ }
+ else
{
existingImage.Path = image.Path;
existingImage.DateModified = image.DateModified;
@@ -2276,15 +1960,6 @@ namespace MediaBrowser.Controller.Entities
existingImage.Height = image.Height;
existingImage.BlurHash = image.BlurHash;
}
- else
- {
- var current = ImageInfos;
- var currentCount = current.Length;
- var newArr = new ItemImageInfo[currentCount + 1];
- current.CopyTo(newArr, 0);
- newArr[currentCount] = image;
- ImageInfos = newArr;
- }
}
public void SetImagePath(ImageType type, int index, FileSystemMetadata file)
@@ -2298,7 +1973,7 @@ namespace MediaBrowser.Controller.Entities
if (image == null)
{
- ImageInfos = ImageInfos.Concat(new[] { GetImageInfo(file, type) }).ToArray();
+ AddImage(GetImageInfo(file, type));
}
else
{
@@ -2342,14 +2017,24 @@ namespace MediaBrowser.Controller.Entities
public void RemoveImage(ItemImageInfo image)
{
- RemoveImages(new List<ItemImageInfo> { image });
+ RemoveImages(new[] { image });
}
- public void RemoveImages(List<ItemImageInfo> deletedImages)
+ public void RemoveImages(IEnumerable<ItemImageInfo> deletedImages)
{
ImageInfos = ImageInfos.Except(deletedImages).ToArray();
}
+ public void AddImage(ItemImageInfo image)
+ {
+ var current = ImageInfos;
+ var currentCount = current.Length;
+ var newArr = new ItemImageInfo[currentCount + 1];
+ current.CopyTo(newArr, 0);
+ newArr[currentCount] = image;
+ ImageInfos = newArr;
+ }
+
public virtual Task UpdateToRepositoryAsync(ItemUpdateType updateReason, CancellationToken cancellationToken)
=> LibraryManager.UpdateItemAsync(this, GetParent(), updateReason, cancellationToken);
@@ -2368,12 +2053,12 @@ namespace MediaBrowser.Controller.Entities
.ToList();
var deletedImages = ImageInfos
- .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparer.OrdinalIgnoreCase))
+ .Where(image => image.IsLocalFile && !allFiles.Contains(image.Path, StringComparison.OrdinalIgnoreCase))
.ToList();
if (deletedImages.Count > 0)
{
- ImageInfos = ImageInfos.Except(deletedImages).ToArray();
+ RemoveImages(deletedImages);
}
return deletedImages.Count > 0;
@@ -2495,11 +2180,11 @@ namespace MediaBrowser.Controller.Entities
}
/// <summary>
- /// Adds the images.
+ /// Adds the images, updating metadata if they already are part of this item.
/// </summary>
/// <param name="imageType">Type of the image.</param>
/// <param name="images">The images.</param>
- /// <returns><c>true</c> if XXXX, <c>false</c> otherwise.</returns>
+ /// <returns><c>true</c> if images were added or updated, <c>false</c> otherwise.</returns>
/// <exception cref="ArgumentException">Cannot call AddImages with chapter images.</exception>
public bool AddImages(ImageType imageType, List<FileSystemMetadata> images)
{
@@ -2512,7 +2197,6 @@ namespace MediaBrowser.Controller.Entities
.ToList();
var newImageList = new List<FileSystemMetadata>();
- var imageAdded = false;
var imageUpdated = false;
foreach (var newImage in images)
@@ -2528,7 +2212,6 @@ namespace MediaBrowser.Controller.Entities
if (existing == null)
{
newImageList.Add(newImage);
- imageAdded = true;
}
else
{
@@ -2549,19 +2232,6 @@ namespace MediaBrowser.Controller.Entities
}
}
- if (imageAdded || images.Count != existingImages.Count)
- {
- var newImagePaths = images.Select(i => i.FullName).ToList();
-
- var deleted = existingImages
- .FindAll(i => i.IsLocalFile && !newImagePaths.Contains(i.Path.AsSpan(), StringComparison.OrdinalIgnoreCase) && !File.Exists(i.Path));
-
- if (deleted.Count > 0)
- {
- ImageInfos = ImageInfos.Except(deleted).ToArray();
- }
- }
-
if (newImageList.Count > 0)
{
ImageInfos = ImageInfos.Concat(newImageList.Select(i => GetImageInfo(i, imageType))).ToArray();
@@ -2612,7 +2282,7 @@ namespace MediaBrowser.Controller.Entities
public bool AllowsMultipleImages(ImageType type)
{
- return type == ImageType.Backdrop || type == ImageType.Screenshot || type == ImageType.Chapter;
+ return type == ImageType.Backdrop || type == ImageType.Chapter;
}
public Task SwapImagesAsync(ImageType type, int index1, int index2)
@@ -2730,7 +2400,7 @@ namespace MediaBrowser.Controller.Entities
protected static string GetMappedPath(BaseItem item, string path, MediaProtocol? protocol)
{
- if (protocol.HasValue && protocol.Value == MediaProtocol.File)
+ if (protocol == MediaProtocol.File)
{
return LibraryManager.GetPathAfterNetworkSubstitution(path, item);
}
@@ -2758,8 +2428,10 @@ namespace MediaBrowser.Controller.Entities
protected Task RefreshMetadataForOwnedItem(BaseItem ownedItem, bool copyTitleMetadata, MetadataRefreshOptions options, CancellationToken cancellationToken)
{
- var newOptions = new MetadataRefreshOptions(options);
- newOptions.SearchResult = null;
+ var newOptions = new MetadataRefreshOptions(options)
+ {
+ SearchResult = null
+ };
var item = this;
@@ -2820,8 +2492,10 @@ namespace MediaBrowser.Controller.Entities
protected Task RefreshMetadataForOwnedVideo(MetadataRefreshOptions options, bool copyTitleMetadata, string path, CancellationToken cancellationToken)
{
- var newOptions = new MetadataRefreshOptions(options);
- newOptions.SearchResult = null;
+ var newOptions = new MetadataRefreshOptions(options)
+ {
+ SearchResult = null
+ };
var id = LibraryManager.GetNewItemId(path, typeof(Video));
@@ -2835,14 +2509,6 @@ namespace MediaBrowser.Controller.Entities
newOptions.ForceSave = true;
}
- // var parentId = Id;
- // if (!video.IsOwnedItem || video.ParentId != parentId)
- // {
- // video.IsOwnedItem = true;
- // video.ParentId = parentId;
- // newOptions.ForceSave = true;
- // }
-
if (video == null)
{
return Task.FromResult(true);
@@ -2926,9 +2592,9 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(i => new Tuple<string, int?>(i, LocalizationManager.GetRatingLevel(i)))
+ .Select(rating => (rating, LocalizationManager.GetRatingLevel(rating)))
.OrderBy(i => i.Item2 ?? 1000)
- .Select(i => i.Item1);
+ .Select(i => i.rating);
OfficialRating = ratings.FirstOrDefault() ?? currentOfficialRating;
@@ -2938,14 +2604,14 @@ namespace MediaBrowser.Controller.Entities
StringComparison.OrdinalIgnoreCase);
}
- public IEnumerable<BaseItem> GetThemeSongs()
+ public IReadOnlyList<BaseItem> GetThemeSongs()
{
- return ThemeSongIds.Select(LibraryManager.GetItemById);
+ return GetExtras().Where(e => e.ExtraType == Model.Entities.ExtraType.ThemeSong).ToArray();
}
- public IEnumerable<BaseItem> GetThemeVideos()
+ public IReadOnlyList<BaseItem> GetThemeVideos()
{
- return ThemeVideoIds.Select(LibraryManager.GetItemById);
+ return GetExtras().Where(e => e.ExtraType == Model.Entities.ExtraType.ThemeVideo).ToArray();
}
/// <summary>
@@ -2973,18 +2639,6 @@ namespace MediaBrowser.Controller.Entities
.Where(i => i.ExtraType.HasValue && extraTypes.Contains(i.ExtraType.Value));
}
- public IEnumerable<BaseItem> GetTrailers()
- {
- if (this is IHasTrailers)
- {
- return ((IHasTrailers)this).LocalTrailerIds.Select(LibraryManager.GetItemById).Where(i => i != null).OrderBy(i => i.SortName);
- }
- else
- {
- return Array.Empty<BaseItem>();
- }
- }
-
public virtual long GetRunTimeTicksForPlayState()
{
return RunTimeTicks ?? 0;
@@ -2997,7 +2651,7 @@ namespace MediaBrowser.Controller.Entities
}
/// <inheritdoc />
- public bool Equals(BaseItem other) => object.Equals(Id, other?.Id);
+ public bool Equals(BaseItem other) => Id == other?.Id;
/// <inheritdoc />
public override int GetHashCode() => HashCode.Combine(Id);
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index e88121212..e0583e630 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -44,14 +44,15 @@ namespace MediaBrowser.Controller.Entities
/// <param name="file">The file.</param>
public static void SetImagePath(this BaseItem item, ImageType imageType, string file)
{
- if (file.StartsWith("http", System.StringComparison.OrdinalIgnoreCase))
+ if (file.StartsWith("http", StringComparison.OrdinalIgnoreCase))
{
item.SetImage(
- new ItemImageInfo
- {
- Path = file,
- Type = imageType
- }, 0);
+ new ItemImageInfo
+ {
+ Path = file,
+ Type = imageType
+ },
+ 0);
}
else
{
diff --git a/MediaBrowser.Controller/Entities/Folder.cs b/MediaBrowser.Controller/Entities/Folder.cs
index 18b4ec3c6..55551e70e 100644
--- a/MediaBrowser.Controller/Entities/Folder.cs
+++ b/MediaBrowser.Controller/Entities/Folder.cs
@@ -303,7 +303,7 @@ namespace MediaBrowser.Controller.Entities
if (dictionary.ContainsKey(id))
{
Logger.LogError(
- "Found folder containing items with duplicate id. Path: {path}, Child Name: {ChildName}",
+ "Found folder containing items with duplicate id. Path: {Path}, Child Name: {ChildName}",
Path ?? Name,
child.Path ?? child.Name);
}
@@ -425,7 +425,7 @@ namespace MediaBrowser.Controller.Entities
{
if (item.IsFileProtocol)
{
- Logger.LogDebug("Removed item: " + item.Path);
+ Logger.LogDebug("Removed item: {Path}", item.Path);
item.SetParent(null);
LibraryManager.DeleteItem(item, new DeleteOptions { DeleteFileLocation = false }, this, false);
@@ -792,7 +792,7 @@ namespace MediaBrowser.Controller.Entities
private bool RequiresPostFiltering2(InternalItemsQuery query)
{
- if (query.IncludeItemTypes.Length == 1 && string.Equals(query.IncludeItemTypes[0], nameof(BoxSet), StringComparison.OrdinalIgnoreCase))
+ if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.BoxSet)
{
Logger.LogDebug("Query requires post-filtering due to BoxSet query");
return true;
@@ -807,7 +807,7 @@ namespace MediaBrowser.Controller.Entities
{
if (this is not ICollectionFolder)
{
- Logger.LogDebug("Query requires post-filtering due to LinkedChildren. Type: " + GetType().Name);
+ Logger.LogDebug("{Type}: Query requires post-filtering due to LinkedChildren.", GetType().Name);
return true;
}
}
@@ -882,7 +882,7 @@ namespace MediaBrowser.Controller.Entities
if (query.IsPlayed.HasValue)
{
- if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(nameof(Series)))
+ if (query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes.Contains(BaseItemKind.Series))
{
Logger.LogDebug("Query requires post-filtering due to IsPlayed");
return true;
@@ -1013,6 +1013,7 @@ namespace MediaBrowser.Controller.Entities
items = CollapseBoxSetItemsIfNeeded(items, query, this, user, ConfigurationManager, CollectionManager);
}
+ #pragma warning disable CA1309
if (!string.IsNullOrEmpty(query.NameStartsWithOrGreater))
{
items = items.Where(i => string.Compare(query.NameStartsWithOrGreater, i.SortName, StringComparison.InvariantCultureIgnoreCase) < 1);
@@ -1027,6 +1028,7 @@ namespace MediaBrowser.Controller.Entities
{
items = items.Where(i => string.Compare(query.NameLessThan, i.SortName, StringComparison.InvariantCultureIgnoreCase) == 1);
}
+ #pragma warning restore CA1309
// This must be the last filter
if (!string.IsNullOrEmpty(query.AdjacentTo))
@@ -1099,7 +1101,7 @@ namespace MediaBrowser.Controller.Entities
return false;
}
- if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains("Movie", StringComparer.OrdinalIgnoreCase))
+ if (query.IncludeItemTypes.Length == 0 || query.IncludeItemTypes.Contains(BaseItemKind.Movie))
{
param = true;
}
diff --git a/MediaBrowser.Controller/Entities/Genre.cs b/MediaBrowser.Controller/Entities/Genre.cs
index 338f96204..4be673237 100644
--- a/MediaBrowser.Controller/Entities/Genre.cs
+++ b/MediaBrowser.Controller/Entities/Genre.cs
@@ -6,7 +6,7 @@ using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
using Diacritics.Extensions;
-using MediaBrowser.Controller.Entities.Audio;
+using Jellyfin.Data.Enums;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Entities
@@ -66,10 +66,10 @@ namespace MediaBrowser.Controller.Entities
query.GenreIds = new[] { Id };
query.ExcludeItemTypes = new[]
{
- nameof(MusicVideo),
- nameof(Entities.Audio.Audio),
- nameof(MusicAlbum),
- nameof(MusicArtist)
+ BaseItemKind.MusicVideo,
+ BaseItemKind.Audio,
+ BaseItemKind.MusicAlbum,
+ BaseItemKind.MusicArtist
};
return LibraryManager.GetItemList(query);
diff --git a/MediaBrowser.Controller/Entities/IHasScreenshots.cs b/MediaBrowser.Controller/Entities/IHasScreenshots.cs
deleted file mode 100644
index ae01c223e..000000000
--- a/MediaBrowser.Controller/Entities/IHasScreenshots.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace MediaBrowser.Controller.Entities
-{
- /// <summary>
- /// The item has screenshots.
- /// </summary>
- public interface IHasScreenshots
- {
- }
-}
diff --git a/MediaBrowser.Controller/Entities/IHasShares.cs b/MediaBrowser.Controller/Entities/IHasShares.cs
index dca5af873..e6fa27703 100644
--- a/MediaBrowser.Controller/Entities/IHasShares.cs
+++ b/MediaBrowser.Controller/Entities/IHasShares.cs
@@ -8,4 +8,4 @@ namespace MediaBrowser.Controller.Entities
{
Share[] Shares { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs
index f317a02ff..f47d2162f 100644
--- a/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs
+++ b/MediaBrowser.Controller/Entities/IHasSpecialFeatures.cs
@@ -10,9 +10,9 @@ namespace MediaBrowser.Controller.Entities
public interface IHasSpecialFeatures
{
/// <summary>
- /// Gets or sets the special feature ids.
+ /// Gets the special feature ids.
/// </summary>
/// <value>The special feature ids.</value>
- IReadOnlyList<Guid> SpecialFeatureIds { get; set; }
+ IReadOnlyList<Guid> SpecialFeatureIds { get; }
}
}
diff --git a/MediaBrowser.Controller/Entities/IHasTrailers.cs b/MediaBrowser.Controller/Entities/IHasTrailers.cs
index f4271678d..bb4a6ea94 100644
--- a/MediaBrowser.Controller/Entities/IHasTrailers.cs
+++ b/MediaBrowser.Controller/Entities/IHasTrailers.cs
@@ -2,7 +2,6 @@
#pragma warning disable CS1591
-using System;
using System.Collections.Generic;
using MediaBrowser.Model.Entities;
@@ -17,18 +16,10 @@ namespace MediaBrowser.Controller.Entities
IReadOnlyList<MediaUrl> RemoteTrailers { get; set; }
/// <summary>
- /// Gets or sets the local trailer ids.
+ /// Gets the local trailers.
/// </summary>
- /// <value>The local trailer ids.</value>
- IReadOnlyList<Guid> LocalTrailerIds { get; set; }
-
- /// <summary>
- /// Gets or sets the remote trailer ids.
- /// </summary>
- /// <value>The remote trailer ids.</value>
- IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
-
- Guid Id { get; set; }
+ /// <value>The local trailers.</value>
+ IReadOnlyList<BaseItem> LocalTrailers { get; }
}
/// <summary>
@@ -42,57 +33,6 @@ namespace MediaBrowser.Controller.Entities
/// <param name="item">Media item.</param>
/// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
public static int GetTrailerCount(this IHasTrailers item)
- => item.LocalTrailerIds.Count + item.RemoteTrailerIds.Count;
-
- /// <summary>
- /// Gets the trailer ids.
- /// </summary>
- /// <param name="item">Media item.</param>
- /// <returns><see cref="IReadOnlyList{Guid}" />.</returns>
- public static IReadOnlyList<Guid> GetTrailerIds(this IHasTrailers item)
- {
- var localIds = item.LocalTrailerIds;
- var remoteIds = item.RemoteTrailerIds;
-
- var all = new Guid[localIds.Count + remoteIds.Count];
- var index = 0;
- foreach (var id in localIds)
- {
- all[index++] = id;
- }
-
- foreach (var id in remoteIds)
- {
- all[index++] = id;
- }
-
- return all;
- }
-
- /// <summary>
- /// Gets the trailers.
- /// </summary>
- /// <param name="item">Media item.</param>
- /// <returns><see cref="IReadOnlyList{BaseItem}" />.</returns>
- public static IReadOnlyList<BaseItem> GetTrailers(this IHasTrailers item)
- {
- var localIds = item.LocalTrailerIds;
- var remoteIds = item.RemoteTrailerIds;
- var libraryManager = BaseItem.LibraryManager;
-
- var all = new BaseItem[localIds.Count + remoteIds.Count];
- var index = 0;
- foreach (var id in localIds)
- {
- all[index++] = libraryManager.GetItemById(id);
- }
-
- foreach (var id in remoteIds)
- {
- all[index++] = libraryManager.GetItemById(id);
- }
-
- return all;
- }
+ => item.LocalTrailers.Count + item.RemoteTrailers.Count;
}
}
diff --git a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index 0baa7725e..db1697c79 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -27,18 +27,18 @@ namespace MediaBrowser.Controller.Entities
ExcludeArtistIds = Array.Empty<Guid>();
ExcludeInheritedTags = Array.Empty<string>();
ExcludeItemIds = Array.Empty<Guid>();
- ExcludeItemTypes = Array.Empty<string>();
+ ExcludeItemTypes = Array.Empty<BaseItemKind>();
ExcludeTags = Array.Empty<string>();
GenreIds = Array.Empty<Guid>();
Genres = Array.Empty<string>();
GroupByPresentationUniqueKey = true;
ImageTypes = Array.Empty<ImageType>();
- IncludeItemTypes = Array.Empty<string>();
+ IncludeItemTypes = Array.Empty<BaseItemKind>();
ItemIds = Array.Empty<Guid>();
MediaTypes = Array.Empty<string>();
MinSimilarityScore = 20;
OfficialRatings = Array.Empty<string>();
- OrderBy = Array.Empty<ValueTuple<string, SortOrder>>();
+ OrderBy = Array.Empty<(string, SortOrder)>();
PersonIds = Array.Empty<Guid>();
PersonTypes = Array.Empty<string>();
PresetViews = Array.Empty<string>();
@@ -87,9 +87,9 @@ namespace MediaBrowser.Controller.Entities
public string[] MediaTypes { get; set; }
- public string[] IncludeItemTypes { get; set; }
+ public BaseItemKind[] IncludeItemTypes { get; set; }
- public string[] ExcludeItemTypes { get; set; }
+ public BaseItemKind[] ExcludeItemTypes { get; set; }
public string[] ExcludeTags { get; set; }
@@ -229,7 +229,7 @@ namespace MediaBrowser.Controller.Entities
public Guid ParentId { get; set; }
- public string? ParentType { get; set; }
+ public BaseItemKind? ParentType { get; set; }
public Guid[] AncestorIds { get; set; }
@@ -271,7 +271,7 @@ namespace MediaBrowser.Controller.Entities
public bool? HasChapterImages { get; set; }
- public IReadOnlyList<(string, SortOrder)> OrderBy { get; set; }
+ public IReadOnlyList<(string OrderBy, SortOrder SortOrder)> OrderBy { get; set; }
public DateTime? MinDateCreated { get; set; }
@@ -314,7 +314,7 @@ namespace MediaBrowser.Controller.Entities
else
{
ParentId = value.Id;
- ParentType = value.GetType().Name;
+ ParentType = value.GetBaseItemKind();
}
}
}
diff --git a/MediaBrowser.Controller/Entities/LinkedChildComparer.cs b/MediaBrowser.Controller/Entities/LinkedChildComparer.cs
index 4e58e2942..de8b16808 100644
--- a/MediaBrowser.Controller/Entities/LinkedChildComparer.cs
+++ b/MediaBrowser.Controller/Entities/LinkedChildComparer.cs
@@ -32,4 +32,4 @@ namespace MediaBrowser.Controller.Entities
return ((obj.Path ?? string.Empty) + (obj.LibraryItemId ?? string.Empty) + obj.Type).GetHashCode(StringComparison.Ordinal);
}
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Entities/LinkedChildType.cs b/MediaBrowser.Controller/Entities/LinkedChildType.cs
index 9ddb7b620..d39e36ff2 100644
--- a/MediaBrowser.Controller/Entities/LinkedChildType.cs
+++ b/MediaBrowser.Controller/Entities/LinkedChildType.cs
@@ -15,4 +15,4 @@
/// </summary>
Shortcut = 1
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
index e46f99cd5..6b93d8d87 100644
--- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
+++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs
@@ -9,7 +9,6 @@ using System.Text.Json.Serialization;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Providers;
-using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
namespace MediaBrowser.Controller.Entities.Movies
@@ -21,10 +20,6 @@ namespace MediaBrowser.Controller.Entities.Movies
{
public BoxSet()
{
- RemoteTrailers = Array.Empty<MediaUrl>();
- LocalTrailerIds = Array.Empty<Guid>();
- RemoteTrailerIds = Array.Empty<Guid>();
-
DisplayOrder = ItemSortBy.PremiereDate;
}
@@ -38,10 +33,9 @@ namespace MediaBrowser.Controller.Entities.Movies
public override bool SupportsPeople => true;
/// <inheritdoc />
- public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
-
- /// <inheritdoc />
- public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
+ public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
+ .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
+ .ToArray();
/// <summary>
/// Gets or sets the display order.
diff --git a/MediaBrowser.Controller/Entities/Movies/Movie.cs b/MediaBrowser.Controller/Entities/Movies/Movie.cs
index b54bbf5eb..dfaf03fda 100644
--- a/MediaBrowser.Controller/Entities/Movies/Movie.cs
+++ b/MediaBrowser.Controller/Entities/Movies/Movie.cs
@@ -7,12 +7,9 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
-using System.Threading;
-using System.Threading.Tasks;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Model.Entities;
-using MediaBrowser.Model.IO;
using MediaBrowser.Model.Providers;
namespace MediaBrowser.Controller.Entities.Movies
@@ -22,22 +19,16 @@ namespace MediaBrowser.Controller.Entities.Movies
/// </summary>
public class Movie : Video, IHasSpecialFeatures, IHasTrailers, IHasLookupInfo<MovieInfo>, ISupportsBoxSetGrouping
{
- public Movie()
- {
- SpecialFeatureIds = Array.Empty<Guid>();
- RemoteTrailers = Array.Empty<MediaUrl>();
- LocalTrailerIds = Array.Empty<Guid>();
- RemoteTrailerIds = Array.Empty<Guid>();
- }
-
- /// <inheritdoc />
- public IReadOnlyList<Guid> SpecialFeatureIds { get; set; }
-
/// <inheritdoc />
- public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
+ public IReadOnlyList<Guid> SpecialFeatureIds => GetExtras()
+ .Where(extra => extra.ExtraType != null && extra is Video)
+ .Select(extra => extra.Id)
+ .ToArray();
/// <inheritdoc />
- public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
+ public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
+ .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
+ .ToArray();
/// <summary>
/// Gets or sets the name of the TMDB collection.
@@ -66,54 +57,6 @@ namespace MediaBrowser.Controller.Entities.Movies
return 2.0 / 3;
}
- protected override async Task<bool> RefreshedOwnedItems(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
- {
- var hasChanges = await base.RefreshedOwnedItems(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
-
- // Must have a parent to have special features
- // In other words, it must be part of the Parent/Child tree
- if (IsFileProtocol && SupportsOwnedItems && !IsInMixedFolder)
- {
- var specialFeaturesChanged = await RefreshSpecialFeatures(options, fileSystemChildren, cancellationToken).ConfigureAwait(false);
-
- if (specialFeaturesChanged)
- {
- hasChanges = true;
- }
- }
-
- return hasChanges;
- }
-
- private async Task<bool> RefreshSpecialFeatures(MetadataRefreshOptions options, List<FileSystemMetadata> fileSystemChildren, CancellationToken cancellationToken)
- {
- var newItems = LibraryManager.FindExtras(this, fileSystemChildren, options.DirectoryService).ToList();
- var newItemIds = newItems.Select(i => i.Id).ToArray();
-
- var itemsChanged = !SpecialFeatureIds.SequenceEqual(newItemIds);
-
- var ownerId = Id;
-
- var tasks = newItems.Select(i =>
- {
- var subOptions = new MetadataRefreshOptions(options);
-
- if (i.OwnerId != ownerId)
- {
- i.OwnerId = ownerId;
- subOptions.ForceSave = true;
- }
-
- return RefreshMetadataForOwnedItem(i, false, subOptions, cancellationToken);
- });
-
- await Task.WhenAll(tasks).ConfigureAwait(false);
-
- SpecialFeatureIds = newItemIds;
-
- return itemsChanged;
- }
-
/// <inheritdoc />
public override UnratedItem GetBlockUnratedType()
{
diff --git a/MediaBrowser.Controller/Entities/TV/Episode.cs b/MediaBrowser.Controller/Entities/TV/Episode.cs
index 27c3ff81b..dcc752f8c 100644
--- a/MediaBrowser.Controller/Entities/TV/Episode.cs
+++ b/MediaBrowser.Controller/Entities/TV/Episode.cs
@@ -20,18 +20,10 @@ namespace MediaBrowser.Controller.Entities.TV
/// </summary>
public class Episode : Video, IHasTrailers, IHasLookupInfo<EpisodeInfo>, IHasSeries
{
- public Episode()
- {
- RemoteTrailers = Array.Empty<MediaUrl>();
- LocalTrailerIds = Array.Empty<Guid>();
- RemoteTrailerIds = Array.Empty<Guid>();
- }
-
- /// <inheritdoc />
- public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
-
/// <inheritdoc />
- public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
+ public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
+ .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
+ .ToArray();
/// <summary>
/// Gets or sets the season in which it aired.
diff --git a/MediaBrowser.Controller/Entities/TV/Series.cs b/MediaBrowser.Controller/Entities/TV/Series.cs
index e4933e968..bdadc2775 100644
--- a/MediaBrowser.Controller/Entities/TV/Series.cs
+++ b/MediaBrowser.Controller/Entities/TV/Series.cs
@@ -27,9 +27,6 @@ namespace MediaBrowser.Controller.Entities.TV
{
public Series()
{
- RemoteTrailers = Array.Empty<MediaUrl>();
- LocalTrailerIds = Array.Empty<Guid>();
- RemoteTrailerIds = Array.Empty<Guid>();
AirDays = Array.Empty<DayOfWeek>();
}
@@ -53,10 +50,9 @@ namespace MediaBrowser.Controller.Entities.TV
public override bool SupportsPeople => true;
/// <inheritdoc />
- public IReadOnlyList<Guid> LocalTrailerIds { get; set; }
-
- /// <inheritdoc />
- public IReadOnlyList<Guid> RemoteTrailerIds { get; set; }
+ public IReadOnlyList<BaseItem> LocalTrailers => GetExtras()
+ .Where(extra => extra.ExtraType == Model.Entities.ExtraType.Trailer)
+ .ToArray();
/// <summary>
/// Gets or sets the display order.
@@ -131,7 +127,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
- IncludeItemTypes = new[] { nameof(Season) },
+ IncludeItemTypes = new[] { BaseItemKind.Season },
IsVirtualItem = false,
Limit = 0,
DtoOptions = new DtoOptions(false)
@@ -159,7 +155,7 @@ namespace MediaBrowser.Controller.Entities.TV
if (query.IncludeItemTypes.Length == 0)
{
- query.IncludeItemTypes = new[] { nameof(Episode) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Episode };
}
query.IsVirtualItem = false;
@@ -213,7 +209,7 @@ namespace MediaBrowser.Controller.Entities.TV
query.AncestorWithPresentationUniqueKey = null;
query.SeriesPresentationUniqueKey = seriesKey;
- query.IncludeItemTypes = new[] { nameof(Season) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Season };
query.OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) };
if (user != null && !user.DisplayMissingEpisodes)
@@ -239,7 +235,7 @@ namespace MediaBrowser.Controller.Entities.TV
if (query.IncludeItemTypes.Length == 0)
{
- query.IncludeItemTypes = new[] { nameof(Episode), nameof(Season) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season };
}
query.IsVirtualItem = false;
@@ -259,7 +255,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
- IncludeItemTypes = new[] { nameof(Episode), nameof(Season) },
+ IncludeItemTypes = new[] { BaseItemKind.Episode, BaseItemKind.Season },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options
};
@@ -363,7 +359,7 @@ namespace MediaBrowser.Controller.Entities.TV
{
AncestorWithPresentationUniqueKey = queryFromSeries ? null : seriesKey,
SeriesPresentationUniqueKey = queryFromSeries ? seriesKey : null,
- IncludeItemTypes = new[] { nameof(Episode) },
+ IncludeItemTypes = new[] { BaseItemKind.Episode },
OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options
};
diff --git a/MediaBrowser.Controller/Entities/TagExtensions.cs b/MediaBrowser.Controller/Entities/TagExtensions.cs
index 2ce396daf..ec3eb0f70 100644
--- a/MediaBrowser.Controller/Entities/TagExtensions.cs
+++ b/MediaBrowser.Controller/Entities/TagExtensions.cs
@@ -2,6 +2,7 @@
using System;
using System.Linq;
+using Jellyfin.Extensions;
namespace MediaBrowser.Controller.Entities
{
@@ -16,7 +17,7 @@ namespace MediaBrowser.Controller.Entities
var current = item.Tags;
- if (!current.Contains(name, StringComparer.OrdinalIgnoreCase))
+ if (!current.Contains(name, StringComparison.OrdinalIgnoreCase))
{
if (current.Length == 0)
{
diff --git a/MediaBrowser.Controller/Entities/UserView.cs b/MediaBrowser.Controller/Entities/UserView.cs
index 62f3c4b55..5c9be7337 100644
--- a/MediaBrowser.Controller/Entities/UserView.cs
+++ b/MediaBrowser.Controller/Entities/UserView.cs
@@ -8,6 +8,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading.Tasks;
using Jellyfin.Data.Entities;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Querying;
@@ -102,7 +103,7 @@ namespace MediaBrowser.Controller.Entities
parent = LibraryManager.GetItemById(ParentId) as Folder ?? parent;
}
- return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager, ConfigurationManager)
+ return new UserViewBuilder(UserViewManager, LibraryManager, Logger, UserDataManager, TVSeriesManager)
.GetUserItems(parent, this, CollectionType, query);
}
@@ -170,12 +171,12 @@ namespace MediaBrowser.Controller.Entities
public static bool IsEligibleForGrouping(string viewType)
{
- return _viewTypesEligibleForGrouping.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ return _viewTypesEligibleForGrouping.Contains(viewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
public static bool EnableOriginalFolder(string viewType)
{
- return _originalFolderViewTypes.Contains(viewType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ return _originalFolderViewTypes.Contains(viewType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
protected override Task ValidateChildrenInternal(IProgress<double> progress, bool recursive, bool refreshChildMetadata, Providers.MetadataRefreshOptions refreshOptions, Providers.IDirectoryService directoryService, System.Threading.CancellationToken cancellationToken)
diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
index 266fda767..fe44f1169 100644
--- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs
+++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs
@@ -8,8 +8,7 @@ using System.Globalization;
using System.Linq;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Entities.Movies;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.TV;
using MediaBrowser.Model.Entities;
@@ -17,8 +16,6 @@ using MediaBrowser.Model.Querying;
using Microsoft.Extensions.Logging;
using Episode = MediaBrowser.Controller.Entities.TV.Episode;
using MetadataProvider = MediaBrowser.Model.Entities.MetadataProvider;
-using Movie = MediaBrowser.Controller.Entities.Movies.Movie;
-using Season = MediaBrowser.Controller.Entities.TV.Season;
using Series = MediaBrowser.Controller.Entities.TV.Series;
namespace MediaBrowser.Controller.Entities
@@ -30,22 +27,19 @@ namespace MediaBrowser.Controller.Entities
private readonly ILogger<BaseItem> _logger;
private readonly IUserDataManager _userDataManager;
private readonly ITVSeriesManager _tvSeriesManager;
- private readonly IServerConfigurationManager _config;
public UserViewBuilder(
IUserViewManager userViewManager,
ILibraryManager libraryManager,
ILogger<BaseItem> logger,
IUserDataManager userDataManager,
- ITVSeriesManager tvSeriesManager,
- IServerConfigurationManager config)
+ ITVSeriesManager tvSeriesManager)
{
_userViewManager = userViewManager;
_libraryManager = libraryManager;
_logger = logger;
_userDataManager = userDataManager;
_tvSeriesManager = tvSeriesManager;
- _config = config;
}
public QueryResult<BaseItem> GetUserItems(Folder queryParent, Folder displayParent, string viewType, InternalItemsQuery query)
@@ -144,7 +138,7 @@ namespace MediaBrowser.Controller.Entities
if (query.IncludeItemTypes.Length == 0)
{
- query.IncludeItemTypes = new[] { nameof(Movie) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Movie };
}
return parent.QueryRecursive(query);
@@ -169,7 +163,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { nameof(Movie) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Movie };
return _libraryManager.GetItemsResult(query);
}
@@ -180,7 +174,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { nameof(Series) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Series };
return _libraryManager.GetItemsResult(query);
}
@@ -191,7 +185,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.IsFavorite = true;
- query.IncludeItemTypes = new[] { nameof(Episode) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Episode };
return _libraryManager.GetItemsResult(query);
}
@@ -202,7 +196,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { nameof(Movie) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Movie };
return _libraryManager.GetItemsResult(query);
}
@@ -210,7 +204,7 @@ namespace MediaBrowser.Controller.Entities
private QueryResult<BaseItem> GetMovieCollections(User user, InternalItemsQuery query)
{
query.Parent = null;
- query.IncludeItemTypes = new[] { nameof(BoxSet) };
+ query.IncludeItemTypes = new[] { BaseItemKind.BoxSet };
query.SetUser(user);
query.Recursive = true;
@@ -224,7 +218,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
- query.IncludeItemTypes = new[] { nameof(Movie) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Movie };
return ConvertToResult(_libraryManager.GetItemList(query));
}
@@ -237,7 +231,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
- query.IncludeItemTypes = new[] { nameof(Movie) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Movie };
return ConvertToResult(_libraryManager.GetItemList(query));
}
@@ -256,7 +250,7 @@ namespace MediaBrowser.Controller.Entities
{
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { nameof(Movie) },
+ IncludeItemTypes = new[] { BaseItemKind.Movie },
Recursive = true,
EnableTotalRecordCount = false
}).Items
@@ -287,7 +281,7 @@ namespace MediaBrowser.Controller.Entities
query.GenreIds = new[] { displayParent.Id };
query.SetUser(user);
- query.IncludeItemTypes = new[] { nameof(Movie) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Movie };
return _libraryManager.GetItemsResult(query);
}
@@ -303,9 +297,9 @@ namespace MediaBrowser.Controller.Entities
{
query.IncludeItemTypes = new[]
{
- nameof(Series),
- nameof(Season),
- nameof(Episode)
+ BaseItemKind.Series,
+ BaseItemKind.Season,
+ BaseItemKind.Episode
};
}
@@ -333,7 +327,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
- query.IncludeItemTypes = new[] { nameof(Episode) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Episode };
query.IsVirtualItem = false;
return ConvertToResult(_libraryManager.GetItemList(query));
@@ -364,7 +358,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
query.Limit = GetSpecialItemsLimit();
- query.IncludeItemTypes = new[] { nameof(Episode) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Episode };
return ConvertToResult(_libraryManager.GetItemList(query));
}
@@ -375,7 +369,7 @@ namespace MediaBrowser.Controller.Entities
query.Parent = parent;
query.SetUser(user);
- query.IncludeItemTypes = new[] { nameof(Series) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Series };
return _libraryManager.GetItemsResult(query);
}
@@ -384,7 +378,7 @@ namespace MediaBrowser.Controller.Entities
{
var genres = parent.QueryRecursive(new InternalItemsQuery(user)
{
- IncludeItemTypes = new[] { nameof(Series) },
+ IncludeItemTypes = new[] { BaseItemKind.Series },
Recursive = true,
EnableTotalRecordCount = false
}).Items
@@ -415,7 +409,7 @@ namespace MediaBrowser.Controller.Entities
query.GenreIds = new[] { displayParent.Id };
query.SetUser(user);
- query.IncludeItemTypes = new[] { nameof(Series) };
+ query.IncludeItemTypes = new[] { BaseItemKind.Series };
return _libraryManager.GetItemsResult(query);
}
@@ -498,17 +492,17 @@ namespace MediaBrowser.Controller.Entities
public static bool Filter(BaseItem item, User user, InternalItemsQuery query, IUserDataManager userDataManager, ILibraryManager libraryManager)
{
- if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparer.OrdinalIgnoreCase))
+ if (query.MediaTypes.Length > 0 && !query.MediaTypes.Contains(item.MediaType ?? string.Empty, StringComparison.OrdinalIgnoreCase))
{
return false;
}
- if (query.IncludeItemTypes.Length > 0 && !query.IncludeItemTypes.Contains(item.GetClientTypeName(), StringComparer.OrdinalIgnoreCase))
+ if (query.IncludeItemTypes.Length > 0 && !query.IncludeItemTypes.Contains(item.GetBaseItemKind()))
{
return false;
}
- if (query.ExcludeItemTypes.Length > 0 && query.ExcludeItemTypes.Contains(item.GetClientTypeName(), StringComparer.OrdinalIgnoreCase))
+ if (query.ExcludeItemTypes.Length > 0 && query.ExcludeItemTypes.Contains(item.GetBaseItemKind()))
{
return false;
}
@@ -749,10 +743,9 @@ namespace MediaBrowser.Controller.Entities
var val = query.HasTrailer.Value;
var trailerCount = 0;
- var hasTrailers = item as IHasTrailers;
- if (hasTrailers != null)
+ if (item is IHasTrailers hasTrailers)
{
- trailerCount = hasTrailers.GetTrailerIds().Count;
+ trailerCount = hasTrailers.GetTrailerCount();
}
var ok = val ? trailerCount > 0 : trailerCount == 0;
@@ -767,7 +760,7 @@ namespace MediaBrowser.Controller.Entities
{
var filterValue = query.HasThemeSong.Value;
- var themeCount = item.ThemeSongIds.Length;
+ var themeCount = item.GetThemeSongs().Count;
var ok = filterValue ? themeCount > 0 : themeCount == 0;
if (!ok)
@@ -780,7 +773,7 @@ namespace MediaBrowser.Controller.Entities
{
var filterValue = query.HasThemeVideo.Value;
- var themeCount = item.ThemeVideoIds.Length;
+ var themeCount = item.GetThemeVideos().Count;
var ok = filterValue ? themeCount > 0 : themeCount == 0;
if (!ok)
@@ -790,7 +783,7 @@ namespace MediaBrowser.Controller.Entities
}
// Apply genre filter
- if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparer.OrdinalIgnoreCase)))
+ if (query.Genres.Count > 0 && !query.Genres.Any(v => item.Genres.Contains(v, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
@@ -814,7 +807,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, StringComparer.OrdinalIgnoreCase);
+ return studioItem != null && item.Studios.Contains(studioItem.Name, StringComparison.OrdinalIgnoreCase);
}))
{
return false;
@@ -824,7 +817,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, StringComparer.OrdinalIgnoreCase);
+ return genreItem != null && item.Genres.Contains(genreItem.Name, StringComparison.OrdinalIgnoreCase);
}))
{
return false;
@@ -857,7 +850,7 @@ namespace MediaBrowser.Controller.Entities
var tags = query.Tags;
if (tags.Length > 0)
{
- if (!tags.Any(v => item.Tags.Contains(v, StringComparer.OrdinalIgnoreCase)))
+ if (!tags.Any(v => item.Tags.Contains(v, StringComparison.OrdinalIgnoreCase)))
{
return false;
}
@@ -975,7 +968,7 @@ namespace MediaBrowser.Controller.Entities
{
var folder = i as ICollectionFolder;
- return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}).ToArray();
}
@@ -984,7 +977,7 @@ namespace MediaBrowser.Controller.Entities
{
var folder = i as ICollectionFolder;
- return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ return folder != null && viewTypes.Contains(folder.CollectionType ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}).ToArray();
}
diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 7dd95b85c..3e125602a 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Text.Json.Serialization;
using System.Threading;
using System.Threading.Tasks;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.Persistence;
@@ -33,6 +34,7 @@ namespace MediaBrowser.Controller.Entities
AdditionalParts = Array.Empty<string>();
LocalAlternateVersions = Array.Empty<string>();
SubtitleFiles = Array.Empty<string>();
+ AudioFiles = Array.Empty<string>();
LinkedAlternateVersions = Array.Empty<LinkedChild>();
}
@@ -98,6 +100,12 @@ namespace MediaBrowser.Controller.Entities
public string[] SubtitleFiles { get; set; }
/// <summary>
+ /// Gets or sets the audio paths.
+ /// </summary>
+ /// <value>The audio paths.</value>
+ public string[] AudioFiles { get; set; }
+
+ /// <summary>
/// Gets or sets a value indicating whether this instance has subtitles.
/// </summary>
/// <value><c>true</c> if this instance has subtitles; otherwise, <c>false</c>.</value>
@@ -185,7 +193,7 @@ namespace MediaBrowser.Controller.Entities
{
if (SourceType == SourceType.Channel)
{
- return !Tags.Contains("livestream", StringComparer.OrdinalIgnoreCase);
+ return !Tags.Contains("livestream", StringComparison.OrdinalIgnoreCase);
}
return !IsActiveRecording();
@@ -509,35 +517,35 @@ namespace MediaBrowser.Controller.Entities
}).FirstOrDefault();
}
- protected override List<Tuple<BaseItem, MediaSourceType>> GetAllItemsForMediaSources()
+ protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
{
- var list = new List<Tuple<BaseItem, MediaSourceType>>();
+ var list = new List<(BaseItem, MediaSourceType)>
+ {
+ (this, MediaSourceType.Default)
+ };
- list.Add(new Tuple<BaseItem, MediaSourceType>(this, MediaSourceType.Default));
- list.AddRange(GetLinkedAlternateVersions().Select(i => new Tuple<BaseItem, MediaSourceType>(i, MediaSourceType.Grouping)));
+ list.AddRange(GetLinkedAlternateVersions().Select(i => ((BaseItem)i, MediaSourceType.Grouping)));
if (!string.IsNullOrEmpty(PrimaryVersionId))
{
- var primary = LibraryManager.GetItemById(PrimaryVersionId) as Video;
- if (primary != null)
+ if (LibraryManager.GetItemById(PrimaryVersionId) is Video primary)
{
var existingIds = list.Select(i => i.Item1.Id).ToList();
- list.Add(new Tuple<BaseItem, MediaSourceType>(primary, MediaSourceType.Grouping));
- list.AddRange(primary.GetLinkedAlternateVersions().Where(i => !existingIds.Contains(i.Id)).Select(i => new Tuple<BaseItem, MediaSourceType>(i, MediaSourceType.Grouping)));
+ list.Add((primary, MediaSourceType.Grouping));
+ list.AddRange(primary.GetLinkedAlternateVersions().Where(i => !existingIds.Contains(i.Id)).Select(i => ((BaseItem)i, MediaSourceType.Grouping)));
}
}
var localAlternates = list
.SelectMany(i =>
{
- var video = i.Item1 as Video;
- return video == null ? new List<Guid>() : video.GetLocalAlternateVersionIds();
+ return i.Item1 is Video video ? video.GetLocalAlternateVersionIds() : Enumerable.Empty<Guid>();
})
.Select(LibraryManager.GetItemById)
.Where(i => i != null)
.ToList();
- list.AddRange(localAlternates.Select(i => new Tuple<BaseItem, MediaSourceType>(i, MediaSourceType.Default)));
+ list.AddRange(localAlternates.Select(i => (i, MediaSourceType.Default)));
return list;
}
diff --git a/MediaBrowser.Controller/Entities/Year.cs b/MediaBrowser.Controller/Entities/Year.cs
index 0853200dd..afdaf448b 100644
--- a/MediaBrowser.Controller/Entities/Year.cs
+++ b/MediaBrowser.Controller/Entities/Year.cs
@@ -57,9 +57,7 @@ namespace MediaBrowser.Controller.Entities
public IList<BaseItem> GetTaggedItems(InternalItemsQuery query)
{
- var usCulture = new CultureInfo("en-US");
-
- if (!int.TryParse(Name, NumberStyles.Integer, usCulture, out var year))
+ if (!int.TryParse(Name, NumberStyles.Integer, CultureInfo.InvariantCulture, out var year))
{
return new List<BaseItem>();
}
diff --git a/MediaBrowser.Controller/IO/FileData.cs b/MediaBrowser.Controller/IO/FileData.cs
index b8a0bf331..2429ac42d 100644
--- a/MediaBrowser.Controller/IO/FileData.cs
+++ b/MediaBrowser.Controller/IO/FileData.cs
@@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.IO
if (string.IsNullOrEmpty(newPath))
{
// invalid shortcut - could be old or target could just be unavailable
- logger.LogWarning("Encountered invalid shortcut: " + fullName);
+ logger.LogWarning("Encountered invalid shortcut: {Path}", fullName);
continue;
}
@@ -83,7 +83,7 @@ namespace MediaBrowser.Controller.IO
}
catch (Exception ex)
{
- logger.LogError(ex, "Error resolving shortcut from {path}", fullName);
+ logger.LogError(ex, "Error resolving shortcut from {Path}", fullName);
}
}
else if (flattenFolderDepth > 0 && isDirectory)
diff --git a/MediaBrowser.Controller/IServerApplicationHost.cs b/MediaBrowser.Controller/IServerApplicationHost.cs
index 3da0a5875..75ec5f213 100644
--- a/MediaBrowser.Controller/IServerApplicationHost.cs
+++ b/MediaBrowser.Controller/IServerApplicationHost.cs
@@ -2,7 +2,6 @@
#pragma warning disable CS1591
-using System.Collections.Generic;
using System.Net;
using MediaBrowser.Common;
using MediaBrowser.Model.System;
@@ -43,49 +42,41 @@ namespace MediaBrowser.Controller
string FriendlyName { get; }
/// <summary>
- /// Gets the configured published server url.
- /// </summary>
- string PublishedServerUrl { get; }
-
- /// <summary>
/// Gets the system info.
/// </summary>
- /// <param name="source">The originator of the request.</param>
+ /// <param name="request">The HTTP request.</param>
/// <returns>SystemInfo.</returns>
- SystemInfo GetSystemInfo(IPAddress source);
+ SystemInfo GetSystemInfo(HttpRequest request);
- PublicSystemInfo GetPublicSystemInfo(IPAddress address);
+ PublicSystemInfo GetPublicSystemInfo(HttpRequest request);
/// <summary>
/// Gets a URL specific for the request.
/// </summary>
/// <param name="request">The <see cref="HttpRequest"/> instance.</param>
- /// <param name="port">Optional port number.</param>
/// <returns>An accessible URL.</returns>
- string GetSmartApiUrl(HttpRequest request, int? port = null);
+ string GetSmartApiUrl(HttpRequest request);
/// <summary>
/// Gets a URL specific for the request.
/// </summary>
/// <param name="remoteAddr">The remote <see cref="IPAddress"/> of the connection.</param>
- /// <param name="port">Optional port number.</param>
/// <returns>An accessible URL.</returns>
- string GetSmartApiUrl(IPAddress remoteAddr, int? port = null);
+ string GetSmartApiUrl(IPAddress remoteAddr);
/// <summary>
/// Gets a URL specific for the request.
/// </summary>
/// <param name="hostname">The hostname used in the connection.</param>
- /// <param name="port">Optional port number.</param>
/// <returns>An accessible URL.</returns>
- string GetSmartApiUrl(string hostname, int? port = null);
+ string GetSmartApiUrl(string hostname);
/// <summary>
- /// Gets a localhost URL that can be used to access the API using the loop-back IP address.
- /// over HTTP (not HTTPS).
+ /// Gets an URL that can be used to access the API over LAN.
/// </summary>
+ /// <param name="allowHttps">A value indicating whether to allow HTTPS.</param>
/// <returns>The API URL.</returns>
- string GetLoopbackHttpApiUrl();
+ string GetApiUrlForLocalAccess(bool allowHttps = true);
/// <summary>
/// Gets a local (LAN) URL that can be used to access the API.
@@ -103,8 +94,6 @@ namespace MediaBrowser.Controller
/// <returns>The API URL.</returns>
string GetLocalApiUrl(string hostname, string scheme = null, int? port = null);
- IEnumerable<WakeOnLanInfo> GetWakeOnLanInfo();
-
string ExpandVirtualPath(string path);
string ReverseVirtualPath(string path);
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index d40e56c7d..8db528330 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -6,7 +6,6 @@ using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
-using Emby.Naming.Common;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Dto;
@@ -59,10 +58,12 @@ namespace MediaBrowser.Controller.Library
/// </summary>
/// <param name="fileInfo">The file information.</param>
/// <param name="parent">The parent.</param>
+ /// <param name="directoryService">An instance of <see cref="IDirectoryService"/>.</param>
/// <returns>BaseItem.</returns>
BaseItem ResolvePath(
FileSystemMetadata fileInfo,
- Folder parent = null);
+ Folder parent = null,
+ IDirectoryService directoryService = null);
/// <summary>
/// Resolves a set of files into a list of BaseItem.
@@ -211,7 +212,7 @@ namespace MediaBrowser.Controller.Library
/// <returns>IEnumerable{BaseItem}.</returns>
IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<string> sortBy, SortOrder sortOrder);
- IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy);
+ IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy);
/// <summary>
/// Gets the user root folder.
@@ -397,20 +398,6 @@ namespace MediaBrowser.Controller.Library
string sortName);
/// <summary>
- /// Determines whether [is video file] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is video file] [the specified path]; otherwise, <c>false</c>.</returns>
- bool IsVideoFile(string path);
-
- /// <summary>
- /// Determines whether [is audio file] [the specified path].
- /// </summary>
- /// <param name="path">The path.</param>
- /// <returns><c>true</c> if [is audio file] [the specified path]; otherwise, <c>false</c>.</returns>
- bool IsAudioFile(string path);
-
- /// <summary>
/// Gets the season number from path.
/// </summary>
/// <param name="path">The path.</param>
@@ -441,28 +428,13 @@ namespace MediaBrowser.Controller.Library
Guid GetNewItemId(string key, Type type);
/// <summary>
- /// Finds the trailers.
- /// </summary>
- /// <param name="owner">The owner.</param>
- /// <param name="fileSystemChildren">The file system children.</param>
- /// <param name="directoryService">The directory service.</param>
- /// <returns>IEnumerable&lt;Trailer&gt;.</returns>
- IEnumerable<Video> FindTrailers(
- BaseItem owner,
- List<FileSystemMetadata> fileSystemChildren,
- IDirectoryService directoryService);
-
- /// <summary>
/// Finds the extras.
/// </summary>
/// <param name="owner">The owner.</param>
/// <param name="fileSystemChildren">The file system children.</param>
- /// <param name="directoryService">The directory service.</param>
- /// <returns>IEnumerable&lt;Video&gt;.</returns>
- IEnumerable<Video> FindExtras(
- BaseItem owner,
- List<FileSystemMetadata> fileSystemChildren,
- IDirectoryService directoryService);
+ /// <param name="directoryService">An instance of <see cref="IDirectoryService"/>.</param>
+ /// <returns>IEnumerable&lt;BaseItem&gt;.</returns>
+ IEnumerable<BaseItem> FindExtras(BaseItem owner, List<FileSystemMetadata> fileSystemChildren, IDirectoryService directoryService);
/// <summary>
/// Gets the collection folders.
@@ -601,17 +573,17 @@ namespace MediaBrowser.Controller.Library
void RemoveMediaPath(string virtualFolderName, string mediaPath);
- QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query);
int GetCount(InternalItemsQuery query);
@@ -625,11 +597,5 @@ namespace MediaBrowser.Controller.Library
BaseItem GetParentItem(string parentId, Guid? userId);
BaseItem GetParentItem(Guid? parentId, Guid? userId);
-
- /// <summary>
- /// Gets or creates a static instance of <see cref="NamingOptions"/>.
- /// </summary>
- /// <returns>An instance of the <see cref="NamingOptions"/> class.</returns>
- NamingOptions GetNamingOptions();
}
}
diff --git a/MediaBrowser.Controller/Library/IMediaSourceManager.cs b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
index e802796d3..f1758a9d8 100644
--- a/MediaBrowser.Controller/Library/IMediaSourceManager.cs
+++ b/MediaBrowser.Controller/Library/IMediaSourceManager.cs
@@ -33,13 +33,6 @@ namespace MediaBrowser.Controller.Library
/// <summary>
/// Gets the media streams.
/// </summary>
- /// <param name="mediaSourceId">The media source identifier.</param>
- /// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
- List<MediaStream> GetMediaStreams(string mediaSourceId);
-
- /// <summary>
- /// Gets the media streams.
- /// </summary>
/// <param name="query">The query.</param>
/// <returns>IEnumerable&lt;MediaStream&gt;.</returns>
List<MediaStream> GetMediaStreams(MediaStreamQuery query);
diff --git a/MediaBrowser.Controller/Library/ItemResolveArgs.cs b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
index bfc1e4857..91d162b41 100644
--- a/MediaBrowser.Controller/Library/ItemResolveArgs.cs
+++ b/MediaBrowser.Controller/Library/ItemResolveArgs.cs
@@ -36,6 +36,7 @@ namespace MediaBrowser.Controller.Library
DirectoryService = directoryService;
}
+ // TODO remove dependencies as properties, they should be injected where it makes sense
public IDirectoryService DirectoryService { get; }
/// <summary>
@@ -237,6 +238,40 @@ 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);
+ }
+
+ /// <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()
+ {
+ var numberOfChildren = FileSystemChildren.Length;
+ for (var i = 0; i < numberOfChildren; i++)
+ {
+ var child = FileSystemChildren[i];
+ if (BaseItem.LibraryManager.IgnoreFile(child, Parent))
+ {
+ continue;
+ }
+
+ yield return child;
+ }
+ }
+
+ /// <summary>
/// Returns a hash code for this instance.
/// </summary>
/// <returns>A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table.</returns>
diff --git a/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs b/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs
index 463061e68..1a81a8a31 100644
--- a/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs
@@ -16,4 +16,4 @@ namespace MediaBrowser.Controller.LiveTv
public CancellationTokenSource CancellationTokenSource { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
index dbd18165d..6dc5665b2 100644
--- a/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
+++ b/MediaBrowser.Controller/LiveTv/ILiveTvManager.cs
@@ -251,7 +251,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="fields">The fields.</param>
/// <param name="user">The user.</param>
/// <returns>Task.</returns>
- Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
+ Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null);
/// <summary>
/// Saves the tuner host.
@@ -292,7 +292,7 @@ namespace MediaBrowser.Controller.LiveTv
/// <param name="items">The items.</param>
/// <param name="options">The options.</param>
/// <param name="user">The user.</param>
- void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user);
+ void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user);
Task<List<ChannelInfo>> GetChannelsForListingsProvider(string id, CancellationToken cancellationToken);
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
index 074e023e8..e63874f21 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvChannel.cs
@@ -5,9 +5,9 @@
using System;
using System.Collections.Generic;
using System.Globalization;
-using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
@@ -74,7 +74,7 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase);
+ public bool IsKids => Tags.Contains("Kids", StringComparison.OrdinalIgnoreCase);
[JsonIgnore]
public bool IsRepeat { get; set; }
@@ -107,9 +107,7 @@ namespace MediaBrowser.Controller.LiveTv
{
if (!string.IsNullOrEmpty(Number))
{
- double number = 0;
-
- if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out number))
+ if (double.TryParse(Number, NumberStyles.Any, CultureInfo.InvariantCulture, out double number))
{
return string.Format(CultureInfo.InvariantCulture, "{0:00000.0}", number) + "-" + (Name ?? string.Empty);
}
diff --git a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
index 111dc0d27..6c4a5ea17 100644
--- a/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
+++ b/MediaBrowser.Controller/LiveTv/LiveTvProgram.cs
@@ -8,6 +8,7 @@ using System.Globalization;
using System.Linq;
using System.Text.Json.Serialization;
using Jellyfin.Data.Enums;
+using Jellyfin.Extensions;
using MediaBrowser.Common.Configuration;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Providers;
@@ -66,7 +67,7 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is sports; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsSports => Tags.Contains("Sports", StringComparer.OrdinalIgnoreCase);
+ public bool IsSports => Tags.Contains("Sports", StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Gets or sets a value indicating whether this instance is series.
@@ -80,28 +81,28 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase);
+ public bool IsLive => Tags.Contains("Live", StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Gets a value indicating whether this instance is news.
/// </summary>
/// <value><c>true</c> if this instance is news; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase);
+ public bool IsNews => Tags.Contains("News", StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Gets a value indicating whether this instance is kids.
/// </summary>
/// <value><c>true</c> if this instance is kids; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase);
+ public bool IsKids => Tags.Contains("Kids", StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Gets a value indicating whether this instance is premiere.
/// </summary>
/// <value><c>true</c> if this instance is premiere; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase);
+ public bool IsPremiere => Tags.Contains("Premiere", StringComparison.OrdinalIgnoreCase);
/// <summary>
/// Gets the folder containing the item.
diff --git a/MediaBrowser.Controller/LiveTv/TimerInfo.cs b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
index 1a2e8acb3..62541ea8b 100644
--- a/MediaBrowser.Controller/LiveTv/TimerInfo.cs
+++ b/MediaBrowser.Controller/LiveTv/TimerInfo.cs
@@ -4,8 +4,8 @@
using System;
using System.Collections.Generic;
-using System.Linq;
using System.Text.Json.Serialization;
+using Jellyfin.Extensions;
using MediaBrowser.Model.LiveTv;
namespace MediaBrowser.Controller.LiveTv
@@ -123,11 +123,11 @@ namespace MediaBrowser.Controller.LiveTv
public bool IsMovie { get; set; }
- public bool IsKids => Tags.Contains("Kids", StringComparer.OrdinalIgnoreCase);
+ public bool IsKids => Tags.Contains("Kids", StringComparison.OrdinalIgnoreCase);
- public bool IsSports => Tags.Contains("Sports", StringComparer.OrdinalIgnoreCase);
+ public bool IsSports => Tags.Contains("Sports", StringComparison.OrdinalIgnoreCase);
- public bool IsNews => Tags.Contains("News", StringComparer.OrdinalIgnoreCase);
+ public bool IsNews => Tags.Contains("News", StringComparison.OrdinalIgnoreCase);
public bool IsSeries { get; set; }
@@ -136,10 +136,10 @@ namespace MediaBrowser.Controller.LiveTv
/// </summary>
/// <value><c>true</c> if this instance is live; otherwise, <c>false</c>.</value>
[JsonIgnore]
- public bool IsLive => Tags.Contains("Live", StringComparer.OrdinalIgnoreCase);
+ public bool IsLive => Tags.Contains("Live", StringComparison.OrdinalIgnoreCase);
[JsonIgnore]
- public bool IsPremiere => Tags.Contains("Premiere", StringComparer.OrdinalIgnoreCase);
+ public bool IsPremiere => Tags.Contains("Premiere", StringComparison.OrdinalIgnoreCase);
public int? ProductionYear { get; set; }
diff --git a/MediaBrowser.Controller/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index 47cec7d77..432159d5d 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -13,12 +13,16 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<ItemGroup>
- <PackageReference Include="Diacritics" Version="2.1.20036.1" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
- <PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="5.0.0" />
- <PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All" />
- <PackageReference Include="System.Threading.Tasks.Dataflow" Version="5.0.0" />
+ <PackageReference Include="Diacritics" Version="3.3.10" />
+ <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" />
</ItemGroup>
<ItemGroup>
@@ -39,11 +43,6 @@
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
- <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
- </PropertyGroup>
-
- <PropertyGroup Condition=" '$(Configuration)' == 'Release'">
- <TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
@@ -54,7 +53,7 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
index dd6f468da..462585ce3 100644
--- a/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
+++ b/MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs
@@ -201,4 +201,4 @@ namespace MediaBrowser.Controller.MediaEncoding
return null;
}
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
index bdb379332..d6f69a150 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -21,12 +21,17 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
{
- private static readonly CultureInfo _usCulture = new CultureInfo("en-US");
+ private const string QsvAlias = "qs";
+ private const string VaapiAlias = "va";
+ private const string D3d11vaAlias = "dx11";
+ private const string VideotoolboxAlias = "vt";
+ private const string OpenclAlias = "ocl";
+ private const string CudaAlias = "cu";
private readonly IMediaEncoder _mediaEncoder;
private readonly ISubtitleEncoder _subtitleEncoder;
- private static readonly string[] _videoProfiles = new[]
+ private static readonly string[] _videoProfilesH264 = new[]
{
"ConstrainedBaseline",
"Baseline",
@@ -34,10 +39,15 @@ namespace MediaBrowser.Controller.MediaEncoding
"Main",
"High",
"ProgressiveHigh",
- "ConstrainedHigh"
+ "ConstrainedHigh",
+ "High10"
};
- private static readonly Version _minVersionForCudaOverlay = new Version(4, 4);
+ private static readonly string[] _videoProfilesH265 = new[]
+ {
+ "Main",
+ "Main10"
+ };
public EncodingHelper(
IMediaEncoder mediaEncoder,
@@ -64,15 +74,13 @@ namespace MediaBrowser.Controller.MediaEncoding
var codecMap = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
- { "qsv", hwEncoder + "_qsv" },
- { hwEncoder + "_qsv", hwEncoder + "_qsv" },
- { "nvenc", hwEncoder + "_nvenc" },
{ "amf", hwEncoder + "_amf" },
- { "omx", hwEncoder + "_omx" },
- { hwEncoder + "_v4l2m2m", hwEncoder + "_v4l2m2m" },
- { "mediacodec", hwEncoder + "_mediacodec" },
+ { "nvenc", hwEncoder + "_nvenc" },
+ { "qsv", hwEncoder + "_qsv" },
{ "vaapi", hwEncoder + "_vaapi" },
- { "videotoolbox", hwEncoder + "_videotoolbox" }
+ { "videotoolbox", hwEncoder + "_videotoolbox" },
+ { "v4l2m2m", hwEncoder + "_v4l2m2m" },
+ { "omx", hwEncoder + "_omx" },
};
if (!string.IsNullOrEmpty(hwType)
@@ -93,11 +101,9 @@ namespace MediaBrowser.Controller.MediaEncoding
private bool IsVaapiSupported(EncodingJobInfo state)
{
- var videoStream = state.VideoStream;
-
// vaapi will throw an error with this input
// [vaapi @ 0x7faed8000960] No VAAPI support for codec mpeg4 profile -99.
- if (string.Equals(videoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(state.VideoStream?.Codec, "mpeg4", StringComparison.OrdinalIgnoreCase))
{
return false;
}
@@ -105,82 +111,59 @@ namespace MediaBrowser.Controller.MediaEncoding
return _mediaEncoder.SupportsHwaccel("vaapi");
}
- private bool IsCudaSupported()
+ private bool IsVaapiFullSupported()
{
- return _mediaEncoder.SupportsHwaccel("cuda")
- && _mediaEncoder.SupportsFilter("scale_cuda")
- && _mediaEncoder.SupportsFilter("yadif_cuda")
- && _mediaEncoder.SupportsFilter("hwupload_cuda");
+ return _mediaEncoder.SupportsHwaccel("vaapi")
+ && _mediaEncoder.SupportsFilter("scale_vaapi")
+ && _mediaEncoder.SupportsFilter("deinterlace_vaapi")
+ && _mediaEncoder.SupportsFilter("tonemap_vaapi")
+ && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayVaapiFrameSync)
+ && _mediaEncoder.SupportsFilter("hwupload_vaapi");
}
- private bool IsOpenclTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ private bool IsOpenclFullSupported()
{
- var videoStream = state.VideoStream;
- if (videoStream == null)
- {
- return false;
- }
+ return _mediaEncoder.SupportsHwaccel("opencl")
+ && _mediaEncoder.SupportsFilter("scale_opencl")
+ && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390)
+ && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.OverlayOpenclFrameSync);
+ }
- return options.EnableTonemapping
- && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
- && IsColorDepth10(state)
- && _mediaEncoder.SupportsHwaccel("opencl")
- && _mediaEncoder.SupportsFilter("tonemap_opencl");
+ private bool IsCudaFullSupported()
+ {
+ return _mediaEncoder.SupportsHwaccel("cuda")
+ && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat)
+ && _mediaEncoder.SupportsFilter("yadif_cuda")
+ && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName)
+ && _mediaEncoder.SupportsFilter("overlay_cuda")
+ && _mediaEncoder.SupportsFilter("hwupload_cuda");
}
- private bool IsCudaTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ private bool IsHwTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- var videoStream = state.VideoStream;
- if (videoStream == null)
+ if (state.VideoStream == null)
{
return false;
}
return options.EnableTonemapping
- && (string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
- && IsColorDepth10(state)
- && _mediaEncoder.SupportsHwaccel("cuda")
- && _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapCudaName);
+ && (string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.VideoStream.ColorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ && GetVideoColorBitDepth(state) == 10;
}
- private bool IsVppTonemappingSupported(EncodingJobInfo state, EncodingOptions options)
+ private bool IsVaapiVppTonemapAvailable(EncodingJobInfo state, EncodingOptions options)
{
- var videoStream = state.VideoStream;
- if (videoStream == null)
+ if (state.VideoStream == null)
{
- // Remote stream doesn't have media info, disable vpp tonemapping.
return false;
}
- var codec = videoStream.Codec;
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- // Limited to HEVC for now since the filter doesn't accept master data from VP9.
- return options.EnableVppTonemapping
- && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- && IsColorDepth10(state)
- && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- && _mediaEncoder.SupportsHwaccel("vaapi")
- && _mediaEncoder.SupportsFilter("tonemap_vaapi");
- }
-
- // Hybrid VPP tonemapping for QSV with VAAPI
- if (OperatingSystem.IsLinux() && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- // Limited to HEVC for now since the filter doesn't accept master data from VP9.
- return options.EnableVppTonemapping
- && string.Equals(videoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
- && IsColorDepth10(state)
- && string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- && _mediaEncoder.SupportsHwaccel("vaapi")
- && _mediaEncoder.SupportsFilter("tonemap_vaapi")
- && _mediaEncoder.SupportsHwaccel("qsv");
- }
-
// Native VPP tonemapping may come to QSV in the future.
- return false;
+
+ return options.EnableVppTonemapping
+ && string.Equals(state.VideoStream.ColorTransfer, "smpte2084", StringComparison.OrdinalIgnoreCase)
+ && GetVideoColorBitDepth(state) == 10;
}
/// <summary>
@@ -465,11 +448,20 @@ namespace MediaBrowser.Controller.MediaEncoding
return "copy";
}
- public int GetVideoProfileScore(string profile)
+ public int GetVideoProfileScore(string videoCodec, string videoProfile)
{
// strip spaces because they may be stripped out on the query string
- profile = profile.Replace(" ", string.Empty, StringComparison.Ordinal);
- return Array.FindIndex(_videoProfiles, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
+ string profile = videoProfile.Replace(" ", string.Empty, StringComparison.Ordinal);
+ if (string.Equals("h264", videoCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return Array.FindIndex(_videoProfilesH264, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
+ }
+ else if (string.Equals("hevc", videoCodec, StringComparison.OrdinalIgnoreCase))
+ {
+ return Array.FindIndex(_videoProfilesH265, x => string.Equals(x, profile, StringComparison.OrdinalIgnoreCase));
+ }
+
+ return -1;
}
public string GetInputPathArgument(EncodingJobInfo state)
@@ -528,161 +520,342 @@ namespace MediaBrowser.Controller.MediaEncoding
return codec.ToLowerInvariant();
}
+ private string GetVideoToolboxDeviceArgs(string alias)
+ {
+ alias ??= VideotoolboxAlias;
+
+ // device selection in vt is not supported.
+ return " -init_hw_device videotoolbox=" + alias;
+ }
+
+ private string GetCudaDeviceArgs(int deviceIndex, string alias)
+ {
+ alias ??= CudaAlias;
+ deviceIndex = deviceIndex >= 0
+ ? deviceIndex
+ : 0;
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device cuda={0}:{1}",
+ alias,
+ deviceIndex);
+ }
+
+ private string GetOpenclDeviceArgs(int deviceIndex, string deviceVendorName, string srcDeviceAlias, string alias)
+ {
+ alias ??= OpenclAlias;
+ deviceIndex = deviceIndex >= 0
+ ? deviceIndex
+ : 0;
+ var vendorOpts = string.IsNullOrEmpty(deviceVendorName)
+ ? ":0.0"
+ : ":." + deviceIndex + ",device_vendor=\"" + deviceVendorName + "\"";
+ var options = string.IsNullOrEmpty(srcDeviceAlias)
+ ? vendorOpts
+ : "@" + srcDeviceAlias;
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device opencl={0}{1}",
+ alias,
+ options);
+ }
+
+ private string GetD3d11vaDeviceArgs(int deviceIndex, string deviceVendorId, string alias)
+ {
+ alias ??= D3d11vaAlias;
+ deviceIndex = deviceIndex >= 0 ? deviceIndex : 0;
+ var options = string.IsNullOrEmpty(deviceVendorId)
+ ? deviceIndex.ToString(CultureInfo.InvariantCulture)
+ : ",vendor=" + deviceVendorId;
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device d3d11va={0}:{1}",
+ alias,
+ options);
+ }
+
+ private string GetVaapiDeviceArgs(string renderNodePath, string kernelDriver, string driver, string alias)
+ {
+ alias ??= VaapiAlias;
+ renderNodePath = renderNodePath ?? "/dev/dri/renderD128";
+ var options = string.IsNullOrEmpty(kernelDriver) || string.IsNullOrEmpty(driver)
+ ? renderNodePath
+ : ",kernel_driver=" + kernelDriver + ",driver=" + driver;
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -init_hw_device vaapi={0}:{1}",
+ alias,
+ options);
+ }
+
+ private string GetQsvDeviceArgs(string alias)
+ {
+ var arg = " -init_hw_device qsv=" + (alias ?? QsvAlias);
+ if (OperatingSystem.IsLinux())
+ {
+ // derive qsv from vaapi device
+ return GetVaapiDeviceArgs(null, "i915", "iHD", VaapiAlias) + arg + "@" + VaapiAlias;
+ }
+
+ if (OperatingSystem.IsWindows())
+ {
+ // derive qsv from d3d11va device
+ return GetD3d11vaDeviceArgs(0, "0x8086", D3d11vaAlias) + arg + "@" + D3d11vaAlias;
+ }
+
+ return null;
+ }
+
+ private string GetFilterHwDeviceArgs(string alias)
+ {
+ return string.IsNullOrEmpty(alias)
+ ? string.Empty
+ : " -filter_hw_device " + alias;
+ }
+
+ public string GetGraphicalSubCanvasSize(EncodingJobInfo state)
+ {
+ if (state.SubtitleStream != null
+ && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
+ && !state.SubtitleStream.IsTextSubtitleStream)
+ {
+ 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;
+
+ // setup a relative small canvas_size for overlay_qsv/vaapi to reduce transfer overhead
+ var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, 1080);
+
+ if (overlayW.HasValue && overlayH.HasValue)
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ " -canvas_size {0}x{1}",
+ overlayW.Value,
+ overlayH.Value);
+ }
+ }
+
+ return string.Empty;
+ }
+
/// <summary>
- /// Gets the input argument.
+ /// Gets the input video hwaccel argument.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
- /// <returns>Input arguments.</returns>
- public string GetInputArgument(EncodingJobInfo state, EncodingOptions options)
+ /// <returns>Input video hwaccel arguments.</returns>
+ public string GetInputVideoHwaccelArgs(EncodingJobInfo state, EncodingOptions options)
{
- var arg = new StringBuilder();
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
- var outputVideoCodec = GetVideoEncoder(state, options) ?? string.Empty;
+ if (!state.IsVideoRequest)
+ {
+ return string.Empty;
+ }
+
+ var vidEncoder = GetVideoEncoder(state, options) ?? string.Empty;
+ if (IsCopyCodec(vidEncoder))
+ {
+ return string.Empty;
+ }
+
+ var args = new StringBuilder();
var isWindows = OperatingSystem.IsWindows();
var isLinux = OperatingSystem.IsLinux();
var isMacOS = OperatingSystem.IsMacOS();
-#pragma warning disable CA1508 // Defaults to string.Empty
- var isSwDecoder = string.IsNullOrEmpty(videoDecoder);
-#pragma warning restore CA1508
- var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isQsvDecoder = videoDecoder.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isQsvEncoder = outputVideoCodec.IndexOf("qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
- var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
- var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase);
- var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
- var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
- var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
-
- if (!IsCopyCodec(outputVideoCodec))
- {
- if (state.IsVideoRequest
- && _mediaEncoder.SupportsHwaccel("vaapi")
- && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- if (isVaapiDecoder)
+ var optHwaccelType = options.HardwareAccelerationType;
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isHwTonemapAvailable = IsHwTonemapAvailable(state, options);
+
+ if (string.Equals(optHwaccelType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ if (!isLinux || !_mediaEncoder.SupportsHwaccel("vaapi"))
+ {
+ return string.Empty;
+ }
+
+ var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ if (!isVaapiDecoder && !isVaapiEncoder)
+ {
+ return string.Empty;
+ }
+
+ args.Append(GetVaapiDeviceArgs(options.VaapiDevice, null, null, VaapiAlias));
+ var filterDevArgs = GetFilterHwDeviceArgs(VaapiAlias);
+
+ if (isHwTonemapAvailable && IsOpenclFullSupported())
+ {
+ if (_mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965)
{
- if (isOpenclTonemappingSupported && !isVppTonemappingSupported)
- {
- arg.Append("-init_hw_device vaapi=va:")
- .Append(options.VaapiDevice)
- .Append(" -init_hw_device opencl=ocl@va ")
- .Append("-hwaccel_device va ")
- .Append("-hwaccel_output_format vaapi ")
- .Append("-filter_hw_device ocl ");
- }
- else
+ if (!isVaapiDecoder)
{
- arg.Append("-hwaccel_output_format vaapi ")
- .Append("-vaapi_device ")
- .Append(options.VaapiDevice)
- .Append(' ');
+ args.Append(GetOpenclDeviceArgs(0, null, VaapiAlias, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
}
- else if (!isVaapiDecoder && isVaapiEncoder)
+ else if (_mediaEncoder.IsVaapiDeviceAmd)
+ {
+ args.Append(GetOpenclDeviceArgs(0, "Advanced Micro Devices", null, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
+ }
+ else
{
- arg.Append("-vaapi_device ")
- .Append(options.VaapiDevice)
- .Append(' ');
+ args.Append(GetOpenclDeviceArgs(0, null, null, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
+ }
- arg.Append("-autorotate 0 ");
+ args.Append(filterDevArgs);
+ }
+ else if (string.Equals(optHwaccelType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ if ((!isLinux && !isWindows) || !_mediaEncoder.SupportsHwaccel("qsv"))
+ {
+ return string.Empty;
}
- if (state.IsVideoRequest
- && string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
+ var isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isHwDecoder = isQsvDecoder || isVaapiDecoder || isD3d11vaDecoder;
+ if (!isHwDecoder && !isQsvEncoder)
{
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ return string.Empty;
+ }
- if (isQsvEncoder)
+ args.Append(GetQsvDeviceArgs(QsvAlias));
+ var filterDevArgs = GetFilterHwDeviceArgs(QsvAlias);
+ // child device used by qsv.
+ if (_mediaEncoder.SupportsHwaccel("vaapi") || _mediaEncoder.SupportsHwaccel("d3d11va"))
+ {
+ if (isHwTonemapAvailable && IsOpenclFullSupported())
{
- if (isQsvDecoder)
+ var srcAlias = isLinux ? VaapiAlias : D3d11vaAlias;
+ args.Append(GetOpenclDeviceArgs(0, null, srcAlias, OpenclAlias));
+ if (!isHwDecoder)
{
- if (isLinux)
- {
- if (hasGraphicalSubs)
- {
- arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
- }
- else
- {
- arg.Append("-hwaccel qsv ");
- }
- }
-
- if (isWindows)
- {
- arg.Append("-hwaccel qsv ");
- }
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
+ }
+ }
- // While using SW decoder
- else if (isSwDecoder)
- {
- arg.Append("-init_hw_device qsv=hw -filter_hw_device hw ");
- }
+ args.Append(filterDevArgs);
+ }
+ else if (string.Equals(optHwaccelType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ if ((!isLinux && !isWindows) || !IsCudaFullSupported())
+ {
+ return string.Empty;
+ }
- // Hybrid VPP tonemapping with VAAPI
- else if (isVaapiDecoder && isVppTonemappingSupported)
- {
- arg.Append("-init_hw_device vaapi=va:")
- .Append(options.VaapiDevice)
- .Append(" -init_hw_device qsv@va ")
- .Append("-hwaccel_output_format vaapi ");
- }
+ var isCuvidDecoder = vidDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase);
+ var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
+ var isHwDecoder = isNvdecDecoder || isCuvidDecoder;
+ if (!isHwDecoder && !isNvencEncoder)
+ {
+ return string.Empty;
+ }
- arg.Append("-autorotate 0 ");
- }
+ args.Append(GetCudaDeviceArgs(0, CudaAlias))
+ .Append(GetFilterHwDeviceArgs(CudaAlias));
+
+ // workaround for "No decoder surfaces left" error,
+ // but will increase vram usage. https://trac.ffmpeg.org/ticket/7562
+ args.Append(" -extra_hw_frames 3");
+ }
+ else if (string.Equals(optHwaccelType, "amf", StringComparison.OrdinalIgnoreCase))
+ {
+ if (!isWindows || !_mediaEncoder.SupportsHwaccel("d3d11va"))
+ {
+ return string.Empty;
}
- if (state.IsVideoRequest
- && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
- && isNvdecDecoder)
+ var isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
+ var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
+ if (!isD3d11vaDecoder && !isAmfEncoder)
{
- // Fix for 'No decoder surfaces left' error. https://trac.ffmpeg.org/ticket/7562
- arg.Append("-hwaccel_output_format cuda -extra_hw_frames 3 -autorotate 0 ");
+ return string.Empty;
}
- if (state.IsVideoRequest
- && string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase)
- && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder))
+ // no dxva video processor hw filter.
+ args.Append(GetD3d11vaDeviceArgs(0, "0x1002", D3d11vaAlias));
+ var filterDevArgs = string.Empty;
+ if (IsOpenclFullSupported())
{
- if (!isCudaTonemappingSupported && isOpenclTonemappingSupported)
- {
- arg.Append("-init_hw_device opencl=ocl:")
- .Append(options.OpenclDevice)
- .Append(" -filter_hw_device ocl ");
- }
+ args.Append(GetOpenclDeviceArgs(0, null, D3d11vaAlias, OpenclAlias));
+ filterDevArgs = GetFilterHwDeviceArgs(OpenclAlias);
}
- if (state.IsVideoRequest
- && string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase)
- && (isD3d11vaDecoder || isSwDecoder))
+ args.Append(filterDevArgs);
+ }
+ else if (string.Equals(optHwaccelType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ if (!isMacOS || !_mediaEncoder.SupportsHwaccel("videotoolbox"))
{
- if (isOpenclTonemappingSupported)
- {
- arg.Append("-init_hw_device opencl=ocl:")
- .Append(options.OpenclDevice)
- .Append(" -filter_hw_device ocl ");
- }
+ return string.Empty;
}
- if (state.IsVideoRequest
- && string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ var isVideotoolboxDecoder = vidDecoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+ var isVideotoolboxEncoder = vidEncoder.Contains("videotoolbox", StringComparison.OrdinalIgnoreCase);
+ if (!isVideotoolboxDecoder && !isVideotoolboxEncoder)
{
- arg.Append("-hwaccel videotoolbox ");
+ return string.Empty;
}
+
+ // no videotoolbox hw filter.
+ args.Append(GetVideoToolboxDeviceArgs(VideotoolboxAlias));
+ }
+
+ if (!string.IsNullOrEmpty(vidDecoder))
+ {
+ args.Append(vidDecoder);
+ }
+
+ // hw transpose filters should be added manually.
+ args.Append(" -autorotate 0");
+
+ return args.ToString().Trim();
+ }
+
+ /// <summary>
+ /// Gets the input argument.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <returns>Input arguments.</returns>
+ public string GetInputArgument(EncodingJobInfo state, EncodingOptions options)
+ {
+ var arg = new StringBuilder();
+ var inputVidHwaccelArgs = GetInputVideoHwaccelArgs(state, options);
+
+ if (!string.IsNullOrEmpty(inputVidHwaccelArgs))
+ {
+ arg.Append(inputVidHwaccelArgs);
+ }
+
+ var canvasArgs = GetGraphicalSubCanvasSize(state);
+ if (!string.IsNullOrEmpty(canvasArgs))
+ {
+ arg.Append(canvasArgs);
}
- arg.Append("-i ")
+ arg.Append(" -i ")
.Append(GetInputPathArgument(state));
+ // sub2video for external graphical subtitles
if (state.SubtitleStream != null
&& state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode
- && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream)
+ && !state.SubtitleStream.IsTextSubtitleStream
+ && state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
@@ -695,7 +868,24 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- arg.Append(" -i \"").Append(subtitlePath).Append('\"');
+ // Also seek the external subtitles stream.
+ var seekSubParam = GetFastSeekCommandLineParameter(state, options);
+ if (!string.IsNullOrEmpty(seekSubParam))
+ {
+ arg.Append(' ').Append(seekSubParam);
+ }
+
+ if (!string.IsNullOrEmpty(canvasArgs))
+ {
+ arg.Append(canvasArgs);
+ }
+
+ arg.Append(" -i file:\"").Append(subtitlePath).Append('\"');
+ }
+
+ if (state.AudioStream != null && state.AudioStream.IsExternal)
+ {
+ arg.Append(" -i \"").Append(state.AudioStream.Path).Append('"');
}
return arg.ToString();
@@ -811,12 +1001,32 @@ namespace MediaBrowser.Controller.MediaEncoding
return FormattableString.Invariant($" -maxrate {bitrate} -bufsize {bufsize}");
}
+ if (string.Equals(videoCodec, "h264_amf", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "hevc_amf", StringComparison.OrdinalIgnoreCase))
+ {
+ return FormattableString.Invariant($" -qmin 18 -qmax 32 -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
+ }
+
+ if (string.Equals(videoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ // VBR in i965 driver may result in pixelated output.
+ if (_mediaEncoder.IsVaapiDeviceInteli965)
+ {
+ 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($" -b:v {bitrate} -maxrate {bitrate} -bufsize {bufsize}");
}
public static string NormalizeTranscodingLevel(EncodingJobInfo state, string level)
{
- if (double.TryParse(level, NumberStyles.Any, _usCulture, out double requestLevel))
+ if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double requestLevel))
{
if (string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
|| string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase))
@@ -847,8 +1057,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// Gets the text subtitle param.
/// </summary>
/// <param name="state">The state.</param>
+ /// <param name="enableAlpha">Enable alpha processing.</param>
+ /// <param name="enableSub2video">Enable sub2video mode.</param>
/// <returns>System.String.</returns>
- public string GetTextSubtitleParam(EncodingJobInfo state)
+ public string GetTextSubtitlesFilter(EncodingJobInfo state, bool enableAlpha, bool enableSub2video)
{
var seconds = Math.Round(TimeSpan.FromTicks(state.StartTimeTicks ?? 0).TotalSeconds);
@@ -857,6 +1069,9 @@ namespace MediaBrowser.Controller.MediaEncoding
? string.Empty
: string.Format(CultureInfo.InvariantCulture, ",setpts=PTS -{0}/TB", seconds);
+ var alphaParam = enableAlpha ? ":alpha=1" : string.Empty;
+ var sub2videoParam = enableSub2video ? ":sub2video=1" : string.Empty;
+
// TODO
// var fallbackFontPath = Path.Combine(_appPaths.ProgramDataPath, "fonts", "DroidSansFallback.ttf");
// string fallbackFontParam = string.Empty;
@@ -878,7 +1093,6 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.SubtitleStream.IsExternal)
{
var subtitlePath = state.SubtitleStream.Path;
-
var charsetParam = string.Empty;
if (!string.IsNullOrEmpty(state.SubtitleStream.Language))
@@ -898,9 +1112,11 @@ namespace MediaBrowser.Controller.MediaEncoding
// TODO: Perhaps also use original_size=1920x800 ??
return string.Format(
CultureInfo.InvariantCulture,
- "subtitles=filename='{0}'{1}{2}",
+ "subtitles=f='{0}'{1}{2}{3}{4}",
_mediaEncoder.EscapeSubtitleFilterPath(subtitlePath),
charsetParam,
+ alphaParam,
+ sub2videoParam,
// fallbackFontParam,
setPtsParam);
}
@@ -909,9 +1125,11 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(
CultureInfo.InvariantCulture,
- "subtitles='{0}:si={1}'{2}",
+ "subtitles='{0}:si={1}{2}{3}'{4}",
_mediaEncoder.EscapeSubtitleFilterPath(mediaPath),
- state.InternalSubtitleStreamOffset.ToString(_usCulture),
+ state.InternalSubtitleStreamOffset.ToString(CultureInfo.InvariantCulture),
+ alphaParam,
+ sub2videoParam,
// fallbackFontParam,
setPtsParam);
}
@@ -996,11 +1214,11 @@ namespace MediaBrowser.Controller.MediaEncoding
|| string.Equals(codec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{
- args += " " + keyFrameArg;
+ args += keyFrameArg;
}
else
{
- args += " " + keyFrameArg + gopArg;
+ args += keyFrameArg + gopArg;
}
return args;
@@ -1018,54 +1236,49 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var param = string.Empty;
- if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
- && !string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
- {
- param += " -pix_fmt yuv420p";
- }
-
- if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "h264_amf", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
- {
- var videoStream = state.VideoStream;
- var isColorDepth10 = IsColorDepth10(state);
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions) ?? string.Empty;
- var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
-
- if (!isNvdecDecoder)
- {
- if (isColorDepth10
- && _mediaEncoder.SupportsHwaccel("opencl")
- && encodingOptions.EnableTonemapping
- && !string.IsNullOrEmpty(videoStream.VideoRange)
- && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
- {
- param += " -pix_fmt nv12";
- }
- else
- {
- param += " -pix_fmt yuv420p";
- }
+ // Tutorials: Enable Intel GuC / HuC firmware loading for Low Power Encoding.
+ // https://01.org/linuxgraphics/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;
+
+ if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ var isIntelVaapiDriver = _mediaEncoder.IsVaapiDeviceInteliHD || _mediaEncoder.IsVaapiDeviceInteli965;
+
+ if (string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder && isIntelVaapiDriver;
+ }
+ else if (string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder && isIntelVaapiDriver;
+ }
+ }
+ else if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerH264HwEncoder;
+ }
+ else if (string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ intelLowPowerHwEncoding = encodingOptions.EnableIntelLowPowerHevcHwEncoder;
}
}
+ if (intelLowPowerHwEncoding)
+ {
+ param += " -low_power 1";
+ }
+
if (string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase))
{
param += " -pix_fmt nv21";
}
- var isVc1 = state.VideoStream != null &&
- string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
+ var isVc1 = string.Equals(state.VideoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase);
var isLibX265 = string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase);
if (string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase) || isLibX265)
@@ -1176,19 +1389,6 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
- var videoStream = state.VideoStream;
- var isColorDepth10 = IsColorDepth10(state);
-
- if (isColorDepth10
- && _mediaEncoder.SupportsHwaccel("opencl")
- && encodingOptions.EnableTonemapping
- && !string.IsNullOrEmpty(videoStream.VideoRange)
- && videoStream.VideoRange.Contains("HDR", StringComparison.OrdinalIgnoreCase))
- {
- // Enhance workload when tone mapping with AMF on some APUs
- param += " -preanalysis true";
- }
-
if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase))
{
param += " -header_insertion_mode gop -gops_per_idr 1";
@@ -1217,7 +1417,7 @@ namespace MediaBrowser.Controller.MediaEncoding
param += string.Format(
CultureInfo.InvariantCulture,
" -speed 16 -quality good -profile:v {0} -slices 8 -crf {1} -qmin {2} -qmax {3}",
- profileScore.ToString(_usCulture),
+ profileScore.ToString(CultureInfo.InvariantCulture),
crf,
qmin,
qmax);
@@ -1289,7 +1489,7 @@ namespace MediaBrowser.Controller.MediaEncoding
var framerate = GetFramerateParam(state);
if (framerate.HasValue)
{
- param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(_usCulture));
+ param += string.Format(CultureInfo.InvariantCulture, " -r {0}", framerate.Value.ToString(CultureInfo.InvariantCulture));
}
var targetVideoCodec = state.ActualOutputVideoCodec;
@@ -1361,13 +1561,6 @@ namespace MediaBrowser.Controller.MediaEncoding
profile = "constrained_high";
}
- // Currently hevc_amf only support encoding HEVC Main Profile, otherwise force Main Profile.
- if (string.Equals(videoEncoder, "hevc_amf", StringComparison.OrdinalIgnoreCase)
- && profile.Contains("main10", StringComparison.OrdinalIgnoreCase))
- {
- profile = "main";
- }
-
if (!string.IsNullOrEmpty(profile))
{
if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
@@ -1384,7 +1577,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
level = NormalizeTranscodingLevel(state, level);
- // libx264, QSV, AMF, VAAPI can adjust the given level to match the output.
+ // libx264, QSV, AMF can adjust the given level to match the output.
if (string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
|| string.Equals(videoEncoder, "libx264", StringComparison.OrdinalIgnoreCase))
{
@@ -1393,7 +1586,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, _usCulture, out double hevcLevel))
+ if (double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out double hevcLevel))
{
param += " -level " + (hevcLevel / 3);
}
@@ -1404,10 +1597,13 @@ namespace MediaBrowser.Controller.MediaEncoding
param += " -level " + level;
}
else if (string.Equals(videoEncoder, "h264_nvenc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(videoEncoder, "hevc_nvenc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
{
// level option may cause NVENC to fail.
// NVENC cannot adjust the given level, just throw an error.
+ // level option may cause corrupted frames on AMD VAAPI.
}
else if (!string.Equals(videoEncoder, "h264_omx", StringComparison.OrdinalIgnoreCase)
|| !string.Equals(videoEncoder, "libx265", StringComparison.OrdinalIgnoreCase))
@@ -1493,8 +1689,8 @@ namespace MediaBrowser.Controller.MediaEncoding
if (!string.IsNullOrEmpty(videoStream.Profile)
&& !requestedProfiles.Contains(videoStream.Profile.Replace(" ", string.Empty, StringComparison.Ordinal), StringComparer.OrdinalIgnoreCase))
{
- var currentScore = GetVideoProfileScore(videoStream.Profile);
- var requestedScore = GetVideoProfileScore(requestedProfile);
+ var currentScore = GetVideoProfileScore(videoStream.Codec, videoStream.Profile);
+ var requestedScore = GetVideoProfileScore(videoStream.Codec, requestedProfile);
if (currentScore == -1 || currentScore > requestedScore)
{
@@ -1555,7 +1751,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, _usCulture, out var requestLevel))
+ && double.TryParse(level, NumberStyles.Any, CultureInfo.InvariantCulture, out var requestLevel))
{
if (!videoStream.Level.HasValue)
{
@@ -1705,7 +1901,8 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
|| string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(codec, "av1", StringComparison.OrdinalIgnoreCase))
{
return .6;
}
@@ -1803,7 +2000,7 @@ namespace MediaBrowser.Controller.MediaEncoding
&& state.AudioStream.Channels.Value > 5
&& !encodingOptions.DownMixAudioBoost.Equals(1))
{
- filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(_usCulture));
+ filters.Add("volume=" + encodingOptions.DownMixAudioBoost.ToString(CultureInfo.InvariantCulture));
}
var isCopyingTimestamps = state.CopyTimestamps || state.TranscodingType != TranscodingJobType.Progressive;
@@ -1942,19 +2139,35 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// Gets the fast seek command line parameter.
/// </summary>
- /// <param name="request">The request.</param>
+ /// <param name="state">The state.</param>
+ /// <param name="options">The options.</param>
/// <returns>System.String.</returns>
/// <value>The fast seek command line parameter.</value>
- public string GetFastSeekCommandLineParameter(BaseEncodingJobOptions request)
+ public string GetFastSeekCommandLineParameter(EncodingJobInfo state, EncodingOptions options)
{
- var time = request.StartTimeTicks ?? 0;
+ var time = state.BaseRequest.StartTimeTicks ?? 0;
+ var seekParam = string.Empty;
if (time > 0)
{
- return string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(time));
+ seekParam += string.Format(CultureInfo.InvariantCulture, "-ss {0}", _mediaEncoder.GetTimeParameter(time));
+
+ if (state.IsVideoRequest)
+ {
+ var outputVideoCodec = GetVideoEncoder(state, options);
+
+ // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
+ if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
+ && state.TranscodingType != TranscodingJobType.Progressive
+ && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
+ && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
+ {
+ seekParam += " -noaccurate_seek";
+ }
+ }
}
- return string.Empty;
+ return seekParam;
}
/// <summary>
@@ -2001,10 +2214,24 @@ namespace MediaBrowser.Controller.MediaEncoding
if (state.AudioStream != null)
{
- args += string.Format(
- CultureInfo.InvariantCulture,
- " -map 0:{0}",
- state.AudioStream.Index);
+ if (state.AudioStream.IsExternal)
+ {
+ int externalAudioMapIndex = state.SubtitleStream != null && state.SubtitleStream.IsExternal ? 2 : 1;
+ int externalAudioStream = state.MediaSource.MediaStreams.Where(i => i.Path == state.AudioStream.Path).ToList().IndexOf(state.AudioStream);
+
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ " -map {0}:{1}",
+ externalAudioMapIndex,
+ externalAudioStream);
+ }
+ else
+ {
+ args += string.Format(
+ CultureInfo.InvariantCulture,
+ " -map 0:{0}",
+ state.AudioStream.Index);
+ }
}
else
{
@@ -2063,180 +2290,51 @@ namespace MediaBrowser.Controller.MediaEncoding
return returnFirstIfNoIndex ? streams.FirstOrDefault() : null;
}
- /// <summary>
- /// Gets the graphical subtitle parameter.
- /// </summary>
- /// <param name="state">Encoding state.</param>
- /// <param name="options">Encoding options.</param>
- /// <param name="outputVideoCodec">Video codec to use.</param>
- /// <returns>Graphical subtitle parameter.</returns>
- public string GetGraphicalSubtitleParam(
- EncodingJobInfo state,
- EncodingOptions options,
- string outputVideoCodec)
+ public static (int? Width, int? Height) GetFixedOutputSize(
+ int? videoWidth,
+ int? videoHeight,
+ int? requestedWidth,
+ int? requestedHeight,
+ int? requestedMaxWidth,
+ int? requestedMaxHeight)
{
- outputVideoCodec ??= string.Empty;
-
- var outputSizeParam = ReadOnlySpan<char>.Empty;
- var request = state.BaseRequest;
-
- outputSizeParam = GetOutputSizeParamInternal(state, options, outputVideoCodec);
-
- var videoSizeParam = string.Empty;
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
- var isLinux = OperatingSystem.IsLinux();
-
- var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isQsvH264Encoder = outputVideoCodec.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
- var isQsvHevcEncoder = outputVideoCodec.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
- var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
- var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
- var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
- var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
- var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
- var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
-
- var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
- var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay;
- var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
-
- // Tonemapping and burn-in graphical subtitles requires overlay_vaapi.
- // But it's still in ffmpeg mailing list. Disable it for now.
- if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
+ if (!videoWidth.HasValue && !requestedWidth.HasValue)
{
- return GetOutputSizeParam(state, options, outputVideoCodec);
+ return (null, null);
}
- // Setup subtitle scaling
- if (state.VideoStream != null && state.VideoStream.Width.HasValue && state.VideoStream.Height.HasValue)
+ if (!videoHeight.HasValue && !requestedHeight.HasValue)
{
- // Adjust the size of graphical subtitles to fit the video stream.
- var videoStream = state.VideoStream;
- var inputWidth = videoStream.Width;
- var inputHeight = videoStream.Height;
- var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
-
- if (width.HasValue && height.HasValue)
- {
- videoSizeParam = string.Format(
- CultureInfo.InvariantCulture,
- "scale={0}x{1}",
- width.Value,
- height.Value);
- }
-
- if (!string.IsNullOrEmpty(videoSizeParam)
- && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
- {
- // upload graphical subtitle to QSV
- if (isLinux && (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase)))
- {
- videoSizeParam += ",hwupload=extra_hw_frames=64";
- }
- }
-
- if (!string.IsNullOrEmpty(videoSizeParam))
- {
- // upload graphical subtitle to cuda
- if (isNvdecDecoder && isNvencEncoder && isCudaOverlaySupported && isCudaFormatConversionSupported)
- {
- videoSizeParam += ",hwupload_cuda";
- }
- }
+ return (null, null);
}
- var mapPrefix = state.SubtitleStream.IsExternal ?
- 1 :
- 0;
-
- var subtitleStreamIndex = state.SubtitleStream.IsExternal
- ? 0
- : state.SubtitleStream.Index;
+ int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
+ int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
+ int outputWidth = requestedWidth ?? inputWidth;
+ int outputHeight = requestedHeight ?? inputHeight;
- // Setup default filtergraph utilizing FFMpeg overlay() and FFMpeg scale() (see the return of this function for index reference)
- // Always put the scaler before the overlay for better performance
- var retStr = outputSizeParam.IsEmpty
- ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""
- : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
+ // Don't transcode video to bigger than 4k when using HW.
+ int maximumWidth = Math.Min(requestedMaxWidth ?? outputWidth, 4096);
+ int maximumHeight = Math.Min(requestedMaxHeight ?? outputHeight, 4096);
- // When the input may or may not be hardware VAAPI decodable
- if (string.Equals(outputVideoCodec, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(outputVideoCodec, "hevc_vaapi", StringComparison.OrdinalIgnoreCase))
+ if (outputWidth > maximumWidth || outputHeight > maximumHeight)
{
- /*
- [base]: HW scaling video to OutputSize
- [sub]: SW scaling subtitle to FixedOutputSize
- [base][sub]: SW overlay
- */
- retStr = outputSizeParam.IsEmpty
- ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]hwdownload[base];[base][sub]overlay,format=nv12,hwupload\""
- : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload[base];[base][sub]overlay,format=nv12,hwupload\"";
+ var scaleW = (double)maximumWidth / (double)outputWidth;
+ var scaleH = (double)maximumHeight / (double)outputHeight;
+ var scale = Math.Min(scaleW, scaleH);
+ outputWidth = Math.Min(maximumWidth, (int)(outputWidth * scale));
+ outputHeight = Math.Min(maximumHeight, (int)(outputHeight * scale));
}
- // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first
- else if (_mediaEncoder.SupportsHwaccel("vaapi") && videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1
- && (string.Equals(outputVideoCodec, "libx264", StringComparison.OrdinalIgnoreCase)
- || string.Equals(outputVideoCodec, "libx265", StringComparison.OrdinalIgnoreCase)))
- {
- /*
- [base]: SW scaling video to OutputSize
- [sub]: SW scaling subtitle to FixedOutputSize
- [base][sub]: SW overlay
- */
- retStr = outputSizeParam.IsEmpty
- ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay\""
- : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay\"";
- }
- else if (string.Equals(outputVideoCodec, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(outputVideoCodec, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
- {
- /*
- QSV in FFMpeg can now setup hardware overlay for transcodes.
- For software decoding and hardware encoding option, frames must be hwuploaded into hardware
- with fixed frame size.
- Currently only supports linux.
- */
- if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
- {
- retStr = " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3},hwdownload,format=nv12[base];[base][sub]overlay\"";
- }
- else if (isLinux)
- {
- retStr = outputSizeParam.IsEmpty
- ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay_qsv\""
- : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_qsv\"";
- }
- }
- else if (isNvdecDecoder && isNvencEncoder)
- {
- if (isCudaOverlaySupported && isCudaFormatConversionSupported)
- {
- retStr = outputSizeParam.IsEmpty
- ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]scale_cuda=format=yuv420p[base];[base][sub]overlay_cuda\""
- : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay_cuda\"";
- }
- else
- {
- retStr = outputSizeParam.IsEmpty
- ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]overlay,format=nv12|yuv420p,hwupload_cuda\""
- : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[base];[base][sub]overlay,format=nv12|yuv420p,hwupload_cuda\"";
- }
- }
+ outputWidth = 2 * (outputWidth / 2);
+ outputHeight = 2 * (outputHeight / 2);
- return string.Format(
- CultureInfo.InvariantCulture,
- retStr,
- mapPrefix,
- subtitleStreamIndex,
- state.VideoStream.Index,
- outputSizeParam.ToString(),
- videoSizeParam);
+ return (outputWidth, outputHeight);
}
- public static (int? width, int? height) GetFixedOutputSize(
+ public static string GetHwScaleFilter(
+ string hwScaleSuffix,
+ string videoFormat,
int? videoWidth,
int? videoHeight,
int? requestedWidth,
@@ -2244,51 +2342,81 @@ namespace MediaBrowser.Controller.MediaEncoding
int? requestedMaxWidth,
int? requestedMaxHeight)
{
- if (!videoWidth.HasValue && !requestedWidth.HasValue)
+ 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 arg1 = isSizeFixed ? ("=w=" + outWidth.Value + ":h=" + outHeight.Value) : string.Empty;
+ var arg2 = isFormatFixed ? ("format=" + videoFormat) : string.Empty;
+ if (isFormatFixed)
{
- return (null, null);
+ arg2 = (isSizeFixed ? ':' : '=') + arg2;
}
- if (!videoHeight.HasValue && !requestedHeight.HasValue)
+ if (!string.IsNullOrEmpty(hwScaleSuffix) && (isSizeFixed || isFormatFixed))
{
- return (null, null);
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale_{0}{1}{2}",
+ hwScaleSuffix,
+ arg1,
+ arg2);
}
- decimal inputWidth = Convert.ToDecimal(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
- decimal inputHeight = Convert.ToDecimal(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
- decimal outputWidth = requestedWidth.HasValue ? Convert.ToDecimal(requestedWidth.Value) : inputWidth;
- decimal outputHeight = requestedHeight.HasValue ? Convert.ToDecimal(requestedHeight.Value) : inputHeight;
- decimal maximumWidth = requestedMaxWidth.HasValue ? Convert.ToDecimal(requestedMaxWidth.Value) : outputWidth;
- decimal maximumHeight = requestedMaxHeight.HasValue ? Convert.ToDecimal(requestedMaxHeight.Value) : outputHeight;
+ return string.Empty;
+ }
- if (outputWidth > maximumWidth || outputHeight > maximumHeight)
+ public static string GetCustomSwScaleFilter(
+ int? videoWidth,
+ int? videoHeight,
+ int? requestedWidth,
+ int? requestedHeight,
+ int? requestedMaxWidth,
+ int? requestedMaxHeight)
+ {
+ var (outWidth, outHeight) = GetFixedOutputSize(
+ videoWidth,
+ videoHeight,
+ requestedWidth,
+ requestedHeight,
+ requestedMaxWidth,
+ requestedMaxHeight);
+
+ if (outWidth.HasValue && outHeight.HasValue)
{
- var scale = Math.Min(maximumWidth / outputWidth, maximumHeight / outputHeight);
- outputWidth = Math.Min(maximumWidth, Math.Truncate(outputWidth * scale));
- outputHeight = Math.Min(maximumHeight, Math.Truncate(outputHeight * scale));
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale=s={0}x{1}:flags=fast_bilinear",
+ outWidth.Value,
+ outHeight.Value);
}
- outputWidth = 2 * Math.Truncate(outputWidth / 2);
- outputHeight = 2 * Math.Truncate(outputHeight / 2);
-
- return (Convert.ToInt32(outputWidth), Convert.ToInt32(outputHeight));
+ return string.Empty;
}
- public List<string> GetScalingFilters(
+ public static string GetAlphaSrcFilter(
EncodingJobInfo state,
- EncodingOptions options,
int? videoWidth,
int? videoHeight,
- Video3DFormat? threedFormat,
- string videoDecoder,
- string videoEncoder,
int? requestedWidth,
int? requestedHeight,
int? requestedMaxWidth,
- int? requestedMaxHeight)
+ int? requestedMaxHeight,
+ int? framerate)
{
- var filters = new List<string>();
- var (width, height) = GetFixedOutputSize(
+ var reqTicks = state.BaseRequest.StartTimeTicks ?? 0;
+ var startTime = TimeSpan.FromTicks(reqTicks).ToString(@"hh\\\:mm\\\:ss\\\.fff", CultureInfo.InvariantCulture);
+ var (outWidth, outHeight) = GetFixedOutputSize(
videoWidth,
videoHeight,
requestedWidth,
@@ -2296,280 +2424,128 @@ namespace MediaBrowser.Controller.MediaEncoding
requestedMaxWidth,
requestedMaxHeight);
- if ((string.Equals(videoEncoder, "h264_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_vaapi", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase))
- && width.HasValue
- && height.HasValue)
- {
- // Given the input dimensions (inputWidth, inputHeight), determine the output dimensions
- // (outputWidth, outputHeight). The user may request precise output dimensions or maximum
- // output dimensions. Output dimensions are guaranteed to be even.
- var outputWidth = width.Value;
- var outputHeight = height.Value;
- var qsv_or_vaapi = string.Equals(videoEncoder, "h264_qsv", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoEncoder, "hevc_qsv", StringComparison.OrdinalIgnoreCase);
- var isDeintEnabled = state.DeInterlace("h264", true)
- || state.DeInterlace("avc", true)
- || state.DeInterlace("h265", true)
- || state.DeInterlace("hevc", true);
-
- var isVaapiDecoder = videoDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
- var isVaapiH264Encoder = videoEncoder.Contains("h264_vaapi", StringComparison.OrdinalIgnoreCase);
- var isVaapiHevcEncoder = videoEncoder.Contains("hevc_vaapi", StringComparison.OrdinalIgnoreCase);
- var isQsvH264Encoder = videoEncoder.Contains("h264_qsv", StringComparison.OrdinalIgnoreCase);
- var isQsvHevcEncoder = videoEncoder.Contains("hevc_qsv", StringComparison.OrdinalIgnoreCase);
- var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
- var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
- var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
- var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
- var isP010PixFmtRequired = (isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported))
- || (isTonemappingSupportedOnQsv && isVppTonemappingSupported);
-
- var outputPixFmt = "format=nv12";
- if (isP010PixFmtRequired)
- {
- outputPixFmt = "format=p010";
- }
-
- if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
- {
- qsv_or_vaapi = false;
- }
-
- if (!videoWidth.HasValue
- || outputWidth != videoWidth.Value
- || !videoHeight.HasValue
- || outputHeight != videoHeight.Value)
- {
- // Force nv12 pixel format to enable 10-bit to 8-bit colour conversion.
- // use vpp_qsv filter to avoid green bar when the fixed output size is requested.
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0}=w={1}:h={2}{3}{4}",
- qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
- outputWidth,
- outputHeight,
- ":" + outputPixFmt,
- (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
- }
+ if (outWidth.HasValue && outHeight.HasValue)
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "alphasrc=s={0}x{1}:r={2}:start='{3}'",
+ outWidth.Value,
+ outHeight.Value,
+ framerate ?? 10,
+ reqTicks > 0 ? startTime : 0);
+ }
- // Assert 10-bit is P010 so as we can avoid the extra scaler to get a bit more fps on high res HDR videos.
- else if (!isP010PixFmtRequired)
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "{0}={1}{2}",
- qsv_or_vaapi ? "vpp_qsv" : "scale_vaapi",
- outputPixFmt,
- (qsv_or_vaapi && isDeintEnabled) ? ":deinterlace=1" : string.Empty));
- }
- }
- else if ((videoDecoder ?? string.Empty).Contains("cuda", StringComparison.OrdinalIgnoreCase)
- && width.HasValue
- && height.HasValue)
- {
- var outputWidth = width.Value;
- var outputHeight = height.Value;
-
- var isNvencEncoder = videoEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
- var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
- var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
- var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase);
- var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
- var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay;
- var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ return string.Empty;
+ }
- var outputPixFmt = string.Empty;
- if (isCudaFormatConversionSupported)
- {
- outputPixFmt = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
- ? "format=yuv420p"
- : "format=nv12";
- if ((isOpenclTonemappingSupported || isCudaTonemappingSupported)
- && isTonemappingSupportedOnNvenc)
- {
- outputPixFmt = "format=p010";
- }
- }
+ public static string GetSwScaleFilter(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string videoEncoder,
+ int? videoWidth,
+ int? videoHeight,
+ Video3DFormat? threedFormat,
+ int? requestedWidth,
+ int? requestedHeight,
+ int? requestedMaxWidth,
+ int? requestedMaxHeight)
+ {
+ var isV4l2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
+ var scaleVal = isV4l2 ? 64 : 2;
- if (!videoWidth.HasValue
- || outputWidth != videoWidth.Value
- || !videoHeight.HasValue
- || outputHeight != videoHeight.Value)
+ // If fixed dimensions were supplied
+ if (requestedWidth.HasValue && requestedHeight.HasValue)
+ {
+ if (isV4l2)
{
- filters.Add(
- string.Format(
+ var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
+ var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
+
+ return string.Format(
CultureInfo.InvariantCulture,
- "scale_cuda=w={0}:h={1}{2}",
- outputWidth,
- outputHeight,
- isCudaFormatConversionSupported ? (":" + outputPixFmt) : string.Empty));
+ "scale=trunc({0}/64)*64:trunc({1}/2)*2",
+ widthParam,
+ heightParam);
}
- else if (isCudaFormatConversionSupported)
+ else
{
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale_cuda={0}",
- outputPixFmt));
+ return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, requestedHeight.Value);
}
}
- else if ((videoDecoder ?? string.Empty).IndexOf("cuvid", StringComparison.OrdinalIgnoreCase) != -1
- && width.HasValue
- && height.HasValue)
- {
- // Nothing to do, it's handled as an input resize filter
- }
- else
+
+ // 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)
{
- var isExynosV4L2 = string.Equals(videoEncoder, "h264_v4l2m2m", StringComparison.OrdinalIgnoreCase);
+ var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
+ var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
- // If fixed dimensions were supplied
- if (requestedWidth.HasValue && requestedHeight.HasValue)
- {
- if (isExynosV4L2)
- {
- var widthParam = requestedWidth.Value.ToString(_usCulture);
- var heightParam = requestedHeight.Value.ToString(_usCulture);
-
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc({0}/64)*64:trunc({1}/2)*2",
- widthParam,
- heightParam));
- }
- else
- {
- filters.Add(GetFixedSizeScalingFilter(threedFormat, requestedWidth.Value, requestedHeight.Value));
- }
- }
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/{2})*{2}:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
+ maxWidthParam,
+ maxHeightParam,
+ scaleVal);
+ }
- // 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 a fixed width was requested
+ else if (requestedWidth.HasValue)
+ {
+ if (threedFormat.HasValue)
{
- var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture);
- var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture);
-
- if (isExynosV4L2)
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/64)*64:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
- maxWidthParam,
- maxHeightParam));
- }
- else
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*dar)\\,min({0}\\,{1}*dar))/2)*2:trunc(min(max(iw/dar\\,ih)\\,min({0}/dar\\,{1}))/2)*2",
- maxWidthParam,
- maxHeightParam));
- }
+ // This method can handle 0 being passed in for the requested height
+ return GetFixedSwScaleFilter(threedFormat, requestedWidth.Value, 0);
}
-
- // If a fixed width was requested
- else if (requestedWidth.HasValue)
+ else
{
- if (threedFormat.HasValue)
- {
- // This method can handle 0 being passed in for the requested height
- filters.Add(GetFixedSizeScalingFilter(threedFormat, requestedWidth.Value, 0));
- }
- else
- {
- var widthParam = requestedWidth.Value.ToString(_usCulture);
+ var widthParam = requestedWidth.Value.ToString(CultureInfo.InvariantCulture);
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale={0}:trunc(ow/a/2)*2",
- widthParam));
- }
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale={0}:trunc(ow/a/2)*2",
+ widthParam);
}
+ }
- // If a fixed height was requested
- else if (requestedHeight.HasValue)
- {
- var heightParam = requestedHeight.Value.ToString(_usCulture);
+ // If a fixed height was requested
+ else if (requestedHeight.HasValue)
+ {
+ var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
- if (isExynosV4L2)
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/64)*64:{0}",
- heightParam));
- }
- else
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/2)*2:{0}",
- heightParam));
- }
- }
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale=trunc(oh*a/{1})*{1}:{0}",
+ heightParam,
+ scaleVal);
+ }
- // If a max width was requested
- else if (requestedMaxWidth.HasValue)
- {
- var maxWidthParam = requestedMaxWidth.Value.ToString(_usCulture);
+ // If a max width was requested
+ else if (requestedMaxWidth.HasValue)
+ {
+ var maxWidthParam = requestedMaxWidth.Value.ToString(CultureInfo.InvariantCulture);
- if (isExynosV4L2)
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/64)*64:trunc(ow/dar/2)*2",
- maxWidthParam));
- }
- else
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/2)*2:trunc(ow/dar/2)*2",
- maxWidthParam));
- }
- }
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale=trunc(min(max(iw\\,ih*dar)\\,{0})/{1})*{1}:trunc(ow/dar/2)*2",
+ maxWidthParam,
+ scaleVal);
+ }
- // If a max height was requested
- else if (requestedMaxHeight.HasValue)
- {
- var maxHeightParam = requestedMaxHeight.Value.ToString(_usCulture);
+ // If a max height was requested
+ else if (requestedMaxHeight.HasValue)
+ {
+ var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
- if (isExynosV4L2)
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/64)*64:min(max(iw/dar\\,ih)\\,{0})",
- maxHeightParam));
- }
- else
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "scale=trunc(oh*a/2)*2:min(max(iw/dar\\,ih)\\,{0})",
- maxHeightParam));
- }
- }
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "scale=trunc(oh*a/{1})*{1}:min(max(iw/dar\\,ih)\\,{0})",
+ maxHeightParam,
+ scaleVal);
}
- return filters;
+ return string.Empty;
}
- private string GetFixedSizeScalingFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight)
+ private static string GetFixedSwScaleFilter(Video3DFormat? threedFormat, int requestedWidth, int requestedHeight)
{
var widthParam = requestedWidth.ToString(CultureInfo.InvariantCulture);
var heightParam = requestedHeight.ToString(CultureInfo.InvariantCulture);
@@ -2617,572 +2593,2171 @@ namespace MediaBrowser.Controller.MediaEncoding
return string.Format(CultureInfo.InvariantCulture, filter, widthParam, heightParam);
}
+ public static string GetSwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options)
+ {
+ var doubleRateDeint = options.DeinterlaceDoubleRate && state.VideoStream?.AverageFrameRate <= 30;
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}={1}:-1:0",
+ string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase) ? "bwdif" : "yadif",
+ doubleRateDeint ? "1" : "0");
+ }
+
+ public static string GetHwDeinterlaceFilter(EncodingJobInfo state, EncodingOptions options, string hwDeintSuffix)
+ {
+ var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30;
+ if (hwDeintSuffix.Contains("cuda", StringComparison.OrdinalIgnoreCase))
+ {
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ "yadif_cuda={0}:-1:0",
+ doubleRateDeint ? "1" : "0");
+ }
+ else 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))
+ {
+ return "deinterlace_qsv=mode=2";
+ }
+
+ return string.Empty;
+ }
+
+ public static string GetHwTonemapFilter(EncodingOptions options, string hwTonemapSuffix, string videoFormat)
+ {
+ if (string.IsNullOrEmpty(hwTonemapSuffix))
+ {
+ return string.Empty;
+ }
+
+ var args = "tonemap_{0}=format={1}:p=bt709:t=bt709:m=bt709";
+
+ if (!hwTonemapSuffix.Contains("vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ args += ":tonemap={2}:peak={3}:desat={4}";
+
+ if (options.TonemappingParam != 0)
+ {
+ args += ":param={5}";
+ }
+
+ if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ {
+ args += ":range={6}";
+ }
+ }
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ args,
+ hwTonemapSuffix,
+ videoFormat ?? "nv12",
+ options.TonemappingAlgorithm,
+ options.TonemappingPeak,
+ options.TonemappingDesat,
+ options.TonemappingParam,
+ options.TonemappingRange);
+ }
+
/// <summary>
- /// Gets the output size parameter.
+ /// Gets the parameter of software filter chain.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
- /// <param name="outputVideoCodec">Video codec to use.</param>
- /// <returns>The output size parameter.</returns>
- public string GetOutputSizeParam(
+ /// <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) GetSwVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
- string outputVideoCodec)
+ string vidEncoder)
{
- string filters = GetOutputSizeParamInternal(state, options, outputVideoCodec);
- return string.IsNullOrEmpty(filters) ? string.Empty : " -vf \"" + filters + "\"";
+ 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 vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+
+ 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 hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, false));
+
+ // INPUT sw surface(memory/copy-back from vram)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(deintFilter);
+ }
+
+ var outFormat = isSwDecoder ? "yuv420p" : "nv12";
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ if (isVaapiEncoder)
+ {
+ outFormat = "nv12";
+ }
+
+ // sw scale
+ mainFilters.Add(swScaleFilter);
+ mainFilters.Add("format=" + outFormat);
+
+ // sw tonemap <= TODO: finsh the fast tonemap filter
+
+ // OUTPUT yuv420p/nv12 surface(memory)
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (hasTextSubs)
+ {
+ // subtitles=f='*.ass':alpha=0
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ else if (hasGraphicalSubs)
+ {
+ // [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");
+ }
+
+ return (mainFilters, subFilters, overlayFilters);
}
/// <summary>
- /// Gets the output size parameter.
- /// If we're going to put a fixed size on the command line, this will calculate it.
+ /// Gets the parameter of Nvidia NVENC filter chain.
/// </summary>
/// <param name="state">Encoding state.</param>
/// <param name="options">Encoding options.</param>
- /// <param name="outputVideoCodec">Video codec to use.</param>
- /// <returns>The output size parameter.</returns>
- public string GetOutputSizeParamInternal(
+ /// <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) GetNvidiaVidFilterChain(
EncodingJobInfo state,
EncodingOptions options,
- string outputVideoCodec)
+ string vidEncoder)
{
- // http://sonnati.wordpress.com/2012/10/19/ffmpeg-the-swiss-army-knife-of-internet-streaming-part-vi/
+ if (!string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
- var request = state.BaseRequest;
- var videoStream = state.VideoStream;
- var filters = new List<string>();
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
+
+ // legacy cuvid(resize/deint/sw) pipeline(copy-back)
+ if ((isSwDecoder && isSwEncoder)
+ || !IsCudaFullSupported()
+ || !options.EnableEnhancedNvdecDecoder
+ || !_mediaEncoder.SupportsFilter("alphasrc"))
+ {
+ return GetSwVidFilterChain(state, options, vidEncoder);
+ }
+
+ // prefered nvdec + cuda filters + nvenc pipeline
+ return GetNvidiaVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, options) ?? string.Empty;
- var inputWidth = videoStream?.Width;
- var inputHeight = videoStream?.Height;
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetNvidiaVidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ 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 isSwDecoder = string.IsNullOrEmpty(videoDecoder);
- var isD3d11vaDecoder = videoDecoder.IndexOf("d3d11va", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiDecoder = videoDecoder.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiEncoder = outputVideoCodec.IndexOf("vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiH264Encoder = outputVideoCodec.IndexOf("h264_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isVaapiHevcEncoder = outputVideoCodec.IndexOf("hevc_vaapi", StringComparison.OrdinalIgnoreCase) != -1;
- var isQsvH264Encoder = outputVideoCodec.IndexOf("h264_qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isQsvHevcEncoder = outputVideoCodec.IndexOf("hevc_qsv", StringComparison.OrdinalIgnoreCase) != -1;
- var isNvdecDecoder = videoDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
- var isNvencEncoder = outputVideoCodec.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
- var isCuvidH264Decoder = videoDecoder.Contains("h264_cuvid", StringComparison.OrdinalIgnoreCase);
- var isCuvidHevcDecoder = videoDecoder.Contains("hevc_cuvid", StringComparison.OrdinalIgnoreCase);
- var isCuvidVp9Decoder = videoDecoder.Contains("vp9_cuvid", StringComparison.OrdinalIgnoreCase);
- var isLibX264Encoder = outputVideoCodec.IndexOf("libx264", StringComparison.OrdinalIgnoreCase) != -1;
- var isLibX265Encoder = outputVideoCodec.IndexOf("libx265", StringComparison.OrdinalIgnoreCase) != -1;
- var isLinux = OperatingSystem.IsLinux();
- var isColorDepth10 = IsColorDepth10(state);
-
- var isTonemappingSupportedOnNvenc = string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase) && (isNvdecDecoder || isCuvidHevcDecoder || isCuvidVp9Decoder || isSwDecoder);
- var isTonemappingSupportedOnAmf = string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase) && (isD3d11vaDecoder || isSwDecoder);
- var isTonemappingSupportedOnVaapi = string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isVaapiH264Encoder || isVaapiHevcEncoder);
- var isTonemappingSupportedOnQsv = string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase) && isVaapiDecoder && (isQsvH264Encoder || isQsvHevcEncoder);
- var isOpenclTonemappingSupported = IsOpenclTonemappingSupported(state, options);
- var isVppTonemappingSupported = IsVppTonemappingSupported(state, options);
- var isCudaTonemappingSupported = IsCudaTonemappingSupported(state, options);
- var mediaEncoderVersion = _mediaEncoder.GetMediaEncoderVersion();
- var isCudaOverlaySupported = _mediaEncoder.SupportsFilter("overlay_cuda") && mediaEncoderVersion != null && mediaEncoderVersion >= _minVersionForCudaOverlay;
+ var isNvdecDecoder = vidDecoder.Contains("cuda", StringComparison.OrdinalIgnoreCase);
+ var isNvencEncoder = vidEncoder.Contains("nvenc", StringComparison.OrdinalIgnoreCase);
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !isNvencEncoder;
+ var isCuInCuOut = isNvdecDecoder && isNvencEncoder;
+
+ var doubleRateDeint = options.DeinterlaceDoubleRate && (state.VideoStream?.AverageFrameRate ?? 60) <= 30;
+ 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 doCuTonemap = IsHwTonemapAvailable(state, options);
var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
- var hasTextSubs = state.SubtitleStream != null && state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
- // If double rate deinterlacing is enabled and the input framerate is 30fps or below, otherwise the output framerate will be too high for many devices
- var doubleRateDeinterlace = options.DeinterlaceDoubleRate && (videoStream?.AverageFrameRate ?? 60) <= 30;
-
- var isScalingInAdvance = false;
- var isCudaDeintInAdvance = false;
- var isHwuploadCudaRequired = false;
- var isNoTonemapFilterApplied = true;
- var isDeinterlaceH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
- var isDeinterlaceHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
-
- // Add OpenCL tonemapping filter for NVENC/AMF/VAAPI.
- if ((isTonemappingSupportedOnNvenc && !isCudaTonemappingSupported) || isTonemappingSupportedOnAmf || (isTonemappingSupportedOnVaapi && !isVppTonemappingSupported))
- {
- // NVIDIA Pascal and Turing or higher are recommended.
- // AMD Polaris and Vega or higher are recommended.
- // Intel Kaby Lake or newer is required.
- if (isOpenclTonemappingSupported)
- {
- isNoTonemapFilterApplied = false;
- var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer);
- if (!string.IsNullOrEmpty(inputHdrParams))
- {
- filters.Add(inputHdrParams);
- }
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doCuTonemap));
+
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ var outFormat = doCuTonemap ? "yuv420p10le" : "yuv420p";
+ var swScaleFilter = GetSwScaleFilter(state, options, vidEncoder, inW, inH, threeDFormat, reqW, reqH, reqMaxW, reqMaxH);
+ // sw scale
+ mainFilters.Add(swScaleFilter);
+ mainFilters.Add("format=" + outFormat);
+
+ // sw => hw
+ if (doCuTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
+
+ if (isNvdecDecoder)
+ {
+ // INPUT cuda surface(vram)
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "cuda");
+ mainFilters.Add(deintFilter);
+ }
+
+ var outFormat = doCuTonemap ? string.Empty : "yuv420p";
+ var hwScaleFilter = GetHwScaleFilter("cuda", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
+ }
+
+ // hw tonemap
+ if (doCuTonemap)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "cuda", "yuv420p");
+ mainFilters.Add(tonemapFilter);
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = isSwDecoder && doCuTonemap;
+ if ((isNvdecDecoder && isSwEncoder) || isUploadForOclTonemap)
+ {
+ memoryOutput = true;
+
+ // OUTPUT yuv420p surface(memory)
+ mainFilters.Add("hwdownload");
+ mainFilters.Add("format=yuv420p");
+ }
+
+ // OUTPUT yuv420p surface(memory)
+ if (isSwDecoder && isNvencEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
+ {
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ }
- var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
+ // OUTPUT cuda(yuv420p) surface(vram)
- if (options.TonemappingParam != 0)
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (isCuInCuOut)
+ {
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
{
- parameters += ":param={4}";
+ // scale=s=1280x720,format=yuva420p,hwupload
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ subFilters.Add("format=yuva420p");
}
-
- if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ else if (hasTextSubs)
{
- parameters += ":range={5}";
+ // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
+ 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=yuva420p");
+ subFilters.Add(subTextSubtitlesFilter);
}
- if (isSwDecoder || isD3d11vaDecoder)
- {
- isScalingInAdvance = true;
- // Add zscale filter before tone mapping filter for performance.
- var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
- if (width.HasValue && height.HasValue)
- {
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "zscale=s={0}x{1}",
- width.Value,
- height.Value));
- }
+ subFilters.Add("hwupload");
+ overlayFilters.Add("overlay_cuda=eof_action=endall:shortest=1:repeatlast=0");
+ }
+ }
+ else
+ {
+ if (hasGraphicalSubs)
+ {
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ }
+ }
- // Convert to hardware pixel format p010 when using SW decoder.
- filters.Add("format=p010");
- }
+ return (mainFilters, subFilters, overlayFilters);
+ }
- if ((isDeinterlaceH264 || isDeinterlaceHevc) && isNvdecDecoder)
+ /// <summary>
+ /// Gets the parameter of AMD AMF 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) GetAmdVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var isWindows = OperatingSystem.IsWindows();
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
+ var isAmfDx11OclSupported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va") && IsOpenclFullSupported();
+
+ // legacy d3d11va pipeline(copy-back)
+ if ((isSwDecoder && isSwEncoder)
+ || !isAmfDx11OclSupported
+ || !_mediaEncoder.SupportsFilter("alphasrc"))
+ {
+ return GetSwVidFilterChain(state, options, vidEncoder);
+ }
+
+ // prefered d3d11va + opencl filters + amf pipeline
+ return GetAmdDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
+
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetAmdDx11VidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ 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 isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
+ var isAmfEncoder = vidEncoder.Contains("amf", StringComparison.OrdinalIgnoreCase);
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !isAmfEncoder;
+ var isDxInDxOut = isD3d11vaDecoder && isAmfEncoder;
+
+ 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 doOclTonemap = IsHwTonemapAvailable(state, options);
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
+
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
+ 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 ocl tonemap,
+ // since the overhead caused by hwupload >>> using sw filter.
+ // sw => hw
+ if (doOclTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
+
+ if (isD3d11vaDecoder)
+ {
+ // INPUT d3d11 surface(vram)
+ // map from d3d11va to opencl via d3d11-opencl interop.
+ mainFilters.Add("hwmap=derive_device=opencl");
+
+ // hw deint <= TODO: finsh the 'yadif_opencl' filter
+
+ var outFormat = doOclTonemap ? string.Empty : "nv12";
+ var hwScaleFilter = GetHwScaleFilter("opencl", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
+ }
+
+ // hw tonemap
+ if (doOclTonemap)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ mainFilters.Add(tonemapFilter);
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
+ if ((isD3d11vaDecoder && isSwEncoder) || isUploadForOclTonemap)
+ {
+ memoryOutput = true;
+
+ // OUTPUT nv12 surface(memory)
+ // prefer hwmap to hwdownload on opencl.
+ var hwTransferFilter = hasGraphicalSubs ? "hwdownload" : "hwmap";
+ mainFilters.Add(hwTransferFilter);
+ mainFilters.Add("format=nv12");
+ }
+
+ // OUTPUT yuv420p surface
+ if (isSwDecoder && isAmfEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
+ {
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ }
+
+ if (isDxInDxOut && !hasSubs)
+ {
+ // OUTPUT d3d11(nv12) surface(vram)
+ // reverse-mapping via d3d11-opencl interop.
+ mainFilters.Add("hwmap=derive_device=d3d11va:reverse=1");
+ mainFilters.Add("format=d3d11");
+ }
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (isDxInDxOut)
+ {
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
{
- isCudaDeintInAdvance = true;
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "yadif_cuda={0}:-1:0",
- doubleRateDeinterlace ? "1" : "0"));
+ // scale=s=1280x720,format=yuva420p,hwupload
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ subFilters.Add("format=yuva420p");
}
-
- if (isVaapiDecoder || isNvdecDecoder)
+ else if (hasTextSubs)
{
- isScalingInAdvance = true;
- filters.AddRange(
- GetScalingFilters(
- state,
- options,
- inputWidth,
- inputHeight,
- threeDFormat,
- videoDecoder,
- outputVideoCodec,
- request.Width,
- request.Height,
- request.MaxWidth,
- request.MaxHeight));
+ // alphasrc=s=1280x720:r=10:start=0,format=yuva420p,subtitles,hwupload
+ 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=yuva420p");
+ subFilters.Add(subTextSubtitlesFilter);
}
- // hwmap the HDR data to opencl device by cl-va p010 interop.
- if (isVaapiDecoder)
+ subFilters.Add("hwupload");
+ overlayFilters.Add("overlay_opencl=eof_action=endall:shortest=1:repeatlast=0");
+ overlayFilters.Add("hwmap=derive_device=d3d11va:reverse=1");
+ overlayFilters.Add("format=d3d11");
+ }
+ }
+ else if (memoryOutput)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ }
+ }
+
+ return (mainFilters, subFilters, overlayFilters);
+ }
+
+ /// <summary>
+ /// Gets the parameter of Intel QSV 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) GetIntelVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var isWindows = OperatingSystem.IsWindows();
+ var isLinux = OperatingSystem.IsLinux();
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
+ var isIntelDx11OclSupported = isWindows
+ && _mediaEncoder.SupportsHwaccel("d3d11va")
+ && isQsvOclSupported;
+ var isIntelVaapiOclSupported = isLinux
+ && IsVaapiSupported(state)
+ && isQsvOclSupported;
+
+ // legacy qsv pipeline(copy-back)
+ if ((isSwDecoder && isSwEncoder)
+ || (!isIntelVaapiOclSupported && !isIntelDx11OclSupported)
+ || !_mediaEncoder.SupportsFilter("alphasrc"))
+ {
+ return GetSwVidFilterChain(state, options, vidEncoder);
+ }
+
+ // prefered qsv(vaapi) + opencl filters pipeline
+ if (isIntelVaapiOclSupported)
+ {
+ return GetIntelQsvVaapiVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
+
+ // prefered qsv(d3d11) + opencl filters pipeline
+ if (isIntelDx11OclSupported)
+ {
+ return GetIntelQsvDx11VidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
+
+ return (null, null, null);
+ }
+
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvDx11VidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ 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 isD3d11vaDecoder = vidDecoder.Contains("d3d11va", StringComparison.OrdinalIgnoreCase);
+ var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isHwDecoder = isD3d11vaDecoder || isQsvDecoder;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !isQsvEncoder;
+ var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
+
+ 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 doOclTonemap = IsHwTonemapAvailable(state, options);
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
+
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
+ 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 ocl tonemap,
+ // since the overhead caused by hwupload >>> using sw filter.
+ // sw => hw
+ if (doOclTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
+ else if (isD3d11vaDecoder || isQsvDecoder)
+ {
+ var outFormat = doOclTonemap ? string.Empty : "nv12";
+ var hwScaleFilter = GetHwScaleFilter("qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+
+ if (isD3d11vaDecoder)
+ {
+ if (!string.IsNullOrEmpty(hwScaleFilter) || doDeintH2645)
{
- filters.Add("hwmap");
+ // INPUT d3d11 surface(vram)
+ // map from d3d11va to qsv.
+ mainFilters.Add("hwmap=derive_device=qsv");
}
+ }
+
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "qsv");
+ mainFilters.Add(deintFilter);
+ }
+
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
+ }
+
+ if (doOclTonemap && isHwDecoder)
+ {
+ // map from qsv to opencl via qsv(d3d11)-opencl interop.
+ mainFilters.Add("hwmap=derive_device=opencl");
+ }
+
+ // hw tonemap
+ if (doOclTonemap)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ mainFilters.Add(tonemapFilter);
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
+ var isHwmapUsable = isSwEncoder && doOclTonemap;
+ if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
+ {
+ memoryOutput = true;
+
+ // 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("format=nv12");
+ }
+
+ // OUTPUT nv12 surface(memory)
+ if (isSwDecoder && isQsvEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
+ {
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ }
+
+ if (isQsvInQsvOut && doOclTonemap)
+ {
+ // OUTPUT qsv(nv12) surface(vram)
+ // reverse-mapping via qsv(d3d11)-opencl interop.
+ mainFilters.Add("hwmap=derive_device=qsv:reverse=1");
+ mainFilters.Add("format=qsv");
+ }
- // convert cuda device data to p010 host data.
- if (isNvdecDecoder)
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (isQsvInQsvOut)
+ {
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
{
- filters.Add("hwdownload,format=p010");
+ // scale,format=bgra,hwupload
+ // overlay_qsv can handle overlay scaling,
+ // add a dummy scale filter to pair with -canvas_size.
+ subFilters.Add("scale=flags=fast_bilinear");
+ subFilters.Add("format=bgra");
}
-
- if (isNvdecDecoder
- || isCuvidHevcDecoder
- || isCuvidVp9Decoder
- || isSwDecoder
- || isD3d11vaDecoder)
+ else if (hasTextSubs)
{
- // Upload the HDR10 or HLG data to the OpenCL device,
- // use tonemap_opencl filter for tone mapping,
- // and then download the SDR data to memory.
- filters.Add("hwupload");
+ // alphasrc=s=1280x720:r=10:start=0,format=bgra,subtitles,hwupload
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
}
- // Fallback to hable if bt2390 is chosen but not supported in tonemap_opencl.
- var isBt2390SupportedInOpenclTonemap = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.TonemapOpenclBt2390);
- if (string.Equals(options.TonemappingAlgorithm, "bt2390", StringComparison.OrdinalIgnoreCase)
- && !isBt2390SupportedInOpenclTonemap)
+ // qsv requires a fixed pool size.
+ subFilters.Add("hwupload=extra_hw_frames=32");
+
+ var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var overlaySize = (overlayW.HasValue && overlayH.HasValue)
+ ? (":w=" + overlayW.Value + ":h=" + overlayH.Value)
+ : string.Empty;
+ var overlayQsvFilter = string.Format(
+ CultureInfo.InvariantCulture,
+ "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}",
+ overlaySize);
+ overlayFilters.Add(overlayQsvFilter);
+ }
+ }
+ else if (memoryOutput)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ overlayFilters.Add("overlay=eof_action=endall:shortest=1:repeatlast=0");
+ }
+ }
+
+ return (mainFilters, subFilters, overlayFilters);
+ }
+
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetIntelQsvVaapiVidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ 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 isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isQsvDecoder = vidDecoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isQsvEncoder = vidEncoder.Contains("qsv", StringComparison.OrdinalIgnoreCase);
+ var isHwDecoder = isVaapiDecoder || isQsvDecoder;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !isQsvEncoder;
+ var isQsvInQsvOut = isHwDecoder && isQsvEncoder;
+
+ var doDeintH264 = state.DeInterlace("h264", true) || state.DeInterlace("avc", true);
+ var doDeintHevc = state.DeInterlace("h265", true) || state.DeInterlace("hevc", true);
+ var doVaVppTonemap = IsVaapiVppTonemapAvailable(state, options);
+ var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
+ var doTonemap = doVaVppTonemap || doOclTonemap;
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
+
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ var outFormat = doOclTonemap ? "yuv420p10le" : "yuv420p";
+ 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 ocl tonemap,
+ // since the overhead caused by hwupload >>> using sw filter.
+ // sw => hw
+ if (doOclTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
+ else if (isVaapiDecoder || isQsvDecoder)
+ {
+ // INPUT vaapi/qsv surface(vram)
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, isVaapiDecoder ? "vaapi" : "qsv");
+ mainFilters.Add(deintFilter);
+ }
+
+ var outFormat = doTonemap ? string.Empty : "nv12";
+ var hwScaleFilter = GetHwScaleFilter(isVaapiDecoder ? "vaapi" : "qsv", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
+ }
+
+ // vaapi vpp tonemap
+ if (doVaVppTonemap && isHwDecoder)
+ {
+ if (isQsvDecoder)
+ {
+ // map from qsv to vaapi.
+ mainFilters.Add("hwmap=derive_device=vaapi");
+ }
+
+ var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
+ mainFilters.Add(tonemapFilter);
+
+ if (isQsvDecoder)
+ {
+ // map from vaapi to qsv.
+ mainFilters.Add("hwmap=derive_device=qsv");
+ }
+ }
+
+ if (doOclTonemap && isHwDecoder)
+ {
+ // map from qsv to opencl via qsv(vaapi)-opencl interop.
+ mainFilters.Add("hwmap=derive_device=opencl");
+ }
+
+ // ocl tonemap
+ if (doOclTonemap)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ mainFilters.Add(tonemapFilter);
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
+ var isHwmapUsable = isSwEncoder && (doOclTonemap || isVaapiDecoder);
+ if ((isHwDecoder && isSwEncoder) || isUploadForOclTonemap)
+ {
+ memoryOutput = true;
+
+ // 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("format=nv12");
+ }
+
+ // OUTPUT nv12 surface(memory)
+ if (isSwDecoder && isQsvEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
+ {
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ }
+
+ if (isQsvInQsvOut)
+ {
+ if (doOclTonemap)
+ {
+ // OUTPUT qsv(nv12) surface(vram)
+ // reverse-mapping via qsv(vaapi)-opencl interop.
+ // add extra pool size to avoid the 'cannot allocate memory' error on hevc_qsv.
+ mainFilters.Add("hwmap=derive_device=qsv:reverse=1:extra_hw_frames=16");
+ mainFilters.Add("format=qsv");
+ }
+ else if (isVaapiDecoder)
+ {
+ mainFilters.Add("hwmap=derive_device=qsv");
+ mainFilters.Add("format=qsv");
+ }
+ }
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (isQsvInQsvOut)
+ {
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
{
- options.TonemappingAlgorithm = "hable";
+ subFilters.Add("scale=flags=fast_bilinear");
+ subFilters.Add("format=bgra");
}
-
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- parameters,
- options.TonemappingAlgorithm,
- options.TonemappingDesat,
- options.TonemappingThreshold,
- options.TonemappingPeak,
- options.TonemappingParam,
- options.TonemappingRange));
-
- if (isNvdecDecoder
- || isCuvidHevcDecoder
- || isCuvidVp9Decoder
- || isSwDecoder
- || isD3d11vaDecoder)
+ else if (hasTextSubs)
{
- filters.Add("hwdownload");
- filters.Add("format=nv12");
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
}
- if (isNvdecDecoder && isNvencEncoder)
+ // qsv requires a fixed pool size.
+ subFilters.Add("hwupload=extra_hw_frames=32");
+
+ var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var overlaySize = (overlayW.HasValue && overlayH.HasValue)
+ ? (":w=" + overlayW.Value + ":h=" + overlayH.Value)
+ : string.Empty;
+ var overlayQsvFilter = string.Format(
+ CultureInfo.InvariantCulture,
+ "overlay_qsv=eof_action=endall:shortest=1:repeatlast=0{0}",
+ overlaySize);
+ overlayFilters.Add(overlayQsvFilter);
+ }
+ }
+ else if (memoryOutput)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+ }
+ }
+
+ return (mainFilters, subFilters, overlayFilters);
+ }
+
+ /// <summary>
+ /// Gets the parameter of Intel/AMD VAAPI 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) GetVaapiVidFilterChain(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidEncoder)
+ {
+ if (!string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ return (null, null, null);
+ }
+
+ var isLinux = OperatingSystem.IsLinux();
+ var vidDecoder = GetHardwareVideoDecoder(state, options) ?? string.Empty;
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isVaapiOclSupported = isLinux && IsVaapiSupported(state) && IsVaapiFullSupported() && IsOpenclFullSupported();
+
+ // legacy vaapi pipeline(copy-back)
+ if ((isSwDecoder && isSwEncoder)
+ || !isVaapiOclSupported
+ || !_mediaEncoder.SupportsFilter("alphasrc"))
+ {
+ var swFilterChain = GetSwVidFilterChain(state, options, vidEncoder);
+
+ if (!isSwEncoder)
+ {
+ var newfilters = new List<string>();
+ var noOverlay = swFilterChain.OverlayFilters.Count == 0;
+ newfilters.AddRange(noOverlay ? swFilterChain.MainFilters : swFilterChain.OverlayFilters);
+ newfilters.Add("hwupload");
+
+ var mainFilters = noOverlay ? newfilters : swFilterChain.MainFilters;
+ var overlayFilters = noOverlay ? swFilterChain.OverlayFilters : newfilters;
+ return (mainFilters, swFilterChain.SubFilters, overlayFilters);
+ }
+
+ return swFilterChain;
+ }
+
+ // prefered vaapi + opencl filters pipeline
+ if (_mediaEncoder.IsVaapiDeviceInteliHD)
+ {
+ // Intel iHD path, with extra vpp tonemap and overlay support.
+ return GetVaapiFullVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
+
+ // Intel i965 and Amd radeonsi/r600 path, only featuring scale and deinterlace support.
+ return GetVaapiLimitedVidFiltersPrefered(state, options, vidDecoder, vidEncoder);
+ }
+
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiFullVidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ 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 isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ 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 doVaVppTonemap = isVaapiDecoder && IsVaapiVppTonemapAvailable(state, options);
+ var doOclTonemap = !doVaVppTonemap && IsHwTonemapAvailable(state, options);
+ var doTonemap = doVaVppTonemap || doOclTonemap;
+ var doDeintH2645 = doDeintH264 || doDeintHevc;
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+ var hasAssSubs = hasSubs
+ && (string.Equals(state.SubtitleStream.Codec, "ass", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(state.SubtitleStream.Codec, "ssa", StringComparison.OrdinalIgnoreCase));
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doTonemap));
+
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ var outFormat = doOclTonemap ? "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 ocl tonemap,
+ // since the overhead caused by hwupload >>> using sw filter.
+ // sw => hw
+ if (doOclTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
+ else if (isVaapiDecoder)
+ {
+ // INPUT vaapi surface(vram)
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ mainFilters.Add(deintFilter);
+ }
+
+ var outFormat = doTonemap ? string.Empty : "nv12";
+ var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
+ }
+
+ // vaapi vpp tonemap
+ if (doVaVppTonemap && isVaapiDecoder)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "vaapi", "nv12");
+ mainFilters.Add(tonemapFilter);
+ }
+
+ if (doOclTonemap && isVaapiDecoder)
+ {
+ // map from vaapi to opencl via vaapi-opencl interop(Intel only).
+ mainFilters.Add("hwmap=derive_device=opencl");
+ }
+
+ // ocl tonemap
+ if (doOclTonemap)
+ {
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ mainFilters.Add(tonemapFilter);
+ }
+
+ if (doOclTonemap && isVaInVaOut)
+ {
+ // OUTPUT vaapi(nv12) surface(vram)
+ // reverse-mapping via vaapi-opencl interop.
+ mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
+ mainFilters.Add("format=vaapi");
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = isSwDecoder && doOclTonemap;
+ var isHwmapNotUsable = isUploadForOclTonemap && isVaapiEncoder;
+ if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap)
+ {
+ memoryOutput = true;
+
+ // OUTPUT nv12 surface(memory)
+ // prefer hwmap to hwdownload on opencl/vaapi.
+ mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
+ mainFilters.Add("format=nv12");
+ }
+
+ // OUTPUT nv12 surface(memory)
+ if (isSwDecoder && isVaapiEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
+ {
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
+ }
+ }
+
+ if (memoryOutput && isVaapiEncoder)
+ {
+ if (!hasGraphicalSubs)
+ {
+ mainFilters.Add("hwupload_vaapi");
+ }
+ }
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (isVaInVaOut)
+ {
+ if (hasSubs)
+ {
+ if (hasGraphicalSubs)
{
- isHwuploadCudaRequired = true;
+ subFilters.Add("scale=flags=fast_bilinear");
+ subFilters.Add("format=bgra");
}
-
- if (isVaapiDecoder)
+ else if (hasTextSubs)
{
- // Reverse the data route from opencl to vaapi.
- filters.Add("hwmap=derive_device=vaapi:reverse=1");
+ var alphaSrcFilter = GetAlphaSrcFilter(state, inW, inH, reqW, reqH, reqMaxW, 1080, hasAssSubs ? 10 : 5);
+ var subTextSubtitlesFilter = GetTextSubtitlesFilter(state, true, true);
+ subFilters.Add(alphaSrcFilter);
+ subFilters.Add("format=bgra");
+ subFilters.Add(subTextSubtitlesFilter);
}
- var outputSdrParams = GetOutputSdrParams(options.TonemappingRange);
- if (!string.IsNullOrEmpty(outputSdrParams))
+ subFilters.Add("hwupload");
+
+ var (overlayW, overlayH) = GetFixedOutputSize(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ var overlaySize = (overlayW.HasValue && overlayH.HasValue)
+ ? (":w=" + overlayW.Value + ":h=" + overlayH.Value)
+ : string.Empty;
+ var overlayVaapiFilter = string.Format(
+ CultureInfo.InvariantCulture,
+ "overlay_vaapi=eof_action=endall:shortest=1:repeatlast=0{0}",
+ overlaySize);
+ overlayFilters.Add(overlayVaapiFilter);
+ }
+ }
+ else if (memoryOutput)
+ {
+ if (hasGraphicalSubs)
+ {
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+
+ if (isVaapiEncoder)
{
- filters.Add(outputSdrParams);
+ overlayFilters.Add("hwupload_vaapi");
}
}
}
- // When the input may or may not be hardware VAAPI decodable.
- if ((isVaapiH264Encoder || isVaapiHevcEncoder)
- && !(isTonemappingSupportedOnVaapi && (isOpenclTonemappingSupported || isVppTonemappingSupported)))
+ return (mainFilters, subFilters, overlayFilters);
+ }
+
+ public (List<string> MainFilters, List<string> SubFilters, List<string> OverlayFilters) GetVaapiLimitedVidFiltersPrefered(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string vidDecoder,
+ string vidEncoder)
+ {
+ 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 isVaapiDecoder = vidDecoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isVaapiEncoder = vidEncoder.Contains("vaapi", StringComparison.OrdinalIgnoreCase);
+ var isSwDecoder = string.IsNullOrEmpty(vidDecoder);
+ var isSwEncoder = !isVaapiEncoder;
+ var isVaInVaOut = isVaapiDecoder && isVaapiEncoder;
+ var isi965Driver = _mediaEncoder.IsVaapiDeviceInteli965;
+ var isAmdDriver = _mediaEncoder.IsVaapiDeviceAmd;
+
+ 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 doOclTonemap = IsHwTonemapAvailable(state, options);
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+
+ /* Make main filters for video stream */
+ var mainFilters = new List<string>();
+
+ mainFilters.Add(GetOverwriteColorPropertiesParam(state, doOclTonemap));
+
+ var outFormat = string.Empty;
+ if (isSwDecoder)
+ {
+ // INPUT sw surface(memory)
+ // sw deint
+ if (doDeintH2645)
+ {
+ var swDeintFilter = GetSwDeinterlaceFilter(state, options);
+ mainFilters.Add(swDeintFilter);
+ }
+
+ outFormat = doOclTonemap ? "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 ocl tonemap,
+ // since the overhead caused by hwupload >>> using sw filter.
+ // sw => hw
+ if (doOclTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
+ else if (isVaapiDecoder)
{
- filters.Add("format=nv12|vaapi");
- filters.Add("hwupload");
+ // INPUT vaapi surface(vram)
+ // hw deint
+ if (doDeintH2645)
+ {
+ var deintFilter = GetHwDeinterlaceFilter(state, options, "vaapi");
+ mainFilters.Add(deintFilter);
+ }
+
+ outFormat = doOclTonemap ? string.Empty : "nv12";
+ var hwScaleFilter = GetHwScaleFilter("vaapi", outFormat, inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ // hw scale
+ mainFilters.Add(hwScaleFilter);
}
- // When burning in graphical subtitles using overlay_qsv, upload videostream to the same qsv context.
- else if (isLinux && hasGraphicalSubs && (isQsvH264Encoder || isQsvHevcEncoder)
- && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
+ if (doOclTonemap && isVaapiDecoder)
{
- filters.Add("hwupload=extra_hw_frames=64");
+ if (isi965Driver)
+ {
+ // map from vaapi to opencl via vaapi-opencl interop(Intel only).
+ mainFilters.Add("hwmap=derive_device=opencl");
+ }
+ else
+ {
+ mainFilters.Add("hwdownload");
+ mainFilters.Add("format=p010le");
+ mainFilters.Add("hwupload");
+ }
}
- // If we're hardware VAAPI decoding and software encoding, download frames from the decoder first.
- else if ((IsVaapiSupported(state) && isVaapiDecoder) && (isLibX264Encoder || isLibX265Encoder)
- && !(isTonemappingSupportedOnQsv && isVppTonemappingSupported))
+ // ocl tonemap
+ if (doOclTonemap)
{
- var codec = videoStream.Codec;
+ var tonemapFilter = GetHwTonemapFilter(options, "opencl", "nv12");
+ mainFilters.Add(tonemapFilter);
+ }
- // Assert 10-bit hardware VAAPI decodable
- if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+ if (doOclTonemap && isVaInVaOut)
+ {
+ if (isi965Driver)
{
- /*
- Download data from GPU to CPU as p010le format.
- Colorspace conversion is unnecessary here as libx264 will handle it.
- If this step is missing, it will fail on AMD but not on intel.
- */
- filters.Add("hwdownload");
- filters.Add("format=p010le");
+ // OUTPUT vaapi(nv12) surface(vram)
+ // reverse-mapping via vaapi-opencl interop.
+ mainFilters.Add("hwmap=derive_device=vaapi:reverse=1");
+ mainFilters.Add("format=vaapi");
}
+ }
+
+ var memoryOutput = false;
+ var isUploadForOclTonemap = doOclTonemap && (isSwDecoder || (isVaapiDecoder && !isi965Driver));
+ var isHwmapNotUsable = hasGraphicalSubs || isUploadForOclTonemap;
+ var isHwmapForSubs = hasSubs && isVaapiDecoder;
+ var isHwUnmapForTextSubs = hasTextSubs && isVaInVaOut && !isUploadForOclTonemap;
+ if ((isVaapiDecoder && isSwEncoder) || isUploadForOclTonemap || isHwmapForSubs)
+ {
+ memoryOutput = true;
- // Assert 8-bit hardware VAAPI decodable
- else if (!isColorDepth10)
+ // OUTPUT nv12 surface(memory)
+ // prefer hwmap to hwdownload on opencl/vaapi.
+ mainFilters.Add(isHwmapNotUsable ? "hwdownload" : "hwmap");
+ mainFilters.Add("format=nv12");
+ }
+
+ // OUTPUT nv12 surface(memory)
+ if (isSwDecoder && isVaapiEncoder)
+ {
+ memoryOutput = true;
+ }
+
+ if (memoryOutput)
+ {
+ // text subtitles
+ if (hasTextSubs)
{
- filters.Add("hwdownload");
- filters.Add("format=nv12");
+ var textSubtitlesFilter = GetTextSubtitlesFilter(state, false, false);
+ mainFilters.Add(textSubtitlesFilter);
}
}
- // Add hardware deinterlace filter before scaling filter.
- if (isDeinterlaceH264 || isDeinterlaceHevc)
+ if (isHwUnmapForTextSubs)
+ {
+ mainFilters.Add("hwmap");
+ mainFilters.Add("format=vaapi");
+ }
+ else if (memoryOutput && isVaapiEncoder)
{
- if (isVaapiEncoder
- || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
+ if (!hasGraphicalSubs)
{
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "deinterlace_vaapi=rate={0}",
- doubleRateDeinterlace ? "field" : "frame"));
+ mainFilters.Add("hwupload_vaapi");
}
- else if (isNvdecDecoder && !isCudaDeintInAdvance)
+ }
+
+ /* Make sub and overlay filters for subtitle stream */
+ var subFilters = new List<string>();
+ var overlayFilters = new List<string>();
+ if (memoryOutput)
+ {
+ if (hasGraphicalSubs)
{
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "yadif_cuda={0}:-1:0",
- doubleRateDeinterlace ? "1" : "0"));
+ var subSwScaleFilter = GetCustomSwScaleFilter(inW, inH, reqW, reqH, reqMaxW, reqMaxH);
+ subFilters.Add(subSwScaleFilter);
+ overlayFilters.Add("overlay=eof_action=pass:shortest=1:repeatlast=0");
+
+ if (isVaapiEncoder)
+ {
+ overlayFilters.Add("hwupload_vaapi");
+ }
}
}
- // Add software deinterlace filter before scaling filter.
- if ((isDeinterlaceH264 || isDeinterlaceHevc)
- && !isVaapiH264Encoder
- && !isVaapiHevcEncoder
- && !isQsvH264Encoder
- && !isQsvHevcEncoder
- && !isNvdecDecoder
- && !isCuvidH264Decoder)
+ return (mainFilters, subFilters, overlayFilters);
+ }
+
+ /// <summary>
+ /// Gets the parameter of video processing filters.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="outputVideoCodec">Video codec to use.</param>
+ /// <returns>The video processing filters parameter.</returns>
+ public string GetVideoProcessingFilterParam(
+ EncodingJobInfo state,
+ EncodingOptions options,
+ string outputVideoCodec)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream == null)
+ {
+ return string.Empty;
+ }
+
+ var hasSubs = state.SubtitleStream != null && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
+ var hasTextSubs = hasSubs && state.SubtitleStream.IsTextSubtitleStream;
+ var hasGraphicalSubs = hasSubs && !state.SubtitleStream.IsTextSubtitleStream;
+
+ List<string> mainFilters;
+ List<string> subFilters;
+ List<string> overlayFilters;
+
+ if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetVaapiVidFilterChain(state, options, outputVideoCodec);
+ }
+ else if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetIntelVidFilterChain(state, options, outputVideoCodec);
+ }
+ else if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetNvidiaVidFilterChain(state, options, outputVideoCodec);
+ }
+ else if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
+ {
+ (mainFilters, subFilters, overlayFilters) = GetAmdVidFilterChain(state, options, outputVideoCodec);
+ }
+ else
+ {
+ (mainFilters, subFilters, overlayFilters) = GetSwVidFilterChain(state, options, outputVideoCodec);
+ }
+
+ mainFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter));
+ subFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter));
+ overlayFilters?.RemoveAll(filter => string.IsNullOrEmpty(filter));
+
+ var mainStr = string.Empty;
+ if (mainFilters?.Count > 0)
+ {
+ mainStr = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}",
+ string.Join(',', mainFilters));
+ }
+
+ if (overlayFilters?.Count == 0)
+ {
+ // -vf "scale..."
+ return string.IsNullOrEmpty(mainStr) ? string.Empty : " -vf \"" + mainStr + "\"";
+ }
+
+ if (overlayFilters?.Count > 0
+ && subFilters?.Count > 0
+ && state.SubtitleStream != null)
{
- if (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
+ // overlay graphical/text subtitles
+ var subStr = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}",
+ string.Join(',', subFilters));
+
+ var overlayStr = string.Format(
+ CultureInfo.InvariantCulture,
+ "{0}",
+ string.Join(',', overlayFilters));
+
+ var mapPrefix = Convert.ToInt32(state.SubtitleStream.IsExternal);
+ var subtitleStreamIndex = state.SubtitleStream.IsExternal
+ ? 0
+ : state.SubtitleStream.Index;
+
+ if (hasSubs)
{
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "bwdif={0}:-1:0",
- doubleRateDeinterlace ? "1" : "0"));
+ // -filter_complex "[0:s]scale=s[sub]..."
+ var filterStr = string.IsNullOrEmpty(mainStr)
+ ? " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}][sub]{5}\""
+ : " -filter_complex \"[{0}:{1}]{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
+
+ if (hasTextSubs)
+ {
+ filterStr = string.IsNullOrEmpty(mainStr)
+ ? " -filter_complex \"{4}[sub];[0:{2}][sub]{5}\""
+ : " -filter_complex \"{4}[sub];[0:{2}]{3}[main];[main][sub]{5}\"";
+ }
+
+ return string.Format(
+ CultureInfo.InvariantCulture,
+ filterStr,
+ mapPrefix,
+ subtitleStreamIndex,
+ state.VideoStream.Index,
+ mainStr,
+ subStr,
+ overlayStr);
+ }
+ }
+
+ return string.Empty;
+ }
+
+ public string GetOverwriteColorPropertiesParam(EncodingJobInfo state, bool isTonemapAvailable)
+ {
+ if (isTonemapAvailable)
+ {
+ return GetInputHdrParam(state.VideoStream?.ColorTransfer);
+ }
+
+ return GetOutputSdrParam(null);
+ }
+
+ public string GetInputHdrParam(string colorTransfer)
+ {
+ if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ {
+ // HLG
+ return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
+ }
+
+ // HDR10
+ return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
+ }
+
+ public string GetOutputSdrParam(string tonemappingRange)
+ {
+ // SDR
+ if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
+ {
+ return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
+ }
+
+ if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
+ {
+ return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
+ }
+
+ return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+ }
+
+ public static int GetVideoColorBitDepth(EncodingJobInfo state)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream != null)
+ {
+ if (videoStream.BitDepth.HasValue)
+ {
+ return videoStream.BitDepth.Value;
+ }
+ else if (string.Equals(videoStream.PixelFormat, "yuv420p", 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))
+ {
+ return 10;
+ }
+ else if (string.Equals(videoStream.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
+ {
+ return 12;
}
else
{
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "yadif={0}:-1:0",
- doubleRateDeinterlace ? "1" : "0"));
+ return 8;
}
}
- // Add scaling filter: scale_*=format=nv12 or scale_*=w=*:h=*:format=nv12 or scale=expr
- if (!isScalingInAdvance)
+ return 0;
+ }
+
+ /// <summary>
+ /// Gets the ffmpeg option string for the hardware accelerated video decoder.
+ /// </summary>
+ /// <param name="state">The encoding job info.</param>
+ /// <param name="options">The encoding options.</param>
+ /// <returns>The option string or null if none available.</returns>
+ protected string GetHardwareVideoDecoder(EncodingJobInfo state, EncodingOptions options)
+ {
+ var videoStream = state.VideoStream;
+ if (videoStream == null)
+ {
+ return null;
+ }
+
+ // Only use alternative encoders for video files.
+ var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
+ if (videoType != VideoType.VideoFile)
+ {
+ return null;
+ }
+
+ if (IsCopyCodec(state.OutputVideoCodec))
{
- filters.AddRange(
- GetScalingFilters(
- state,
- options,
- inputWidth,
- inputHeight,
- threeDFormat,
- videoDecoder,
- outputVideoCodec,
- request.Width,
- request.Height,
- request.MaxWidth,
- request.MaxHeight));
+ return null;
}
- // Add Cuda tonemapping filter.
- if (isNvdecDecoder && isCudaTonemappingSupported)
+ if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(options.HardwareAccelerationType))
{
- isNoTonemapFilterApplied = false;
- var inputHdrParams = GetInputHdrParams(videoStream.ColorTransfer);
- if (!string.IsNullOrEmpty(inputHdrParams))
+ var bitDepth = GetVideoColorBitDepth(state);
+
+ // Only HEVC, VP9 and AV1 formats have 10-bit hardware decoder support now.
+ if (bitDepth == 10
+ && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase)))
{
- filters.Add(inputHdrParams);
+ return null;
}
- var parameters = (hasGraphicalSubs && isCudaOverlaySupported && isNvencEncoder)
- ? "tonemap_cuda=format=yuv420p:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}"
- : "tonemap_cuda=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:peak={1}:desat={2}";
+ if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetQsvHwVidDecoder(state, options, videoStream, bitDepth);
+ }
- if (options.TonemappingParam != 0)
+ if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
{
- parameters += ":param={3}";
+ return GetNvdecVidDecoder(state, options, videoStream, bitDepth);
}
- if (!string.Equals(options.TonemappingRange, "auto", StringComparison.OrdinalIgnoreCase))
+ if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
{
- parameters += ":range={4}";
+ return GetAmfVidDecoder(state, options, videoStream, bitDepth);
}
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- parameters,
- options.TonemappingAlgorithm,
- options.TonemappingPeak,
- options.TonemappingDesat,
- options.TonemappingParam,
- options.TonemappingRange));
-
- if (isLibX264Encoder
- || isLibX265Encoder
- || hasTextSubs
- || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
- {
- if (isNvencEncoder)
- {
- isHwuploadCudaRequired = true;
- }
+ if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetVaapiVidDecoder(state, options, videoStream, bitDepth);
+ }
- filters.Add("hwdownload");
- filters.Add("format=nv12");
+ if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetVideotoolboxVidDecoder(state, options, videoStream, bitDepth);
}
- var outputSdrParams = GetOutputSdrParams(options.TonemappingRange);
- if (!string.IsNullOrEmpty(outputSdrParams))
+ if (string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
{
- filters.Add(outputSdrParams);
+ return GetOmxVidDecoder(state, options, videoStream, bitDepth);
}
}
- // Add VPP tonemapping filter for VAAPI.
- // Full hardware based video post processing, faster than OpenCL but lacks fine tuning options.
- if ((isTonemappingSupportedOnVaapi || isTonemappingSupportedOnQsv)
- && isVppTonemappingSupported)
+ var whichCodec = videoStream.Codec;
+ if (string.Equals(whichCodec, "avc", StringComparison.OrdinalIgnoreCase))
+ {
+ whichCodec = "h264";
+ }
+ else if (string.Equals(whichCodec, "h265", StringComparison.OrdinalIgnoreCase))
{
- filters.Add("tonemap_vaapi=format=nv12:transfer=bt709:matrix=bt709:primaries=bt709");
+ whichCodec = "hevc";
}
- // Another case is when using Nvenc decoder.
- if (isNvdecDecoder && !isOpenclTonemappingSupported && !isCudaTonemappingSupported)
+ // Avoid a second attempt if no hardware acceleration is being used
+ options.HardwareDecodingCodecs = Array.FindAll(options.HardwareDecodingCodecs, val => !string.Equals(val, whichCodec, StringComparison.OrdinalIgnoreCase));
+
+ // leave blank so ffmpeg will decide
+ return null;
+ }
+
+ /// <summary>
+ /// Gets a hw decoder name.
+ /// </summary>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="decoderPrefix">Decoder prefix.</param>
+ /// <param name="decoderSuffix">Decoder suffix.</param>
+ /// <param name="videoCodec">Video codec to use.</param>
+ /// <param name="bitDepth">Video color bit depth.</param>
+ /// <returns>Hardware decoder name.</returns>
+ public string GetHwDecoderName(EncodingOptions options, string decoderPrefix, string decoderSuffix, string videoCodec, int bitDepth)
+ {
+ if (string.IsNullOrEmpty(decoderPrefix) || string.IsNullOrEmpty(decoderSuffix))
{
- var codec = videoStream.Codec;
- var isCudaFormatConversionSupported = _mediaEncoder.SupportsFilterWithOption(FilterOptionType.ScaleCudaFormat);
+ return null;
+ }
+
+ var decoderName = decoderPrefix + '_' + decoderSuffix;
- // Assert 10-bit hardware decodable
- if (isColorDepth10 && (string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "vp9", StringComparison.OrdinalIgnoreCase)))
+ var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoderName) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
+ if (bitDepth == 10 && isCodecAvailable)
+ {
+ if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
+ || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
{
- if (isCudaFormatConversionSupported)
- {
- if (isLibX264Encoder
- || isLibX265Encoder
- || hasTextSubs
- || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder))
- {
- if (isNvencEncoder)
- {
- isHwuploadCudaRequired = true;
- }
+ return null;
+ }
+ }
- filters.Add("hwdownload");
- filters.Add("format=nv12");
- }
- }
- else
+ if (string.Equals(decoderSuffix, "cuvid", StringComparison.OrdinalIgnoreCase) && options.EnableEnhancedNvdecDecoder)
+ {
+ return null;
+ }
+
+ if (string.Equals(decoderSuffix, "qsv", StringComparison.OrdinalIgnoreCase) && options.PreferSystemNativeHwDecoder)
+ {
+ return null;
+ }
+
+ return isCodecAvailable ? (" -c:v " + decoderName) : null;
+ }
+
+ /// <summary>
+ /// Gets a hwaccel type to use as a hardware decoder depending on the system.
+ /// </summary>
+ /// <param name="state">Encoding state.</param>
+ /// <param name="options">Encoding options.</param>
+ /// <param name="videoCodec">Video codec to use.</param>
+ /// <param name="bitDepth">Video color bit depth.</param>
+ /// <param name="outputHwSurface">Specifies if output hw surface.</param>
+ /// <returns>Hardware accelerator type.</returns>
+ public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, int bitDepth, bool outputHwSurface)
+ {
+ var isWindows = OperatingSystem.IsWindows();
+ var isLinux = OperatingSystem.IsLinux();
+ var isMacOS = OperatingSystem.IsMacOS();
+ var isD3d11Supported = isWindows && _mediaEncoder.SupportsHwaccel("d3d11va");
+ var isVaapiSupported = isLinux && IsVaapiSupported(state);
+ var isCudaSupported = (isLinux || isWindows) && IsCudaFullSupported();
+ var isQsvSupported = (isLinux || isWindows) && _mediaEncoder.SupportsHwaccel("qsv");
+ var isVideotoolboxSupported = isMacOS && _mediaEncoder.SupportsHwaccel("videotoolbox");
+ var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
+
+ // Set the av1 codec explicitly to trigger hw accelerator, otherwise libdav1d will be used.
+ var isAv1 = string.Equals(videoCodec, "av1", StringComparison.OrdinalIgnoreCase);
+
+ if (bitDepth == 10 && isCodecAvailable)
+ {
+ if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
+ || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
+ {
+ return null;
+ }
+ }
+
+ // Intel qsv/d3d11va/vaapi
+ if (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ if (options.PreferSystemNativeHwDecoder)
+ {
+ if (isVaapiSupported && isCodecAvailable)
{
- // Download data from GPU to CPU as p010 format.
- filters.Add("hwdownload");
- filters.Add("format=p010");
+ return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ }
- // Cuda lacks of a pixel format converter.
- if (isNvencEncoder)
- {
- isHwuploadCudaRequired = true;
- filters.Add("format=yuv420p");
- }
+ if (isD3d11Supported && isCodecAvailable)
+ {
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
}
-
- // Assert 8-bit hardware decodable
- else if (!isColorDepth10
- && (isLibX264Encoder
- || isLibX265Encoder
- || hasTextSubs
- || (hasGraphicalSubs && !isCudaOverlaySupported && isNvencEncoder)))
+ else
{
- if (isNvencEncoder)
+ if (isQsvSupported && isCodecAvailable)
{
- isHwuploadCudaRequired = true;
+ return " -hwaccel qsv" + (outputHwSurface ? " -hwaccel_output_format qsv" : string.Empty);
}
+ }
+ }
- filters.Add("hwdownload");
- filters.Add("format=nv12");
+ // Nvidia cuda
+ if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ if (options.EnableEnhancedNvdecDecoder && isCudaSupported && isCodecAvailable)
+ {
+ return " -hwaccel cuda" + (outputHwSurface ? " -hwaccel_output_format cuda" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
}
}
- // Add parameters to use VAAPI with burn-in text subtitles (GH issue #642)
- if (isVaapiH264Encoder
- || isVaapiHevcEncoder
- || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
+ // Amd d3d11va
+ if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
{
- if (hasTextSubs)
+ if (isD3d11Supported && isCodecAvailable)
{
- // Convert hw context from ocl to va.
- // For tonemapping and text subs burn-in.
- if (isTonemappingSupportedOnVaapi && isOpenclTonemappingSupported && !isVppTonemappingSupported)
- {
- filters.Add("scale_vaapi");
- }
+ return " -hwaccel d3d11va" + (outputHwSurface ? " -hwaccel_output_format d3d11" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ }
+ }
+
+ // Vaapi
+ if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
+ && isVaapiSupported
+ && isCodecAvailable)
+ {
+ return " -hwaccel vaapi" + (outputHwSurface ? " -hwaccel_output_format vaapi" : string.Empty) + (isAv1 ? " -c:v av1" : string.Empty);
+ }
+
+ if (string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase)
+ && isVideotoolboxSupported
+ && isCodecAvailable)
+ {
+ return " -hwaccel videotoolbox" + (outputHwSurface ? " -hwaccel_output_format videotoolbox_vld" : string.Empty);
+ }
+
+ return null;
+ }
+
+ public string GetQsvHwVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ var isWindows = OperatingSystem.IsWindows();
+ var isLinux = OperatingSystem.IsLinux();
+
+ if ((!isWindows && !isLinux)
+ || !string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var isQsvOclSupported = _mediaEncoder.SupportsHwaccel("qsv") && IsOpenclFullSupported();
+ var isIntelDx11OclSupported = isWindows
+ && _mediaEncoder.SupportsHwaccel("d3d11va")
+ && isQsvOclSupported;
+ var isIntelVaapiOclSupported = isLinux
+ && IsVaapiSupported(state)
+ && isQsvOclSupported;
+ var hwSurface = (isIntelDx11OclSupported || isIntelVaapiOclSupported)
+ && _mediaEncoder.SupportsFilter("alphasrc");
+
+ var is8bitSwFormatsQsv = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsQsv = is8bitSwFormatsQsv || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ // TODO: add more 8/10bit and 4:4:4 formats for Qsv after finishing the ffcheck tool
+
+ if (is8bitSwFormatsQsv)
+ {
+ if (string.Equals(videoStream.Codec, "avc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h264", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "qsv", "h264", bitDepth);
+ }
+
+ if (string.Equals(videoStream.Codec, "vc1", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "qsv", "vc1", bitDepth);
+ }
- // Test passed on Intel and AMD gfx
- filters.Add("hwmap=mode=read+write");
- filters.Add("format=nv12");
+ if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "qsv", "vp8", bitDepth);
+ }
+
+ if (string.Equals(videoStream.Codec, "mpeg2video", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "qsv", "mpeg2video", bitDepth);
}
}
- if (hasTextSubs)
+ if (is8_10bitSwFormatsQsv)
+ {
+ if (string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "qsv", "hevc", bitDepth);
+ }
+
+ if (string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "qsv", "vp9", bitDepth);
+ }
+
+ if (string.Equals(videoStream.Codec, "av1", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "qsv", "av1", bitDepth);
+ }
+ }
+
+ return null;
+ }
+
+ public string GetNvdecVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ if ((!OperatingSystem.IsWindows() && !OperatingSystem.IsLinux())
+ || !string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var hwSurface = IsCudaFullSupported()
+ && options.EnableEnhancedNvdecDecoder
+ && _mediaEncoder.SupportsFilter("alphasrc");
+ var is8bitSwFormatsNvdec = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsNvdec = is8bitSwFormatsNvdec || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ // TODO: add more 8/10/12bit and 4:4:4 formats for Nvdec after finishing the ffcheck tool
+
+ if (is8bitSwFormatsNvdec)
+ {
+ if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "h264", bitDepth, hwSurface) + GetHwDecoderName(options, "h264", "cuvid", "h264", bitDepth);
+ }
+
+ if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "cuvid", "mpeg2video", bitDepth);
+ }
+
+ if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "cuvid", "vc1", bitDepth);
+ }
+
+ if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg4", "cuvid", "mpeg4", bitDepth);
+ }
+
+ if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "cuvid", "vp8", bitDepth);
+ }
+ }
+
+ if (is8_10bitSwFormatsNvdec)
+ {
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface) + GetHwDecoderName(options, "hevc", "cuvid", "hevc", bitDepth);
+ }
+
+ if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface) + GetHwDecoderName(options, "vp9", "cuvid", "vp9", bitDepth);
+ }
+
+ if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, hwSurface) + GetHwDecoderName(options, "av1", "cuvid", "av1", bitDepth);
+ }
+ }
+
+ return null;
+ }
+
+ public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ if (!OperatingSystem.IsWindows()
+ || !string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
{
- var subParam = GetTextSubtitleParam(state);
+ return null;
+ }
+
+ var hwSurface = _mediaEncoder.SupportsHwaccel("d3d11va")
+ && IsOpenclFullSupported()
+ && _mediaEncoder.SupportsFilter("alphasrc");
+ var is8bitSwFormatsAmf = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsAmf = is8bitSwFormatsAmf || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
- filters.Add(subParam);
+ if (is8bitSwFormatsAmf)
+ {
+ if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
+ }
- // Ensure proper filters are passed to ffmpeg in case of hardware acceleration via VA-API
- // Reference: https://trac.ffmpeg.org/wiki/Hardware/VAAPI
- if (isVaapiH264Encoder || isVaapiHevcEncoder)
+ if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- filters.Add("hwmap");
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
}
- if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
+ if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- filters.Add("hwmap,format=vaapi");
+ return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
}
- if (isNvdecDecoder && isNvencEncoder)
+ if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- isHwuploadCudaRequired = true;
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
}
}
- // Interop the VAAPI data to QSV for hybrid tonemapping
- if (isTonemappingSupportedOnQsv && isVppTonemappingSupported && !hasGraphicalSubs)
+ if (is8_10bitSwFormatsAmf)
{
- filters.Add("hwmap=derive_device=qsv,scale_qsv");
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
+ }
}
- if (isHwuploadCudaRequired && !hasGraphicalSubs)
+ return null;
+ }
+
+ public string GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ if (!OperatingSystem.IsLinux()
+ || !string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- filters.Add("hwupload_cuda");
+ return null;
}
- // If no tonemap filter is applied,
- // tag the video range as SDR to prevent the encoder from encoding HDR video.
- if (isNoTonemapFilterApplied)
+ var hwSurface = IsVaapiSupported(state)
+ && IsVaapiFullSupported()
+ && IsOpenclFullSupported()
+ && _mediaEncoder.SupportsFilter("alphasrc");
+ var is8bitSwFormatsVaapi = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsVaapi = is8bitSwFormatsVaapi || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+
+ if (is8bitSwFormatsVaapi)
{
- var outputSdrParams = GetOutputSdrParams(null);
- if (!string.IsNullOrEmpty(outputSdrParams))
+ if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "h264", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- filters.Add(outputSdrParams);
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
}
}
- var output = string.Empty;
- if (filters.Count > 0)
+ if (is8_10bitSwFormatsVaapi)
{
- output += string.Format(
- CultureInfo.InvariantCulture,
- "{0}",
- string.Join(',', filters));
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp9", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("av1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "av1", bitDepth, hwSurface);
+ }
}
- return output;
+ return null;
}
- public static string GetInputHdrParams(string colorTransfer)
+ public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
- if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ if (!OperatingSystem.IsMacOS()
+ || !string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{
- // HLG
- return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
+ return null;
}
- else
+
+ var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+
+ if (is8bitSwFormatsVt)
+ {
+ if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "h264", bitDepth, false);
+ }
+
+ if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, false);
+ }
+
+ if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, false);
+ }
+ }
+
+ if (is8_10bitSwFormatsVt)
{
- // HDR10
- return "setparams=color_primaries=bt2020:color_trc=smpte2084:colorspace=bt2020nc";
+ if (string.Equals("hevc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h265", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "hevc", bitDepth, false);
+ }
+
+ if (string.Equals("vp9", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp9", bitDepth, false);
+ }
}
+
+ return null;
}
- public static string GetOutputSdrParams(string tonemappingRange)
+ public string GetOmxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
- // SDR
- if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
+ if (!OperatingSystem.IsLinux()
+ || !string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
{
- return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
+ return null;
}
- if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
+ var is8bitSwFormatsOmx = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+
+ if (is8bitSwFormatsOmx)
{
- return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=pc";
+ if (string.Equals("avc", videoStream.Codec, StringComparison.OrdinalIgnoreCase)
+ || string.Equals("h264", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwDecoderName(options, "h264", "mmal", "h264", bitDepth);
+ }
+
+ if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwDecoderName(options, "mpeg2", "mmal", "mpeg2video", bitDepth);
+ }
+
+ if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwDecoderName(options, "mpeg4", "mmal", "mpeg4", bitDepth);
+ }
+
+ if (string.Equals("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwDecoderName(options, "vc1", "mmal", "vc1", bitDepth);
+ }
}
- return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+ return null;
}
/// <summary>
@@ -3285,7 +4860,7 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier = inputModifier.Trim();
- inputModifier += " " + GetFastSeekCommandLineParameter(state.BaseRequest);
+ inputModifier += " " + GetFastSeekCommandLineParameter(state, encodingOptions);
inputModifier = inputModifier.Trim();
if (state.InputProtocol == MediaProtocol.Rtsp)
@@ -3339,61 +4914,8 @@ namespace MediaBrowser.Controller.MediaEncoding
inputModifier += " -fflags " + string.Join(string.Empty, flags);
}
- var videoDecoder = GetHardwareAcceleratedVideoDecoder(state, encodingOptions);
-
- if (!string.IsNullOrEmpty(videoDecoder))
- {
- inputModifier += " " + videoDecoder;
-
- if (!IsCopyCodec(state.OutputVideoCodec)
- && videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase))
- {
- var videoStream = state.VideoStream;
- var inputWidth = videoStream?.Width;
- var inputHeight = videoStream?.Height;
- var request = state.BaseRequest;
-
- var (width, height) = GetFixedOutputSize(inputWidth, inputHeight, request.Width, request.Height, request.MaxWidth, request.MaxHeight);
-
- if (videoDecoder.Contains("cuvid", StringComparison.OrdinalIgnoreCase)
- && width.HasValue
- && height.HasValue)
- {
- if (width.HasValue && height.HasValue)
- {
- inputModifier += string.Format(
- CultureInfo.InvariantCulture,
- " -resize {0}x{1}",
- width.Value,
- height.Value);
- }
-
- if (state.DeInterlace("h264", true))
- {
- inputModifier += " -deint 1";
-
- if (!encodingOptions.DeinterlaceDoubleRate || (videoStream?.AverageFrameRate ?? 60) > 30)
- {
- inputModifier += " -drop_second_field 1";
- }
- }
- }
- }
- }
-
if (state.IsVideoRequest)
{
- var outputVideoCodec = GetVideoEncoder(state, encodingOptions);
-
- // Important: If this is ever re-enabled, make sure not to use it with wtv because it breaks seeking
- if (!string.Equals(state.InputContainer, "wtv", StringComparison.OrdinalIgnoreCase)
- && state.TranscodingType != TranscodingJobType.Progressive
- && !state.EnableBreakOnNonKeyFrames(outputVideoCodec)
- && (state.BaseRequest.StartTimeTicks ?? 0) > 0)
- {
- inputModifier += " -noaccurate_seek";
- }
-
if (!string.IsNullOrEmpty(state.InputContainer) && state.VideoType == VideoType.VideoFile && string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
{
var inputFormat = GetInputFormat(state.InputContainer);
@@ -3606,322 +5128,6 @@ namespace MediaBrowser.Controller.MediaEncoding
}
}
- /// <summary>
- /// Gets the ffmpeg option string for the hardware accelerated video decoder.
- /// </summary>
- /// <param name="state">The encoding job info.</param>
- /// <param name="encodingOptions">The encoding options.</param>
- /// <returns>The option string or null if none available.</returns>
- protected string GetHardwareAcceleratedVideoDecoder(EncodingJobInfo state, EncodingOptions encodingOptions)
- {
- var videoStream = state.VideoStream;
-
- if (videoStream == null)
- {
- return null;
- }
-
- var videoType = state.MediaSource.VideoType ?? VideoType.VideoFile;
- // Only use alternative encoders for video files.
- // When using concat with folder rips, if the mfx session fails to initialize, ffmpeg will be stuck retrying and will not exit gracefully
- // Since transcoding of folder rips is experimental anyway, it's not worth adding additional variables such as this.
- if (videoType != VideoType.VideoFile)
- {
- return null;
- }
-
- if (IsCopyCodec(state.OutputVideoCodec))
- {
- return null;
- }
-
- if (!string.IsNullOrEmpty(videoStream.Codec) && !string.IsNullOrEmpty(encodingOptions.HardwareAccelerationType))
- {
- var isColorDepth10 = IsColorDepth10(state);
-
- // Only hevc and vp9 formats have 10-bit hardware decoder support now.
- if (isColorDepth10 && !(string.Equals(videoStream.Codec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.Codec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(videoStream.Codec, "vp9", StringComparison.OrdinalIgnoreCase)))
- {
- return null;
- }
-
- // Hybrid VPP tonemapping with VAAPI
- if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
- && IsVppTonemappingSupported(state, encodingOptions))
- {
- var outputVideoCodec = GetVideoEncoder(state, encodingOptions) ?? string.Empty;
- var isQsvEncoder = outputVideoCodec.Contains("qsv", StringComparison.OrdinalIgnoreCase);
- if (isQsvEncoder)
- {
- // Since tonemap_vaapi only support HEVC for now, no need to check the codec again.
- return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
- }
- }
-
- if (string.Equals(encodingOptions.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return GetHwDecoderName(encodingOptions, "h264_qsv", "h264", isColorDepth10);
- case "hevc":
- case "h265":
- return GetHwDecoderName(encodingOptions, "hevc_qsv", "hevc", isColorDepth10);
- case "mpeg2video":
- return GetHwDecoderName(encodingOptions, "mpeg2_qsv", "mpeg2video", isColorDepth10);
- case "vc1":
- return GetHwDecoderName(encodingOptions, "vc1_qsv", "vc1", isColorDepth10);
- case "vp8":
- return GetHwDecoderName(encodingOptions, "vp8_qsv", "vp8", isColorDepth10);
- case "vp9":
- return GetHwDecoderName(encodingOptions, "vp9_qsv", "vp9", isColorDepth10);
- }
- }
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "h264", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "h264_cuvid", "h264", isColorDepth10);
- case "hevc":
- case "h265":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "hevc_cuvid", "hevc", isColorDepth10);
- case "mpeg2video":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "mpeg2_cuvid", "mpeg2video", isColorDepth10);
- case "vc1":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "vc1_cuvid", "vc1", isColorDepth10);
- case "mpeg4":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "mpeg4_cuvid", "mpeg4", isColorDepth10);
- case "vp8":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "vp8_cuvid", "vp8", isColorDepth10);
- case "vp9":
- return encodingOptions.EnableEnhancedNvdecDecoder && IsCudaSupported()
- ? GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10)
- : GetHwDecoderName(encodingOptions, "vp9_cuvid", "vp9", isColorDepth10);
- }
- }
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "mediacodec", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return GetHwDecoderName(encodingOptions, "h264_mediacodec", "h264", isColorDepth10);
- case "hevc":
- case "h265":
- return GetHwDecoderName(encodingOptions, "hevc_mediacodec", "hevc", isColorDepth10);
- case "mpeg2video":
- return GetHwDecoderName(encodingOptions, "mpeg2_mediacodec", "mpeg2video", isColorDepth10);
- case "mpeg4":
- return GetHwDecoderName(encodingOptions, "mpeg4_mediacodec", "mpeg4", isColorDepth10);
- case "vp8":
- return GetHwDecoderName(encodingOptions, "vp8_mediacodec", "vp8", isColorDepth10);
- case "vp9":
- return GetHwDecoderName(encodingOptions, "vp9_mediacodec", "vp9", isColorDepth10);
- }
- }
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return GetHwDecoderName(encodingOptions, "h264_mmal", "h264", isColorDepth10);
- case "mpeg2video":
- return GetHwDecoderName(encodingOptions, "mpeg2_mmal", "mpeg2video", isColorDepth10);
- case "mpeg4":
- return GetHwDecoderName(encodingOptions, "mpeg4_mmal", "mpeg4", isColorDepth10);
- case "vc1":
- return GetHwDecoderName(encodingOptions, "vc1_mmal", "vc1", isColorDepth10);
- }
- }
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10);
- case "hevc":
- case "h265":
- return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
- case "mpeg2video":
- return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10);
- case "vc1":
- return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10);
- case "mpeg4":
- return GetHwaccelType(state, encodingOptions, "mpeg4", isColorDepth10);
- case "vp9":
- return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10);
- }
- }
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return GetHwaccelType(state, encodingOptions, "h264", isColorDepth10);
- case "hevc":
- case "h265":
- return GetHwaccelType(state, encodingOptions, "hevc", isColorDepth10);
- case "mpeg2video":
- return GetHwaccelType(state, encodingOptions, "mpeg2video", isColorDepth10);
- case "vc1":
- return GetHwaccelType(state, encodingOptions, "vc1", isColorDepth10);
- case "vp8":
- return GetHwaccelType(state, encodingOptions, "vp8", isColorDepth10);
- case "vp9":
- return GetHwaccelType(state, encodingOptions, "vp9", isColorDepth10);
- }
- }
- else if (string.Equals(encodingOptions.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
- {
- switch (videoStream.Codec.ToLowerInvariant())
- {
- case "avc":
- case "h264":
- return GetHwDecoderName(encodingOptions, "h264_opencl", "h264", isColorDepth10);
- case "hevc":
- case "h265":
- return GetHwDecoderName(encodingOptions, "hevc_opencl", "hevc", isColorDepth10);
- case "mpeg2video":
- return GetHwDecoderName(encodingOptions, "mpeg2_opencl", "mpeg2video", isColorDepth10);
- case "mpeg4":
- return GetHwDecoderName(encodingOptions, "mpeg4_opencl", "mpeg4", isColorDepth10);
- case "vc1":
- return GetHwDecoderName(encodingOptions, "vc1_opencl", "vc1", isColorDepth10);
- case "vp8":
- return GetHwDecoderName(encodingOptions, "vp8_opencl", "vp8", isColorDepth10);
- case "vp9":
- return GetHwDecoderName(encodingOptions, "vp9_opencl", "vp9", isColorDepth10);
- }
- }
- }
-
- var whichCodec = videoStream.Codec?.ToLowerInvariant();
- switch (whichCodec)
- {
- case "avc":
- whichCodec = "h264";
- break;
- case "h265":
- whichCodec = "hevc";
- break;
- }
-
- // Avoid a second attempt if no hardware acceleration is being used
- encodingOptions.HardwareDecodingCodecs = encodingOptions.HardwareDecodingCodecs.Where(val => val != whichCodec).ToArray();
-
- // leave blank so ffmpeg will decide
- return null;
- }
-
- /// <summary>
- /// Gets a hw decoder name.
- /// </summary>
- /// <param name="options">Encoding options.</param>
- /// <param name="decoder">Decoder to use.</param>
- /// <param name="videoCodec">Video codec to use.</param>
- /// <param name="isColorDepth10">Specifies if color depth 10.</param>
- /// <returns>Hardware decoder name.</returns>
- public string GetHwDecoderName(EncodingOptions options, string decoder, string videoCodec, bool isColorDepth10)
- {
- var isCodecAvailable = _mediaEncoder.SupportsDecoder(decoder) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
- if (isColorDepth10 && isCodecAvailable)
- {
- if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
- || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
- {
- return null;
- }
- }
-
- return isCodecAvailable ? ("-c:v " + decoder) : null;
- }
-
- /// <summary>
- /// Gets a hwaccel type to use as a hardware decoder(dxva/vaapi) depending on the system.
- /// </summary>
- /// <param name="state">Encoding state.</param>
- /// <param name="options">Encoding options.</param>
- /// <param name="videoCodec">Video codec to use.</param>
- /// <param name="isColorDepth10">Specifies if color depth 10.</param>
- /// <returns>Hardware accelerator type.</returns>
- public string GetHwaccelType(EncodingJobInfo state, EncodingOptions options, string videoCodec, bool isColorDepth10)
- {
- var isWindows = OperatingSystem.IsWindows();
- var isLinux = OperatingSystem.IsLinux();
- var isWindows8orLater = Environment.OSVersion.Version.Major > 6 || (Environment.OSVersion.Version.Major == 6 && Environment.OSVersion.Version.Minor > 1);
- var isDxvaSupported = _mediaEncoder.SupportsHwaccel("dxva2") || _mediaEncoder.SupportsHwaccel("d3d11va");
- var isCodecAvailable = options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase);
-
- if (isColorDepth10 && isCodecAvailable)
- {
- if ((options.HardwareDecodingCodecs.Contains("hevc", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Hevc)
- || (options.HardwareDecodingCodecs.Contains("vp9", StringComparer.OrdinalIgnoreCase) && !options.EnableDecodingColorDepth10Vp9))
- {
- return null;
- }
- }
-
- if (string.Equals(options.HardwareAccelerationType, "amf", StringComparison.OrdinalIgnoreCase))
- {
- // Currently there is no AMF decoder on Linux, only have h264 encoder.
- if (isDxvaSupported && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
- {
- if (isWindows && isWindows8orLater)
- {
- return "-hwaccel d3d11va";
- }
-
- if (isWindows && !isWindows8orLater)
- {
- return "-hwaccel dxva2";
- }
- }
- }
-
- if (string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase)
- || (string.Equals(options.HardwareAccelerationType, "qsv", StringComparison.OrdinalIgnoreCase)
- && IsVppTonemappingSupported(state, options)))
- {
- if (IsVaapiSupported(state) && options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
- {
- if (isLinux)
- {
- return "-hwaccel vaapi";
- }
- }
- }
-
- if (string.Equals(options.HardwareAccelerationType, "nvenc", StringComparison.OrdinalIgnoreCase))
- {
- if (options.HardwareDecodingCodecs.Contains(videoCodec, StringComparer.OrdinalIgnoreCase))
- {
- return "-hwaccel cuda";
- }
- }
-
- return null;
- }
-
public string GetSubtitleEmbedArguments(EncodingJobInfo state)
{
if (state.SubtitleStream == null || state.SubtitleDeliveryMethod != SubtitleDeliveryMethod.Embed)
@@ -4039,25 +5245,12 @@ namespace MediaBrowser.Controller.MediaEncoding
var hasCopyTs = false;
- // Add resolution params, if specified
- if (!hasGraphicalSubs)
- {
- var outputSizeParam = GetOutputSizeParam(state, encodingOptions, videoCodec);
-
- args += outputSizeParam;
-
- hasCopyTs = outputSizeParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
- }
-
- // This is for graphical subs
- if (hasGraphicalSubs)
- {
- var graphicalSubtitleParam = GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
+ // video processing filters.
+ var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
- args += graphicalSubtitleParam;
+ args += videoProcessParam;
- hasCopyTs = graphicalSubtitleParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
- }
+ hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
{
@@ -4122,12 +5315,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
- args += " -ab " + bitrate.Value.ToString(_usCulture);
+ args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
}
if (state.OutputAudioSampleRate.HasValue)
{
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture);
+ args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
}
args += GetAudioFilterParam(state, encodingOptions);
@@ -4143,12 +5336,12 @@ namespace MediaBrowser.Controller.MediaEncoding
if (bitrate.HasValue)
{
- audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(_usCulture));
+ audioTranscodeParams.Add("-ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture));
}
if (state.OutputAudioChannels.HasValue)
{
- audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(_usCulture));
+ audioTranscodeParams.Add("-ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture));
}
// opus will fail on 44100
@@ -4156,7 +5349,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
if (state.OutputAudioSampleRate.HasValue)
{
- audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(_usCulture));
+ audioTranscodeParams.Add("-ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture));
}
}
@@ -4182,41 +5375,5 @@ namespace MediaBrowser.Controller.MediaEncoding
{
return string.Equals(codec, "copy", StringComparison.OrdinalIgnoreCase);
}
-
- public static bool IsColorDepth10(EncodingJobInfo state)
- {
- var result = false;
- var videoStream = state.VideoStream;
-
- if (videoStream != null)
- {
- if (videoStream.BitDepth.HasValue)
- {
- return videoStream.BitDepth.Value == 10;
- }
-
- if (!string.IsNullOrEmpty(videoStream.PixelFormat))
- {
- result = videoStream.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase);
- if (result)
- {
- return true;
- }
- }
-
- if (!string.IsNullOrEmpty(videoStream.Profile))
- {
- result = videoStream.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
- || videoStream.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase);
- if (result)
- {
- return true;
- }
- }
- }
-
- return result;
- }
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
index e92c4a08a..c4affa567 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingJobInfo.cs
@@ -110,23 +110,7 @@ namespace MediaBrowser.Controller.MediaEncoding
public string OutputContainer { get; set; }
- public string OutputVideoSync
- {
- get
- {
- // For live tv + in progress recordings
- if (string.Equals(InputContainer, "mpegts", StringComparison.OrdinalIgnoreCase)
- || string.Equals(InputContainer, "ts", StringComparison.OrdinalIgnoreCase))
- {
- if (!MediaSource.RunTimeTicks.HasValue)
- {
- return "cfr";
- }
- }
-
- return "-1";
- }
- }
+ public string OutputVideoSync { get; set; }
public string AlbumCoverPath { get; set; }
diff --git a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs
index 7ce707b19..a4869cb67 100644
--- a/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs
+++ b/MediaBrowser.Controller/MediaEncoding/FilterOptionType.cs
@@ -18,6 +18,16 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <summary>
/// The tonemap_opencl_bt2390.
/// </summary>
- TonemapOpenclBt2390 = 2
+ TonemapOpenclBt2390 = 2,
+
+ /// <summary>
+ /// The overlay_opencl_framesync.
+ /// </summary>
+ OverlayOpenclFrameSync = 3,
+
+ /// <summary>
+ /// The overlay_vaapi_framesync.
+ /// </summary>
+ OverlayVaapiFrameSync = 4
}
}
diff --git a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
index c38e7ec3b..4e7e26624 100644
--- a/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IAttachmentExtractor.cs
@@ -12,7 +12,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public interface IAttachmentExtractor
{
- Task<(MediaAttachment attachment, Stream stream)> GetAttachment(
+ Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(
BaseItem item,
string mediaSourceId,
int attachmentStreamIndex,
diff --git a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
index 1f4e08222..6bf3e7b46 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Dlna;
+using MediaBrowser.Model.Drawing;
using MediaBrowser.Model.Dto;
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.MediaInfo;
@@ -31,6 +32,30 @@ namespace MediaBrowser.Controller.MediaEncoding
string ProbePath { get; }
/// <summary>
+ /// Gets the version of encoder.
+ /// </summary>
+ /// <returns>The version of encoder.</returns>
+ Version EncoderVersion { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the configured Vaapi device is from AMD(radeonsi/r600 Mesa driver).
+ /// </summary>
+ /// <value><c>true</c> if the Vaapi device is an AMD(radeonsi/r600 Mesa driver) GPU, <c>false</c> otherwise.</value>
+ bool IsVaapiDeviceAmd { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the configured Vaapi device is from Intel(iHD driver).
+ /// </summary>
+ /// <value><c>true</c> if the Vaapi device is an Intel(iHD driver) GPU, <c>false</c> otherwise.</value>
+ bool IsVaapiDeviceInteliHD { get; }
+
+ /// <summary>
+ /// Gets a value indicating whether the configured Vaapi device is from Intel(legacy i965 driver).
+ /// </summary>
+ /// <value><c>true</c> if the Vaapi device is an Intel(legacy i965 driver) GPU, <c>false</c> otherwise.</value>
+ bool IsVaapiDeviceInteli965 { get; }
+
+ /// <summary>
/// Whether given encoder codec is supported.
/// </summary>
/// <param name="encoder">The encoder.</param>
@@ -66,12 +91,6 @@ namespace MediaBrowser.Controller.MediaEncoding
bool SupportsFilterWithOption(FilterOptionType option);
/// <summary>
- /// Get the version of media encoder.
- /// </summary>
- /// <returns>The version of media encoder.</returns>
- Version GetMediaEncoderVersion();
-
- /// <summary>
/// Extracts the audio image.
/// </summary>
/// <param name="path">The path.</param>
@@ -101,35 +120,10 @@ namespace MediaBrowser.Controller.MediaEncoding
/// <param name="mediaSource">Media source information.</param>
/// <param name="imageStream">Media stream information.</param>
/// <param name="imageStreamIndex">Index of the stream to extract from.</param>
+ /// <param name="targetFormat">The format of the file to write.</param>
/// <param name="cancellationToken">CancellationToken to use for operation.</param>
/// <returns>Location of video image.</returns>
- Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, CancellationToken cancellationToken);
-
- /// <summary>
- /// Extracts the video images on interval.
- /// </summary>
- /// <param name="inputFile">Input file.</param>
- /// <param name="container">Video container type.</param>
- /// <param name="videoStream">Media stream information.</param>
- /// <param name="mediaSource">Media source information.</param>
- /// <param name="threedFormat">Video 3D format.</param>
- /// <param name="interval">Time interval.</param>
- /// <param name="targetDirectory">Directory to write images.</param>
- /// <param name="filenamePrefix">Filename prefix to use.</param>
- /// <param name="maxWidth">Maximum width of image.</param>
- /// <param name="cancellationToken">CancellationToken to use for operation.</param>
- /// <returns>A task.</returns>
- Task ExtractVideoImagesOnInterval(
- string inputFile,
- string container,
- MediaStream videoStream,
- MediaSourceInfo mediaSource,
- Video3DFormat? threedFormat,
- TimeSpan interval,
- string targetDirectory,
- string filenamePrefix,
- int? maxWidth,
- CancellationToken cancellationToken);
+ Task<string> ExtractVideoImage(string inputFile, string container, MediaSourceInfo mediaSource, MediaStream imageStream, int? imageStreamIndex, ImageFormat? targetFormat, CancellationToken cancellationToken);
/// <summary>
/// Gets the media info.
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index c4ddc5618..8b2837ee3 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -13,7 +13,6 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class JobLogger
{
- private static readonly CultureInfo _usCulture = CultureInfo.ReadOnly(new CultureInfo("en-US"));
private readonly ILogger _logger;
public JobLogger(ILogger logger)
@@ -42,7 +41,7 @@ namespace MediaBrowser.Controller.MediaEncoding
break;
}
- await target.WriteAsync(bytes, 0, bytes.Length).ConfigureAwait(false);
+ await target.WriteAsync(bytes).ConfigureAwait(false);
// Check again, the stream could have been closed
if (!target.CanWrite)
@@ -87,7 +86,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var rate = parts[i + 1];
- if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
+ if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
framerate = val;
}
@@ -96,7 +95,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var rate = part.Split('=', 2)[^1];
- if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
+ if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
framerate = val;
}
@@ -106,7 +105,7 @@ namespace MediaBrowser.Controller.MediaEncoding
{
var time = part.Split('=', 2)[^1];
- if (TimeSpan.TryParse(time, _usCulture, out var val))
+ if (TimeSpan.TryParse(time, CultureInfo.InvariantCulture, out var val))
{
var currentMs = startMs + val.TotalMilliseconds;
@@ -128,7 +127,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (scale.HasValue)
{
- if (long.TryParse(size, NumberStyles.Any, _usCulture, out var val))
+ if (long.TryParse(size, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
bytesTranscoded = val * scale.Value;
}
@@ -147,7 +146,7 @@ namespace MediaBrowser.Controller.MediaEncoding
if (scale.HasValue)
{
- if (float.TryParse(rate, NumberStyles.Any, _usCulture, out var val))
+ if (float.TryParse(rate, NumberStyles.Any, CultureInfo.InvariantCulture, out var val))
{
bitRate = (int)Math.Ceiling(val * scale.Value);
}
diff --git a/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs
index 66b628371..c1bb387e1 100644
--- a/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs
+++ b/MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs
@@ -20,4 +20,4 @@
/// </summary>
Dash
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
index 0813a8e7d..eadc09fd4 100644
--- a/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/BasePeriodicWebSocketListener.cs
@@ -11,6 +11,7 @@ using System.Threading;
using System.Threading.Tasks;
using MediaBrowser.Model.Net;
using MediaBrowser.Model.Session;
+using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
namespace MediaBrowser.Controller.Net
@@ -95,7 +96,7 @@ namespace MediaBrowser.Controller.Net
}
/// <inheritdoc />
- public Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection) => Task.CompletedTask;
+ public Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext) => Task.CompletedTask;
/// <summary>
/// Starts sending messages over a web socket.
diff --git a/MediaBrowser.Controller/Net/IWebSocketConnection.cs b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
index c8c5caf80..2c6483ae2 100644
--- a/MediaBrowser.Controller/Net/IWebSocketConnection.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketConnection.cs
@@ -30,12 +30,6 @@ namespace MediaBrowser.Controller.Net
DateTime LastKeepAliveDate { get; set; }
/// <summary>
- /// Gets the query string.
- /// </summary>
- /// <value>The query string.</value>
- IQueryCollection QueryString { get; }
-
- /// <summary>
/// Gets or sets the receive action.
/// </summary>
/// <value>The receive action.</value>
diff --git a/MediaBrowser.Controller/Net/IWebSocketListener.cs b/MediaBrowser.Controller/Net/IWebSocketListener.cs
index f1a75d518..672bb8cbf 100644
--- a/MediaBrowser.Controller/Net/IWebSocketListener.cs
+++ b/MediaBrowser.Controller/Net/IWebSocketListener.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using Microsoft.AspNetCore.Http;
namespace MediaBrowser.Controller.Net
{
@@ -18,7 +19,8 @@ namespace MediaBrowser.Controller.Net
/// Processes a new web socket connection.
/// </summary>
/// <param name="connection">An instance of the <see cref="IWebSocketConnection"/> interface.</param>
+ /// <param name="httpContext">The current http context.</param>
/// <returns>Task.</returns>
- Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection);
+ Task ProcessWebSocketConnectedAsync(IWebSocketConnection connection, HttpContext httpContext);
}
}
diff --git a/MediaBrowser.Controller/Net/WebSocketListenerState.cs b/MediaBrowser.Controller/Net/WebSocketListenerState.cs
index 70604d60a..2410801d6 100644
--- a/MediaBrowser.Controller/Net/WebSocketListenerState.cs
+++ b/MediaBrowser.Controller/Net/WebSocketListenerState.cs
@@ -14,4 +14,4 @@ namespace MediaBrowser.Controller.Net
public long IntervalMs { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Persistence/IItemRepository.cs b/MediaBrowser.Controller/Persistence/IItemRepository.cs
index a084f9196..837bf0bb2 100644
--- a/MediaBrowser.Controller/Persistence/IItemRepository.cs
+++ b/MediaBrowser.Controller/Persistence/IItemRepository.cs
@@ -161,17 +161,17 @@ namespace MediaBrowser.Controller.Persistence
int GetCount(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query);
- QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query);
+ QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query);
List<string> GetMusicGenreNames();
diff --git a/MediaBrowser.Controller/Playlists/Playlist.cs b/MediaBrowser.Controller/Playlists/Playlist.cs
index 5e671a725..89f3bdf46 100644
--- a/MediaBrowser.Controller/Playlists/Playlist.cs
+++ b/MediaBrowser.Controller/Playlists/Playlist.cs
@@ -189,7 +189,7 @@ namespace MediaBrowser.Controller.Playlists
return LibraryManager.GetItemList(new InternalItemsQuery(user)
{
Recursive = true,
- IncludeItemTypes = new[] { nameof(Audio) },
+ IncludeItemTypes = new[] { BaseItemKind.Audio },
GenreIds = new[] { musicGenre.Id },
OrderBy = new[] { (ItemSortBy.AlbumArtist, SortOrder.Ascending), (ItemSortBy.Album, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options
@@ -201,7 +201,7 @@ namespace MediaBrowser.Controller.Playlists
return LibraryManager.GetItemList(new InternalItemsQuery(user)
{
Recursive = true,
- IncludeItemTypes = new[] { nameof(Audio) },
+ IncludeItemTypes = new[] { BaseItemKind.Audio },
ArtistIds = new[] { musicArtist.Id },
OrderBy = new[] { (ItemSortBy.AlbumArtist, SortOrder.Ascending), (ItemSortBy.Album, SortOrder.Ascending), (ItemSortBy.SortName, SortOrder.Ascending) },
DtoOptions = options
diff --git a/MediaBrowser.Controller/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs
index b31270270..d4de97651 100644
--- a/MediaBrowser.Controller/Providers/DirectoryService.cs
+++ b/MediaBrowser.Controller/Providers/DirectoryService.cs
@@ -12,11 +12,11 @@ namespace MediaBrowser.Controller.Providers
{
private readonly IFileSystem _fileSystem;
- private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new (StringComparer.Ordinal);
+ private readonly ConcurrentDictionary<string, FileSystemMetadata[]> _cache = new(StringComparer.Ordinal);
- private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new (StringComparer.Ordinal);
+ private readonly ConcurrentDictionary<string, FileSystemMetadata> _fileCache = new(StringComparer.Ordinal);
- private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new (StringComparer.Ordinal);
+ private readonly ConcurrentDictionary<string, List<string>> _filePathCache = new(StringComparer.Ordinal);
public DirectoryService(IFileSystem fileSystem)
{
@@ -25,7 +25,7 @@ namespace MediaBrowser.Controller.Providers
public FileSystemMetadata[] GetFileSystemEntries(string path)
{
- return _cache.GetOrAdd(path, (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem);
+ return _cache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFileSystemEntries(p).ToArray(), _fileSystem);
}
public List<FileSystemMetadata> GetFiles(string path)
@@ -69,7 +69,7 @@ namespace MediaBrowser.Controller.Providers
_filePathCache.TryRemove(path, out _);
}
- var filePaths = _filePathCache.GetOrAdd(path, (p, fileSystem) => fileSystem.GetFilePaths(p).ToList(), _fileSystem);
+ var filePaths = _filePathCache.GetOrAdd(path, static (p, fileSystem) => fileSystem.GetFilePaths(p).ToList(), _fileSystem);
if (sort)
{
diff --git a/MediaBrowser.Controller/Providers/IExternalId.cs b/MediaBrowser.Controller/Providers/IExternalId.cs
index e2dbef2bc..0d847520d 100644
--- a/MediaBrowser.Controller/Providers/IExternalId.cs
+++ b/MediaBrowser.Controller/Providers/IExternalId.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Providers;
@@ -35,7 +33,7 @@ namespace MediaBrowser.Controller.Providers
/// <summary>
/// Gets the URL format string for this id.
/// </summary>
- string UrlFormatString { get; }
+ string? UrlFormatString { get; }
/// <summary>
/// Determines whether this id supports a given item type.
diff --git a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
index 2ac4c728b..08d129a82 100644
--- a/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/ImageRefreshOptions.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CA1819, CS1591
using System;
diff --git a/MediaBrowser.Controller/Providers/ItemInfo.cs b/MediaBrowser.Controller/Providers/ItemInfo.cs
index b8dd416a2..3a97127ea 100644
--- a/MediaBrowser.Controller/Providers/ItemInfo.cs
+++ b/MediaBrowser.Controller/Providers/ItemInfo.cs
@@ -1,5 +1,3 @@
-#nullable disable
-
#pragma warning disable CS1591
using System;
diff --git a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
index a42c7f8b5..90fd6e269 100644
--- a/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
+++ b/MediaBrowser.Controller/Providers/MetadataRefreshOptions.cs
@@ -4,6 +4,7 @@
using System;
using System.Linq;
+using Jellyfin.Extensions;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Model.Providers;
@@ -58,7 +59,7 @@ namespace MediaBrowser.Controller.Providers
{
if (RefreshPaths != null && RefreshPaths.Length > 0)
{
- return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparer.OrdinalIgnoreCase);
+ return RefreshPaths.Contains(item.Path ?? string.Empty, StringComparison.OrdinalIgnoreCase);
}
return true;
diff --git a/MediaBrowser.Controller/Providers/MetadataResult.cs b/MediaBrowser.Controller/Providers/MetadataResult.cs
index 2085ae4ad..58a0fa2a9 100644
--- a/MediaBrowser.Controller/Providers/MetadataResult.cs
+++ b/MediaBrowser.Controller/Providers/MetadataResult.cs
@@ -14,7 +14,7 @@ namespace MediaBrowser.Controller.Providers
{
// Images aren't always used so the allocation is a waste a lot of the time
private List<LocalImageInfo> _images;
- private List<(string url, ImageType type)> _remoteImages;
+ private List<(string Url, ImageType Type)> _remoteImages;
public MetadataResult()
{
@@ -27,9 +27,9 @@ namespace MediaBrowser.Controller.Providers
set => _images = value;
}
- public List<(string url, ImageType type)> RemoteImages
+ public List<(string Url, ImageType Type)> RemoteImages
{
- get => _remoteImages ??= new List<(string url, ImageType type)>();
+ get => _remoteImages ??= new List<(string Url, ImageType Type)>();
set => _remoteImages = value;
}
diff --git a/MediaBrowser.Controller/Providers/RefreshPriority.cs b/MediaBrowser.Controller/Providers/RefreshPriority.cs
index 3619f679d..e4c39cea1 100644
--- a/MediaBrowser.Controller/Providers/RefreshPriority.cs
+++ b/MediaBrowser.Controller/Providers/RefreshPriority.cs
@@ -20,4 +20,4 @@
/// </summary>
Low = 2
}
-} \ No newline at end of file
+}
diff --git a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
index 3330dd540..52aa44024 100644
--- a/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
+++ b/MediaBrowser.Controller/Subtitles/ISubtitleManager.cs
@@ -31,12 +31,14 @@ namespace MediaBrowser.Controller.Subtitles
/// <param name="video">The video.</param>
/// <param name="language">Subtitle language.</param>
/// <param name="isPerfectMatch">Require perfect match.</param>
+ /// <param name="isAutomated">Request is automated.</param>
/// <param name="cancellationToken">CancellationToken to use for the operation.</param>
/// <returns>Subtitles, wrapped in task.</returns>
Task<RemoteSubtitleInfo[]> SearchSubtitles(
Video video,
string language,
bool? isPerfectMatch,
+ bool isAutomated,
CancellationToken cancellationToken);
/// <summary>
diff --git a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
index 767d87d46..ef052237a 100644
--- a/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
+++ b/MediaBrowser.Controller/Subtitles/SubtitleSearchRequest.cs
@@ -51,5 +51,7 @@ namespace MediaBrowser.Controller.Subtitles
public string[] DisabledSubtitleFetchers { get; set; }
public string[] SubtitleFetcherOrder { get; set; }
+
+ public bool IsAutomated { get; set; }
}
}
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs
index b9786ddb0..2523ec709 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PausedGroupState.cs
@@ -18,18 +18,12 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
public class PausedGroupState : AbstractGroupState
{
/// <summary>
- /// The logger.
- /// </summary>
- private readonly ILogger<PausedGroupState> _logger;
-
- /// <summary>
/// Initializes a new instance of the <see cref="PausedGroupState"/> class.
/// </summary>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
public PausedGroupState(ILoggerFactory loggerFactory)
: base(loggerFactory)
{
- _logger = LoggerFactory.CreateLogger<PausedGroupState>();
}
/// <inheritdoc />
diff --git a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs
index cb1cadf0b..4f29ca1c6 100644
--- a/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs
+++ b/MediaBrowser.Controller/SyncPlay/GroupStates/PlayingGroupState.cs
@@ -18,18 +18,12 @@ namespace MediaBrowser.Controller.SyncPlay.GroupStates
public class PlayingGroupState : AbstractGroupState
{
/// <summary>
- /// The logger.
- /// </summary>
- private readonly ILogger<PlayingGroupState> _logger;
-
- /// <summary>
/// Initializes a new instance of the <see cref="PlayingGroupState"/> class.
/// </summary>
/// <param name="loggerFactory">Instance of the <see cref="ILoggerFactory"/> interface.</param>
public PlayingGroupState(ILoggerFactory loggerFactory)
: base(loggerFactory)
{
- _logger = LoggerFactory.CreateLogger<PlayingGroupState>();
}
/// <inheritdoc />
diff --git a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
index b8ae9f3ff..f49876cca 100644
--- a/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
+++ b/MediaBrowser.Controller/SyncPlay/Queue/PlayQueueManager.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using Jellyfin.Extensions;
using MediaBrowser.Model.SyncPlay;
namespace MediaBrowser.Controller.SyncPlay.Queue
@@ -19,10 +20,16 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
private const int NoPlayingItemIndex = -1;
/// <summary>
- /// Random number generator used to shuffle lists.
+ /// The sorted playlist.
/// </summary>
- /// <value>The random number generator.</value>
- private readonly Random _randomNumberGenerator = new Random();
+ /// <value>The sorted playlist, or play queue of the group.</value>
+ private List<QueueItem> _sortedPlaylist = new List<QueueItem>();
+
+ /// <summary>
+ /// The shuffled playlist.
+ /// </summary>
+ /// <value>The shuffled playlist, or play queue of the group.</value>
+ private List<QueueItem> _shuffledPlaylist = new List<QueueItem>();
/// <summary>
/// Initializes a new instance of the <see cref="PlayQueueManager" /> class.
@@ -57,18 +64,6 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
public GroupRepeatMode RepeatMode { get; private set; } = GroupRepeatMode.RepeatNone;
/// <summary>
- /// Gets or sets the sorted playlist.
- /// </summary>
- /// <value>The sorted playlist, or play queue of the group.</value>
- private List<QueueItem> SortedPlaylist { get; set; } = new List<QueueItem>();
-
- /// <summary>
- /// Gets or sets the shuffled playlist.
- /// </summary>
- /// <value>The shuffled playlist, or play queue of the group.</value>
- private List<QueueItem> ShuffledPlaylist { get; set; } = new List<QueueItem>();
-
- /// <summary>
/// Checks if an item is playing.
/// </summary>
/// <returns><c>true</c> if an item is playing; <c>false</c> otherwise.</returns>
@@ -92,14 +87,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// <param name="items">The new items of the playlist.</param>
public void SetPlaylist(IReadOnlyList<Guid> items)
{
- SortedPlaylist.Clear();
- ShuffledPlaylist.Clear();
+ _sortedPlaylist.Clear();
+ _shuffledPlaylist.Clear();
- SortedPlaylist = CreateQueueItemsFromArray(items);
+ _sortedPlaylist = CreateQueueItemsFromArray(items);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
- ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
- Shuffle(ShuffledPlaylist);
+ _shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
+ _shuffledPlaylist.Shuffle();
}
PlayingItemIndex = NoPlayingItemIndex;
@@ -114,10 +109,10 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
var newItems = CreateQueueItemsFromArray(items);
- SortedPlaylist.AddRange(newItems);
+ _sortedPlaylist.AddRange(newItems);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
- ShuffledPlaylist.AddRange(newItems);
+ _shuffledPlaylist.AddRange(newItems);
}
LastChange = DateTime.UtcNow;
@@ -130,26 +125,26 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (PlayingItemIndex == NoPlayingItemIndex)
{
- ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
- Shuffle(ShuffledPlaylist);
+ _shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
+ _shuffledPlaylist.Shuffle();
}
else if (ShuffleMode.Equals(GroupShuffleMode.Sorted))
{
// First time shuffle.
- var playingItem = SortedPlaylist[PlayingItemIndex];
- ShuffledPlaylist = new List<QueueItem>(SortedPlaylist);
- ShuffledPlaylist.RemoveAt(PlayingItemIndex);
- Shuffle(ShuffledPlaylist);
- ShuffledPlaylist.Insert(0, playingItem);
+ var playingItem = _sortedPlaylist[PlayingItemIndex];
+ _shuffledPlaylist = new List<QueueItem>(_sortedPlaylist);
+ _shuffledPlaylist.RemoveAt(PlayingItemIndex);
+ _shuffledPlaylist.Shuffle();
+ _shuffledPlaylist.Insert(0, playingItem);
PlayingItemIndex = 0;
}
else
{
// Re-shuffle playlist.
- var playingItem = ShuffledPlaylist[PlayingItemIndex];
- ShuffledPlaylist.RemoveAt(PlayingItemIndex);
- Shuffle(ShuffledPlaylist);
- ShuffledPlaylist.Insert(0, playingItem);
+ var playingItem = _shuffledPlaylist[PlayingItemIndex];
+ _shuffledPlaylist.RemoveAt(PlayingItemIndex);
+ _shuffledPlaylist.Shuffle();
+ _shuffledPlaylist.Insert(0, playingItem);
PlayingItemIndex = 0;
}
@@ -164,11 +159,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (PlayingItemIndex != NoPlayingItemIndex)
{
- var playingItem = ShuffledPlaylist[PlayingItemIndex];
- PlayingItemIndex = SortedPlaylist.IndexOf(playingItem);
+ var playingItem = _shuffledPlaylist[PlayingItemIndex];
+ PlayingItemIndex = _sortedPlaylist.IndexOf(playingItem);
}
- ShuffledPlaylist.Clear();
+ _shuffledPlaylist.Clear();
ShuffleMode = GroupShuffleMode.Sorted;
LastChange = DateTime.UtcNow;
@@ -181,16 +176,16 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
public void ClearPlaylist(bool clearPlayingItem)
{
var playingItem = GetPlayingItem();
- SortedPlaylist.Clear();
- ShuffledPlaylist.Clear();
+ _sortedPlaylist.Clear();
+ _shuffledPlaylist.Clear();
LastChange = DateTime.UtcNow;
if (!clearPlayingItem && playingItem != null)
{
- SortedPlaylist.Add(playingItem);
+ _sortedPlaylist.Add(playingItem);
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
- ShuffledPlaylist.Add(playingItem);
+ _shuffledPlaylist.Add(playingItem);
}
PlayingItemIndex = 0;
@@ -212,14 +207,14 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
var playingItem = GetPlayingItem();
- var sortedPlayingItemIndex = SortedPlaylist.IndexOf(playingItem);
+ var sortedPlayingItemIndex = _sortedPlaylist.IndexOf(playingItem);
// Append items to sorted and shuffled playlist as they are.
- SortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems);
- ShuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
+ _sortedPlaylist.InsertRange(sortedPlayingItemIndex + 1, newItems);
+ _shuffledPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
}
else
{
- SortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
+ _sortedPlaylist.InsertRange(PlayingItemIndex + 1, newItems);
}
LastChange = DateTime.UtcNow;
@@ -298,8 +293,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
var playingItem = GetPlayingItem();
- SortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
- ShuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
+ _sortedPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
+ _shuffledPlaylist.RemoveAll(item => playlistItemIds.Contains(item.PlaylistItemId));
LastChange = DateTime.UtcNow;
@@ -313,7 +308,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
// Was first element, picking next if available.
// Default to no playing item otherwise.
- PlayingItemIndex = SortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
+ PlayingItemIndex = _sortedPlaylist.Count > 0 ? 0 : NoPlayingItemIndex;
}
return true;
@@ -363,8 +358,8 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
/// </summary>
public void Reset()
{
- SortedPlaylist.Clear();
- ShuffledPlaylist.Clear();
+ _sortedPlaylist.Clear();
+ _shuffledPlaylist.Clear();
PlayingItemIndex = NoPlayingItemIndex;
ShuffleMode = GroupShuffleMode.Sorted;
RepeatMode = GroupRepeatMode.RepeatNone;
@@ -460,7 +455,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
PlayingItemIndex++;
- if (PlayingItemIndex >= SortedPlaylist.Count)
+ if (PlayingItemIndex >= _sortedPlaylist.Count)
{
if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
{
@@ -468,7 +463,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
else
{
- PlayingItemIndex = SortedPlaylist.Count - 1;
+ PlayingItemIndex = _sortedPlaylist.Count - 1;
return false;
}
}
@@ -494,7 +489,7 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (RepeatMode.Equals(GroupRepeatMode.RepeatAll))
{
- PlayingItemIndex = SortedPlaylist.Count - 1;
+ PlayingItemIndex = _sortedPlaylist.Count - 1;
}
else
{
@@ -508,23 +503,6 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
/// <summary>
- /// Shuffles a given list.
- /// </summary>
- /// <param name="list">The list to shuffle.</param>
- private void Shuffle<T>(IList<T> list)
- {
- int n = list.Count;
- while (n > 1)
- {
- n--;
- int k = _randomNumberGenerator.Next(n + 1);
- T value = list[k];
- list[k] = list[n];
- list[n] = value;
- }
- }
-
- /// <summary>
/// Creates a list from the array of items. Each item is given an unique playlist identifier.
/// </summary>
/// <returns>The list of queue items.</returns>
@@ -548,11 +526,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
{
if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
- return ShuffledPlaylist;
+ return _shuffledPlaylist;
}
else
{
- return SortedPlaylist;
+ return _sortedPlaylist;
}
}
@@ -568,11 +546,11 @@ namespace MediaBrowser.Controller.SyncPlay.Queue
}
else if (ShuffleMode.Equals(GroupShuffleMode.Shuffle))
{
- return ShuffledPlaylist[PlayingItemIndex];
+ return _shuffledPlaylist[PlayingItemIndex];
}
else
{
- return SortedPlaylist[PlayingItemIndex];
+ return _sortedPlaylist[PlayingItemIndex];
}
}
}