aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Directory.Build.props2
-rw-r--r--Emby.Dlna/ContentDirectory/ControlHandler.cs46
-rw-r--r--Emby.Dlna/Didl/Filter.cs3
-rw-r--r--Emby.Dlna/DlnaManager.cs16
-rw-r--r--Emby.Dlna/Emby.Dlna.csproj6
-rw-r--r--Emby.Dlna/PlayTo/Device.cs59
-rw-r--r--Emby.Dlna/PlayTo/PlayToController.cs6
-rw-r--r--Emby.Dlna/PlayTo/TransportCommands.cs2
-rw-r--r--Emby.Dlna/Profiles/DefaultProfile.cs3
-rw-r--r--Emby.Dlna/Server/DescriptionXmlBuilder.cs8
-rw-r--r--Emby.Dlna/Service/ServiceXmlBuilder.cs12
-rw-r--r--Emby.Drawing/Emby.Drawing.csproj6
-rw-r--r--Emby.Drawing/ImageProcessor.cs13
-rw-r--r--Emby.Naming/AudioBook/AudioBookListResolver.cs1
-rw-r--r--Emby.Naming/Common/NamingOptions.cs6
-rw-r--r--Emby.Naming/Emby.Naming.csproj6
-rw-r--r--Emby.Naming/TV/SeasonPathParser.cs6
-rw-r--r--Emby.Naming/TV/SeriesPathParser.cs2
-rw-r--r--Emby.Naming/Video/ExtraRuleResolver.cs (renamed from Emby.Naming/Video/ExtraResolver.cs)65
-rw-r--r--Emby.Naming/Video/Format3DParser.cs2
-rw-r--r--Emby.Naming/Video/VideoListResolver.cs7
-rw-r--r--Emby.Naming/Video/VideoResolver.cs2
-rw-r--r--Emby.Notifications/CoreNotificationTypes.cs32
-rw-r--r--Emby.Notifications/Emby.Notifications.csproj2
-rw-r--r--Emby.Photos/Emby.Photos.csproj2
-rw-r--r--Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs2
-rw-r--r--Emby.Server.Implementations/ApplicationHost.cs2
-rw-r--r--Emby.Server.Implementations/Channels/ChannelManager.cs8
-rw-r--r--Emby.Server.Implementations/Collections/CollectionManager.cs2
-rw-r--r--Emby.Server.Implementations/Data/BaseSqliteRepository.cs19
-rw-r--r--Emby.Server.Implementations/Data/SqliteItemRepository.cs603
-rw-r--r--Emby.Server.Implementations/Data/SqliteUserDataRepository.cs83
-rw-r--r--Emby.Server.Implementations/Devices/DeviceId.cs2
-rw-r--r--Emby.Server.Implementations/Dto/DtoService.cs9
-rw-r--r--Emby.Server.Implementations/Emby.Server.Implementations.csproj6
-rw-r--r--Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs6
-rw-r--r--Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs2
-rw-r--r--Emby.Server.Implementations/HttpServer/Security/SessionContext.cs2
-rw-r--r--Emby.Server.Implementations/HttpServer/WebSocketConnection.cs3
-rw-r--r--Emby.Server.Implementations/IO/FileRefresher.cs7
-rw-r--r--Emby.Server.Implementations/IO/LibraryMonitor.cs2
-rw-r--r--Emby.Server.Implementations/IO/ManagedFileSystem.cs10
-rw-r--r--Emby.Server.Implementations/IO/StreamHelper.cs20
-rw-r--r--Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs4
-rw-r--r--Emby.Server.Implementations/Library/LibraryManager.cs107
-rw-r--r--Emby.Server.Implementations/Library/LiveStreamHelper.cs7
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs7
-rw-r--r--Emby.Server.Implementations/Library/MusicManager.cs2
-rw-r--r--Emby.Server.Implementations/Library/PathExtensions.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs2
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs93
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs12
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs7
-rw-r--r--Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs1
-rw-r--r--Emby.Server.Implementations/Library/SearchEngine.cs2
-rw-r--r--Emby.Server.Implementations/Library/UserDataManager.cs4
-rw-r--r--Emby.Server.Implementations/Library/UserViewManager.cs4
-rw-r--r--Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs6
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs10
-rw-r--r--Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs11
-rw-r--r--Emby.Server.Implementations/LiveTv/LiveTvManager.cs37
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs5
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs2
-rw-r--r--Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs2
-rw-r--r--Emby.Server.Implementations/Localization/Core/ca.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/de.json24
-rw-r--r--Emby.Server.Implementations/Localization/Core/el.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/gsw.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/he.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ko.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/lt-LT.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ml.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/my.json1
-rw-r--r--Emby.Server.Implementations/Localization/Core/ru.json6
-rw-r--r--Emby.Server.Implementations/Localization/Core/sv.json2
-rw-r--r--Emby.Server.Implementations/Localization/Core/th.json3
-rw-r--r--Emby.Server.Implementations/Localization/Core/ur_PK.json4
-rw-r--r--Emby.Server.Implementations/Net/SocketFactory.cs6
-rw-r--r--Emby.Server.Implementations/Playlists/PlaylistManager.cs2
-rw-r--r--Emby.Server.Implementations/Plugins/PluginManager.cs14
-rw-r--r--Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs1
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs2
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs6
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs6
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs4
-rw-r--r--Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs6
-rw-r--r--Emby.Server.Implementations/Session/SessionManager.cs2
-rw-r--r--Emby.Server.Implementations/Sorting/AlbumComparer.cs2
-rw-r--r--Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs5
-rw-r--r--Emby.Server.Implementations/TV/TVSeriesManager.cs9
-rw-r--r--Emby.Server.Implementations/Updates/InstallationManager.cs6
-rw-r--r--Jellyfin.Api/Controllers/DynamicHlsController.cs329
-rw-r--r--Jellyfin.Api/Controllers/FilterController.cs8
-rw-r--r--Jellyfin.Api/Controllers/ItemLookupController.cs3
-rw-r--r--Jellyfin.Api/Controllers/ItemsController.cs2
-rw-r--r--Jellyfin.Api/Controllers/LiveTvController.cs58
-rw-r--r--Jellyfin.Api/Controllers/RemoteImageController.cs3
-rw-r--r--Jellyfin.Api/Controllers/UserLibraryController.cs3
-rw-r--r--Jellyfin.Api/Controllers/VideoHlsController.cs586
-rw-r--r--Jellyfin.Api/Helpers/RequestHelpers.cs2
-rw-r--r--Jellyfin.Api/Helpers/TranscodingJobHelper.cs3
-rw-r--r--Jellyfin.Api/Jellyfin.Api.csproj6
-rw-r--r--Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs2
-rw-r--r--Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs2
-rw-r--r--Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs2
-rw-r--r--Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs2
-rw-r--r--Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs2
-rw-r--r--Jellyfin.Data/Enums/BaseItemKind.cs2
-rw-r--r--Jellyfin.Data/Enums/UnratedItem.cs2
-rw-r--r--Jellyfin.Data/Jellyfin.Data.csproj2
-rw-r--r--Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj2
-rw-r--r--Jellyfin.Networking/Jellyfin.Networking.csproj2
-rw-r--r--Jellyfin.Server.Implementations/Devices/DeviceManager.cs6
-rw-r--r--Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj6
-rw-r--r--Jellyfin.Server.Implementations/ModelBuilderExtensions.cs2
-rw-r--r--Jellyfin.Server.Implementations/Users/UserManager.cs14
-rw-r--r--Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs2
-rw-r--r--Jellyfin.Server/Jellyfin.Server.csproj8
-rw-r--r--Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs2
-rw-r--r--Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs2
-rw-r--r--Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs4
-rw-r--r--MediaBrowser.Common/MediaBrowser.Common.csproj6
-rw-r--r--MediaBrowser.Common/Net/IPNetAddress.cs2
-rw-r--r--MediaBrowser.Common/Net/IPObject.cs2
-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/Collections/CollectionCreatedEventArgs.cs2
-rw-r--r--MediaBrowser.Controller/Drawing/IImageProcessor.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Audio/Audio.cs2
-rw-r--r--MediaBrowser.Controller/Entities/BaseItem.cs8
-rw-r--r--MediaBrowser.Controller/Entities/BaseItemExtensions.cs11
-rw-r--r--MediaBrowser.Controller/Entities/IHasShares.cs2
-rw-r--r--MediaBrowser.Controller/Entities/InternalItemsQuery.cs4
-rw-r--r--MediaBrowser.Controller/Entities/LinkedChildComparer.cs2
-rw-r--r--MediaBrowser.Controller/Entities/LinkedChildType.cs2
-rw-r--r--MediaBrowser.Controller/Entities/Video.cs2
-rw-r--r--MediaBrowser.Controller/Library/ILibraryManager.cs14
-rw-r--r--MediaBrowser.Controller/LiveTv/ActiveRecordingInfo.cs2
-rw-r--r--MediaBrowser.Controller/LiveTv/ILiveTvManager.cs4
-rw-r--r--MediaBrowser.Controller/MediaBrowser.Controller.csproj6
-rw-r--r--MediaBrowser.Controller/MediaEncoding/BaseEncodingJobOptions.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs4146
-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.cs30
-rw-r--r--MediaBrowser.Controller/MediaEncoding/JobLogger.cs2
-rw-r--r--MediaBrowser.Controller/MediaEncoding/TranscodingJobType.cs2
-rw-r--r--MediaBrowser.Controller/Net/WebSocketListenerState.cs2
-rw-r--r--MediaBrowser.Controller/Persistence/IItemRepository.cs12
-rw-r--r--MediaBrowser.Controller/Providers/DirectoryService.cs6
-rw-r--r--MediaBrowser.Controller/Providers/MetadataResult.cs6
-rw-r--r--MediaBrowser.Controller/Providers/RefreshPriority.cs2
-rw-r--r--MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj2
-rw-r--r--MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs2
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs80
-rw-r--r--MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs140
-rw-r--r--MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj6
-rw-r--r--MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs29
-rw-r--r--MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs10
-rw-r--r--MediaBrowser.Model/Configuration/EncodingOptions.cs16
-rw-r--r--MediaBrowser.Model/Dlna/StreamBuilder.cs26
-rw-r--r--MediaBrowser.Model/Entities/MetadataField.cs (renamed from MediaBrowser.Model/Entities/MetadataFields.cs)0
-rw-r--r--MediaBrowser.Model/MediaBrowser.Model.csproj6
-rw-r--r--MediaBrowser.Model/Plugins/PluginStatus.cs2
-rw-r--r--MediaBrowser.Model/Session/GeneralCommandType.cs4
-rw-r--r--MediaBrowser.Model/Session/HardwareEncodingType.cs16
-rw-r--r--MediaBrowser.Model/Tasks/ITaskTrigger.cs2
-rw-r--r--MediaBrowser.Providers/Manager/MetadataService.cs4
-rw-r--r--MediaBrowser.Providers/MediaBrowser.Providers.csproj6
-rw-r--r--MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs10
-rw-r--r--MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs2
-rw-r--r--MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs2
-rw-r--r--MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj2
-rw-r--r--MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs2
-rw-r--r--fedora/Makefile10
-rw-r--r--jellyfin.ruleset15
-rw-r--r--src/Jellyfin.Extensions/Jellyfin.Extensions.csproj2
-rw-r--r--src/Jellyfin.Extensions/Json/JsonDefaults.cs6
-rw-r--r--src/Jellyfin.Extensions/SplitStringExtensions.cs4
-rw-r--r--tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj2
-rw-r--r--tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj2
-rw-r--r--tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs12
-rw-r--r--tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj2
-rw-r--r--tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs6
-rw-r--r--tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj2
-rw-r--r--tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs2
-rw-r--r--tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj2
-rw-r--r--tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj2
-rw-r--r--tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs5
-rw-r--r--tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj2
-rw-r--r--tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj2
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs4
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs2
-rw-r--r--tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs4
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs73
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs8
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj2
-rw-r--r--tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs3
-rw-r--r--tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj2
-rw-r--r--tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs33
219 files changed, 4252 insertions, 3347 deletions
diff --git a/Directory.Build.props b/Directory.Build.props
index d243cde2b..b27782918 100644
--- a/Directory.Build.props
+++ b/Directory.Build.props
@@ -6,7 +6,7 @@
<CodeAnalysisRuleSet>$(MSBuildThisFileDirectory)/jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
+ <PropertyGroup>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
</PropertyGroup>
diff --git a/Emby.Dlna/ContentDirectory/ControlHandler.cs b/Emby.Dlna/ContentDirectory/ControlHandler.cs
index fde3f2f89..010f90c62 100644
--- a/Emby.Dlna/ContentDirectory/ControlHandler.cs
+++ b/Emby.Dlna/ContentDirectory/ControlHandler.cs
@@ -690,16 +690,16 @@ namespace Emby.Dlna.ContentDirectory
var serverItems = new ServerItem[]
{
- new (item, StubType.Latest),
- new (item, StubType.Playlists),
- new (item, StubType.Albums),
- new (item, StubType.AlbumArtists),
- new (item, StubType.Artists),
- new (item, StubType.Songs),
- new (item, StubType.Genres),
- new (item, StubType.FavoriteArtists),
- new (item, StubType.FavoriteAlbums),
- new (item, StubType.FavoriteSongs)
+ new(item, StubType.Latest),
+ new(item, StubType.Playlists),
+ new(item, StubType.Albums),
+ new(item, StubType.AlbumArtists),
+ new(item, StubType.Artists),
+ new(item, StubType.Songs),
+ new(item, StubType.Genres),
+ new(item, StubType.FavoriteArtists),
+ new(item, StubType.FavoriteAlbums),
+ new(item, StubType.FavoriteSongs)
};
if (limit < serverItems.Length)
@@ -751,12 +751,12 @@ namespace Emby.Dlna.ContentDirectory
var array = new ServerItem[]
{
- new (item, StubType.ContinueWatching),
- new (item, StubType.Latest),
- new (item, StubType.Movies),
- new (item, StubType.Collections),
- new (item, StubType.Favorites),
- new (item, StubType.Genres)
+ new(item, StubType.ContinueWatching),
+ new(item, StubType.Latest),
+ new(item, StubType.Movies),
+ new(item, StubType.Collections),
+ new(item, StubType.Favorites),
+ new(item, StubType.Genres)
};
if (limit < array.Length)
@@ -836,13 +836,13 @@ namespace Emby.Dlna.ContentDirectory
var serverItems = new ServerItem[]
{
- new (item, StubType.ContinueWatching),
- new (item, StubType.NextUp),
- new (item, StubType.Latest),
- new (item, StubType.Series),
- new (item, StubType.FavoriteSeries),
- new (item, StubType.FavoriteEpisodes),
- new (item, StubType.Genres)
+ new(item, StubType.ContinueWatching),
+ new(item, StubType.NextUp),
+ new(item, StubType.Latest),
+ new(item, StubType.Series),
+ new(item, StubType.FavoriteSeries),
+ new(item, StubType.FavoriteEpisodes),
+ new(item, StubType.Genres)
};
if (limit < serverItems.Length)
diff --git a/Emby.Dlna/Didl/Filter.cs b/Emby.Dlna/Didl/Filter.cs
index d703f043e..6db6f3ae3 100644
--- a/Emby.Dlna/Didl/Filter.cs
+++ b/Emby.Dlna/Didl/Filter.cs
@@ -17,8 +17,7 @@ namespace Emby.Dlna.Didl
public Filter(string filter)
{
_all = string.Equals(filter, "*", StringComparison.OrdinalIgnoreCase);
-
- _fields = (filter ?? string.Empty).Split(',', StringSplitOptions.RemoveEmptyEntries);
+ _fields = filter.Split(',', StringSplitOptions.RemoveEmptyEntries);
}
public bool Contains(string field)
diff --git a/Emby.Dlna/DlnaManager.cs b/Emby.Dlna/DlnaManager.cs
index d9d2a345a..f2a0548c2 100644
--- a/Emby.Dlna/DlnaManager.cs
+++ b/Emby.Dlna/DlnaManager.cs
@@ -83,8 +83,7 @@ namespace Emby.Dlna
{
lock (_profiles)
{
- var list = _profiles.Values.ToList();
- return list
+ return _profiles.Values
.OrderBy(i => i.Item1.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Item1.Info.Name)
.Select(i => i.Item2)
@@ -226,11 +225,8 @@ namespace Emby.Dlna
{
try
{
- var xmlFies = _fileSystem.GetFilePaths(path)
+ return _fileSystem.GetFilePaths(path)
.Where(i => string.Equals(Path.GetExtension(i), ".xml", StringComparison.OrdinalIgnoreCase))
- .ToList();
-
- return xmlFies
.Select(i => ParseProfileFile(i, type))
.Where(i => i != null)
.ToList()!; // We just filtered out all the nulls
@@ -252,11 +248,8 @@ namespace Emby.Dlna
try
{
- DeviceProfile profile;
-
var tempProfile = (DeviceProfile)_xmlSerializer.DeserializeFromFile(typeof(DeviceProfile), path);
-
- profile = ReserializeProfile(tempProfile);
+ var profile = ReserializeProfile(tempProfile);
profile.Id = path.ToLowerInvariant().GetMD5().ToString("N", CultureInfo.InvariantCulture);
@@ -295,8 +288,7 @@ namespace Emby.Dlna
{
lock (_profiles)
{
- var list = _profiles.Values.ToList();
- return list
+ return _profiles.Values
.Select(i => i.Item1)
.OrderBy(i => i.Info.Type == DeviceProfileType.User ? 0 : 1)
.ThenBy(i => i.Info.Name);
diff --git a/Emby.Dlna/Emby.Dlna.csproj b/Emby.Dlna/Emby.Dlna.csproj
index 7fdbd44f0..fd95041fe 100644
--- a/Emby.Dlna/Emby.Dlna.csproj
+++ b/Emby.Dlna/Emby.Dlna.csproj
@@ -22,10 +22,14 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<!-- 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/Emby.Dlna/PlayTo/Device.cs b/Emby.Dlna/PlayTo/Device.cs
index 0b2288000..34fb8fddd 100644
--- a/Emby.Dlna/PlayTo/Device.cs
+++ b/Emby.Dlna/PlayTo/Device.cs
@@ -1179,6 +1179,7 @@ namespace Emby.Dlna.PlayTo
return new Device(deviceProperties, httpClientFactory, logger);
}
+#nullable enable
private static DeviceIcon CreateIcon(XElement element)
{
if (element == null)
@@ -1186,68 +1187,60 @@ namespace Emby.Dlna.PlayTo
throw new ArgumentNullException(nameof(element));
}
- var mimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype"));
var width = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("width"));
var height = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("height"));
- var depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth"));
- var url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url"));
- var widthValue = int.Parse(width, NumberStyles.Integer, CultureInfo.InvariantCulture);
- var heightValue = int.Parse(height, NumberStyles.Integer, CultureInfo.InvariantCulture);
+ _ = int.TryParse(width, NumberStyles.Integer, CultureInfo.InvariantCulture, out var widthValue);
+ _ = int.TryParse(height, NumberStyles.Integer, CultureInfo.InvariantCulture, out var heightValue);
return new DeviceIcon
{
- Depth = depth,
+ Depth = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("depth")) ?? string.Empty,
Height = heightValue,
- MimeType = mimeType,
- Url = url,
+ MimeType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("mimetype")) ?? string.Empty,
+ Url = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("url")) ?? string.Empty,
Width = widthValue
};
}
private static DeviceService Create(XElement element)
- {
- var type = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType"));
- var id = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId"));
- var scpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL"));
- var controlURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL"));
- var eventSubURL = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL"));
-
- return new DeviceService
- {
- ControlUrl = controlURL,
- EventSubUrl = eventSubURL,
- ScpdUrl = scpdUrl,
- ServiceId = id,
- ServiceType = type
+ => new DeviceService()
+ {
+ ControlUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("controlURL")) ?? string.Empty,
+ EventSubUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("eventSubURL")) ?? string.Empty,
+ ScpdUrl = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("SCPDURL")) ?? string.Empty,
+ ServiceId = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceId")) ?? string.Empty,
+ ServiceType = element.GetDescendantValue(UPnpNamespaces.Ud.GetName("serviceType")) ?? string.Empty
};
- }
- private void UpdateMediaInfo(UBaseObject mediaInfo, TransportState state)
+ private void UpdateMediaInfo(UBaseObject? mediaInfo, TransportState state)
{
TransportState = state;
var previousMediaInfo = CurrentMediaInfo;
CurrentMediaInfo = mediaInfo;
- if (previousMediaInfo == null && mediaInfo != null)
+ if (mediaInfo == null)
{
- if (state != TransportState.Stopped)
+ if (previousMediaInfo != null)
{
- OnPlaybackStart(mediaInfo);
+ OnPlaybackStop(previousMediaInfo);
}
}
- else if (mediaInfo != null && previousMediaInfo != null && !mediaInfo.Equals(previousMediaInfo))
+ else if (previousMediaInfo == null)
{
- OnMediaChanged(previousMediaInfo, mediaInfo);
+ if (state != TransportState.Stopped)
+ {
+ OnPlaybackStart(mediaInfo);
+ }
}
- else if (mediaInfo == null && previousMediaInfo != null)
+ else if (mediaInfo.Equals(previousMediaInfo))
{
- OnPlaybackStop(previousMediaInfo);
+ OnPlaybackProgress(mediaInfo);
}
- else if (mediaInfo != null && mediaInfo.Equals(previousMediaInfo))
+ else
{
- OnPlaybackProgress(mediaInfo);
+ OnMediaChanged(previousMediaInfo, mediaInfo);
}
}
diff --git a/Emby.Dlna/PlayTo/PlayToController.cs b/Emby.Dlna/PlayTo/PlayToController.cs
index f25d8017e..d0c9df68e 100644
--- a/Emby.Dlna/PlayTo/PlayToController.cs
+++ b/Emby.Dlna/PlayTo/PlayToController.cs
@@ -210,9 +210,9 @@ namespace Emby.Dlna.PlayTo
var mediaSource = await streamInfo.GetMediaSource(CancellationToken.None).ConfigureAwait(false);
- var duration = mediaSource == null ?
- (_device.Duration == null ? (long?)null : _device.Duration.Value.Ticks) :
- mediaSource.RunTimeTicks;
+ var duration = mediaSource == null
+ ? _device.Duration?.Ticks
+ : mediaSource.RunTimeTicks;
var playedToCompletion = positionTicks.HasValue && positionTicks.Value == 0;
diff --git a/Emby.Dlna/PlayTo/TransportCommands.cs b/Emby.Dlna/PlayTo/TransportCommands.cs
index b58669355..d373b57f5 100644
--- a/Emby.Dlna/PlayTo/TransportCommands.cs
+++ b/Emby.Dlna/PlayTo/TransportCommands.cs
@@ -175,7 +175,7 @@ namespace Emby.Dlna.PlayTo
var sendValue = state.AllowedValues.FirstOrDefault(a => string.Equals(a, commandParameter, StringComparison.OrdinalIgnoreCase)) ??
(state.AllowedValues.Count > 0 ? state.AllowedValues[0] : value);
- return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType ?? "string", sendValue);
+ return string.Format(CultureInfo.InvariantCulture, "<{0} xmlns:dt=\"urn:schemas-microsoft-com:datatypes\" dt:dt=\"{1}\">{2}</{0}>", argument.Name, state.DataType, sendValue);
}
return string.Format(CultureInfo.InvariantCulture, "<{0}>{1}</{0}>", argument.Name, value);
diff --git a/Emby.Dlna/Profiles/DefaultProfile.cs b/Emby.Dlna/Profiles/DefaultProfile.cs
index 8eaf12ba9..8f4f2bd38 100644
--- a/Emby.Dlna/Profiles/DefaultProfile.cs
+++ b/Emby.Dlna/Profiles/DefaultProfile.cs
@@ -167,8 +167,7 @@ namespace Emby.Dlna.Profiles
public void AddXmlRootAttribute(string name, string value)
{
- var atts = XmlRootAttributes ?? System.Array.Empty<XmlAttribute>();
- var list = atts.ToList();
+ var list = XmlRootAttributes.ToList();
list.Add(new XmlAttribute
{
diff --git a/Emby.Dlna/Server/DescriptionXmlBuilder.cs b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
index 80a45f2b2..8adaaea77 100644
--- a/Emby.Dlna/Server/DescriptionXmlBuilder.cs
+++ b/Emby.Dlna/Server/DescriptionXmlBuilder.cs
@@ -189,7 +189,7 @@ namespace Emby.Dlna.Server
builder.Append("<icon>");
builder.Append("<mimetype>")
- .Append(SecurityElement.Escape(icon.MimeType ?? string.Empty))
+ .Append(SecurityElement.Escape(icon.MimeType))
.Append("</mimetype>");
builder.Append("<width>")
.Append(SecurityElement.Escape(icon.Width.ToString(CultureInfo.InvariantCulture)))
@@ -198,7 +198,7 @@ namespace Emby.Dlna.Server
.Append(SecurityElement.Escape(icon.Height.ToString(CultureInfo.InvariantCulture)))
.Append("</height>");
builder.Append("<depth>")
- .Append(SecurityElement.Escape(icon.Depth ?? string.Empty))
+ .Append(SecurityElement.Escape(icon.Depth))
.Append("</depth>");
builder.Append("<url>")
.Append(BuildUrl(icon.Url))
@@ -219,10 +219,10 @@ namespace Emby.Dlna.Server
builder.Append("<service>");
builder.Append("<serviceType>")
- .Append(SecurityElement.Escape(service.ServiceType ?? string.Empty))
+ .Append(SecurityElement.Escape(service.ServiceType))
.Append("</serviceType>");
builder.Append("<serviceId>")
- .Append(SecurityElement.Escape(service.ServiceId ?? string.Empty))
+ .Append(SecurityElement.Escape(service.ServiceId))
.Append("</serviceId>");
builder.Append("<SCPDURL>")
.Append(BuildUrl(service.ScpdUrl))
diff --git a/Emby.Dlna/Service/ServiceXmlBuilder.cs b/Emby.Dlna/Service/ServiceXmlBuilder.cs
index 1e56d09b2..6e0bc6ad8 100644
--- a/Emby.Dlna/Service/ServiceXmlBuilder.cs
+++ b/Emby.Dlna/Service/ServiceXmlBuilder.cs
@@ -38,7 +38,7 @@ namespace Emby.Dlna.Service
builder.Append("<action>");
builder.Append("<name>")
- .Append(SecurityElement.Escape(item.Name ?? string.Empty))
+ .Append(SecurityElement.Escape(item.Name))
.Append("</name>");
builder.Append("<argumentList>");
@@ -48,13 +48,13 @@ namespace Emby.Dlna.Service
builder.Append("<argument>");
builder.Append("<name>")
- .Append(SecurityElement.Escape(argument.Name ?? string.Empty))
+ .Append(SecurityElement.Escape(argument.Name))
.Append("</name>");
builder.Append("<direction>")
- .Append(SecurityElement.Escape(argument.Direction ?? string.Empty))
+ .Append(SecurityElement.Escape(argument.Direction))
.Append("</direction>");
builder.Append("<relatedStateVariable>")
- .Append(SecurityElement.Escape(argument.RelatedStateVariable ?? string.Empty))
+ .Append(SecurityElement.Escape(argument.RelatedStateVariable))
.Append("</relatedStateVariable>");
builder.Append("</argument>");
@@ -81,10 +81,10 @@ namespace Emby.Dlna.Service
.Append("\">");
builder.Append("<name>")
- .Append(SecurityElement.Escape(item.Name ?? string.Empty))
+ .Append(SecurityElement.Escape(item.Name))
.Append("</name>");
builder.Append("<dataType>")
- .Append(SecurityElement.Escape(item.DataType ?? string.Empty))
+ .Append(SecurityElement.Escape(item.DataType))
.Append("</dataType>");
if (item.AllowedValues.Count > 0)
diff --git a/Emby.Drawing/Emby.Drawing.csproj b/Emby.Drawing/Emby.Drawing.csproj
index 149e4b5d9..b9a2c5d5d 100644
--- a/Emby.Drawing/Emby.Drawing.csproj
+++ b/Emby.Drawing/Emby.Drawing.csproj
@@ -11,6 +11,10 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<ItemGroup>
<ProjectReference Include="..\MediaBrowser.Model\MediaBrowser.Model.csproj" />
<ProjectReference Include="..\MediaBrowser.Controller\MediaBrowser.Controller.csproj" />
@@ -24,7 +28,7 @@
<!-- Code analysers-->
<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/Emby.Drawing/ImageProcessor.cs b/Emby.Drawing/ImageProcessor.cs
index 3f75e4fc7..b530c6942 100644
--- a/Emby.Drawing/ImageProcessor.cs
+++ b/Emby.Drawing/ImageProcessor.cs
@@ -101,8 +101,7 @@ namespace Emby.Drawing
public async Task ProcessImage(ImageProcessingOptions options, Stream toStream)
{
var file = await ProcessImage(options).ConfigureAwait(false);
-
- using (var fileStream = AsyncFile.OpenRead(file.Item1))
+ using (var fileStream = AsyncFile.OpenRead(file.Path))
{
await fileStream.CopyToAsync(toStream).ConfigureAwait(false);
}
@@ -117,7 +116,7 @@ namespace Emby.Drawing
=> _transparentImageTypes.Contains(Path.GetExtension(path));
/// <inheritdoc />
- public async Task<(string path, string? mimeType, DateTime dateModified)> ProcessImage(ImageProcessingOptions options)
+ public async Task<(string Path, string? MimeType, DateTime DateModified)> ProcessImage(ImageProcessingOptions options)
{
ItemImageInfo originalImage = options.Image;
BaseItem item = options.Item;
@@ -136,14 +135,14 @@ namespace Emby.Drawing
}
var supportedImageInfo = await GetSupportedImage(originalImagePath, dateModified).ConfigureAwait(false);
- originalImagePath = supportedImageInfo.path;
+ originalImagePath = supportedImageInfo.Path;
if (!File.Exists(originalImagePath))
{
return (originalImagePath, MimeTypes.GetMimeType(originalImagePath), dateModified);
}
- dateModified = supportedImageInfo.dateModified;
+ dateModified = supportedImageInfo.DateModified;
bool requiresTransparency = _transparentImageTypes.Contains(Path.GetExtension(originalImagePath));
bool autoOrient = false;
@@ -243,7 +242,7 @@ namespace Emby.Drawing
return ImageFormat.Jpg;
}
- private string? GetMimeType(ImageFormat format, string path)
+ private string GetMimeType(ImageFormat format, string path)
=> format switch
{
ImageFormat.Bmp => MimeTypes.GetMimeType("i.bmp"),
@@ -437,7 +436,7 @@ namespace Emby.Drawing
.ToString("N", CultureInfo.InvariantCulture);
}
- private async Task<(string path, DateTime dateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
+ private async Task<(string Path, DateTime DateModified)> GetSupportedImage(string originalImagePath, DateTime dateModified)
{
var inputFormat = Path.GetExtension(originalImagePath)
.TrimStart('.')
diff --git a/Emby.Naming/AudioBook/AudioBookListResolver.cs b/Emby.Naming/AudioBook/AudioBookListResolver.cs
index dd8a05bb3..2efe7d526 100644
--- a/Emby.Naming/AudioBook/AudioBookListResolver.cs
+++ b/Emby.Naming/AudioBook/AudioBookListResolver.cs
@@ -33,7 +33,6 @@ namespace Emby.Naming.AudioBook
/// <returns>Returns IEnumerable of <see cref="AudioBookInfo"/>.</returns>
public IEnumerable<AudioBookInfo> Resolve(IEnumerable<FileSystemMetadata> files)
{
-
// File with empty fullname will be sorted out here.
var audiobookFileInfos = files
.Select(i => _audioBookResolver.Resolve(i.FullName))
diff --git a/Emby.Naming/Common/NamingOptions.cs b/Emby.Naming/Common/NamingOptions.cs
index aa62a47f1..c0be0b7c6 100644
--- a/Emby.Naming/Common/NamingOptions.cs
+++ b/Emby.Naming/Common/NamingOptions.cs
@@ -471,6 +471,12 @@ namespace Emby.Naming.Common
MediaType.Video),
new ExtraRule(
+ ExtraType.ThemeVideo,
+ ExtraRuleType.DirectoryName,
+ "backdrops",
+ MediaType.Video),
+
+ new ExtraRule(
ExtraType.ThemeSong,
ExtraRuleType.Filename,
"theme",
diff --git a/Emby.Naming/Emby.Naming.csproj b/Emby.Naming/Emby.Naming.csproj
index 2bf8eacb1..433ad137b 100644
--- a/Emby.Naming/Emby.Naming.csproj
+++ b/Emby.Naming/Emby.Naming.csproj
@@ -15,6 +15,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
@@ -44,7 +48,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/Emby.Naming/TV/SeasonPathParser.cs b/Emby.Naming/TV/SeasonPathParser.cs
index 6236f86c4..fc9ee8e56 100644
--- a/Emby.Naming/TV/SeasonPathParser.cs
+++ b/Emby.Naming/TV/SeasonPathParser.cs
@@ -55,7 +55,7 @@ namespace Emby.Naming.TV
/// <param name="supportSpecialAliases">if set to <c>true</c> [support special aliases].</param>
/// <param name="supportNumericSeasonFolders">if set to <c>true</c> [support numeric season folders].</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPath(
+ private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPath(
string path,
bool supportSpecialAliases,
bool supportNumericSeasonFolders)
@@ -99,7 +99,7 @@ namespace Emby.Naming.TV
if (filename.Contains(name, StringComparison.OrdinalIgnoreCase))
{
var result = GetSeasonNumberFromPathSubstring(filename.Replace(name, " ", StringComparison.OrdinalIgnoreCase));
- if (result.seasonNumber.HasValue)
+ if (result.SeasonNumber.HasValue)
{
return result;
}
@@ -142,7 +142,7 @@ namespace Emby.Naming.TV
/// </summary>
/// <param name="path">The path.</param>
/// <returns>System.Nullable{System.Int32}.</returns>
- private static (int? seasonNumber, bool isSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
+ private static (int? SeasonNumber, bool IsSeasonFolder) GetSeasonNumberFromPathSubstring(ReadOnlySpan<char> path)
{
var numericStart = -1;
var length = 0;
diff --git a/Emby.Naming/TV/SeriesPathParser.cs b/Emby.Naming/TV/SeriesPathParser.cs
index 4dfbb36a3..23067e6a4 100644
--- a/Emby.Naming/TV/SeriesPathParser.cs
+++ b/Emby.Naming/TV/SeriesPathParser.cs
@@ -50,7 +50,7 @@ namespace Emby.Naming.TV
if (expression.IsNamed)
{
result.SeriesName = match.Groups["seriesname"].Value;
- result.Success = !string.IsNullOrEmpty(result.SeriesName) && !string.IsNullOrEmpty(match.Groups["seasonnumber"]?.Value);
+ result.Success = !string.IsNullOrEmpty(result.SeriesName) && !match.Groups["seasonnumber"].ValueSpan.IsEmpty;
}
}
diff --git a/Emby.Naming/Video/ExtraResolver.cs b/Emby.Naming/Video/ExtraRuleResolver.cs
index fbdca859f..0970e509a 100644
--- a/Emby.Naming/Video/ExtraResolver.cs
+++ b/Emby.Naming/Video/ExtraRuleResolver.cs
@@ -1,7 +1,5 @@
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Linq;
using System.Text.RegularExpressions;
using Emby.Naming.Audio;
using Emby.Naming.Common;
@@ -11,7 +9,7 @@ namespace Emby.Naming.Video
/// <summary>
/// Resolve if file is extra for video.
/// </summary>
- public static class ExtraResolver
+ public static class ExtraRuleResolver
{
private static readonly char[] _digits = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' };
@@ -86,66 +84,5 @@ namespace Emby.Naming.Video
return result;
}
-
- /// <summary>
- /// Finds extras matching the video info.
- /// </summary>
- /// <param name="files">The list of file video infos.</param>
- /// <param name="videoInfo">The video to compare against.</param>
- /// <param name="videoFlagDelimiters">The video flag delimiters.</param>
- /// <returns>A list of video extras for [videoInfo].</returns>
- public static IReadOnlyList<VideoFileInfo> GetExtras(IReadOnlyList<VideoInfo> files, VideoFileInfo videoInfo, ReadOnlySpan<char> videoFlagDelimiters)
- {
- var parentDir = videoInfo.IsDirectory ? videoInfo.Path : Path.GetDirectoryName(videoInfo.Path.AsSpan());
-
- var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(videoInfo.FileNameWithoutExtension, videoFlagDelimiters);
- var trimmedVideoInfoName = TrimFilenameDelimiters(videoInfo.Name, videoFlagDelimiters);
-
- var result = new List<VideoFileInfo>();
- for (var pos = files.Count - 1; pos >= 0; pos--)
- {
- var current = files[pos];
- // ignore non-extras and multi-file (can this happen?)
- if (current.ExtraType == null || current.Files.Count > 1)
- {
- continue;
- }
-
- var currentFile = current.Files[0];
- var trimmedCurrentFileName = TrimFilenameDelimiters(currentFile.Name, videoFlagDelimiters);
-
- // first check filenames
- bool isValid = StartsWith(trimmedCurrentFileName, trimmedFileNameWithoutExtension)
- || (StartsWith(trimmedCurrentFileName, trimmedVideoInfoName) && currentFile.Year == videoInfo.Year);
-
- // then by directory
- if (!isValid)
- {
- // When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
- var currentParentDir = currentFile.ExtraRule?.RuleType == ExtraRuleType.DirectoryName
- ? Path.GetDirectoryName(Path.GetDirectoryName(currentFile.Path.AsSpan()))
- : Path.GetDirectoryName(currentFile.Path.AsSpan());
-
- isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
- }
-
- if (isValid)
- {
- result.Add(currentFile);
- }
- }
-
- return result.OrderBy(r => r.Path).ToArray();
- }
-
- private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
- {
- return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
- }
-
- private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
- {
- return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
- }
}
}
diff --git a/Emby.Naming/Video/Format3DParser.cs b/Emby.Naming/Video/Format3DParser.cs
index 089089989..eb5e71d78 100644
--- a/Emby.Naming/Video/Format3DParser.cs
+++ b/Emby.Naming/Video/Format3DParser.cs
@@ -9,7 +9,7 @@ namespace Emby.Naming.Video
public static class Format3DParser
{
// Static default result to save on allocation costs.
- private static readonly Format3DResult _defaultResult = new (false, null);
+ private static readonly Format3DResult _defaultResult = new(false, null);
/// <summary>
/// Parse 3D format related flags.
diff --git a/Emby.Naming/Video/VideoListResolver.cs b/Emby.Naming/Video/VideoListResolver.cs
index 4fc849256..11f82525f 100644
--- a/Emby.Naming/Video/VideoListResolver.cs
+++ b/Emby.Naming/Video/VideoListResolver.cs
@@ -42,11 +42,14 @@ namespace Emby.Naming.Video
continue;
}
- remainingFiles.Add(current);
if (current.ExtraType == null)
{
standaloneMedia.Add(current);
}
+ else
+ {
+ remainingFiles.Add(current);
+ }
}
var list = new List<VideoInfo>();
@@ -69,8 +72,6 @@ namespace Emby.Naming.Video
var info = new VideoInfo(media.Name) { Files = new[] { media } };
info.Year = info.Files[0].Year;
-
- remainingFiles.Remove(media);
list.Add(info);
}
diff --git a/Emby.Naming/Video/VideoResolver.cs b/Emby.Naming/Video/VideoResolver.cs
index 9cadc1465..de8e177d8 100644
--- a/Emby.Naming/Video/VideoResolver.cs
+++ b/Emby.Naming/Video/VideoResolver.cs
@@ -75,7 +75,7 @@ namespace Emby.Naming.Video
var format3DResult = Format3DParser.Parse(path, namingOptions);
- var extraResult = ExtraResolver.GetExtraInfo(path, namingOptions);
+ var extraResult = ExtraRuleResolver.GetExtraInfo(path, namingOptions);
var name = Path.GetFileNameWithoutExtension(path);
diff --git a/Emby.Notifications/CoreNotificationTypes.cs b/Emby.Notifications/CoreNotificationTypes.cs
index ec3490e23..35aac3a11 100644
--- a/Emby.Notifications/CoreNotificationTypes.cs
+++ b/Emby.Notifications/CoreNotificationTypes.cs
@@ -24,63 +24,63 @@ namespace Emby.Notifications
{
new NotificationTypeInfo
{
- Type = NotificationType.ApplicationUpdateInstalled.ToString()
+ Type = nameof(NotificationType.ApplicationUpdateInstalled)
},
new NotificationTypeInfo
{
- Type = NotificationType.InstallationFailed.ToString()
+ Type = nameof(NotificationType.InstallationFailed)
},
new NotificationTypeInfo
{
- Type = NotificationType.PluginInstalled.ToString()
+ Type = nameof(NotificationType.PluginInstalled)
},
new NotificationTypeInfo
{
- Type = NotificationType.PluginError.ToString()
+ Type = nameof(NotificationType.PluginError)
},
new NotificationTypeInfo
{
- Type = NotificationType.PluginUninstalled.ToString()
+ Type = nameof(NotificationType.PluginUninstalled)
},
new NotificationTypeInfo
{
- Type = NotificationType.PluginUpdateInstalled.ToString()
+ Type = nameof(NotificationType.PluginUpdateInstalled)
},
new NotificationTypeInfo
{
- Type = NotificationType.ServerRestartRequired.ToString()
+ Type = nameof(NotificationType.ServerRestartRequired)
},
new NotificationTypeInfo
{
- Type = NotificationType.TaskFailed.ToString()
+ Type = nameof(NotificationType.TaskFailed)
},
new NotificationTypeInfo
{
- Type = NotificationType.NewLibraryContent.ToString()
+ Type = nameof(NotificationType.NewLibraryContent)
},
new NotificationTypeInfo
{
- Type = NotificationType.AudioPlayback.ToString()
+ Type = nameof(NotificationType.AudioPlayback)
},
new NotificationTypeInfo
{
- Type = NotificationType.VideoPlayback.ToString()
+ Type = nameof(NotificationType.VideoPlayback)
},
new NotificationTypeInfo
{
- Type = NotificationType.AudioPlaybackStopped.ToString()
+ Type = nameof(NotificationType.AudioPlaybackStopped)
},
new NotificationTypeInfo
{
- Type = NotificationType.VideoPlaybackStopped.ToString()
+ Type = nameof(NotificationType.VideoPlaybackStopped)
},
new NotificationTypeInfo
{
- Type = NotificationType.UserLockedOut.ToString()
+ Type = nameof(NotificationType.UserLockedOut)
},
new NotificationTypeInfo
{
- Type = NotificationType.ApplicationUpdateAvailable.ToString()
+ Type = nameof(NotificationType.ApplicationUpdateAvailable)
}
};
@@ -98,7 +98,7 @@ namespace Emby.Notifications
private void Update(NotificationTypeInfo note)
{
- note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type) ?? note.Type;
+ note.Name = _localization.GetLocalizedString("NotificationOption" + note.Type);
note.IsBasedOnUserEvent = note.Type.IndexOf("Playback", StringComparison.OrdinalIgnoreCase) != -1;
diff --git a/Emby.Notifications/Emby.Notifications.csproj b/Emby.Notifications/Emby.Notifications.csproj
index d200682e6..7fd2e9bb4 100644
--- a/Emby.Notifications/Emby.Notifications.csproj
+++ b/Emby.Notifications/Emby.Notifications.csproj
@@ -24,7 +24,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/Emby.Photos/Emby.Photos.csproj b/Emby.Photos/Emby.Photos.csproj
index bf6252c19..4964265c9 100644
--- a/Emby.Photos/Emby.Photos.csproj
+++ b/Emby.Photos/Emby.Photos.csproj
@@ -26,7 +26,7 @@
<!-- Code Analyzers-->
<ItemGroup Condition=" '$(Configuration)' == 'Debug' ">
- <PackageReference Include="StyleCop.Analyzers" Version="1.1.118" PrivateAssets="All" />
+ <PackageReference Include="StyleCop.Analyzers" Version="1.2.0-beta.376" PrivateAssets="All" />
<PackageReference Include="SerilogAnalyzer" Version="0.15.0" PrivateAssets="All" />
<PackageReference Include="SmartAnalyzers.MultithreadingAnalyzer" Version="1.1.31" PrivateAssets="All" />
</ItemGroup>
diff --git a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
index 5ba4749a6..19fe0b108 100644
--- a/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
+++ b/Emby.Server.Implementations/AppBase/BaseConfigurationManager.cs
@@ -371,7 +371,7 @@ namespace Emby.Server.Implementations.AppBase
NewConfiguration = configuration
});
- _configurations.AddOrUpdate(key, configuration, (k, v) => configuration);
+ _configurations.AddOrUpdate(key, configuration, (_, _) => configuration);
var path = GetConfigurationFile(key);
Directory.CreateDirectory(Path.GetDirectoryName(path));
diff --git a/Emby.Server.Implementations/ApplicationHost.cs b/Emby.Server.Implementations/ApplicationHost.cs
index 8892f7f40..8ed51a194 100644
--- a/Emby.Server.Implementations/ApplicationHost.cs
+++ b/Emby.Server.Implementations/ApplicationHost.cs
@@ -120,7 +120,7 @@ namespace Emby.Server.Implementations
/// <summary>
/// The disposable parts.
/// </summary>
- private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new ();
+ private readonly ConcurrentDictionary<IDisposable, byte> _disposableParts = new();
private readonly IFileSystem _fileSystemManager;
private readonly IConfiguration _startupConfig;
diff --git a/Emby.Server.Implementations/Channels/ChannelManager.cs b/Emby.Server.Implementations/Channels/ChannelManager.cs
index 8702691d1..43c8a451b 100644
--- a/Emby.Server.Implementations/Channels/ChannelManager.cs
+++ b/Emby.Server.Implementations/Channels/ChannelManager.cs
@@ -130,16 +130,14 @@ namespace Emby.Server.Implementations.Channels
var internalChannel = _libraryManager.GetItemById(item.ChannelId);
if (internalChannel == null)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(item.ChannelId));
}
var channel = Channels.FirstOrDefault(i => GetInternalChannelId(i.Name).Equals(internalChannel.Id));
- var supportsDelete = channel as ISupportsDelete;
-
- if (supportsDelete == null)
+ if (channel is not ISupportsDelete supportsDelete)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(channel));
}
return supportsDelete.DeleteItem(item.ExternalId, CancellationToken.None);
diff --git a/Emby.Server.Implementations/Collections/CollectionManager.cs b/Emby.Server.Implementations/Collections/CollectionManager.cs
index 79ef70fff..b5b8fea65 100644
--- a/Emby.Server.Implementations/Collections/CollectionManager.cs
+++ b/Emby.Server.Implementations/Collections/CollectionManager.cs
@@ -140,7 +140,7 @@ namespace Emby.Server.Implementations.Collections
if (parentFolder == null)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(parentFolder));
}
var path = Path.Combine(parentFolder.Path, folderName);
diff --git a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
index 5030cbacb..450688491 100644
--- a/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
+++ b/Emby.Server.Implementations/Data/BaseSqliteRepository.cs
@@ -160,21 +160,22 @@ namespace Emby.Server.Implementations.Data
protected bool TableExists(ManagedConnection connection, string name)
{
return connection.RunInTransaction(
- db =>
- {
- using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
+ db =>
{
- foreach (var row in statement.ExecuteQuery())
+ using (var statement = PrepareStatement(db, "select DISTINCT tbl_name from sqlite_master"))
{
- if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
+ foreach (var row in statement.ExecuteQuery())
{
- return true;
+ if (string.Equals(name, row.GetString(0), StringComparison.OrdinalIgnoreCase))
+ {
+ return true;
+ }
}
}
- }
- return false;
- }, ReadTransactionMode);
+ return false;
+ },
+ ReadTransactionMode);
}
protected List<string> GetColumnNames(IDatabaseConnection connection, string table)
diff --git a/Emby.Server.Implementations/Data/SqliteItemRepository.cs b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
index beae7e243..d5c0b7107 100644
--- a/Emby.Server.Implementations/Data/SqliteItemRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteItemRepository.cs
@@ -248,42 +248,6 @@ namespace Emby.Server.Implementations.Data
BaseItemKind.AudioBook
};
- private static readonly Type[] _knownTypes =
- {
- typeof(LiveTvProgram),
- typeof(LiveTvChannel),
- typeof(Series),
- typeof(Audio),
- typeof(MusicAlbum),
- typeof(MusicArtist),
- typeof(MusicGenre),
- typeof(MusicVideo),
- typeof(Movie),
- typeof(Playlist),
- typeof(AudioBook),
- typeof(Trailer),
- typeof(BoxSet),
- typeof(Episode),
- typeof(Season),
- typeof(Series),
- typeof(Book),
- typeof(CollectionFolder),
- typeof(Folder),
- typeof(Genre),
- typeof(Person),
- typeof(Photo),
- typeof(PhotoAlbum),
- typeof(Studio),
- typeof(UserRootFolder),
- typeof(UserView),
- typeof(Video),
- typeof(Year),
- typeof(Channel),
- typeof(AggregateFolder)
- };
-
- private readonly Dictionary<string, string> _types = GetTypeMapDictionary();
-
private static readonly Dictionary<BaseItemKind, string> _baseItemKindNames = new()
{
{ BaseItemKind.AggregateFolder, typeof(AggregateFolder).FullName },
@@ -498,109 +462,110 @@ namespace Emby.Server.Implementations.Data
connection.RunQueries(queries);
connection.RunInTransaction(
- db =>
- {
- var existingColumnNames = GetColumnNames(db, "AncestorIds");
- AddColumn(db, "AncestorIds", "AncestorIdText", "Text", existingColumnNames);
-
- existingColumnNames = GetColumnNames(db, "TypedBaseItems");
-
- AddColumn(db, "TypedBaseItems", "Path", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "StartDate", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "EndDate", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ChannelId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsMovie", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "CommunityRating", "Float", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "CustomRating", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IndexNumber", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsLocked", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Name", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "OfficialRating", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "MediaType", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Overview", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ParentIndexNumber", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "PremiereDate", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ProductionYear", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ParentId", "GUID", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Genres", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "SortName", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "EpisodeTitle", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsRepeat", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "PreferredMetadataLanguage", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "PreferredMetadataCountryCode", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "DateLastRefreshed", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "DateLastSaved", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsInMixedFolder", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "LockedFields", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Studios", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Audio", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ExternalServiceId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Tags", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsFolder", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "InheritedParentalRatingValue", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "UnratedType", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "TopParentId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "TrailerTypes", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "CriticRating", "Float", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "CleanName", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "PresentationUniqueKey", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "OriginalTitle", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Album", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "SeasonName", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "SeasonId", "GUID", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Tagline", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Images", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ProductionLocations", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ExtraIds", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "TotalBitrate", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ExtraType", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Artists", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Width", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Height", "INT", existingColumnNames);
- AddColumn(db, "TypedBaseItems", "Size", "BIGINT", existingColumnNames);
-
- existingColumnNames = GetColumnNames(db, "ItemValues");
- AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames);
-
- existingColumnNames = GetColumnNames(db, ChaptersTableName);
- AddColumn(db, ChaptersTableName, "ImageDateModified", "DATETIME", existingColumnNames);
-
- existingColumnNames = GetColumnNames(db, "MediaStreams");
- AddColumn(db, "MediaStreams", "IsAvc", "BIT", existingColumnNames);
- AddColumn(db, "MediaStreams", "TimeBase", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "CodecTimeBase", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "Title", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "NalLengthSize", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "Comment", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "CodecTag", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "PixelFormat", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "BitDepth", "INT", existingColumnNames);
- AddColumn(db, "MediaStreams", "RefFrames", "INT", existingColumnNames);
- AddColumn(db, "MediaStreams", "KeyFrames", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "IsAnamorphic", "BIT", existingColumnNames);
-
- AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
- AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
- }, TransactionMode);
+ db =>
+ {
+ var existingColumnNames = GetColumnNames(db, "AncestorIds");
+ AddColumn(db, "AncestorIds", "AncestorIdText", "Text", existingColumnNames);
+
+ existingColumnNames = GetColumnNames(db, "TypedBaseItems");
+
+ AddColumn(db, "TypedBaseItems", "Path", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "StartDate", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "EndDate", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ChannelId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsMovie", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "CommunityRating", "Float", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "CustomRating", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IndexNumber", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsLocked", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Name", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "OfficialRating", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "MediaType", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Overview", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ParentIndexNumber", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "PremiereDate", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ProductionYear", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ParentId", "GUID", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Genres", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "SortName", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ForcedSortName", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "RunTimeTicks", "BIGINT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "DateCreated", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "DateModified", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsSeries", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "EpisodeTitle", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsRepeat", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "PreferredMetadataLanguage", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "PreferredMetadataCountryCode", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "DateLastRefreshed", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "DateLastSaved", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsInMixedFolder", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "LockedFields", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Studios", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Audio", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ExternalServiceId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Tags", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsFolder", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "InheritedParentalRatingValue", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "UnratedType", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "TopParentId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "TrailerTypes", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "CriticRating", "Float", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "CleanName", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "PresentationUniqueKey", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "OriginalTitle", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "PrimaryVersionId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "DateLastMediaAdded", "DATETIME", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Album", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "IsVirtualItem", "BIT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "SeriesName", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "UserDataKey", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "SeasonName", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "SeasonId", "GUID", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "SeriesId", "GUID", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ExternalSeriesId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Tagline", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ProviderIds", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Images", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ProductionLocations", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ExtraIds", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "TotalBitrate", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ExtraType", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Artists", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "AlbumArtists", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ExternalId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "SeriesPresentationUniqueKey", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "ShowId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "OwnerId", "Text", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Width", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Height", "INT", existingColumnNames);
+ AddColumn(db, "TypedBaseItems", "Size", "BIGINT", existingColumnNames);
+
+ existingColumnNames = GetColumnNames(db, "ItemValues");
+ AddColumn(db, "ItemValues", "CleanValue", "Text", existingColumnNames);
+
+ existingColumnNames = GetColumnNames(db, ChaptersTableName);
+ AddColumn(db, ChaptersTableName, "ImageDateModified", "DATETIME", existingColumnNames);
+
+ existingColumnNames = GetColumnNames(db, "MediaStreams");
+ AddColumn(db, "MediaStreams", "IsAvc", "BIT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "TimeBase", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "CodecTimeBase", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "Title", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "NalLengthSize", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "Comment", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "CodecTag", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "PixelFormat", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "BitDepth", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "RefFrames", "INT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "KeyFrames", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "IsAnamorphic", "BIT", existingColumnNames);
+
+ AddColumn(db, "MediaStreams", "ColorPrimaries", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "ColorSpace", "TEXT", existingColumnNames);
+ AddColumn(db, "MediaStreams", "ColorTransfer", "TEXT", existingColumnNames);
+ },
+ TransactionMode);
connection.RunQueries(postQueries);
}
@@ -636,16 +601,17 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
+ db =>
{
- saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
- saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
+ using (var saveImagesStatement = PrepareStatement(db, "Update TypedBaseItems set Images=@Images where guid=@Id"))
+ {
+ saveImagesStatement.TryBind("@Id", item.Id.ToByteArray());
+ saveImagesStatement.TryBind("@Images", SerializeImages(item.ImageInfos));
- saveImagesStatement.MoveNext();
- }
- }, TransactionMode);
+ saveImagesStatement.MoveNext();
+ }
+ },
+ TransactionMode);
}
}
@@ -686,14 +652,15 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- SaveItemsInTranscation(db, tuples);
- }, TransactionMode);
+ db =>
+ {
+ SaveItemsInTransaction(db, tuples);
+ },
+ TransactionMode);
}
}
- private void SaveItemsInTranscation(IDatabaseConnection db, IEnumerable<(BaseItem, List<Guid>, BaseItem, string, List<string>)> tuples)
+ private void SaveItemsInTransaction(IDatabaseConnection db, IEnumerable<(BaseItem Item, List<Guid> AncestorIds, BaseItem TopParent, string UserDataKey, List<string> InheritedTags)> tuples)
{
var statements = PrepareAll(db, new string[]
{
@@ -712,17 +679,17 @@ namespace Emby.Server.Implementations.Data
saveItemStatement.Reset();
}
- var item = tuple.Item1;
- var topParent = tuple.Item3;
- var userDataKey = tuple.Item4;
+ var item = tuple.Item;
+ var topParent = tuple.TopParent;
+ var userDataKey = tuple.UserDataKey;
SaveItem(item, topParent, userDataKey, saveItemStatement);
- var inheritedTags = tuple.Item5;
+ var inheritedTags = tuple.InheritedTags;
if (item.SupportsAncestors)
{
- UpdateAncestors(item.Id, tuple.Item2, db, deleteAncestorsStatement);
+ UpdateAncestors(item.Id, tuple.AncestorIds, db, deleteAncestorsStatement);
}
UpdateItemValues(item.Id, GetItemValuesToSave(item, inheritedTags), db);
@@ -2134,13 +2101,14 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- // First delete chapters
- db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob);
+ db =>
+ {
+ // First delete chapters
+ db.Execute("delete from " + ChaptersTableName + " where ItemId=@ItemId", idBlob);
- InsertChapters(idBlob, chapters, db);
- }, TransactionMode);
+ InsertChapters(idBlob, chapters, db);
+ },
+ TransactionMode);
}
}
@@ -2199,7 +2167,7 @@ namespace Emby.Server.Implementations.Data
return false;
}
- var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.Item1), StringComparer.OrdinalIgnoreCase);
+ var sortingFields = new HashSet<string>(query.OrderBy.Select(i => i.OrderBy), StringComparer.OrdinalIgnoreCase);
return sortingFields.Contains(ItemSortBy.IsFavoriteOrLiked)
|| sortingFields.Contains(ItemSortBy.IsPlayed)
@@ -2944,69 +2912,70 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection(true))
{
connection.RunInTransaction(
- db =>
- {
- var itemQueryStatement = PrepareStatement(db, itemQuery);
- var totalRecordCountQueryStatement = PrepareStatement(db, totalRecordCountQuery);
-
- if (!isReturningZeroItems)
+ db =>
{
- using (var statement = itemQueryStatement)
+ var itemQueryStatement = PrepareStatement(db, itemQuery);
+ var totalRecordCountQueryStatement = PrepareStatement(db, totalRecordCountQuery);
+
+ if (!isReturningZeroItems)
{
- if (EnableJoinUserData(query))
+ using (var statement = itemQueryStatement)
{
- statement.TryBind("@UserId", query.User.InternalId);
- }
+ if (EnableJoinUserData(query))
+ {
+ statement.TryBind("@UserId", query.User.InternalId);
+ }
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
+ BindSimilarParams(query, statement);
+ BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
- var hasEpisodeAttributes = HasEpisodeAttributes(query);
- var hasServiceName = HasServiceName(query);
- var hasProgramAttributes = HasProgramAttributes(query);
- var hasStartDate = HasStartDate(query);
- var hasTrailerTypes = HasTrailerTypes(query);
- var hasArtistFields = HasArtistFields(query);
- var hasSeriesFields = HasSeriesFields(query);
+ var hasEpisodeAttributes = HasEpisodeAttributes(query);
+ var hasServiceName = HasServiceName(query);
+ var hasProgramAttributes = HasProgramAttributes(query);
+ var hasStartDate = HasStartDate(query);
+ var hasTrailerTypes = HasTrailerTypes(query);
+ var hasArtistFields = HasArtistFields(query);
+ var hasSeriesFields = HasSeriesFields(query);
- foreach (var row in statement.ExecuteQuery())
- {
- var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
- if (item != null)
+ foreach (var row in statement.ExecuteQuery())
{
- list.Add(item);
+ var item = GetItem(row, query, hasProgramAttributes, hasEpisodeAttributes, hasServiceName, hasStartDate, hasTrailerTypes, hasArtistFields, hasSeriesFields);
+ if (item != null)
+ {
+ list.Add(item);
+ }
}
}
- }
- LogQueryTime("GetItems.ItemQuery", itemQuery, now);
- }
+ LogQueryTime("GetItems.ItemQuery", itemQuery, now);
+ }
- now = DateTime.UtcNow;
- if (query.EnableTotalRecordCount)
- {
- using (var statement = totalRecordCountQueryStatement)
+ now = DateTime.UtcNow;
+ if (query.EnableTotalRecordCount)
{
- if (EnableJoinUserData(query))
+ using (var statement = totalRecordCountQueryStatement)
{
- statement.TryBind("@UserId", query.User.InternalId);
- }
+ if (EnableJoinUserData(query))
+ {
+ statement.TryBind("@UserId", query.User.InternalId);
+ }
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
+ BindSimilarParams(query, statement);
+ BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
- }
+ result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ }
- LogQueryTime("GetItems.TotalRecordCount", totalRecordCountQuery, now);
- }
- }, ReadTransactionMode);
+ LogQueryTime("GetItems.TotalRecordCount", totalRecordCountQuery, now);
+ }
+ },
+ ReadTransactionMode);
}
result.Items = list;
@@ -3046,88 +3015,86 @@ namespace Emby.Server.Implementations.Data
return " ORDER BY " + string.Join(',', orderBy.Select(i =>
{
- var columnMap = MapOrderByField(i.Item1, query);
-
- var sortOrder = i.Item2 == SortOrder.Ascending ? "ASC" : "DESC";
-
- return columnMap.Item1 + " " + sortOrder;
+ var columnMap = MapOrderByField(i.OrderBy, query);
+ var sortOrder = columnMap.SortOrder == SortOrder.Ascending ? "ASC" : "DESC";
+ return columnMap.SortBy + " " + sortOrder;
}));
}
- private (string, bool) MapOrderByField(string name, InternalItemsQuery query)
+ private (string SortBy, SortOrder SortOrder) MapOrderByField(string name, InternalItemsQuery query)
{
if (string.Equals(name, ItemSortBy.AirTime, StringComparison.OrdinalIgnoreCase))
{
// TODO
- return ("SortName", false);
+ return ("SortName", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.Runtime, StringComparison.OrdinalIgnoreCase))
{
- return ("RuntimeTicks", false);
+ return ("RuntimeTicks", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.Random, StringComparison.OrdinalIgnoreCase))
{
- return ("RANDOM()", false);
+ return ("RANDOM()", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.DatePlayed, StringComparison.OrdinalIgnoreCase))
{
if (query.GroupBySeriesPresentationUniqueKey)
{
- return ("MAX(LastPlayedDate)", false);
+ return ("MAX(LastPlayedDate)", SortOrder.Descending);
}
- return ("LastPlayedDate", false);
+ return ("LastPlayedDate", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.PlayCount, StringComparison.OrdinalIgnoreCase))
{
- return ("PlayCount", false);
+ return ("PlayCount", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.IsFavoriteOrLiked, StringComparison.OrdinalIgnoreCase))
{
- return ("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", true);
+ return ("(Select Case When IsFavorite is null Then 0 Else IsFavorite End )", SortOrder.Ascending);
}
else if (string.Equals(name, ItemSortBy.IsFolder, StringComparison.OrdinalIgnoreCase))
{
- return ("IsFolder", true);
+ return ("IsFolder", SortOrder.Ascending);
}
else if (string.Equals(name, ItemSortBy.IsPlayed, StringComparison.OrdinalIgnoreCase))
{
- return ("played", true);
+ return ("played", SortOrder.Ascending);
}
else if (string.Equals(name, ItemSortBy.IsUnplayed, StringComparison.OrdinalIgnoreCase))
{
- return ("played", false);
+ return ("played", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.DateLastContentAdded, StringComparison.OrdinalIgnoreCase))
{
- return ("DateLastMediaAdded", false);
+ return ("DateLastMediaAdded", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.Artist, StringComparison.OrdinalIgnoreCase))
{
- return ("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", false);
+ return ("(select CleanValue from itemvalues where ItemId=Guid and Type=0 LIMIT 1)", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.AlbumArtist, StringComparison.OrdinalIgnoreCase))
{
- return ("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", false);
+ return ("(select CleanValue from itemvalues where ItemId=Guid and Type=1 LIMIT 1)", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.OfficialRating, StringComparison.OrdinalIgnoreCase))
{
- return ("InheritedParentalRatingValue", false);
+ return ("InheritedParentalRatingValue", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.Studio, StringComparison.OrdinalIgnoreCase))
{
- return ("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", false);
+ return ("(select CleanValue from itemvalues where ItemId=Guid and Type=3 LIMIT 1)", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.SeriesDatePlayed, StringComparison.OrdinalIgnoreCase))
{
- return ("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", false);
+ return ("(Select MAX(LastPlayedDate) from TypedBaseItems B" + GetJoinUserDataText(query) + " where Played=1 and B.SeriesPresentationUniqueKey=A.PresentationUniqueKey)", SortOrder.Descending);
}
else if (string.Equals(name, ItemSortBy.SeriesSortName, StringComparison.OrdinalIgnoreCase))
{
- return ("SeriesName", false);
+ return ("SeriesName", SortOrder.Descending);
}
- return (name, false);
+ return (name, SortOrder.Descending);
}
public List<Guid> GetItemIdsList(InternalItemsQuery query)
@@ -3363,51 +3330,52 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection(true))
{
connection.RunInTransaction(
- db =>
- {
- var statements = PrepareAll(db, statementTexts);
-
- if (!isReturningZeroItems)
+ db =>
{
- using (var statement = statements[0])
+ var statements = PrepareAll(db, statementTexts);
+
+ if (!isReturningZeroItems)
{
- if (EnableJoinUserData(query))
+ using (var statement = statements[0])
{
- statement.TryBind("@UserId", query.User.InternalId);
- }
+ if (EnableJoinUserData(query))
+ {
+ statement.TryBind("@UserId", query.User.InternalId);
+ }
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
+ BindSimilarParams(query, statement);
+ BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
- foreach (var row in statement.ExecuteQuery())
- {
- list.Add(row[0].ReadGuidFromBlob());
+ foreach (var row in statement.ExecuteQuery())
+ {
+ list.Add(row[0].ReadGuidFromBlob());
+ }
}
}
- }
- if (query.EnableTotalRecordCount)
- {
- using (var statement = statements[statements.Length - 1])
+ if (query.EnableTotalRecordCount)
{
- if (EnableJoinUserData(query))
+ using (var statement = statements[statements.Length - 1])
{
- statement.TryBind("@UserId", query.User.InternalId);
- }
+ if (EnableJoinUserData(query))
+ {
+ statement.TryBind("@UserId", query.User.InternalId);
+ }
- BindSimilarParams(query, statement);
- BindSearchParams(query, statement);
+ BindSimilarParams(query, statement);
+ BindSearchParams(query, statement);
- // Running this again will bind the params
- GetWhereClauses(query, statement);
+ // Running this again will bind the params
+ GetWhereClauses(query, statement);
- result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ result.TotalRecordCount = statement.ExecuteQuery().SelectScalarInt().First();
+ }
}
- }
- }, ReadTransactionMode);
+ },
+ ReadTransactionMode);
}
LogQueryTime("GetItemIds", commandText, now);
@@ -3434,11 +3402,6 @@ namespace Emby.Server.Implementations.Data
return true;
}
- private bool IsValidType(string value)
- {
- return IsAlphaNumeric(value);
- }
-
private bool IsValidMediaType(string value)
{
return IsAlphaNumeric(value);
@@ -4705,7 +4668,7 @@ namespace Emby.Server.Implementations.Data
if (statement == null)
{
int index = 0;
- string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(t => paramName + index++));
+ string excludedTags = string.Join(',', query.ExcludeInheritedTags.Select(_ => paramName + index++));
whereClauses.Add("((select CleanValue from itemvalues where ItemId=Guid and Type=6 and cleanvalue in (" + excludedTags + ")) is null)");
}
else
@@ -4954,26 +4917,12 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- connection.ExecuteAll(sql);
- }, TransactionMode);
- }
- }
-
- private static Dictionary<string, string> GetTypeMapDictionary()
- {
- var dict = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
-
- foreach (var t in _knownTypes)
- {
- dict[t.Name] = t.FullName;
+ db =>
+ {
+ connection.ExecuteAll(sql);
+ },
+ TransactionMode);
}
-
- dict["Program"] = typeof(LiveTvProgram).FullName;
- dict["TvChannel"] = typeof(LiveTvChannel).FullName;
-
- return dict;
}
public void DeleteItem(Guid id)
@@ -4988,28 +4937,29 @@ where AncestorIdText not null and ItemValues.Value not null and ItemValues.Type
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- var idBlob = id.ToByteArray();
+ db =>
+ {
+ var idBlob = id.ToByteArray();
- // Delete people
- ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
+ // Delete people
+ ExecuteWithSingleParam(db, "delete from People where ItemId=@Id", idBlob);
- // Delete chapters
- ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", idBlob);
+ // Delete chapters
+ ExecuteWithSingleParam(db, "delete from " + ChaptersTableName + " where ItemId=@Id", idBlob);
- // Delete media streams
- ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", idBlob);
+ // Delete media streams
+ ExecuteWithSingleParam(db, "delete from mediastreams where ItemId=@Id", idBlob);
- // Delete ancestors
- ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", idBlob);
+ // Delete ancestors
+ ExecuteWithSingleParam(db, "delete from AncestorIds where ItemId=@Id", idBlob);
- // Delete item values
- ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", idBlob);
+ // Delete item values
+ ExecuteWithSingleParam(db, "delete from ItemValues where ItemId=@Id", idBlob);
- // Delete the item
- ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob);
- }, TransactionMode);
+ // Delete the item
+ ExecuteWithSingleParam(db, "delete from TypedBaseItems where guid=@Id", idBlob);
+ },
+ TransactionMode);
}
}
@@ -5244,32 +5194,32 @@ AND Type = @InternalPersonType)");
}
}
- public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0, 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 0 }, typeof(MusicArtist).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 1 }, typeof(MusicArtist).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 3 }, typeof(Studio).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(Genre).FullName);
}
- public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
return GetItemValues(query, new[] { 2 }, typeof(MusicGenre).FullName);
}
@@ -5365,7 +5315,7 @@ AND Type = @InternalPersonType)");
return list;
}
- private QueryResult<(BaseItem, ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
+ private QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetItemValues(InternalItemsQuery query, int[] itemValueTypes, string returnType)
{
if (query == null)
{
@@ -5690,7 +5640,7 @@ AND Type = @InternalPersonType)");
return counts;
}
- private List<(int, string)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
+ private List<(int MagicNumber, string Value)> GetItemValuesToSave(BaseItem item, List<string> inheritedTags)
{
var list = new List<(int, string)>();
@@ -5715,7 +5665,7 @@ AND Type = @InternalPersonType)");
return list;
}
- private void UpdateItemValues(Guid itemId, List<(int, string)> values, IDatabaseConnection db)
+ private void UpdateItemValues(Guid itemId, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
{
if (itemId.Equals(Guid.Empty))
{
@@ -5737,7 +5687,7 @@ AND Type = @InternalPersonType)");
InsertItemValues(guidBlob, values, db);
}
- private void InsertItemValues(byte[] idBlob, List<(int, string)> values, IDatabaseConnection db)
+ private void InsertItemValues(byte[] idBlob, List<(int MagicNumber, string Value)> values, IDatabaseConnection db)
{
const int Limit = 100;
var startIndex = 0;
@@ -5769,7 +5719,7 @@ AND Type = @InternalPersonType)");
var currentValueInfo = values[i];
- var itemValue = currentValueInfo.Item2;
+ var itemValue = currentValueInfo.Value;
// Don't save if invalid
if (string.IsNullOrWhiteSpace(itemValue))
@@ -5777,7 +5727,7 @@ AND Type = @InternalPersonType)");
continue;
}
- statement.TryBind("@Type" + index, currentValueInfo.Item1);
+ statement.TryBind("@Type" + index, currentValueInfo.MagicNumber);
statement.TryBind("@Value" + index, itemValue);
statement.TryBind("@CleanValue" + index, GetCleanValue(itemValue));
}
@@ -5808,15 +5758,16 @@ AND Type = @InternalPersonType)");
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- var itemIdBlob = itemId.ToByteArray();
+ db =>
+ {
+ var itemIdBlob = itemId.ToByteArray();
- // First delete chapters
- db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
+ // First delete chapters
+ db.Execute("delete from People where ItemId=@ItemId", itemIdBlob);
- InsertPeople(itemIdBlob, people, db);
- }, TransactionMode);
+ InsertPeople(itemIdBlob, people, db);
+ },
+ TransactionMode);
}
}
@@ -5974,7 +5925,8 @@ AND Type = @InternalPersonType)");
db.Execute("delete from mediastreams where ItemId=@ItemId", itemIdBlob);
InsertMediaStreams(itemIdBlob, streams, db);
- }, TransactionMode);
+ },
+ TransactionMode);
}
}
@@ -6308,7 +6260,8 @@ AND Type = @InternalPersonType)");
db.Execute("delete from mediaattachments where ItemId=@ItemId", itemIdBlob);
InsertMediaAttachments(itemIdBlob, attachments, db, cancellationToken);
- }, TransactionMode);
+ },
+ TransactionMode);
}
}
diff --git a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
index 107096b5f..80b8f9ebf 100644
--- a/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
+++ b/Emby.Server.Implementations/Data/SqliteUserDataRepository.cs
@@ -50,41 +50,42 @@ namespace Emby.Server.Implementations.Data
var users = userDatasTableExists ? null : userManager.Users;
connection.RunInTransaction(
- db =>
- {
- db.ExecuteAll(string.Join(';', new[]
- {
- "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
-
- "drop index if exists idx_userdata",
- "drop index if exists idx_userdata1",
- "drop index if exists idx_userdata2",
- "drop index if exists userdataindex1",
- "drop index if exists userdataindex",
- "drop index if exists userdataindex3",
- "drop index if exists userdataindex4",
- "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
- "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
- "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
- "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
- }));
-
- if (userDataTableExists)
+ db =>
{
- var existingColumnNames = GetColumnNames(db, "userdata");
+ db.ExecuteAll(string.Join(';', new[]
+ {
+ "create table if not exists UserDatas (key nvarchar not null, userId INT not null, rating float null, played bit not null, playCount int not null, isFavorite bit not null, playbackPositionTicks bigint not null, lastPlayedDate datetime null, AudioStreamIndex INT, SubtitleStreamIndex INT)",
+
+ "drop index if exists idx_userdata",
+ "drop index if exists idx_userdata1",
+ "drop index if exists idx_userdata2",
+ "drop index if exists userdataindex1",
+ "drop index if exists userdataindex",
+ "drop index if exists userdataindex3",
+ "drop index if exists userdataindex4",
+ "create unique index if not exists UserDatasIndex1 on UserDatas (key, userId)",
+ "create index if not exists UserDatasIndex2 on UserDatas (key, userId, played)",
+ "create index if not exists UserDatasIndex3 on UserDatas (key, userId, playbackPositionTicks)",
+ "create index if not exists UserDatasIndex4 on UserDatas (key, userId, isFavorite)"
+ }));
+
+ if (userDataTableExists)
+ {
+ var existingColumnNames = GetColumnNames(db, "userdata");
- AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
- AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
- AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
+ AddColumn(db, "userdata", "InternalUserId", "int", existingColumnNames);
+ AddColumn(db, "userdata", "AudioStreamIndex", "int", existingColumnNames);
+ AddColumn(db, "userdata", "SubtitleStreamIndex", "int", existingColumnNames);
- if (!userDatasTableExists)
- {
- ImportUserIds(db, users);
+ if (!userDatasTableExists)
+ {
+ ImportUserIds(db, users);
- db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
+ db.ExecuteAll("INSERT INTO UserDatas (key, userId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex) SELECT key, InternalUserId, rating, played, playCount, isFavorite, playbackPositionTicks, lastPlayedDate, AudioStreamIndex, SubtitleStreamIndex from userdata where InternalUserId not null");
+ }
}
- }
- }, TransactionMode);
+ },
+ TransactionMode);
}
}
@@ -183,10 +184,11 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- SaveUserData(db, internalUserId, key, userData);
- }, TransactionMode);
+ db =>
+ {
+ SaveUserData(db, internalUserId, key, userData);
+ },
+ TransactionMode);
}
}
@@ -252,13 +254,14 @@ namespace Emby.Server.Implementations.Data
using (var connection = GetConnection())
{
connection.RunInTransaction(
- db =>
- {
- foreach (var userItemData in userDataList)
+ db =>
{
- SaveUserData(db, internalUserId, userItemData.Key, userItemData);
- }
- }, TransactionMode);
+ foreach (var userItemData in userDataList)
+ {
+ SaveUserData(db, internalUserId, userItemData.Key, userItemData);
+ }
+ },
+ TransactionMode);
}
}
diff --git a/Emby.Server.Implementations/Devices/DeviceId.cs b/Emby.Server.Implementations/Devices/DeviceId.cs
index 0cfced8be..b3f5549bc 100644
--- a/Emby.Server.Implementations/Devices/DeviceId.cs
+++ b/Emby.Server.Implementations/Devices/DeviceId.cs
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Devices
{
var value = File.ReadAllText(CachePath, Encoding.UTF8);
- if (Guid.TryParse(value, out var guid))
+ if (Guid.TryParse(value, out _))
{
return value;
}
diff --git a/Emby.Server.Implementations/Dto/DtoService.cs b/Emby.Server.Implementations/Dto/DtoService.cs
index a6406827c..7ba34e74a 100644
--- a/Emby.Server.Implementations/Dto/DtoService.cs
+++ b/Emby.Server.Implementations/Dto/DtoService.cs
@@ -109,7 +109,7 @@ namespace Emby.Server.Implementations.Dto
}
});
- SetItemByNameInfo(item, dto, libraryItems, user);
+ SetItemByNameInfo(item, dto, libraryItems);
}
}
@@ -153,8 +153,7 @@ namespace Emby.Server.Implementations.Dto
new DtoOptions(false)
{
EnableImages = false
- }),
- user);
+ }));
}
return dto;
@@ -311,13 +310,13 @@ namespace Emby.Server.Implementations.Dto
if (taggedItems != null && options.ContainsField(ItemFields.ItemCounts))
{
- SetItemByNameInfo(item, dto, taggedItems, user);
+ SetItemByNameInfo(item, dto, taggedItems);
}
return dto;
}
- private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems, User user = null)
+ private static void SetItemByNameInfo(BaseItem item, BaseItemDto dto, IList<BaseItem> taggedItems)
{
if (item is MusicArtist)
{
diff --git a/Emby.Server.Implementations/Emby.Server.Implementations.csproj b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
index 329a84acb..1e09a98cf 100644
--- a/Emby.Server.Implementations/Emby.Server.Implementations.csproj
+++ b/Emby.Server.Implementations/Emby.Server.Implementations.csproj
@@ -49,10 +49,14 @@
<NoWarn>AD0001</NoWarn>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<!-- 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/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
index 331de45c1..d43996c69 100644
--- a/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/LibraryChangedNotifier.cs
@@ -101,7 +101,7 @@ namespace Emby.Server.Implementations.EntryPoints
}
}
- _lastProgressMessageTimes.AddOrUpdate(item.Id, key => DateTime.UtcNow, (key, existing) => DateTime.UtcNow);
+ _lastProgressMessageTimes.AddOrUpdate(item.Id, _ => DateTime.UtcNow, (_, _) => DateTime.UtcNow);
var dict = new Dictionary<string, string>();
dict["ItemId"] = item.Id.ToString("N", CultureInfo.InvariantCulture);
@@ -144,7 +144,7 @@ namespace Emby.Server.Implementations.EntryPoints
{
OnProviderRefreshProgress(sender, new GenericEventArgs<Tuple<BaseItem, double>>(new Tuple<BaseItem, double>(e.Argument, 100)));
- _lastProgressMessageTimes.TryRemove(e.Argument.Id, out DateTime removed);
+ _lastProgressMessageTimes.TryRemove(e.Argument.Id, out _);
}
private static bool EnableRefreshMessage(BaseItem item)
@@ -423,7 +423,6 @@ namespace Emby.Server.Implementations.EntryPoints
continue;
}
- var collectionFolders = _libraryManager.GetCollectionFolders(item, allUserRootChildren);
foreach (var folder in allUserRootChildren)
{
list.Add(folder.Id.ToString("N", CultureInfo.InvariantCulture));
@@ -465,6 +464,7 @@ namespace Emby.Server.Implementations.EntryPoints
public void Dispose()
{
Dispose(true);
+ GC.SuppressFinalize(this);
}
/// <summary>
diff --git a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
index d3bcd5e13..82c8d3ab6 100644
--- a/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
+++ b/Emby.Server.Implementations/EntryPoints/UserDataChangeNotifier.cs
@@ -95,7 +95,7 @@ namespace Emby.Server.Implementations.EntryPoints
var changes = _changedItems.ToList();
_changedItems.Clear();
- var task = SendNotifications(changes, CancellationToken.None);
+ SendNotifications(changes, CancellationToken.None).GetAwaiter().GetResult();
if (_updateTimer != null)
{
diff --git a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
index a7647caf9..bb6041f28 100644
--- a/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
+++ b/Emby.Server.Implementations/HttpServer/Security/SessionContext.cs
@@ -47,7 +47,7 @@ namespace Emby.Server.Implementations.HttpServer.Security
{
var session = await GetSession(requestContext).ConfigureAwait(false);
- return session == null || session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
+ return session.UserId.Equals(Guid.Empty) ? null : _userManager.GetUserById(session.UserId);
}
public Task<User?> GetUser(object requestContext)
diff --git a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
index 0eca2a608..b3bd3421a 100644
--- a/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
+++ b/Emby.Server.Implementations/HttpServer/WebSocketConnection.cs
@@ -236,7 +236,8 @@ namespace Emby.Server.Implementations.HttpServer
{
MessageId = Guid.NewGuid(),
MessageType = SessionMessageType.KeepAlive
- }, CancellationToken.None);
+ },
+ CancellationToken.None);
}
/// <inheritdoc />
diff --git a/Emby.Server.Implementations/IO/FileRefresher.cs b/Emby.Server.Implementations/IO/FileRefresher.cs
index e62361c1e..6326208f7 100644
--- a/Emby.Server.Implementations/IO/FileRefresher.cs
+++ b/Emby.Server.Implementations/IO/FileRefresher.cs
@@ -217,8 +217,13 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc />
public void Dispose()
{
- _disposed = true;
+ if (_disposed)
+ {
+ return;
+ }
+
DisposeTimer();
+ _disposed = true;
GC.SuppressFinalize(this);
}
}
diff --git a/Emby.Server.Implementations/IO/LibraryMonitor.cs b/Emby.Server.Implementations/IO/LibraryMonitor.cs
index 9fcc7fe59..657daac3f 100644
--- a/Emby.Server.Implementations/IO/LibraryMonitor.cs
+++ b/Emby.Server.Implementations/IO/LibraryMonitor.cs
@@ -99,7 +99,7 @@ namespace Emby.Server.Implementations.IO
// But if we make this delay too high, we risk missing legitimate changes, such as user adding a new file, or hand-editing metadata
await Task.Delay(45000).ConfigureAwait(false);
- _tempIgnoredPaths.TryRemove(path, out var val);
+ _tempIgnoredPaths.TryRemove(path, out _);
if (refreshPath)
{
diff --git a/Emby.Server.Implementations/IO/ManagedFileSystem.cs b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
index 777cd2cd4..5c86dbbb7 100644
--- a/Emby.Server.Implementations/IO/ManagedFileSystem.cs
+++ b/Emby.Server.Implementations/IO/ManagedFileSystem.cs
@@ -544,16 +544,6 @@ namespace Emby.Server.Implementations.IO
/// <inheritdoc />
public virtual bool AreEqual(string path1, string path2)
{
- if (path1 == null && path2 == null)
- {
- return true;
- }
-
- if (path1 == null || path2 == null)
- {
- return false;
- }
-
return string.Equals(
NormalizePath(path1),
NormalizePath(path2),
diff --git a/Emby.Server.Implementations/IO/StreamHelper.cs b/Emby.Server.Implementations/IO/StreamHelper.cs
index e4f5f4cf0..f55c16d6d 100644
--- a/Emby.Server.Implementations/IO/StreamHelper.cs
+++ b/Emby.Server.Implementations/IO/StreamHelper.cs
@@ -17,11 +17,11 @@ namespace Emby.Server.Implementations.IO
try
{
int read;
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
cancellationToken.ThrowIfCancellationRequested();
- await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
if (onStarted != null)
{
@@ -44,11 +44,11 @@ namespace Emby.Server.Implementations.IO
if (emptyReadLimit <= 0)
{
int read;
- while ((read = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((read = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
cancellationToken.ThrowIfCancellationRequested();
- await destination.WriteAsync(buffer, 0, read, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, read), cancellationToken).ConfigureAwait(false);
}
return;
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.IO
{
cancellationToken.ThrowIfCancellationRequested();
- var bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false);
+ var bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
if (bytesRead == 0)
{
@@ -71,7 +71,7 @@ namespace Emby.Server.Implementations.IO
{
eofCount = 0;
- await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
}
}
}
@@ -88,13 +88,13 @@ namespace Emby.Server.Implementations.IO
{
int bytesRead;
- while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
var bytesToWrite = Math.Min(bytesRead, copyLength);
if (bytesToWrite > 0)
{
- await destination.WriteAsync(buffer, 0, Convert.ToInt32(bytesToWrite), cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, Convert.ToInt32(bytesToWrite)), cancellationToken).ConfigureAwait(false);
}
copyLength -= bytesToWrite;
@@ -137,9 +137,9 @@ namespace Emby.Server.Implementations.IO
int bytesRead;
int totalBytesRead = 0;
- while ((bytesRead = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken).ConfigureAwait(false)) != 0)
+ while ((bytesRead = await source.ReadAsync(buffer, cancellationToken).ConfigureAwait(false)) != 0)
{
- await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken).ConfigureAwait(false);
+ await destination.WriteAsync(buffer.AsMemory(0, bytesRead), cancellationToken).ConfigureAwait(false);
totalBytesRead += bytesRead;
}
diff --git a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
index 7e12ebb08..7958eb8f5 100644
--- a/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
+++ b/Emby.Server.Implementations/Images/CollectionFolderImageProvider.cs
@@ -68,9 +68,9 @@ namespace Emby.Server.Implementations.Images
DtoOptions = new DtoOptions(false),
ImageTypes = new ImageType[] { ImageType.Primary },
Limit = 8,
- OrderBy = new ValueTuple<string, SortOrder>[]
+ OrderBy = new[]
{
- new ValueTuple<string, SortOrder>(ItemSortBy.Random, SortOrder.Ascending)
+ (ItemSortBy.Random, SortOrder.Ascending)
},
IncludeItemTypes = includeItemTypes
});
diff --git a/Emby.Server.Implementations/Library/LibraryManager.cs b/Emby.Server.Implementations/Library/LibraryManager.cs
index 270264dba..db2836a70 100644
--- a/Emby.Server.Implementations/Library/LibraryManager.cs
+++ b/Emby.Server.Implementations/Library/LibraryManager.cs
@@ -13,10 +13,10 @@ using System.Threading;
using System.Threading.Tasks;
using Emby.Naming.Common;
using Emby.Naming.TV;
-using Emby.Naming.Video;
+using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Validators;
using Emby.Server.Implementations.Playlists;
-using Emby.Server.Implementations.ScheduledTasks;
+using Emby.Server.Implementations.ScheduledTasks.Tasks;
using Jellyfin.Data.Entities;
using Jellyfin.Data.Enums;
using Jellyfin.Extensions;
@@ -78,6 +78,7 @@ namespace Emby.Server.Implementations.Library
private readonly IItemRepository _itemRepository;
private readonly IImageProcessor _imageProcessor;
private readonly NamingOptions _namingOptions;
+ private readonly ExtraResolver _extraResolver;
/// <summary>
/// The _root folder sync lock.
@@ -146,6 +147,8 @@ namespace Emby.Server.Implementations.Library
_memoryCache = memoryCache;
_namingOptions = namingOptions;
+ _extraResolver = new ExtraResolver(namingOptions);
+
_configurationManager.ConfigurationUpdated += ConfigurationUpdated;
RecordConfigurationValues(configurationManager.Configuration);
@@ -1373,7 +1376,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetItemIdsList(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetStudios(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetStudios(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1384,7 +1387,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetStudios(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1395,7 +1398,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetGenres(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetMusicGenres(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetMusicGenres(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1406,7 +1409,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetMusicGenres(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetAllArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAllArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1417,7 +1420,7 @@ namespace Emby.Server.Implementations.Library
return _itemRepository.GetAllArtists(query);
}
- public QueryResult<(BaseItem, ItemCounts)> GetArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1458,7 +1461,7 @@ namespace Emby.Server.Implementations.Library
}
}
- public QueryResult<(BaseItem, ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
+ public QueryResult<(BaseItem Item, ItemCounts ItemCounts)> GetAlbumArtists(InternalItemsQuery query)
{
if (query.User != null)
{
@@ -1757,7 +1760,7 @@ namespace Emby.Server.Implementations.Library
return orderedItems ?? items;
}
- public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<ValueTuple<string, SortOrder>> orderBy)
+ public IEnumerable<BaseItem> Sort(IEnumerable<BaseItem> items, User user, IEnumerable<(string OrderBy, SortOrder SortOrder)> orderBy)
{
var isFirst = true;
@@ -2678,7 +2681,7 @@ namespace Emby.Server.Implementations.Library
return new ItemLookupInfo
{
- Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName.ToString() : result.Name,
+ Name = VideoResolver.TryCleanString(result.Name, namingOptions, out var newName) ? newName : result.Name,
Year = result.Year
};
}
@@ -2692,95 +2695,55 @@ namespace Emby.Server.Implementations.Library
}
var count = fileSystemChildren.Count;
- var files = new List<VideoFileInfo>();
- var nonVideoFiles = new List<FileSystemMetadata>();
for (var i = 0; i < count; i++)
{
var current = fileSystemChildren[i];
if (current.IsDirectory && _namingOptions.AllExtrasTypesFolderNames.ContainsKey(current.Name))
{
- var filesInSubFolder = _fileSystem.GetFiles(current.FullName, _namingOptions.VideoFileExtensions, false, false);
+ var filesInSubFolder = _fileSystem.GetFiles(current.FullName, null, false, false);
foreach (var file in filesInSubFolder)
{
- var videoInfo = VideoResolver.Resolve(file.FullName, file.IsDirectory, _namingOptions);
- if (videoInfo == null)
+ if (!_extraResolver.TryGetExtraTypeForOwner(file.FullName, ownerVideoInfo, out var extraType))
{
- nonVideoFiles.Add(file);
continue;
}
- files.Add(videoInfo);
+ var extra = GetExtra(file, extraType.Value);
+ if (extra != null)
+ {
+ yield return extra;
+ }
}
}
- else if (!current.IsDirectory)
+ else if (!current.IsDirectory && _extraResolver.TryGetExtraTypeForOwner(current.FullName, ownerVideoInfo, out var extraType))
{
- var videoInfo = VideoResolver.Resolve(current.FullName, current.IsDirectory, _namingOptions);
- if (videoInfo == null)
+ var extra = GetExtra(current, extraType.Value);
+ if (extra != null)
{
- nonVideoFiles.Add(current);
- continue;
+ yield return extra;
}
-
- files.Add(videoInfo);
}
}
- if (files.Count == 0)
- {
- yield break;
- }
-
- var videos = VideoListResolver.Resolve(files, _namingOptions);
- // owner video info cannot be null as that implies it has no path
- var extras = ExtraResolver.GetExtras(videos, ownerVideoInfo, _namingOptions.VideoFlagDelimiters);
- for (var i = 0; i < extras.Count; i++)
+ BaseItem GetExtra(FileSystemMetadata file, ExtraType extraType)
{
- var currentExtra = extras[i];
- var resolved = ResolvePath(_fileSystem.GetFileInfo(currentExtra.Path), null, directoryService);
- if (resolved is not Video video)
- {
- continue;
- }
-
- // Try to retrieve it from the db. If we don't find it, use the resolved version
- if (GetItemById(resolved.Id) is Video dbItem)
+ var extra = ResolvePath(_fileSystem.GetFileInfo(file.FullName), directoryService, _extraResolver.GetResolversForExtraType(extraType));
+ if (extra is not Video && extra is not Audio)
{
- video = dbItem;
- }
-
- video.ExtraType = currentExtra.ExtraType;
- video.ParentId = Guid.Empty;
- video.OwnerId = owner.Id;
- yield return video;
- }
-
- // TODO: theme songs must be handled "manually" (but should we?) since they aren't video files
- for (var i = 0; i < nonVideoFiles.Count; i++)
- {
- var current = nonVideoFiles[i];
- var extraInfo = ExtraResolver.GetExtraInfo(current.FullName, _namingOptions);
- if (extraInfo.ExtraType != ExtraType.ThemeSong)
- {
- continue;
- }
-
- var resolved = ResolvePath(current, null, directoryService);
- if (resolved is not Audio themeSong)
- {
- continue;
+ return null;
}
// Try to retrieve it from the db. If we don't find it, use the resolved version
- if (GetItemById(themeSong.Id) is Audio dbItem)
+ var itemById = GetItemById(extra.Id);
+ if (itemById != null)
{
- themeSong = dbItem;
+ extra = itemById;
}
- themeSong.ExtraType = ExtraType.ThemeSong;
- themeSong.OwnerId = owner.Id;
- themeSong.ParentId = Guid.Empty;
-
- yield return themeSong;
+ extra.ExtraType = extraType;
+ extra.ParentId = Guid.Empty;
+ extra.OwnerId = owner.Id;
+ return extra;
}
}
diff --git a/Emby.Server.Implementations/Library/LiveStreamHelper.cs b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
index 83acd8e9f..20624cc7a 100644
--- a/Emby.Server.Implementations/Library/LiveStreamHelper.cs
+++ b/Emby.Server.Implementations/Library/LiveStreamHelper.cs
@@ -66,11 +66,8 @@ namespace Emby.Server.Implementations.Library
{
var delayMs = mediaSource.AnalyzeDurationMs ?? 0;
delayMs = Math.Max(3000, delayMs);
- if (delayMs > 0)
- {
- _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
- await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
- }
+ _logger.LogInformation("Waiting {0}ms before probing the live stream", delayMs);
+ await Task.Delay(delayMs, cancellationToken).ConfigureAwait(false);
}
mediaSource.AnalyzeDurationMs = 3000;
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 972d4ebbb..a414e7e16 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -464,12 +464,11 @@ namespace Emby.Server.Implementations.Library
try
{
- var tuple = GetProvider(request.OpenToken);
- var provider = tuple.Item1;
+ var (provider, keyId) = GetProvider(request.OpenToken);
var currentLiveStreams = _openStreams.Values.ToList();
- liveStream = await provider.OpenMediaSource(tuple.Item2, currentLiveStreams, cancellationToken).ConfigureAwait(false);
+ liveStream = await provider.OpenMediaSource(keyId, currentLiveStreams, cancellationToken).ConfigureAwait(false);
mediaSource = liveStream.MediaSource;
@@ -829,7 +828,7 @@ namespace Emby.Server.Implementations.Library
}
}
- private (IMediaSourceProvider, string) GetProvider(string key)
+ private (IMediaSourceProvider MediaSourceProvider, string KeyId) GetProvider(string key)
{
if (string.IsNullOrEmpty(key))
{
diff --git a/Emby.Server.Implementations/Library/MusicManager.cs b/Emby.Server.Implementations/Library/MusicManager.cs
index d33213564..d35e74e7b 100644
--- a/Emby.Server.Implementations/Library/MusicManager.cs
+++ b/Emby.Server.Implementations/Library/MusicManager.cs
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.Library
public List<BaseItem> GetInstantMixFromItem(BaseItem item, User user, DtoOptions dtoOptions)
{
- if (item is MusicGenre genre)
+ if (item is MusicGenre)
{
return GetInstantMixFromGenreIds(new[] { item.Id }, user, dtoOptions);
}
diff --git a/Emby.Server.Implementations/Library/PathExtensions.cs b/Emby.Server.Implementations/Library/PathExtensions.cs
index 6f61dc713..64e7d5446 100644
--- a/Emby.Server.Implementations/Library/PathExtensions.cs
+++ b/Emby.Server.Implementations/Library/PathExtensions.cs
@@ -37,7 +37,7 @@ namespace Emby.Server.Implementations.Library
var attributeEnd = attributeIndex + attribute.Length;
if (attributeIndex > 0
&& str[attributeIndex - 1] == '['
- && str[attributeEnd] == '=')
+ && (str[attributeEnd] == '=' || str[attributeEnd] == '-'))
{
var closingIndex = str[attributeEnd..].IndexOf(']');
// Must be at least 1 character before the closing bracket.
diff --git a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
index a9819a364..da00b9cfa 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Audio/MusicAlbumResolver.cs
@@ -112,7 +112,7 @@ namespace Emby.Server.Implementations.Library.Resolvers.Audio
/// Determine if the supplied list contains what we should consider music.
/// </summary>
private bool ContainsMusic(
- IEnumerable<FileSystemMetadata> list,
+ ICollection<FileSystemMetadata> list,
bool allowSubfolders,
IDirectoryService directoryService)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
new file mode 100644
index 000000000..3d06ceb5e
--- /dev/null
+++ b/Emby.Server.Implementations/Library/Resolvers/ExtraResolver.cs
@@ -0,0 +1,93 @@
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using Emby.Naming.Common;
+using Emby.Naming.Video;
+using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Resolvers;
+using MediaBrowser.Model.Entities;
+using static Emby.Naming.Video.ExtraRuleResolver;
+
+namespace Emby.Server.Implementations.Library.Resolvers
+{
+ /// <summary>
+ /// Resolves a Path into a Video or Video subclass.
+ /// </summary>
+ internal class ExtraResolver
+ {
+ private readonly NamingOptions _namingOptions;
+ private readonly IItemResolver[] _trailerResolvers;
+ private readonly IItemResolver[] _videoResolvers;
+
+ /// <summary>
+ /// Initializes an new instance of the <see cref="ExtraResolver"/> class.
+ /// </summary>
+ /// <param name="namingOptions">An instance of <see cref="NamingOptions"/>.</param>
+ public ExtraResolver(NamingOptions namingOptions)
+ {
+ _namingOptions = namingOptions;
+ _trailerResolvers = new IItemResolver[] { new GenericVideoResolver<Trailer>(namingOptions) };
+ _videoResolvers = new IItemResolver[] { new GenericVideoResolver<Video>(namingOptions) };
+ }
+
+ /// <summary>
+ /// Gets the resolvers for the extra type.
+ /// </summary>
+ /// <param name="extraType">The extra type.</param>
+ /// <returns>The resolvers for the extra type.</returns>
+ public IItemResolver[]? GetResolversForExtraType(ExtraType extraType) => extraType switch
+ {
+ ExtraType.Trailer => _trailerResolvers,
+ // For audio we'll have to rely on the AudioResolver, which is a "built-in"
+ ExtraType.ThemeSong => null,
+ _ => _videoResolvers
+ };
+
+ public bool TryGetExtraTypeForOwner(string path, VideoFileInfo ownerVideoFileInfo, [NotNullWhen(true)] out ExtraType? extraType)
+ {
+ var extraResult = GetExtraInfo(path, _namingOptions);
+ if (extraResult.ExtraType == null)
+ {
+ extraType = null;
+ return false;
+ }
+
+ var cleanDateTimeResult = CleanDateTimeParser.Clean(Path.GetFileNameWithoutExtension(path), _namingOptions.CleanDateTimeRegexes);
+ var name = cleanDateTimeResult.Name;
+ var year = cleanDateTimeResult.Year;
+
+ var parentDir = ownerVideoFileInfo.IsDirectory ? ownerVideoFileInfo.Path : Path.GetDirectoryName(ownerVideoFileInfo.Path.AsSpan());
+
+ var trimmedFileNameWithoutExtension = TrimFilenameDelimiters(ownerVideoFileInfo.FileNameWithoutExtension, _namingOptions.VideoFlagDelimiters);
+ var trimmedVideoInfoName = TrimFilenameDelimiters(ownerVideoFileInfo.Name, _namingOptions.VideoFlagDelimiters);
+ var trimmedExtraFileName = TrimFilenameDelimiters(name, _namingOptions.VideoFlagDelimiters);
+
+ // first check filenames
+ bool isValid = StartsWith(trimmedExtraFileName, trimmedFileNameWithoutExtension)
+ || (StartsWith(trimmedExtraFileName, trimmedVideoInfoName) && year == ownerVideoFileInfo.Year);
+
+ if (!isValid)
+ {
+ // When the extra rule type is DirectoryName we must go one level higher to get the "real" dir name
+ var currentParentDir = extraResult.Rule?.RuleType == ExtraRuleType.DirectoryName
+ ? Path.GetDirectoryName(Path.GetDirectoryName(path.AsSpan()))
+ : Path.GetDirectoryName(path.AsSpan());
+
+ isValid = !currentParentDir.IsEmpty && !parentDir.IsEmpty && currentParentDir.Equals(parentDir, StringComparison.OrdinalIgnoreCase);
+ }
+
+ extraType = extraResult.ExtraType;
+ return isValid;
+ }
+
+ private static ReadOnlySpan<char> TrimFilenameDelimiters(ReadOnlySpan<char> name, ReadOnlySpan<char> videoFlagDelimiters)
+ {
+ return name.IsEmpty ? name : name.TrimEnd().TrimEnd(videoFlagDelimiters).TrimEnd();
+ }
+
+ private static bool StartsWith(ReadOnlySpan<char> fileName, ReadOnlySpan<char> baseName)
+ {
+ return !baseName.IsEmpty && fileName.StartsWith(baseName, StringComparison.OrdinalIgnoreCase);
+ }
+ }
+}
diff --git a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
index 72341d9db..b8554bd51 100644
--- a/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/GenericVideoResolver.cs
@@ -1,15 +1,21 @@
-#nullable disable
-
-#pragma warning disable CS1591
+#nullable disable
using Emby.Naming.Common;
using MediaBrowser.Controller.Entities;
namespace Emby.Server.Implementations.Library.Resolvers
{
+ /// <summary>
+ /// Resolves a Path into an instance of the <see cref="Video"/> class.
+ /// </summary>
+ /// <typeparam name="T">The type of item to resolve.</typeparam>
public class GenericVideoResolver<T> : BaseVideoResolver<T>
where T : Video, new()
{
+ /// <summary>
+ /// Initializes a new instance of the <see cref="GenericVideoResolver{T}"/> class.
+ /// </summary>
+ /// <param name="namingOptions">The naming options.</param>
public GenericVideoResolver(NamingOptions namingOptions)
: base(namingOptions)
{
diff --git a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
index 4feaf3fb4..1a9295dc8 100644
--- a/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/Movies/MovieResolver.cs
@@ -105,10 +105,9 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
if (string.IsNullOrEmpty(collectionType))
{
- // Owned items will be caught by the plain video resolver
+ // Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
- // return FindMovie<Video>(args.Path, args.Parent, files, args.DirectoryService, collectionType);
return null;
}
@@ -129,10 +128,10 @@ namespace Emby.Server.Implementations.Library.Resolvers.Movies
return movie?.ExtraType == null ? movie : null;
}
- // Handle owned items
+ // Owned items will be caught by the video extra resolver
if (args.Parent == null)
{
- return base.Resolve(args);
+ return null;
}
if (IsInvalid(args.Parent, collectionType))
diff --git a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
index e52b43050..bc2915db6 100644
--- a/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
+++ b/Emby.Server.Implementations/Library/Resolvers/PhotoResolver.cs
@@ -34,7 +34,6 @@ namespace Emby.Server.Implementations.Library.Resolvers
"default"
};
-
public PhotoResolver(IImageProcessor imageProcessor, NamingOptions namingOptions)
{
_imageProcessor = imageProcessor;
diff --git a/Emby.Server.Implementations/Library/SearchEngine.cs b/Emby.Server.Implementations/Library/SearchEngine.cs
index 4aacf7774..55911933a 100644
--- a/Emby.Server.Implementations/Library/SearchEngine.cs
+++ b/Emby.Server.Implementations/Library/SearchEngine.cs
@@ -190,7 +190,7 @@ namespace Emby.Server.Implementations.Library
searchQuery.ParentId = Guid.Empty;
searchQuery.IncludeItemsByName = true;
searchQuery.IncludeItemTypes = Array.Empty<BaseItemKind>();
- mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item1).ToList();
+ mediaItems = _libraryManager.GetAllArtists(searchQuery).Items.Select(i => i.Item).ToList();
}
else
{
diff --git a/Emby.Server.Implementations/Library/UserDataManager.cs b/Emby.Server.Implementations/Library/UserDataManager.cs
index bb3034142..3810a76c4 100644
--- a/Emby.Server.Implementations/Library/UserDataManager.cs
+++ b/Emby.Server.Implementations/Library/UserDataManager.cs
@@ -75,7 +75,7 @@ namespace Emby.Server.Implementations.Library
}
var cacheKey = GetCacheKey(userId, item.Id);
- _userData.AddOrUpdate(cacheKey, userData, (k, v) => userData);
+ _userData.AddOrUpdate(cacheKey, userData, (_, _) => userData);
UserDataSaved?.Invoke(this, new UserDataSaveEventArgs
{
@@ -125,7 +125,7 @@ namespace Emby.Server.Implementations.Library
var cacheKey = GetCacheKey(userId, itemId);
- return _userData.GetOrAdd(cacheKey, k => GetUserDataInternal(userId, keys));
+ return _userData.GetOrAdd(cacheKey, _ => GetUserDataInternal(userId, keys));
}
private UserItemData GetUserDataInternal(long internalUserId, List<string> keys)
diff --git a/Emby.Server.Implementations/Library/UserViewManager.cs b/Emby.Server.Implementations/Library/UserViewManager.cs
index ab8bc6328..b00bc72e6 100644
--- a/Emby.Server.Implementations/Library/UserViewManager.cs
+++ b/Emby.Server.Implementations/Library/UserViewManager.cs
@@ -173,7 +173,7 @@ namespace Emby.Server.Implementations.Library
string viewType,
string localizationKey,
string sortName,
- Jellyfin.Data.Entities.User user,
+ User user,
string[] presetViews)
{
if (parents.Count == 1 && parents.All(i => string.Equals(i.CollectionType, viewType, StringComparison.OrdinalIgnoreCase)))
@@ -359,7 +359,7 @@ namespace Emby.Server.Implementations.Library
(ItemSortBy.SortName, SortOrder.Descending),
(ItemSortBy.ProductionYear, SortOrder.Descending)
},
- IsFolder = includeItemTypes.Length == 0 ? false : (bool?)null,
+ IsFolder = includeItemTypes.Length == 0 ? false : null,
ExcludeItemTypes = excludeItemTypes,
IsVirtualItem = false,
Limit = limit * 5,
diff --git a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
index 73e58d16c..88b93a211 100644
--- a/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
+++ b/Emby.Server.Implementations/Library/Validators/CollectionPostScanTask.cs
@@ -1,16 +1,16 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
-using System.Collections.Generic;
+using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Collections;
using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Model.Entities;
using MediaBrowser.Model.Querying;
-using Jellyfin.Data.Enums;
using Microsoft.Extensions.Logging;
-using MediaBrowser.Model.Entities;
namespace Emby.Server.Implementations.Library.Validators
{
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
index 7ef93d166..e7834ffd6 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EmbyTV.cs
@@ -398,7 +398,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
}
result = new EpgChannelData(channels);
- _epgChannels.AddOrUpdate(info.Id, result, (k, v) => result);
+ _epgChannels.AddOrUpdate(info.Id, result, (_, _) => result);
}
return result;
@@ -1248,12 +1248,11 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
var remoteMetadata = await FetchInternetMetadata(timer, CancellationToken.None).ConfigureAwait(false);
var recordPath = GetRecordingPath(timer, remoteMetadata, out string seriesPath);
- var recordingStatus = RecordingStatus.New;
-
- string liveStreamId = null;
var channelItem = _liveTvManager.GetLiveTvChannel(timer, this);
+ string liveStreamId = null;
+ RecordingStatus recordingStatus;
try
{
var allMediaSources = await _mediaSourceManager.GetPlaybackMediaSources(channelItem, null, true, false, CancellationToken.None).ConfigureAwait(false);
@@ -1339,7 +1338,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
TriggerRefresh(recordPath);
_libraryMonitor.ReportFileSystemChangeComplete(recordPath, false);
- _activeRecordings.TryRemove(timer.Id, out var removed);
+ _activeRecordings.TryRemove(timer.Id, out _);
if (recordingStatus != RecordingStatus.Completed && DateTime.UtcNow < timer.EndDate && timer.RetryCount < 10)
{
@@ -1937,7 +1936,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
writer.WriteElementString("title", timer.EpisodeTitle);
}
- var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : (DateTime?)null);
+ var premiereDate = item.PremiereDate ?? (!timer.IsRepeat ? DateTime.UtcNow : null);
if (premiereDate.HasValue)
{
@@ -2126,12 +2125,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
private LiveTvProgram GetProgramInfoFromCache(TimerInfo timer)
{
- return GetProgramInfoFromCache(timer.ProgramId, timer.ChannelId);
- }
-
- private LiveTvProgram GetProgramInfoFromCache(string programId, string channelId)
- {
- return GetProgramInfoFromCache(programId);
+ return GetProgramInfoFromCache(timer.ProgramId);
}
private LiveTvProgram GetProgramInfoFromCache(string channelId, DateTime startDateUtc)
@@ -2277,7 +2271,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
{
// Only update if not currently active - test both new timer and existing in case Id's are different
// Id's could be different if the timer was created manually prior to series timer creation
- if (!_activeRecordings.TryGetValue(timer.Id, out var activeRecordingInfo) && !_activeRecordings.TryGetValue(existingTimer.Id, out activeRecordingInfo))
+ if (!_activeRecordings.TryGetValue(timer.Id, out _) && !_activeRecordings.TryGetValue(existingTimer.Id, out _))
{
UpdateExistingTimerWithNewMetadata(existingTimer, timer);
@@ -2298,17 +2292,14 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
enabledTimersForSeries.Add(existingTimer);
}
- if (updateTimerSettings)
- {
- existingTimer.KeepUntil = seriesTimer.KeepUntil;
- existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
- existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
- existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
- existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
- existingTimer.Priority = seriesTimer.Priority;
- }
-
+ existingTimer.KeepUntil = seriesTimer.KeepUntil;
+ existingTimer.IsPostPaddingRequired = seriesTimer.IsPostPaddingRequired;
+ existingTimer.IsPrePaddingRequired = seriesTimer.IsPrePaddingRequired;
+ existingTimer.PostPaddingSeconds = seriesTimer.PostPaddingSeconds;
+ existingTimer.PrePaddingSeconds = seriesTimer.PrePaddingSeconds;
+ existingTimer.Priority = seriesTimer.Priority;
existingTimer.SeriesTimerId = seriesTimer.Id;
+
_timerProvider.Update(existingTimer);
}
}
diff --git a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
index a88a1fe84..7fa47e7db 100644
--- a/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
+++ b/Emby.Server.Implementations/LiveTv/EmbyTV/EncodedRecorder.cs
@@ -62,12 +62,12 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
using var durationToken = new CancellationTokenSource(duration);
using var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, durationToken.Token);
- await RecordFromFile(mediaSource, mediaSource.Path, targetFile, duration, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
+ await RecordFromFile(mediaSource, mediaSource.Path, targetFile, onStarted, cancellationTokenSource.Token).ConfigureAwait(false);
_logger.LogInformation("Recording completed to file {0}", targetFile);
}
- private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, TimeSpan duration, Action onStarted, CancellationToken cancellationToken)
+ private async Task RecordFromFile(MediaSourceInfo mediaSource, string inputFile, string targetFile, Action onStarted, CancellationToken cancellationToken)
{
_targetPath = targetFile;
Directory.CreateDirectory(Path.GetDirectoryName(targetFile));
@@ -81,7 +81,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
RedirectStandardInput = true,
FileName = _mediaEncoder.EncoderPath,
- Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile, duration),
+ Arguments = GetCommandLineArgs(mediaSource, inputFile, targetFile),
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false
@@ -103,7 +103,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
StartInfo = processStartInfo,
EnableRaisingEvents = true
};
- _process.Exited += (sender, args) => OnFfMpegProcessExited(_process);
+ _process.Exited += (_, _) => OnFfMpegProcessExited(_process);
_process.Start();
@@ -117,7 +117,7 @@ namespace Emby.Server.Implementations.LiveTv.EmbyTV
_logger.LogInformation("ffmpeg recording process started for {0}", _targetPath);
}
- private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile, TimeSpan duration)
+ private string GetCommandLineArgs(MediaSourceInfo mediaSource, string inputTempFile, string targetFile)
{
string videoArgs;
if (EncodeVideo(mediaSource))
diff --git a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
index dd0cb6c5d..a8440102d 100644
--- a/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
+++ b/Emby.Server.Implementations/LiveTv/Listings/SchedulesDirect.cs
@@ -159,8 +159,8 @@ namespace Emby.Server.Implementations.LiveTv.Listings
var programEntry = programDict[schedule.ProgramId];
var allImages = images[imageIndex].Data;
- var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase));
- var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase));
+ var imagesWithText = allImages.Where(i => string.Equals(i.Text, "yes", StringComparison.OrdinalIgnoreCase)).ToList();
+ var imagesWithoutText = allImages.Where(i => string.Equals(i.Text, "no", StringComparison.OrdinalIgnoreCase)).ToList();
const double DesiredAspect = 2.0 / 3;
@@ -643,7 +643,9 @@ namespace Emby.Server.Implementations.LiveTv.Listings
CancellationToken cancellationToken)
{
using var options = new HttpRequestMessage(HttpMethod.Post, ApiUrl + "/token");
+#pragma warning disable CA5350 // SchedulesDirect is always SHA1.
var hashedPasswordBytes = SHA1.HashData(Encoding.ASCII.GetBytes(password));
+#pragma warning restore CA5350
// TODO: remove ToLower when Convert.ToHexString supports lowercase
// Schedules Direct requires the hex to be lowercase
string hashedPassword = Convert.ToHexString(hashedPasswordBytes).ToLowerInvariant();
@@ -820,10 +822,5 @@ namespace Emby.Server.Implementations.LiveTv.Listings
return list;
}
-
- private static string NormalizeName(string value)
- {
- return value.Replace(" ", string.Empty, StringComparison.Ordinal).Replace("-", string.Empty, StringComparison.Ordinal);
- }
}
}
diff --git a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
index 047d8e98c..aa3598c8b 100644
--- a/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
+++ b/Emby.Server.Implementations/LiveTv/LiveTvManager.cs
@@ -207,7 +207,7 @@ namespace Emby.Server.Implementations.LiveTv
orderBy.Insert(0, (ItemSortBy.IsFavoriteOrLiked, SortOrder.Descending));
}
- if (!internalQuery.OrderBy.Any(i => string.Equals(i.Item1, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
+ if (!internalQuery.OrderBy.Any(i => string.Equals(i.OrderBy, ItemSortBy.SortName, StringComparison.OrdinalIgnoreCase)))
{
orderBy.Add((ItemSortBy.SortName, SortOrder.Ascending));
}
@@ -520,7 +520,7 @@ namespace Emby.Server.Implementations.LiveTv
return item;
}
- private (LiveTvProgram item, bool isNew, bool isUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
+ private (LiveTvProgram Item, bool IsNew, bool IsUpdated) GetProgram(ProgramInfo info, Dictionary<Guid, LiveTvProgram> allExistingPrograms, LiveTvChannel channel)
{
var id = _tvDtoService.GetInternalProgramId(info.Id);
@@ -779,9 +779,9 @@ namespace Emby.Server.Implementations.LiveTv
var dto = _dtoService.GetBaseItemDto(program, new DtoOptions(), user);
- var list = new List<Tuple<BaseItemDto, string, string>>
+ var list = new List<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)>
{
- new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId)
+ (dto, program.ExternalId, program.ExternalSeriesId)
};
await AddRecordingInfo(list, cancellationToken).ConfigureAwait(false);
@@ -976,16 +976,16 @@ namespace Emby.Server.Implementations.LiveTv
return score;
}
- private async Task AddRecordingInfo(IEnumerable<Tuple<BaseItemDto, string, string>> programs, CancellationToken cancellationToken)
+ private async Task AddRecordingInfo(IEnumerable<(BaseItemDto ItemDto, string ExternalId, string ExternalSeriesId)> programs, CancellationToken cancellationToken)
{
IReadOnlyList<TimerInfo> timerList = null;
IReadOnlyList<SeriesTimerInfo> seriesTimerList = null;
foreach (var programTuple in programs)
{
- var program = programTuple.Item1;
- var externalProgramId = programTuple.Item2;
- string externalSeriesId = programTuple.Item3;
+ var program = programTuple.ItemDto;
+ var externalProgramId = programTuple.ExternalId;
+ string externalSeriesId = programTuple.ExternalSeriesId;
timerList ??= (await GetTimersInternal(new TimerQuery(), cancellationToken).ConfigureAwait(false)).Items;
@@ -1186,13 +1186,13 @@ namespace Emby.Server.Implementations.LiveTv
foreach (var program in channelPrograms)
{
var programTuple = GetProgram(program, existingPrograms, currentChannel);
- var programItem = programTuple.item;
+ var programItem = programTuple.Item;
- if (programTuple.isNew)
+ if (programTuple.IsNew)
{
newPrograms.Add(programItem);
}
- else if (programTuple.isUpdated)
+ else if (programTuple.IsUpdated)
{
updatedPrograms.Add(programItem);
}
@@ -1423,9 +1423,9 @@ namespace Emby.Server.Implementations.LiveTv
return result;
}
- public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem, BaseItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
+ public Task AddInfoToProgramDto(IReadOnlyCollection<(BaseItem Item, BaseItemDto ItemDto)> programs, IReadOnlyList<ItemFields> fields, User user = null)
{
- var programTuples = new List<Tuple<BaseItemDto, string, string>>();
+ var programTuples = new List<(BaseItemDto Dto, string ExternalId, string ExternalSeriesId)>();
var hasChannelImage = fields.Contains(ItemFields.ChannelImage);
var hasChannelInfo = fields.Contains(ItemFields.ChannelInfo);
@@ -1461,7 +1461,7 @@ namespace Emby.Server.Implementations.LiveTv
}
}
- programTuples.Add(new Tuple<BaseItemDto, string, string>(dto, program.ExternalId, program.ExternalSeriesId));
+ programTuples.Add((dto, program.ExternalId, program.ExternalSeriesId));
}
return AddRecordingInfo(programTuples, CancellationToken.None);
@@ -1868,11 +1868,11 @@ namespace Emby.Server.Implementations.LiveTv
return _libraryManager.GetItemById(internalChannelId);
}
- public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto, LiveTvChannel)> items, DtoOptions options, User user)
+ public void AddChannelInfo(IReadOnlyCollection<(BaseItemDto ItemDto, LiveTvChannel Channel)> items, DtoOptions options, User user)
{
var now = DateTime.UtcNow;
- var channelIds = items.Select(i => i.Item2.Id).Distinct().ToArray();
+ var channelIds = items.Select(i => i.Channel.Id).Distinct().ToArray();
var programs = options.AddCurrentProgram ? _libraryManager.GetItemList(new InternalItemsQuery(user)
{
@@ -1893,11 +1893,8 @@ namespace Emby.Server.Implementations.LiveTv
var addCurrentProgram = options.AddCurrentProgram;
- foreach (var tuple in items)
+ foreach (var (dto, channel) in items)
{
- var dto = tuple.Item1;
- var channel = tuple.Item2;
-
dto.Number = channel.Number;
dto.ChannelNumber = channel.Number;
dto.ChannelType = channel.ChannelType;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
index 069b4fab6..aae33503f 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunChannelCommands.cs
@@ -16,7 +16,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
_profile = profile;
}
- public IEnumerable<(string, string)> GetCommands()
+ public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{
if (!string.IsNullOrEmpty(_channel))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
index 78ea7bd0f..532790019 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunHost.cs
@@ -630,7 +630,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
catch (HttpRequestException ex)
{
- if (ex.StatusCode.HasValue && ex.StatusCode.Value == System.Net.HttpStatusCode.NotFound)
+ if (ex.StatusCode.HasValue && ex.StatusCode.Value == HttpStatusCode.NotFound)
{
// HDHR4 doesn't have this api
return;
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
index 9ab4cc628..f1a6ef344 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunManager.cs
@@ -114,7 +114,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
foreach (var command in commands.GetCommands())
{
- var channelMsgLen = WriteSetMessage(buffer, i, command.Item1, command.Item2, lockKeyValue);
+ var channelMsgLen = WriteSetMessage(buffer, i, command.CommandName, command.CommandValue, lockKeyValue);
await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
@@ -122,7 +122,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (!TryGetReturnValueOfGetSet(buffer.AsSpan(0, receivedBytes), out _))
{
await ReleaseLockkey(_tcpClient, lockKeyValue).ConfigureAwait(false);
- continue;
}
}
@@ -168,7 +167,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
foreach (var command in commandList)
{
- var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.Item1, command.Item2, _lockkey);
+ var channelMsgLen = WriteSetMessage(buffer, _activeTuner, command.CommandName, command.CommandValue, _lockkey);
await stream.WriteAsync(buffer.AsMemory(0, channelMsgLen), cancellationToken).ConfigureAwait(false);
int receivedBytes = await stream.ReadAsync(buffer, cancellationToken).ConfigureAwait(false);
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
index d2f033439..9ed0d8d73 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/HdHomerunUdpStream.cs
@@ -212,7 +212,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
if (read > 0)
{
- fileStream.Write(buffer, RtpHeaderBytes, read);
+ await fileStream.WriteAsync(buffer.AsMemory(RtpHeaderBytes, read), linkedSource.Token).ConfigureAwait(false);
}
if (!resolved)
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
index 153354932..11bd40ab1 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/IHdHomerunChannelCommands.cs
@@ -6,6 +6,6 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
{
public interface IHdHomerunChannelCommands
{
- IEnumerable<(string, string)> GetCommands();
+ IEnumerable<(string CommandName, string CommandValue)> GetCommands();
}
}
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
index 26627b8aa..80d9d0724 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/HdHomerun/LegacyHdHomerunChannelCommands.cs
@@ -22,7 +22,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts.HdHomerun
}
}
- public IEnumerable<(string, string)> GetCommands()
+ public IEnumerable<(string CommandName, string CommandValue)> GetCommands()
{
if (!string.IsNullOrEmpty(_channel))
{
diff --git a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
index 99486f25c..dd83f9a53 100644
--- a/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
+++ b/Emby.Server.Implementations/LiveTv/TunerHosts/M3UTunerHost.cs
@@ -131,7 +131,7 @@ namespace Emby.Server.Implementations.LiveTv.TunerHosts
public async Task Validate(TunerHostInfo info)
{
- using (var stream = await new M3uParser(Logger, _httpClientFactory).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
+ using (await new M3uParser(Logger, _httpClientFactory).GetListingsStream(info, CancellationToken.None).ConfigureAwait(false))
{
}
}
diff --git a/Emby.Server.Implementations/Localization/Core/ca.json b/Emby.Server.Implementations/Localization/Core/ca.json
index 2dee5e327..9bab3b9a9 100644
--- a/Emby.Server.Implementations/Localization/Core/ca.json
+++ b/Emby.Server.Implementations/Localization/Core/ca.json
@@ -15,7 +15,7 @@
"Favorites": "Preferits",
"Folders": "Carpetes",
"Genres": "Gèneres",
- "HeaderAlbumArtists": "Àlbum de l'artista",
+ "HeaderAlbumArtists": "Artistes de l'àlbum",
"HeaderContinueWatching": "Continua Veient",
"HeaderFavoriteAlbums": "Àlbums Preferits",
"HeaderFavoriteArtists": "Artistes Predilectes",
diff --git a/Emby.Server.Implementations/Localization/Core/de.json b/Emby.Server.Implementations/Localization/Core/de.json
index c924e5c15..115f36e7c 100644
--- a/Emby.Server.Implementations/Localization/Core/de.json
+++ b/Emby.Server.Implementations/Localization/Core/de.json
@@ -71,7 +71,7 @@
"ScheduledTaskStartedWithName": "{0} wurde gestartet",
"ServerNameNeedsToBeRestarted": "{0} muss neu gestartet werden",
"Shows": "Serien",
- "Songs": "Songs",
+ "Songs": "Lieder",
"StartupEmbyServerIsLoading": "Jellyfin-Server startet, bitte versuche es gleich noch einmal.",
"SubtitleDownloadFailureForItem": "Download der Untertitel fehlgeschlagen für {0}",
"SubtitleDownloadFailureFromForItem": "Untertitel von {0} für {1} konnten nicht heruntergeladen werden",
@@ -92,25 +92,25 @@
"ValueHasBeenAddedToLibrary": "{0} wurde deiner Bibliothek hinzugefügt",
"ValueSpecialEpisodeName": "Extra - {0}",
"VersionNumber": "Version {0}",
- "TaskDownloadMissingSubtitlesDescription": "Durchsucht das Internet nach fehlenden Untertiteln, basierend auf den Meta Einstellungen.",
+ "TaskDownloadMissingSubtitlesDescription": "Suche im Internet basierend auf den Metadaten-Einstellungen nach fehlenden Untertiteln.",
"TaskDownloadMissingSubtitles": "Lade fehlende Untertitel herunter",
- "TaskRefreshChannelsDescription": "Aktualisiere Internet Kanal Informationen.",
+ "TaskRefreshChannelsDescription": "Aktualisiere Internet-Kanal-Informationen.",
"TaskRefreshChannels": "Aktualisiere Kanäle",
- "TaskCleanTranscodeDescription": "Löscht Transkodierdateien, welche älter als einen Tag sind.",
- "TaskCleanTranscode": "Lösche Transkodier-Pfad",
+ "TaskCleanTranscodeDescription": "Löscht Transkodierdateien, die älter als einen Tag sind.",
+ "TaskCleanTranscode": "Räume Transkodierungs-Verzeichnis auf",
"TaskUpdatePluginsDescription": "Lädt Updates für Plugins herunter, welche für automatische Updates konfiguriert sind und installiert diese.",
"TaskUpdatePlugins": "Aktualisiere Plugins",
"TaskRefreshPeopleDescription": "Aktualisiert Metadaten für Schauspieler und Regisseure in deinen Bibliotheken.",
- "TaskRefreshPeople": "Aktualisiere Schauspieler",
- "TaskCleanLogsDescription": "Lösche Log Dateien, die älter als {0} Tage sind.",
- "TaskCleanLogs": "Lösche Log-Verzeichnis",
- "TaskRefreshLibraryDescription": "Scanne alle Bibliotheken nach neu hinzugefügten Dateien und aktualisiere Metadaten.",
+ "TaskRefreshPeople": "Aktualisiere Personen",
+ "TaskCleanLogsDescription": "Lösche Log-Dateien, die älter als {0} Tage sind.",
+ "TaskCleanLogs": "Räumt Log-Verzeichnis auf",
+ "TaskRefreshLibraryDescription": "Scannt alle Bibliotheken nach neu hinzugefügten Dateien und aktualisiere Metadaten.",
"TaskRefreshLibrary": "Scanne Medien-Bibliothek",
- "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videos, welche Kapitel besitzen.",
- "TaskRefreshChapterImages": "Extrahiert Kapitel-Bilder",
+ "TaskRefreshChapterImagesDescription": "Erstellt Vorschaubilder für Videos, die Kapitel besitzen.",
+ "TaskRefreshChapterImages": "Extrahiere Kapitel-Bilder",
"TaskCleanCacheDescription": "Löscht nicht mehr benötigte Zwischenspeicherdateien.",
"TaskCleanCache": "Leere Zwischenspeicher",
- "TasksChannelsCategory": "Internet Kanäle",
+ "TasksChannelsCategory": "Internet-Kanäle",
"TasksApplicationCategory": "Anwendung",
"TasksLibraryCategory": "Bibliothek",
"TasksMaintenanceCategory": "Wartung",
diff --git a/Emby.Server.Implementations/Localization/Core/el.json b/Emby.Server.Implementations/Localization/Core/el.json
index 697063f26..9952c05ca 100644
--- a/Emby.Server.Implementations/Localization/Core/el.json
+++ b/Emby.Server.Implementations/Localization/Core/el.json
@@ -15,7 +15,7 @@
"Favorites": "Αγαπημένα",
"Folders": "Φάκελοι",
"Genres": "Είδη",
- "HeaderAlbumArtists": "Άλμπουμ Καλλιτέχνη",
+ "HeaderAlbumArtists": "Καλλιτέχνες άλμπουμ",
"HeaderContinueWatching": "Συνεχίστε την παρακολούθηση",
"HeaderFavoriteAlbums": "Αγαπημένα Άλμπουμ",
"HeaderFavoriteArtists": "Αγαπημένοι Καλλιτέχνες",
diff --git a/Emby.Server.Implementations/Localization/Core/gsw.json b/Emby.Server.Implementations/Localization/Core/gsw.json
index 3364ee333..5bfe8c0b1 100644
--- a/Emby.Server.Implementations/Localization/Core/gsw.json
+++ b/Emby.Server.Implementations/Localization/Core/gsw.json
@@ -118,5 +118,6 @@
"TaskCleanActivityLog": "Aktivitätsprotokoll aufräumen",
"Undefined": "Undefiniert",
"Forced": "Erzwungen",
- "Default": "Standard"
+ "Default": "Standard",
+ "TaskOptimizeDatabase": "Datenbank optimieren"
}
diff --git a/Emby.Server.Implementations/Localization/Core/he.json b/Emby.Server.Implementations/Localization/Core/he.json
index 981e8a06e..5e299ea0e 100644
--- a/Emby.Server.Implementations/Localization/Core/he.json
+++ b/Emby.Server.Implementations/Localization/Core/he.json
@@ -118,5 +118,6 @@
"TaskCleanActivityLog": "נקה רשומת פעילות",
"Undefined": "לא מוגדר",
"Forced": "כפוי",
- "Default": "ברירת מחדל"
+ "Default": "ברירת מחדל",
+ "TaskOptimizeDatabase": "מיטוב מסד נתונים"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ko.json b/Emby.Server.Implementations/Localization/Core/ko.json
index a37de0748..50d019f90 100644
--- a/Emby.Server.Implementations/Localization/Core/ko.json
+++ b/Emby.Server.Implementations/Localization/Core/ko.json
@@ -15,7 +15,7 @@
"Favorites": "즐겨찾기",
"Folders": "폴더",
"Genres": "장르",
- "HeaderAlbumArtists": "아티스트의 앨범",
+ "HeaderAlbumArtists": "앨범 음악가",
"HeaderContinueWatching": "계속 시청하기",
"HeaderFavoriteAlbums": "즐겨찾는 앨범",
"HeaderFavoriteArtists": "즐겨찾는 아티스트",
diff --git a/Emby.Server.Implementations/Localization/Core/lt-LT.json b/Emby.Server.Implementations/Localization/Core/lt-LT.json
index f0a07f604..881cd4a93 100644
--- a/Emby.Server.Implementations/Localization/Core/lt-LT.json
+++ b/Emby.Server.Implementations/Localization/Core/lt-LT.json
@@ -118,5 +118,6 @@
"Undefined": "Neapibrėžtas",
"Forced": "Priverstas",
"Default": "Numatytas",
- "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius."
+ "TaskCleanActivityLogDescription": "Ištrina veiklos žuranlo įrašus, kurie yra senesni nei nustatytas amžius.",
+ "TaskOptimizeDatabase": "Optimizuoti duomenų bazės"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ml.json b/Emby.Server.Implementations/Localization/Core/ml.json
index 09ef34913..acc7746c1 100644
--- a/Emby.Server.Implementations/Localization/Core/ml.json
+++ b/Emby.Server.Implementations/Localization/Core/ml.json
@@ -6,7 +6,7 @@
"ChapterNameValue": "അധ്യായം {0}",
"DeviceOfflineWithName": "{0} വിച്ഛേദിച്ചു",
"DeviceOnlineWithName": "{0} ബന്ധിപ്പിച്ചു",
- "FailedLoginAttemptWithUserName": "Log 0 from എന്നതിൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു",
+ "FailedLoginAttemptWithUserName": "{0} - എന്നതിൽ നിന്നുള്ള പ്രവേശന ശ്രമം പരാജയപ്പെട്ടു",
"Forced": "നിർബന്ധിച്ചു",
"HeaderFavoriteAlbums": "പ്രിയപ്പെട്ട ആൽബങ്ങൾ",
"HeaderFavoriteArtists": "പ്രിയപ്പെട്ട കലാകാരന്മാർ",
diff --git a/Emby.Server.Implementations/Localization/Core/my.json b/Emby.Server.Implementations/Localization/Core/my.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/Emby.Server.Implementations/Localization/Core/my.json
@@ -0,0 +1 @@
+{}
diff --git a/Emby.Server.Implementations/Localization/Core/ru.json b/Emby.Server.Implementations/Localization/Core/ru.json
index 2d7163275..dc3793f1b 100644
--- a/Emby.Server.Implementations/Localization/Core/ru.json
+++ b/Emby.Server.Implementations/Localization/Core/ru.json
@@ -96,7 +96,7 @@
"TaskRefreshChannels": "Обновление каналов",
"TaskCleanTranscode": "Очистка каталога перекодировки",
"TaskUpdatePlugins": "Обновление плагинов",
- "TaskRefreshPeople": "Подновить людей",
+ "TaskRefreshPeople": "Подновление людей",
"TaskCleanLogs": "Очистка каталога журналов",
"TaskRefreshLibrary": "Сканирование медиатеки",
"TaskRefreshChapterImages": "Извлечение изображений сцен",
@@ -115,10 +115,10 @@
"TaskRefreshChapterImagesDescription": "Создаются эскизы для видео, которые содержат сцены.",
"TaskCleanCacheDescription": "Удаляются файлы кэша, которые больше не нужны системе.",
"TaskCleanActivityLogDescription": "Удаляет записи журнала активности старше установленного возраста.",
- "TaskCleanActivityLog": "Очистить журнал активности",
+ "TaskCleanActivityLog": "Очистка журнала активности",
"Undefined": "Не определено",
"Forced": "Форсир-ые",
"Default": "По умолчанию",
"TaskOptimizeDatabaseDescription": "Сжимает базу данных и вырезает свободные места. Выполнение этой задачи после сканирования библиотеки или внесения других изменений, предполагающих модификации базы данных, может повысить производительность.",
- "TaskOptimizeDatabase": "Оптимизировать базу данных"
+ "TaskOptimizeDatabase": "Оптимизация базы данных"
}
diff --git a/Emby.Server.Implementations/Localization/Core/sv.json b/Emby.Server.Implementations/Localization/Core/sv.json
index f3f601661..5d05361b0 100644
--- a/Emby.Server.Implementations/Localization/Core/sv.json
+++ b/Emby.Server.Implementations/Localization/Core/sv.json
@@ -16,7 +16,7 @@
"Folders": "Mappar",
"Genres": "Genrer",
"HeaderAlbumArtists": "Albumsartister",
- "HeaderContinueWatching": "Fortsätt kolla",
+ "HeaderContinueWatching": "Fortsätt kolla på",
"HeaderFavoriteAlbums": "Favoritalbum",
"HeaderFavoriteArtists": "Favoritartister",
"HeaderFavoriteEpisodes": "Favoritavsnitt",
diff --git a/Emby.Server.Implementations/Localization/Core/th.json b/Emby.Server.Implementations/Localization/Core/th.json
index e26010423..89fbb84b6 100644
--- a/Emby.Server.Implementations/Localization/Core/th.json
+++ b/Emby.Server.Implementations/Localization/Core/th.json
@@ -117,5 +117,6 @@
"TaskCleanActivityLogDescription": "ลบบันทึกกิจกรรมที่เก่ากว่าค่าที่กำหนดไว้",
"TaskCleanActivityLog": "ล้างบันทึกกิจกรรม",
"Undefined": "ไม่ได้กำหนด",
- "Forced": "บังคับใช้"
+ "Forced": "บังคับใช้",
+ "TaskOptimizeDatabase": "ปรับฐานข้อมูลให้เหมาะสม"
}
diff --git a/Emby.Server.Implementations/Localization/Core/ur_PK.json b/Emby.Server.Implementations/Localization/Core/ur_PK.json
index 73c60e263..e7f3e492c 100644
--- a/Emby.Server.Implementations/Localization/Core/ur_PK.json
+++ b/Emby.Server.Implementations/Localization/Core/ur_PK.json
@@ -113,5 +113,7 @@
"CameraImageUploadedFrom": "ایک نئی کیمرہ تصویر اپ لوڈ کی گئی ہے {0}",
"Application": "پروگرام",
"AppDeviceValues": "پروگرام:{0}, ڈیوائس:{1}",
- "Forced": "جَبری"
+ "Forced": "جَبری",
+ "Undefined": "غير وضاحتى",
+ "Default": "طے شدہ"
}
diff --git a/Emby.Server.Implementations/Net/SocketFactory.cs b/Emby.Server.Implementations/Net/SocketFactory.cs
index 6d0c8731e..fd3fc31c9 100644
--- a/Emby.Server.Implementations/Net/SocketFactory.cs
+++ b/Emby.Server.Implementations/Net/SocketFactory.cs
@@ -19,7 +19,7 @@ namespace Emby.Server.Implementations.Net
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
}
- var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
retVal.EnableBroadcast = true;
@@ -44,7 +44,7 @@ namespace Emby.Server.Implementations.Net
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
}
- var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
retVal.EnableBroadcast = true;
@@ -85,7 +85,7 @@ namespace Emby.Server.Implementations.Net
throw new ArgumentException("localPort cannot be less than zero.", nameof(localPort));
}
- var retVal = new Socket(AddressFamily.InterNetwork, System.Net.Sockets.SocketType.Dgram, System.Net.Sockets.ProtocolType.Udp);
+ var retVal = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
try
{
diff --git a/Emby.Server.Implementations/Playlists/PlaylistManager.cs b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
index 9481e26f7..02df2fffe 100644
--- a/Emby.Server.Implementations/Playlists/PlaylistManager.cs
+++ b/Emby.Server.Implementations/Playlists/PlaylistManager.cs
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.Playlists
var parentFolder = GetPlaylistsFolder(Guid.Empty);
if (parentFolder == null)
{
- throw new ArgumentException();
+ throw new ArgumentException(nameof(parentFolder));
}
if (string.IsNullOrEmpty(options.MediaType))
diff --git a/Emby.Server.Implementations/Plugins/PluginManager.cs b/Emby.Server.Implementations/Plugins/PluginManager.cs
index d70a15dbc..a805924dd 100644
--- a/Emby.Server.Implementations/Plugins/PluginManager.cs
+++ b/Emby.Server.Implementations/Plugins/PluginManager.cs
@@ -360,11 +360,6 @@ namespace Emby.Server.Implementations.Plugins
/// <inheritdoc/>
public async Task<bool> GenerateManifest(PackageInfo packageInfo, Version version, string path, PluginStatus status)
{
- if (packageInfo == null)
- {
- return false;
- }
-
var versionInfo = packageInfo.Versions.First(v => v.Version == version.ToString());
var imagePath = string.Empty;
@@ -617,7 +612,7 @@ namespace Emby.Server.Implementations.Plugins
if (versionIndex != -1)
{
// Get the version number from the filename if possible.
- metafile = Path.GetFileName(dir[..versionIndex]) ?? dir[..versionIndex];
+ metafile = Path.GetFileName(dir[..versionIndex]);
version = Version.TryParse(dir.AsSpan()[(versionIndex + 1)..], out Version? parsedVersion) ? parsedVersion : _appVersion;
}
else
@@ -682,7 +677,6 @@ namespace Emby.Server.Implementations.Plugins
continue;
}
- var manifest = entry.Manifest;
var cleaned = false;
var path = entry.Path;
if (_config.RemoveOldPlugins)
@@ -707,12 +701,6 @@ namespace Emby.Server.Implementations.Plugins
}
else
{
- if (manifest == null)
- {
- _logger.LogWarning("Unable to disable plugin {Path}", entry.Path);
- continue;
- }
-
ChangePluginState(entry, PluginStatus.Deleted);
}
}
diff --git a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
index c81c26994..532c8d1e3 100644
--- a/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
+++ b/Emby.Server.Implementations/QuickConnect/QuickConnectManager.cs
@@ -30,8 +30,8 @@ namespace Emby.Server.Implementations.QuickConnect
/// </summary>
private const int Timeout = 10;
- private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new ();
- private readonly ConcurrentDictionary<string, (DateTime Timestamp, AuthenticationResult AuthenticationResult)> _authorizedSecrets = new ();
+ private readonly ConcurrentDictionary<string, QuickConnectResult> _currentRequests = new();
+ private readonly ConcurrentDictionary<string, (DateTime Timestamp, AuthenticationResult AuthenticationResult)> _authorizedSecrets = new();
private readonly IServerConfigurationManager _config;
private readonly ILogger<QuickConnectManager> _logger;
diff --git a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
index 21a7f4f5f..299f10544 100644
--- a/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/ScheduledTaskWorker.cs
@@ -9,6 +9,7 @@ using System.Linq;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
+using Emby.Server.Implementations.ScheduledTasks.Triggers;
using Jellyfin.Data.Events;
using Jellyfin.Extensions.Json;
using MediaBrowser.Common.Configuration;
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
index 8b185419f..a5786a3d7 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/ChapterImagesTask.cs
@@ -17,7 +17,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.IO;
using MediaBrowser.Model.Tasks;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
/// Class ChapterImagesTask.
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
index 53c692a46..34780111b 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PeopleValidationTask.cs
@@ -8,7 +8,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
/// Class PeopleValidationTask.
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
index 11a5fb79f..b3973cecb 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/PluginUpdateTask.cs
@@ -12,7 +12,7 @@ using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
/// Plugin Update Task.
diff --git a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
index 2184b3d03..f7b3cfedc 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Tasks/RefreshMediaLibraryTask.cs
@@ -9,7 +9,7 @@ using MediaBrowser.Controller.Library;
using MediaBrowser.Model.Globalization;
using MediaBrowser.Model.Tasks;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Tasks
{
/// <summary>
/// Class RefreshMediaLibraryTask.
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
index 29ab6a73d..dc5eb7391 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/DailyTrigger.cs
@@ -3,7 +3,7 @@ using System.Threading;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers
{
/// <summary>
/// Represents a task trigger that fires everyday.
@@ -41,7 +41,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="logger">The logger.</param>
/// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+ public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
@@ -54,7 +54,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
logger.LogInformation("Daily trigger for {Task} set to fire at {TriggerDate:yyyy-MM-dd HH:mm:ss.fff zzz}, which is {DueTime:c} from now.", taskName, triggerDate, dueTime);
- _timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
+ _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
index 30568e809..927f57e95 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/IntervalTrigger.cs
@@ -4,7 +4,7 @@ using System.Threading;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers
{
/// <summary>
/// Represents a task trigger that runs repeatedly on an interval.
@@ -43,7 +43,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="logger">The logger.</param>
/// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+ public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
@@ -72,7 +72,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
dueTime = maxDueTime;
}
- _timer = new Timer(state => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
+ _timer = new Timer(_ => OnTriggered(), null, dueTime, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
index 18b9a8b75..b16693c07 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/StartupTrigger.cs
@@ -5,7 +5,7 @@ using System.Threading.Tasks;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers
{
/// <summary>
/// Class StartupTaskTrigger.
@@ -40,7 +40,7 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="logger">The logger.</param>
/// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public async void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+ public async void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
if (isApplicationStartup)
{
diff --git a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
index 36ae190b0..2392b20fd 100644
--- a/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
+++ b/Emby.Server.Implementations/ScheduledTasks/Triggers/WeeklyTrigger.cs
@@ -3,7 +3,7 @@ using System.Threading;
using MediaBrowser.Model.Tasks;
using Microsoft.Extensions.Logging;
-namespace Emby.Server.Implementations.ScheduledTasks
+namespace Emby.Server.Implementations.ScheduledTasks.Triggers
{
/// <summary>
/// Represents a task trigger that fires on a weekly basis.
@@ -44,13 +44,13 @@ namespace Emby.Server.Implementations.ScheduledTasks
/// <param name="logger">The logger.</param>
/// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">if set to <c>true</c> [is application startup].</param>
- public void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup)
+ public void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup)
{
DisposeTimer();
var triggerDate = GetNextTriggerDateTime();
- _timer = new Timer(state => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1));
+ _timer = new Timer(_ => OnTriggered(), null, triggerDate - DateTime.Now, TimeSpan.FromMilliseconds(-1));
}
/// <summary>
diff --git a/Emby.Server.Implementations/Session/SessionManager.cs b/Emby.Server.Implementations/Session/SessionManager.cs
index d10a24bbc..6c679ea20 100644
--- a/Emby.Server.Implementations/Session/SessionManager.cs
+++ b/Emby.Server.Implementations/Session/SessionManager.cs
@@ -60,7 +60,7 @@ namespace Emby.Server.Implementations.Session
/// <summary>
/// The active connections.
/// </summary>
- private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new (StringComparer.OrdinalIgnoreCase);
+ private readonly ConcurrentDictionary<string, SessionInfo> _activeConnections = new(StringComparer.OrdinalIgnoreCase);
private Timer _idleTimer;
diff --git a/Emby.Server.Implementations/Sorting/AlbumComparer.cs b/Emby.Server.Implementations/Sorting/AlbumComparer.cs
index 4d09dda84..4bed0fca1 100644
--- a/Emby.Server.Implementations/Sorting/AlbumComparer.cs
+++ b/Emby.Server.Implementations/Sorting/AlbumComparer.cs
@@ -33,7 +33,7 @@ namespace Emby.Server.Implementations.Sorting
/// </summary>
/// <param name="x">The x.</param>
/// <returns>System.String.</returns>
- private static string? GetValue(BaseItem? x)
+ private static string GetValue(BaseItem? x)
{
return x is Audio audio ? audio.Album : string.Empty;
}
diff --git a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
index 2ebeea717..53e3b3577 100644
--- a/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
+++ b/Emby.Server.Implementations/SyncPlay/SyncPlayManager.cs
@@ -250,7 +250,6 @@ namespace Emby.Server.Implementations.SyncPlay
var error = new GroupUpdate<string>(Guid.Empty, GroupUpdateType.NotInGroup, string.Empty);
_sessionManager.SendSyncPlayGroupUpdate(session.Id, error, CancellationToken.None);
- return;
}
}
}
@@ -365,7 +364,7 @@ namespace Emby.Server.Implementations.SyncPlay
{
var session = e.SessionInfo;
- if (_sessionToGroupMap.TryGetValue(session.Id, out var group))
+ if (_sessionToGroupMap.TryGetValue(session.Id, out _))
{
var leaveGroupRequest = new LeaveGroupRequest();
LeaveGroup(session, leaveGroupRequest, CancellationToken.None);
@@ -378,7 +377,7 @@ namespace Emby.Server.Implementations.SyncPlay
var newSessionsCounter = _activeUsers.AddOrUpdate(
userId,
1,
- (key, sessionsCounter) => sessionsCounter + toAdd);
+ (_, sessionsCounter) => sessionsCounter + toAdd);
// Should never happen.
if (newSessionsCounter < 0)
diff --git a/Emby.Server.Implementations/TV/TVSeriesManager.cs b/Emby.Server.Implementations/TV/TVSeriesManager.cs
index a87831294..c994ffc90 100644
--- a/Emby.Server.Implementations/TV/TVSeriesManager.cs
+++ b/Emby.Server.Implementations/TV/TVSeriesManager.cs
@@ -117,12 +117,13 @@ namespace Emby.Server.Implementations.TV
new InternalItemsQuery(user)
{
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.DatePlayed, SortOrder.Descending) },
+ OrderBy = new[] { (ItemSortBy.DatePlayed, SortOrder.Descending) },
SeriesPresentationUniqueKey = presentationUniqueKey,
Limit = limit,
DtoOptions = new DtoOptions { Fields = new[] { ItemFields.SeriesPresentationUniqueKey }, EnableImages = false },
GroupBySeriesPresentationUniqueKey = true
- }, parentsFolders.ToList())
+ },
+ parentsFolders.ToList())
.Cast<Episode>()
.Where(episode => !string.IsNullOrEmpty(episode.SeriesPresentationUniqueKey))
.Select(GetUniqueSeriesKey);
@@ -192,7 +193,7 @@ namespace Emby.Server.Implementations.TV
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Descending) },
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Descending) },
IsPlayed = true,
Limit = 1,
ParentIndexNumberNotEquals = 0,
@@ -210,7 +211,7 @@ namespace Emby.Server.Implementations.TV
AncestorWithPresentationUniqueKey = null,
SeriesPresentationUniqueKey = seriesKey,
IncludeItemTypes = new[] { BaseItemKind.Episode },
- OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) },
+ OrderBy = new[] { (ItemSortBy.SortName, SortOrder.Ascending) },
Limit = 1,
IsPlayed = false,
IsVirtualItem = false,
diff --git a/Emby.Server.Implementations/Updates/InstallationManager.cs b/Emby.Server.Implementations/Updates/InstallationManager.cs
index 24d592525..5eb4c9ffa 100644
--- a/Emby.Server.Implementations/Updates/InstallationManager.cs
+++ b/Emby.Server.Implementations/Updates/InstallationManager.cs
@@ -52,7 +52,7 @@ namespace Emby.Server.Implementations.Updates
/// <summary>
/// The current installations.
/// </summary>
- private readonly List<(InstallationInfo info, CancellationTokenSource token)> _currentInstallations;
+ private readonly List<(InstallationInfo Info, CancellationTokenSource Token)> _currentInstallations;
/// <summary>
/// The completed installations.
@@ -399,13 +399,13 @@ namespace Emby.Server.Implementations.Updates
{
lock (_currentInstallationsLock)
{
- var install = _currentInstallations.Find(x => x.info.Id == id);
+ var install = _currentInstallations.Find(x => x.Info.Id == id);
if (install == default((InstallationInfo, CancellationTokenSource)))
{
return false;
}
- install.token.Cancel();
+ install.Token.Cancel();
_currentInstallations.Remove(install);
return true;
}
diff --git a/Jellyfin.Api/Controllers/DynamicHlsController.cs b/Jellyfin.Api/Controllers/DynamicHlsController.cs
index caa3d2368..6ef3a2ff9 100644
--- a/Jellyfin.Api/Controllers/DynamicHlsController.cs
+++ b/Jellyfin.Api/Controllers/DynamicHlsController.cs
@@ -39,7 +39,8 @@ namespace Jellyfin.Api.Controllers
[Authorize(Policy = Policies.DefaultAuthorization)]
public class DynamicHlsController : BaseJellyfinApiController
{
- private const string DefaultEncoderPreset = "veryfast";
+ private const string DefaultVodEncoderPreset = "veryfast";
+ private const string DefaultEventEncoderPreset = "superfast";
private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls;
private readonly ILibraryManager _libraryManager;
@@ -106,6 +107,253 @@ namespace Jellyfin.Api.Controllers
}
/// <summary>
+ /// Gets a hls live stream.
+ /// </summary>
+ /// <param name="itemId">The item id.</param>
+ /// <param name="container">The audio container.</param>
+ /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
+ /// <param name="params">The streaming parameters.</param>
+ /// <param name="tag">The tag.</param>
+ /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
+ /// <param name="playSessionId">The play session id.</param>
+ /// <param name="segmentContainer">The segment container.</param>
+ /// <param name="segmentLength">The segment lenght.</param>
+ /// <param name="minSegments">The minimum number of segments.</param>
+ /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
+ /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
+ /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
+ /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
+ /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
+ /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
+ /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
+ /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
+ /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
+ /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
+ /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
+ /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
+ /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
+ /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
+ /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
+ /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
+ /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
+ /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
+ /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
+ /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
+ /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
+ /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
+ /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
+ /// <param name="maxRefFrames">Optional.</param>
+ /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
+ /// <param name="requireAvc">Optional. Whether to require avc.</param>
+ /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
+ /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
+ /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
+ /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
+ /// <param name="liveStreamId">The live stream id.</param>
+ /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
+ /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
+ /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
+ /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
+ /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
+ /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
+ /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
+ /// <param name="streamOptions">Optional. The streaming options.</param>
+ /// <param name="maxWidth">Optional. The max width.</param>
+ /// <param name="maxHeight">Optional. The max height.</param>
+ /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
+ /// <response code="200">Hls live stream retrieved.</response>
+ /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
+ [HttpGet("Videos/{itemId}/live.m3u8")]
+ [ProducesResponseType(StatusCodes.Status200OK)]
+ [ProducesPlaylistFile]
+ public async Task<ActionResult> GetLiveHlsStream(
+ [FromRoute, Required] Guid itemId,
+ [FromQuery] string? container,
+ [FromQuery] bool? @static,
+ [FromQuery] string? @params,
+ [FromQuery] string? tag,
+ [FromQuery] string? deviceProfileId,
+ [FromQuery] string? playSessionId,
+ [FromQuery] string? segmentContainer,
+ [FromQuery] int? segmentLength,
+ [FromQuery] int? minSegments,
+ [FromQuery] string? mediaSourceId,
+ [FromQuery] string? deviceId,
+ [FromQuery] string? audioCodec,
+ [FromQuery] bool? enableAutoStreamCopy,
+ [FromQuery] bool? allowVideoStreamCopy,
+ [FromQuery] bool? allowAudioStreamCopy,
+ [FromQuery] bool? breakOnNonKeyFrames,
+ [FromQuery] int? audioSampleRate,
+ [FromQuery] int? maxAudioBitDepth,
+ [FromQuery] int? audioBitRate,
+ [FromQuery] int? audioChannels,
+ [FromQuery] int? maxAudioChannels,
+ [FromQuery] string? profile,
+ [FromQuery] string? level,
+ [FromQuery] float? framerate,
+ [FromQuery] float? maxFramerate,
+ [FromQuery] bool? copyTimestamps,
+ [FromQuery] long? startTimeTicks,
+ [FromQuery] int? width,
+ [FromQuery] int? height,
+ [FromQuery] int? videoBitRate,
+ [FromQuery] int? subtitleStreamIndex,
+ [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
+ [FromQuery] int? maxRefFrames,
+ [FromQuery] int? maxVideoBitDepth,
+ [FromQuery] bool? requireAvc,
+ [FromQuery] bool? deInterlace,
+ [FromQuery] bool? requireNonAnamorphic,
+ [FromQuery] int? transcodingMaxAudioChannels,
+ [FromQuery] int? cpuCoreLimit,
+ [FromQuery] string? liveStreamId,
+ [FromQuery] bool? enableMpegtsM2TsMode,
+ [FromQuery] string? videoCodec,
+ [FromQuery] string? subtitleCodec,
+ [FromQuery] string? transcodeReasons,
+ [FromQuery] int? audioStreamIndex,
+ [FromQuery] int? videoStreamIndex,
+ [FromQuery] EncodingContext? context,
+ [FromQuery] Dictionary<string, string> streamOptions,
+ [FromQuery] int? maxWidth,
+ [FromQuery] int? maxHeight,
+ [FromQuery] bool? enableSubtitlesInManifest)
+ {
+ VideoRequestDto streamingRequest = new VideoRequestDto
+ {
+ Id = itemId,
+ Container = container,
+ Static = @static ?? false,
+ Params = @params,
+ Tag = tag,
+ DeviceProfileId = deviceProfileId,
+ PlaySessionId = playSessionId,
+ SegmentContainer = segmentContainer,
+ SegmentLength = segmentLength,
+ MinSegments = minSegments,
+ MediaSourceId = mediaSourceId,
+ DeviceId = deviceId,
+ AudioCodec = audioCodec,
+ EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
+ AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
+ AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
+ BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
+ AudioSampleRate = audioSampleRate,
+ MaxAudioChannels = maxAudioChannels,
+ AudioBitRate = audioBitRate,
+ MaxAudioBitDepth = maxAudioBitDepth,
+ AudioChannels = audioChannels,
+ Profile = profile,
+ Level = level,
+ Framerate = framerate,
+ MaxFramerate = maxFramerate,
+ CopyTimestamps = copyTimestamps ?? false,
+ StartTimeTicks = startTimeTicks,
+ Width = width,
+ Height = height,
+ VideoBitRate = videoBitRate,
+ SubtitleStreamIndex = subtitleStreamIndex,
+ SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
+ MaxRefFrames = maxRefFrames,
+ MaxVideoBitDepth = maxVideoBitDepth,
+ RequireAvc = requireAvc ?? false,
+ DeInterlace = deInterlace ?? false,
+ RequireNonAnamorphic = requireNonAnamorphic ?? false,
+ TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
+ CpuCoreLimit = cpuCoreLimit,
+ LiveStreamId = liveStreamId,
+ EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
+ VideoCodec = videoCodec,
+ SubtitleCodec = subtitleCodec,
+ TranscodeReasons = transcodeReasons,
+ AudioStreamIndex = audioStreamIndex,
+ VideoStreamIndex = videoStreamIndex,
+ Context = context ?? EncodingContext.Streaming,
+ StreamOptions = streamOptions,
+ MaxHeight = maxHeight,
+ MaxWidth = maxWidth,
+ EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true
+ };
+
+ // CTS lifecycle is managed internally.
+ var cancellationTokenSource = new CancellationTokenSource();
+ // Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
+ // since it gets disposed when ffmpeg exits
+ var cancellationToken = cancellationTokenSource.Token;
+ using var state = await StreamingHelpers.GetStreamingState(
+ streamingRequest,
+ Request,
+ _authContext,
+ _mediaSourceManager,
+ _userManager,
+ _libraryManager,
+ _serverConfigurationManager,
+ _mediaEncoder,
+ _encodingHelper,
+ _dlnaManager,
+ _deviceManager,
+ _transcodingJobHelper,
+ TranscodingJobType,
+ cancellationToken)
+ .ConfigureAwait(false);
+
+ TranscodingJobDto? job = null;
+ var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
+
+ if (!System.IO.File.Exists(playlistPath))
+ {
+ var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
+ await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
+ try
+ {
+ if (!System.IO.File.Exists(playlistPath))
+ {
+ // If the playlist doesn't already exist, startup ffmpeg
+ try
+ {
+ job = await _transcodingJobHelper.StartFfMpeg(
+ state,
+ playlistPath,
+ GetCommandLineArguments(playlistPath, state, true, 0),
+ Request,
+ TranscodingJobType,
+ cancellationTokenSource)
+ .ConfigureAwait(false);
+ job.IsLiveOutput = true;
+ }
+ catch
+ {
+ state.Dispose();
+ throw;
+ }
+
+ minSegments = state.MinSegments;
+ if (minSegments > 0)
+ {
+ await HlsHelpers.WaitForMinimumSegmentCount(playlistPath, minSegments, _logger, cancellationToken).ConfigureAwait(false);
+ }
+ }
+ }
+ finally
+ {
+ transcodingLock.Release();
+ }
+ }
+
+ job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
+
+ if (job != null)
+ {
+ _transcodingJobHelper.OnTranscodeEndRequest(job);
+ }
+
+ var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state);
+
+ return Content(playlistText, MimeTypes.GetMimeType("playlist.m3u8"));
+ }
+
+ /// <summary>
/// Gets a video hls playlist stream.
/// </summary>
/// <param name="itemId">The item id.</param>
@@ -1149,7 +1397,7 @@ namespace Jellyfin.Api.Controllers
.AppendLine("#EXT-X-MEDIA-SEQUENCE:0");
var index = 0;
- var segmentExtension = GetSegmentFileExtension(streamingRequest.SegmentContainer);
+ var segmentExtension = EncodingHelper.GetSegmentFileExtension(streamingRequest.SegmentContainer);
var queryString = Request.QueryString;
if (isHlsInFmp4)
@@ -1214,7 +1462,7 @@ namespace Jellyfin.Api.Controllers
var segmentPath = GetSegmentPath(state, playlistPath, segmentId);
- var segmentExtension = GetSegmentFileExtension(state.Request.SegmentContainer);
+ var segmentExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
TranscodingJobDto? job;
@@ -1286,7 +1534,7 @@ namespace Jellyfin.Api.Controllers
job = await _transcodingJobHelper.StartFfMpeg(
state,
playlistPath,
- GetCommandLineArguments(playlistPath, state, true, segmentId),
+ GetCommandLineArguments(playlistPath, state, false, segmentId),
Request,
TranscodingJobType,
cancellationTokenSource).ConfigureAwait(false);
@@ -1346,7 +1594,7 @@ namespace Jellyfin.Api.Controllers
return segments;
}
- private string GetCommandLineArguments(string outputPath, StreamState state, bool isEncoding, int startNumber)
+ private string GetCommandLineArguments(string outputPath, StreamState state, bool isEventPlaylist, int startNumber)
{
var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec);
@@ -1361,15 +1609,13 @@ namespace Jellyfin.Api.Controllers
state.BaseRequest.BreakOnNonKeyFrames = false;
}
- // If isEncoding is true we're actually starting ffmpeg
- var startNumberParam = isEncoding ? startNumber.ToString(CultureInfo.InvariantCulture) : "0";
var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath);
var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension);
- var outputExtension = GetSegmentFileExtension(state.Request.SegmentContainer);
+ var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
var outputTsArg = outputPrefix + "%d" + outputExtension;
var segmentFormat = outputExtension.TrimStart('.');
@@ -1398,19 +1644,30 @@ namespace Jellyfin.Api.Controllers
? _encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
: "128";
+ var baseUrlParam = string.Empty;
+ if (isEventPlaylist)
+ {
+ baseUrlParam = string.Format(
+ CultureInfo.InvariantCulture,
+ " -hls_base_url \"hls/{0}/\"",
+ Path.GetFileNameWithoutExtension(outputPath));
+ }
+
return string.Format(
CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9} -hls_segment_filename \"{10}\" -hls_playlist_type vod -hls_list_size 0 -y \"{11}\"",
+ "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number {9}{10} -hls_segment_filename \"{12}\" -hls_playlist_type {11} -hls_list_size 0 -y \"{13}\"",
inputModifier,
_encodingHelper.GetInputArgument(state, _encodingOptions),
threads,
mapArgs,
- GetVideoArguments(state, startNumber),
+ GetVideoArguments(state, startNumber, isEventPlaylist),
GetAudioArguments(state),
maxMuxingQueueSize,
state.SegmentLength.ToString(CultureInfo.InvariantCulture),
segmentFormat,
- startNumberParam,
+ startNumber.ToString(CultureInfo.InvariantCulture),
+ baseUrlParam,
+ isEventPlaylist ? "event" : "vod",
outputTsArg,
outputPath).Trim();
}
@@ -1505,8 +1762,9 @@ namespace Jellyfin.Api.Controllers
/// </summary>
/// <param name="state">The <see cref="StreamState"/>.</param>
/// <param name="startNumber">The first number in the hls sequence.</param>
+ /// <param name="isEventPlaylist">Whether the playlist is EVENT or VOD.</param>
/// <returns>The command line arguments for video transcoding.</returns>
- private string GetVideoArguments(StreamState state, int startNumber)
+ private string GetVideoArguments(StreamState state, int startNumber, bool isEventPlaylist)
{
if (state.VideoStream == null)
{
@@ -1539,6 +1797,7 @@ namespace Jellyfin.Api.Controllers
// See if we can save come cpu cycles by avoiding encoding.
if (EncodingHelper.IsCopyCodec(codec))
{
+ // If h264_mp4toannexb is ever added, do not use it for live tv.
if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
{
string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
@@ -1549,15 +1808,13 @@ namespace Jellyfin.Api.Controllers
}
args += " -start_at_zero";
-
- // args += " -flags -global_header";
}
else
{
- args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, DefaultEncoderPreset);
+ args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, isEventPlaylist ? DefaultEventEncoderPreset : DefaultVodEncoderPreset);
// Set the key frame params for video encoding to match the hls segment time.
- args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, false, startNumber);
+ args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, isEventPlaylist, startNumber);
// Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase))
@@ -1567,27 +1824,25 @@ namespace Jellyfin.Api.Controllers
// args += " -mixed-refs 0 -refs 3 -x264opts b_pyramid=0:weightb=0:weightp=0";
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
- if (hasGraphicalSubs)
- {
- // Graphical subs overlay and resolution params.
- args += _encodingHelper.GetGraphicalSubtitleParam(state, _encodingOptions, codec);
- }
- else
- {
- // Resolution params.
- args += _encodingHelper.GetOutputSizeParam(state, _encodingOptions, codec);
- }
+ // video processing filters.
+ args += _encodingHelper.GetVideoProcessingFilterParam(state, _encodingOptions, codec);
// -start_at_zero is necessary to use with -ss when seeking,
// otherwise the target position cannot be determined.
- if (!(state.SubtitleStream != null && state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
+ if (state.SubtitleStream != null)
{
- args += " -start_at_zero";
+ // Disable start_at_zero for external graphical subs
+ if (!(state.SubtitleStream.IsExternal && !state.SubtitleStream.IsTextSubtitleStream))
+ {
+ args += " -start_at_zero";
+ }
}
+ }
- // args += " -flags -global_header";
+ // TODO why was this not enabled for VOD?
+ if (isEventPlaylist)
+ {
+ args += " -flags -global_header";
}
if (!string.IsNullOrEmpty(state.OutputVideoSync))
@@ -1600,22 +1855,12 @@ namespace Jellyfin.Api.Controllers
return args;
}
- private string GetSegmentFileExtension(string? segmentContainer)
- {
- if (!string.IsNullOrWhiteSpace(segmentContainer))
- {
- return "." + segmentContainer;
- }
-
- return ".ts";
- }
-
private string GetSegmentPath(StreamState state, string playlist, int index)
{
var folder = Path.GetDirectoryName(playlist) ?? throw new ArgumentException($"Provided path ({playlist}) is not valid.", nameof(playlist));
var filename = Path.GetFileNameWithoutExtension(playlist);
- return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + GetSegmentFileExtension(state.Request.SegmentContainer));
+ return Path.Combine(folder, filename + index.ToString(CultureInfo.InvariantCulture) + EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer));
}
private async Task<ActionResult> GetSegmentResult(
diff --git a/Jellyfin.Api/Controllers/FilterController.cs b/Jellyfin.Api/Controllers/FilterController.cs
index e170436d1..a4f12666d 100644
--- a/Jellyfin.Api/Controllers/FilterController.cs
+++ b/Jellyfin.Api/Controllers/FilterController.cs
@@ -197,16 +197,16 @@ namespace Jellyfin.Api.Controllers
{
filters.Genres = _libraryManager.GetMusicGenres(genreQuery).Items.Select(i => new NameGuidPair
{
- Name = i.Item1.Name,
- Id = i.Item1.Id
+ Name = i.Item.Name,
+ Id = i.Item.Id
}).ToArray();
}
else
{
filters.Genres = _libraryManager.GetGenres(genreQuery).Items.Select(i => new NameGuidPair
{
- Name = i.Item1.Name,
- Id = i.Item1.Id
+ Name = i.Item.Name,
+ Id = i.Item.Id
}).ToArray();
}
diff --git a/Jellyfin.Api/Controllers/ItemLookupController.cs b/Jellyfin.Api/Controllers/ItemLookupController.cs
index 8a6f9b8c7..4161e43f6 100644
--- a/Jellyfin.Api/Controllers/ItemLookupController.cs
+++ b/Jellyfin.Api/Controllers/ItemLookupController.cs
@@ -264,7 +264,8 @@ namespace Jellyfin.Api.Controllers
ReplaceAllMetadata = true,
ReplaceAllImages = replaceAllImages,
SearchResult = searchResult
- }, CancellationToken.None).ConfigureAwait(false);
+ },
+ CancellationToken.None).ConfigureAwait(false);
return NoContent();
}
diff --git a/Jellyfin.Api/Controllers/ItemsController.cs b/Jellyfin.Api/Controllers/ItemsController.cs
index 65c0662d2..1b938f4d5 100644
--- a/Jellyfin.Api/Controllers/ItemsController.cs
+++ b/Jellyfin.Api/Controllers/ItemsController.cs
@@ -484,7 +484,7 @@ namespace Jellyfin.Api.Controllers
// Albums by artist
if (query.ArtistIds.Length > 0 && query.IncludeItemTypes.Length == 1 && query.IncludeItemTypes[0] == BaseItemKind.MusicAlbum)
{
- query.OrderBy = new[] { new ValueTuple<string, SortOrder>(ItemSortBy.ProductionYear, SortOrder.Descending), new ValueTuple<string, SortOrder>(ItemSortBy.SortName, SortOrder.Ascending) };
+ query.OrderBy = new[] { (ItemSortBy.ProductionYear, SortOrder.Descending), (ItemSortBy.SortName, SortOrder.Ascending) };
}
}
diff --git a/Jellyfin.Api/Controllers/LiveTvController.cs b/Jellyfin.Api/Controllers/LiveTvController.cs
index b131530c9..9e2ef8c60 100644
--- a/Jellyfin.Api/Controllers/LiveTvController.cs
+++ b/Jellyfin.Api/Controllers/LiveTvController.cs
@@ -278,25 +278,26 @@ namespace Jellyfin.Api.Controllers
return _liveTvManager.GetRecordings(
new RecordingQuery
- {
- ChannelId = channelId,
- UserId = userId ?? Guid.Empty,
- StartIndex = startIndex,
- Limit = limit,
- Status = status,
- SeriesTimerId = seriesTimerId,
- IsInProgress = isInProgress,
- EnableTotalRecordCount = enableTotalRecordCount,
- IsMovie = isMovie,
- IsNews = isNews,
- IsSeries = isSeries,
- IsKids = isKids,
- IsSports = isSports,
- IsLibraryItem = isLibraryItem,
- Fields = fields,
- ImageTypeLimit = imageTypeLimit,
- EnableImages = enableImages
- }, dtoOptions);
+ {
+ ChannelId = channelId,
+ UserId = userId ?? Guid.Empty,
+ StartIndex = startIndex,
+ Limit = limit,
+ Status = status,
+ SeriesTimerId = seriesTimerId,
+ IsInProgress = isInProgress,
+ EnableTotalRecordCount = enableTotalRecordCount,
+ IsMovie = isMovie,
+ IsNews = isNews,
+ IsSeries = isSeries,
+ IsKids = isKids,
+ IsSports = isSports,
+ IsLibraryItem = isLibraryItem,
+ Fields = fields,
+ ImageTypeLimit = imageTypeLimit,
+ EnableImages = enableImages
+ },
+ dtoOptions);
}
/// <summary>
@@ -489,14 +490,14 @@ namespace Jellyfin.Api.Controllers
[FromQuery] bool? isScheduled)
{
return await _liveTvManager.GetTimers(
- new TimerQuery
- {
- ChannelId = channelId,
- SeriesTimerId = seriesTimerId,
- IsActive = isActive,
- IsScheduled = isScheduled
- }, CancellationToken.None)
- .ConfigureAwait(false);
+ new TimerQuery
+ {
+ ChannelId = channelId,
+ SeriesTimerId = seriesTimerId,
+ IsActive = isActive,
+ IsScheduled = isScheduled
+ },
+ CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
@@ -867,7 +868,8 @@ namespace Jellyfin.Api.Controllers
{
SortOrder = sortOrder ?? SortOrder.Ascending,
SortBy = sortBy
- }, CancellationToken.None).ConfigureAwait(false);
+ },
+ CancellationToken.None).ConfigureAwait(false);
}
/// <summary>
diff --git a/Jellyfin.Api/Controllers/RemoteImageController.cs b/Jellyfin.Api/Controllers/RemoteImageController.cs
index 9f57a5cdb..dbee56e14 100644
--- a/Jellyfin.Api/Controllers/RemoteImageController.cs
+++ b/Jellyfin.Api/Controllers/RemoteImageController.cs
@@ -80,7 +80,8 @@ namespace Jellyfin.Api.Controllers
IncludeAllLanguages = includeAllLanguages,
IncludeDisabledProviders = true,
ImageType = type
- }, CancellationToken.None)
+ },
+ CancellationToken.None)
.ConfigureAwait(false);
var imageArray = images.ToArray();
diff --git a/Jellyfin.Api/Controllers/UserLibraryController.cs b/Jellyfin.Api/Controllers/UserLibraryController.cs
index f6fbdc302..8b99170d9 100644
--- a/Jellyfin.Api/Controllers/UserLibraryController.cs
+++ b/Jellyfin.Api/Controllers/UserLibraryController.cs
@@ -301,7 +301,8 @@ namespace Jellyfin.Api.Controllers
Limit = limit,
ParentId = parentId ?? Guid.Empty,
UserId = userId,
- }, dtoOptions);
+ },
+ dtoOptions);
var dtos = list.Select(i =>
{
diff --git a/Jellyfin.Api/Controllers/VideoHlsController.cs b/Jellyfin.Api/Controllers/VideoHlsController.cs
deleted file mode 100644
index 36643e464..000000000
--- a/Jellyfin.Api/Controllers/VideoHlsController.cs
+++ /dev/null
@@ -1,586 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Globalization;
-using System.IO;
-using System.Threading;
-using System.Threading.Tasks;
-using Jellyfin.Api.Attributes;
-using Jellyfin.Api.Constants;
-using Jellyfin.Api.Helpers;
-using Jellyfin.Api.Models.PlaybackDtos;
-using Jellyfin.Api.Models.StreamingDtos;
-using MediaBrowser.Common.Configuration;
-using MediaBrowser.Controller.Configuration;
-using MediaBrowser.Controller.Devices;
-using MediaBrowser.Controller.Dlna;
-using MediaBrowser.Controller.Library;
-using MediaBrowser.Controller.MediaEncoding;
-using MediaBrowser.Controller.Net;
-using MediaBrowser.Model.Configuration;
-using MediaBrowser.Model.Dlna;
-using MediaBrowser.Model.Net;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Http;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.Extensions.Logging;
-
-namespace Jellyfin.Api.Controllers
-{
- /// <summary>
- /// The video hls controller.
- /// </summary>
- [Route("")]
- [Authorize(Policy = Policies.DefaultAuthorization)]
- public class VideoHlsController : BaseJellyfinApiController
- {
- private const string DefaultEncoderPreset = "superfast";
- private const TranscodingJobType TranscodingJobType = MediaBrowser.Controller.MediaEncoding.TranscodingJobType.Hls;
-
- private readonly EncodingHelper _encodingHelper;
- private readonly IDlnaManager _dlnaManager;
- private readonly IAuthorizationContext _authContext;
- private readonly IUserManager _userManager;
- private readonly ILibraryManager _libraryManager;
- private readonly IMediaSourceManager _mediaSourceManager;
- private readonly IServerConfigurationManager _serverConfigurationManager;
- private readonly IMediaEncoder _mediaEncoder;
- private readonly IDeviceManager _deviceManager;
- private readonly TranscodingJobHelper _transcodingJobHelper;
- private readonly ILogger<VideoHlsController> _logger;
- private readonly EncodingOptions _encodingOptions;
-
- /// <summary>
- /// Initializes a new instance of the <see cref="VideoHlsController"/> class.
- /// </summary>
- /// <param name="mediaEncoder">Instance of the <see cref="IMediaEncoder"/> interface.</param>
- /// <param name="dlnaManager">Instance of the <see cref="IDlnaManager"/> interface.</param>
- /// <param name="userManger">Instance of the <see cref="IUserManager"/> interface.</param>
- /// <param name="authorizationContext">Instance of the <see cref="IAuthorizationContext"/> interface.</param>
- /// <param name="libraryManager">Instance of the <see cref="ILibraryManager"/> interface.</param>
- /// <param name="mediaSourceManager">Instance of the <see cref="IMediaSourceManager"/> interface.</param>
- /// <param name="serverConfigurationManager">Instance of the <see cref="IServerConfigurationManager"/> interface.</param>
- /// <param name="deviceManager">Instance of the <see cref="IDeviceManager"/> interface.</param>
- /// <param name="transcodingJobHelper">The <see cref="TranscodingJobHelper"/> singleton.</param>
- /// <param name="logger">Instance of the <see cref="ILogger{VideoHlsController}"/>.</param>
- /// <param name="encodingHelper">Instance of <see cref="EncodingHelper"/>.</param>
- public VideoHlsController(
- IMediaEncoder mediaEncoder,
- IDlnaManager dlnaManager,
- IUserManager userManger,
- IAuthorizationContext authorizationContext,
- ILibraryManager libraryManager,
- IMediaSourceManager mediaSourceManager,
- IServerConfigurationManager serverConfigurationManager,
- IDeviceManager deviceManager,
- TranscodingJobHelper transcodingJobHelper,
- ILogger<VideoHlsController> logger,
- EncodingHelper encodingHelper)
- {
- _dlnaManager = dlnaManager;
- _authContext = authorizationContext;
- _userManager = userManger;
- _libraryManager = libraryManager;
- _mediaSourceManager = mediaSourceManager;
- _serverConfigurationManager = serverConfigurationManager;
- _mediaEncoder = mediaEncoder;
- _deviceManager = deviceManager;
- _transcodingJobHelper = transcodingJobHelper;
- _logger = logger;
- _encodingHelper = encodingHelper;
-
- _encodingOptions = serverConfigurationManager.GetEncodingOptions();
- }
-
- /// <summary>
- /// Gets a hls live stream.
- /// </summary>
- /// <param name="itemId">The item id.</param>
- /// <param name="container">The audio container.</param>
- /// <param name="static">Optional. If true, the original file will be streamed statically without any encoding. Use either no url extension or the original file extension. true/false.</param>
- /// <param name="params">The streaming parameters.</param>
- /// <param name="tag">The tag.</param>
- /// <param name="deviceProfileId">Optional. The dlna device profile id to utilize.</param>
- /// <param name="playSessionId">The play session id.</param>
- /// <param name="segmentContainer">The segment container.</param>
- /// <param name="segmentLength">The segment lenght.</param>
- /// <param name="minSegments">The minimum number of segments.</param>
- /// <param name="mediaSourceId">The media version id, if playing an alternate version.</param>
- /// <param name="deviceId">The device id of the client requesting. Used to stop encoding processes when needed.</param>
- /// <param name="audioCodec">Optional. Specify a audio codec to encode to, e.g. mp3. If omitted the server will auto-select using the url's extension. Options: aac, mp3, vorbis, wma.</param>
- /// <param name="enableAutoStreamCopy">Whether or not to allow automatic stream copy if requested values match the original source. Defaults to true.</param>
- /// <param name="allowVideoStreamCopy">Whether or not to allow copying of the video stream url.</param>
- /// <param name="allowAudioStreamCopy">Whether or not to allow copying of the audio stream url.</param>
- /// <param name="breakOnNonKeyFrames">Optional. Whether to break on non key frames.</param>
- /// <param name="audioSampleRate">Optional. Specify a specific audio sample rate, e.g. 44100.</param>
- /// <param name="maxAudioBitDepth">Optional. The maximum audio bit depth.</param>
- /// <param name="audioBitRate">Optional. Specify an audio bitrate to encode to, e.g. 128000. If omitted this will be left to encoder defaults.</param>
- /// <param name="audioChannels">Optional. Specify a specific number of audio channels to encode to, e.g. 2.</param>
- /// <param name="maxAudioChannels">Optional. Specify a maximum number of audio channels to encode to, e.g. 2.</param>
- /// <param name="profile">Optional. Specify a specific an encoder profile (varies by encoder), e.g. main, baseline, high.</param>
- /// <param name="level">Optional. Specify a level for the encoder profile (varies by encoder), e.g. 3, 3.1.</param>
- /// <param name="framerate">Optional. A specific video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
- /// <param name="maxFramerate">Optional. A specific maximum video framerate to encode to, e.g. 23.976. Generally this should be omitted unless the device has specific requirements.</param>
- /// <param name="copyTimestamps">Whether or not to copy timestamps when transcoding with an offset. Defaults to false.</param>
- /// <param name="startTimeTicks">Optional. Specify a starting offset, in ticks. 1 tick = 10000 ms.</param>
- /// <param name="width">Optional. The fixed horizontal resolution of the encoded video.</param>
- /// <param name="height">Optional. The fixed vertical resolution of the encoded video.</param>
- /// <param name="videoBitRate">Optional. Specify a video bitrate to encode to, e.g. 500000. If omitted this will be left to encoder defaults.</param>
- /// <param name="subtitleStreamIndex">Optional. The index of the subtitle stream to use. If omitted no subtitles will be used.</param>
- /// <param name="subtitleMethod">Optional. Specify the subtitle delivery method.</param>
- /// <param name="maxRefFrames">Optional.</param>
- /// <param name="maxVideoBitDepth">Optional. The maximum video bit depth.</param>
- /// <param name="requireAvc">Optional. Whether to require avc.</param>
- /// <param name="deInterlace">Optional. Whether to deinterlace the video.</param>
- /// <param name="requireNonAnamorphic">Optional. Whether to require a non anamorphic stream.</param>
- /// <param name="transcodingMaxAudioChannels">Optional. The maximum number of audio channels to transcode.</param>
- /// <param name="cpuCoreLimit">Optional. The limit of how many cpu cores to use.</param>
- /// <param name="liveStreamId">The live stream id.</param>
- /// <param name="enableMpegtsM2TsMode">Optional. Whether to enable the MpegtsM2Ts mode.</param>
- /// <param name="videoCodec">Optional. Specify a video codec to encode to, e.g. h264. If omitted the server will auto-select using the url's extension. Options: h265, h264, mpeg4, theora, vp8, vp9, vpx (deprecated), wmv.</param>
- /// <param name="subtitleCodec">Optional. Specify a subtitle codec to encode to.</param>
- /// <param name="transcodeReasons">Optional. The transcoding reason.</param>
- /// <param name="audioStreamIndex">Optional. The index of the audio stream to use. If omitted the first audio stream will be used.</param>
- /// <param name="videoStreamIndex">Optional. The index of the video stream to use. If omitted the first video stream will be used.</param>
- /// <param name="context">Optional. The <see cref="EncodingContext"/>.</param>
- /// <param name="streamOptions">Optional. The streaming options.</param>
- /// <param name="maxWidth">Optional. The max width.</param>
- /// <param name="maxHeight">Optional. The max height.</param>
- /// <param name="enableSubtitlesInManifest">Optional. Whether to enable subtitles in the manifest.</param>
- /// <response code="200">Hls live stream retrieved.</response>
- /// <returns>A <see cref="FileResult"/> containing the hls file.</returns>
- [HttpGet("Videos/{itemId}/live.m3u8")]
- [ProducesResponseType(StatusCodes.Status200OK)]
- [ProducesPlaylistFile]
- public async Task<ActionResult> GetLiveHlsStream(
- [FromRoute, Required] Guid itemId,
- [FromQuery] string? container,
- [FromQuery] bool? @static,
- [FromQuery] string? @params,
- [FromQuery] string? tag,
- [FromQuery] string? deviceProfileId,
- [FromQuery] string? playSessionId,
- [FromQuery] string? segmentContainer,
- [FromQuery] int? segmentLength,
- [FromQuery] int? minSegments,
- [FromQuery] string? mediaSourceId,
- [FromQuery] string? deviceId,
- [FromQuery] string? audioCodec,
- [FromQuery] bool? enableAutoStreamCopy,
- [FromQuery] bool? allowVideoStreamCopy,
- [FromQuery] bool? allowAudioStreamCopy,
- [FromQuery] bool? breakOnNonKeyFrames,
- [FromQuery] int? audioSampleRate,
- [FromQuery] int? maxAudioBitDepth,
- [FromQuery] int? audioBitRate,
- [FromQuery] int? audioChannels,
- [FromQuery] int? maxAudioChannels,
- [FromQuery] string? profile,
- [FromQuery] string? level,
- [FromQuery] float? framerate,
- [FromQuery] float? maxFramerate,
- [FromQuery] bool? copyTimestamps,
- [FromQuery] long? startTimeTicks,
- [FromQuery] int? width,
- [FromQuery] int? height,
- [FromQuery] int? videoBitRate,
- [FromQuery] int? subtitleStreamIndex,
- [FromQuery] SubtitleDeliveryMethod? subtitleMethod,
- [FromQuery] int? maxRefFrames,
- [FromQuery] int? maxVideoBitDepth,
- [FromQuery] bool? requireAvc,
- [FromQuery] bool? deInterlace,
- [FromQuery] bool? requireNonAnamorphic,
- [FromQuery] int? transcodingMaxAudioChannels,
- [FromQuery] int? cpuCoreLimit,
- [FromQuery] string? liveStreamId,
- [FromQuery] bool? enableMpegtsM2TsMode,
- [FromQuery] string? videoCodec,
- [FromQuery] string? subtitleCodec,
- [FromQuery] string? transcodeReasons,
- [FromQuery] int? audioStreamIndex,
- [FromQuery] int? videoStreamIndex,
- [FromQuery] EncodingContext? context,
- [FromQuery] Dictionary<string, string> streamOptions,
- [FromQuery] int? maxWidth,
- [FromQuery] int? maxHeight,
- [FromQuery] bool? enableSubtitlesInManifest)
- {
- VideoRequestDto streamingRequest = new VideoRequestDto
- {
- Id = itemId,
- Container = container,
- Static = @static ?? false,
- Params = @params,
- Tag = tag,
- DeviceProfileId = deviceProfileId,
- PlaySessionId = playSessionId,
- SegmentContainer = segmentContainer,
- SegmentLength = segmentLength,
- MinSegments = minSegments,
- MediaSourceId = mediaSourceId,
- DeviceId = deviceId,
- AudioCodec = audioCodec,
- EnableAutoStreamCopy = enableAutoStreamCopy ?? true,
- AllowAudioStreamCopy = allowAudioStreamCopy ?? true,
- AllowVideoStreamCopy = allowVideoStreamCopy ?? true,
- BreakOnNonKeyFrames = breakOnNonKeyFrames ?? false,
- AudioSampleRate = audioSampleRate,
- MaxAudioChannels = maxAudioChannels,
- AudioBitRate = audioBitRate,
- MaxAudioBitDepth = maxAudioBitDepth,
- AudioChannels = audioChannels,
- Profile = profile,
- Level = level,
- Framerate = framerate,
- MaxFramerate = maxFramerate,
- CopyTimestamps = copyTimestamps ?? false,
- StartTimeTicks = startTimeTicks,
- Width = width,
- Height = height,
- VideoBitRate = videoBitRate,
- SubtitleStreamIndex = subtitleStreamIndex,
- SubtitleMethod = subtitleMethod ?? SubtitleDeliveryMethod.Encode,
- MaxRefFrames = maxRefFrames,
- MaxVideoBitDepth = maxVideoBitDepth,
- RequireAvc = requireAvc ?? false,
- DeInterlace = deInterlace ?? false,
- RequireNonAnamorphic = requireNonAnamorphic ?? false,
- TranscodingMaxAudioChannels = transcodingMaxAudioChannels,
- CpuCoreLimit = cpuCoreLimit,
- LiveStreamId = liveStreamId,
- EnableMpegtsM2TsMode = enableMpegtsM2TsMode ?? false,
- VideoCodec = videoCodec,
- SubtitleCodec = subtitleCodec,
- TranscodeReasons = transcodeReasons,
- AudioStreamIndex = audioStreamIndex,
- VideoStreamIndex = videoStreamIndex,
- Context = context ?? EncodingContext.Streaming,
- StreamOptions = streamOptions,
- MaxHeight = maxHeight,
- MaxWidth = maxWidth,
- EnableSubtitlesInManifest = enableSubtitlesInManifest ?? true
- };
-
- // CTS lifecycle is managed internally.
- var cancellationTokenSource = new CancellationTokenSource();
- // Due to CTS.Token calling ThrowIfDisposed (https://github.com/dotnet/runtime/issues/29970) we have to "cache" the token
- // since it gets disposed when ffmpeg exits
- var cancellationToken = cancellationTokenSource.Token;
- using var state = await StreamingHelpers.GetStreamingState(
- streamingRequest,
- Request,
- _authContext,
- _mediaSourceManager,
- _userManager,
- _libraryManager,
- _serverConfigurationManager,
- _mediaEncoder,
- _encodingHelper,
- _dlnaManager,
- _deviceManager,
- _transcodingJobHelper,
- TranscodingJobType,
- cancellationToken)
- .ConfigureAwait(false);
-
- TranscodingJobDto? job = null;
- var playlistPath = Path.ChangeExtension(state.OutputFilePath, ".m3u8");
-
- if (!System.IO.File.Exists(playlistPath))
- {
- var transcodingLock = _transcodingJobHelper.GetTranscodingLock(playlistPath);
- await transcodingLock.WaitAsync(cancellationToken).ConfigureAwait(false);
- try
- {
- if (!System.IO.File.Exists(playlistPath))
- {
- // If the playlist doesn't already exist, startup ffmpeg
- try
- {
- job = await _transcodingJobHelper.StartFfMpeg(
- state,
- playlistPath,
- GetCommandLineArguments(playlistPath, state),
- Request,
- TranscodingJobType,
- cancellationTokenSource)
- .ConfigureAwait(false);
- job.IsLiveOutput = true;
- }
- catch
- {
- state.Dispose();
- throw;
- }
-
- minSegments = state.MinSegments;
- if (minSegments > 0)
- {
- await HlsHelpers.WaitForMinimumSegmentCount(playlistPath, minSegments, _logger, cancellationToken).ConfigureAwait(false);
- }
- }
- }
- finally
- {
- transcodingLock.Release();
- }
- }
-
- job ??= _transcodingJobHelper.OnTranscodeBeginRequest(playlistPath, TranscodingJobType);
-
- if (job != null)
- {
- _transcodingJobHelper.OnTranscodeEndRequest(job);
- }
-
- var playlistText = HlsHelpers.GetLivePlaylistText(playlistPath, state);
-
- return Content(playlistText, MimeTypes.GetMimeType("playlist.m3u8"));
- }
-
- /// <summary>
- /// Gets the command line arguments for ffmpeg.
- /// </summary>
- /// <param name="outputPath">The output path of the file.</param>
- /// <param name="state">The <see cref="StreamState"/>.</param>
- /// <returns>The command line arguments as a string.</returns>
- private string GetCommandLineArguments(string outputPath, StreamState state)
- {
- var videoCodec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
- var threads = EncodingHelper.GetNumberOfThreads(state, _encodingOptions, videoCodec); // GetNumberOfThreads is static.
- var inputModifier = _encodingHelper.GetInputModifier(state, _encodingOptions);
- var mapArgs = state.IsOutputVideo ? _encodingHelper.GetMapArgs(state) : string.Empty;
-
- var directory = Path.GetDirectoryName(outputPath) ?? throw new ArgumentException($"Provided path ({outputPath}) is not valid.", nameof(outputPath));
- var outputFileNameWithoutExtension = Path.GetFileNameWithoutExtension(outputPath);
- var outputPrefix = Path.Combine(directory, outputFileNameWithoutExtension);
- var outputExtension = EncodingHelper.GetSegmentFileExtension(state.Request.SegmentContainer);
- var outputTsArg = outputPrefix + "%d" + outputExtension;
-
- var segmentFormat = outputExtension.TrimStart('.');
- if (string.Equals(segmentFormat, "ts", StringComparison.OrdinalIgnoreCase))
- {
- segmentFormat = "mpegts";
- }
- else if (string.Equals(segmentFormat, "mp4", StringComparison.OrdinalIgnoreCase))
- {
- string outputFmp4HeaderArg;
- if (OperatingSystem.IsWindows())
- {
- // on Windows, the path of fmp4 header file needs to be configured
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputPrefix + "-1" + outputExtension + "\"";
- }
- else
- {
- // on Linux/Unix, ffmpeg generate fmp4 header file to m3u8 output folder
- outputFmp4HeaderArg = " -hls_fmp4_init_filename \"" + outputFileNameWithoutExtension + "-1" + outputExtension + "\"";
- }
-
- segmentFormat = "fmp4" + outputFmp4HeaderArg;
- }
- else
- {
- _logger.LogError("Invalid HLS segment container: {SegmentFormat}", segmentFormat);
- }
-
- var maxMuxingQueueSize = _encodingOptions.MaxMuxingQueueSize > 128
- ? _encodingOptions.MaxMuxingQueueSize.ToString(CultureInfo.InvariantCulture)
- : "128";
-
- var baseUrlParam = string.Format(
- CultureInfo.InvariantCulture,
- "\"hls/{0}/\"",
- Path.GetFileNameWithoutExtension(outputPath));
-
- return string.Format(
- CultureInfo.InvariantCulture,
- "{0} {1} -map_metadata -1 -map_chapters -1 -threads {2} {3} {4} {5} -copyts -avoid_negative_ts disabled -max_muxing_queue_size {6} -f hls -max_delay 5000000 -hls_time {7} -hls_segment_type {8} -start_number 0 -hls_base_url {9} -hls_playlist_type event -hls_segment_filename \"{10}\" -y \"{11}\"",
- inputModifier,
- _encodingHelper.GetInputArgument(state, _encodingOptions),
- threads,
- mapArgs,
- GetVideoArguments(state),
- GetAudioArguments(state),
- maxMuxingQueueSize,
- state.SegmentLength.ToString(CultureInfo.InvariantCulture),
- segmentFormat,
- baseUrlParam,
- outputTsArg,
- outputPath).Trim();
- }
-
- /// <summary>
- /// Gets the audio arguments for transcoding.
- /// </summary>
- /// <param name="state">The <see cref="StreamState"/>.</param>
- /// <returns>The command line arguments for audio transcoding.</returns>
- private string GetAudioArguments(StreamState state)
- {
- if (state.AudioStream == null)
- {
- return string.Empty;
- }
-
- var audioCodec = _encodingHelper.GetAudioEncoder(state);
-
- if (!state.IsOutputVideo)
- {
- if (EncodingHelper.IsCopyCodec(audioCodec))
- {
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
-
- return "-acodec copy -strict -2" + bitStreamArgs;
- }
-
- var audioTranscodeParams = string.Empty;
-
- audioTranscodeParams += "-acodec " + audioCodec;
-
- if (state.OutputAudioBitrate.HasValue)
- {
- audioTranscodeParams += " -ab " + state.OutputAudioBitrate.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- if (state.OutputAudioChannels.HasValue)
- {
- audioTranscodeParams += " -ac " + state.OutputAudioChannels.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- audioTranscodeParams += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- audioTranscodeParams += " -vn";
- return audioTranscodeParams;
- }
-
- if (EncodingHelper.IsCopyCodec(audioCodec))
- {
- var bitStreamArgs = EncodingHelper.GetAudioBitStreamArguments(state, state.Request.SegmentContainer, state.MediaSource.Container);
-
- return "-acodec copy -strict -2" + bitStreamArgs;
- }
-
- var args = "-codec:a:0 " + audioCodec;
-
- var channels = state.OutputAudioChannels;
-
- if (channels.HasValue)
- {
- args += " -ac " + channels.Value;
- }
-
- var bitrate = state.OutputAudioBitrate;
-
- if (bitrate.HasValue)
- {
- args += " -ab " + bitrate.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- if (state.OutputAudioSampleRate.HasValue)
- {
- args += " -ar " + state.OutputAudioSampleRate.Value.ToString(CultureInfo.InvariantCulture);
- }
-
- args += _encodingHelper.GetAudioFilterParam(state, _encodingOptions);
-
- return args;
- }
-
- /// <summary>
- /// Gets the video arguments for transcoding.
- /// </summary>
- /// <param name="state">The <see cref="StreamState"/>.</param>
- /// <returns>The command line arguments for video transcoding.</returns>
- private string GetVideoArguments(StreamState state)
- {
- if (state.VideoStream == null)
- {
- return string.Empty;
- }
-
- if (!state.IsOutputVideo)
- {
- return string.Empty;
- }
-
- var codec = _encodingHelper.GetVideoEncoder(state, _encodingOptions);
-
- var args = "-codec:v:0 " + codec;
-
- // Prefer hvc1 to hev1.
- if (string.Equals(state.ActualOutputVideoCodec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(state.ActualOutputVideoCodec, "hevc", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "h265", StringComparison.OrdinalIgnoreCase)
- || string.Equals(codec, "hevc", StringComparison.OrdinalIgnoreCase))
- {
- args += " -tag:v:0 hvc1";
- }
-
- // if (state.EnableMpegtsM2TsMode)
- // {
- // args += " -mpegts_m2ts_mode 1";
- // }
-
- // See if we can save come cpu cycles by avoiding encoding.
- if (EncodingHelper.IsCopyCodec(codec))
- {
- // If h264_mp4toannexb is ever added, do not use it for live tv.
- if (state.VideoStream != null && !string.Equals(state.VideoStream.NalLengthSize, "0", StringComparison.OrdinalIgnoreCase))
- {
- string bitStreamArgs = EncodingHelper.GetBitStreamArgs(state.VideoStream);
- if (!string.IsNullOrEmpty(bitStreamArgs))
- {
- args += " " + bitStreamArgs;
- }
- }
-
- args += " -start_at_zero";
- }
- else
- {
- args += _encodingHelper.GetVideoQualityParam(state, codec, _encodingOptions, DefaultEncoderPreset);
-
- // Set the key frame params for video encoding to match the hls segment time.
- args += _encodingHelper.GetHlsVideoKeyFrameArguments(state, codec, state.SegmentLength, true, null);
-
- // Currenly b-frames in libx265 breaks the FMP4-HLS playback on iOS, disable it for now.
- if (string.Equals(codec, "libx265", StringComparison.OrdinalIgnoreCase))
- {
- args += " -bf 0";
- }
-
- var hasGraphicalSubs = state.SubtitleStream != null && !state.SubtitleStream.IsTextSubtitleStream && state.SubtitleDeliveryMethod == SubtitleDeliveryMethod.Encode;
-
- if (hasGraphicalSubs)
- {
- // Graphical subs overlay and resolution params.
- args += _encodingHelper.GetGraphicalSubtitleParam(state, _encodingOptions, codec);
- }
- else
- {
- // Resolution params.
- args += _encodingHelper.GetOutputSizeParam(state, _encodingOptions, codec);
- }
-
- if (state.SubtitleStream == null || !state.SubtitleStream.IsExternal || state.SubtitleStream.IsTextSubtitleStream)
- {
- args += " -start_at_zero";
- }
- }
-
- args += " -flags -global_header";
-
- if (!string.IsNullOrEmpty(state.OutputVideoSync))
- {
- args += " -vsync " + state.OutputVideoSync;
- }
-
- args += _encodingHelper.GetOutputFFlags(state);
-
- return args;
- }
- }
-}
diff --git a/Jellyfin.Api/Helpers/RequestHelpers.cs b/Jellyfin.Api/Helpers/RequestHelpers.cs
index ca8bc0bdd..1471f5a24 100644
--- a/Jellyfin.Api/Helpers/RequestHelpers.cs
+++ b/Jellyfin.Api/Helpers/RequestHelpers.cs
@@ -30,7 +30,7 @@ namespace Jellyfin.Api.Helpers
{
if (sortBy.Count == 0)
{
- return Array.Empty<ValueTuple<string, SortOrder>>();
+ return Array.Empty<(string, SortOrder)>();
}
var result = new (string, SortOrder)[sortBy.Count];
diff --git a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
index 9d80070eb..3526d56c6 100644
--- a/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
+++ b/Jellyfin.Api/Helpers/TranscodingJobHelper.cs
@@ -218,7 +218,8 @@ namespace Jellyfin.Api.Helpers
return KillTranscodingJobs(
j => string.IsNullOrWhiteSpace(playSessionId)
? string.Equals(deviceId, j.DeviceId, StringComparison.OrdinalIgnoreCase)
- : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase), deleteFiles);
+ : string.Equals(playSessionId, j.PlaySessionId, StringComparison.OrdinalIgnoreCase),
+ deleteFiles);
}
/// <summary>
diff --git a/Jellyfin.Api/Jellyfin.Api.csproj b/Jellyfin.Api/Jellyfin.Api.csproj
index ccd647ebe..b5af07408 100644
--- a/Jellyfin.Api/Jellyfin.Api.csproj
+++ b/Jellyfin.Api/Jellyfin.Api.csproj
@@ -12,6 +12,10 @@
<NoWarn>AD0001</NoWarn>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authorization" Version="6.0.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="6.0.0" />
@@ -27,7 +31,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/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs b/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs
index bc12ad05d..2ccfd0c06 100644
--- a/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs
+++ b/Jellyfin.Api/ModelBinders/NullableEnumModelBinderProvider.cs
@@ -24,4 +24,4 @@ namespace Jellyfin.Api.ModelBinders
return new NullableEnumModelBinder(logger);
}
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs b/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs
index f65988259..8b26ec317 100644
--- a/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs
+++ b/Jellyfin.Api/Models/LibraryStructureDto/MediaPathDto.cs
@@ -24,4 +24,4 @@ namespace Jellyfin.Api.Models.LibraryStructureDto
/// </summary>
public MediaPathInfo? PathInfo { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs
index 2ddaa89e8..e7501bd9f 100644
--- a/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs
+++ b/Jellyfin.Api/Models/LiveTvDtos/SetChannelMappingDto.cs
@@ -25,4 +25,4 @@ namespace Jellyfin.Api.Models.LiveTvDtos
[Required]
public string ProviderChannelId { get; set; } = string.Empty;
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs b/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs
index 2cfdba507..c6bd5e56e 100644
--- a/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs
+++ b/Jellyfin.Api/Models/MediaInfoDtos/PlaybackInfoDto.cs
@@ -83,4 +83,4 @@ namespace Jellyfin.Api.Models.MediaInfoDtos
/// </summary>
public bool? AutoOpenLiveStream { get; set; }
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs
index 30473255e..be0595798 100644
--- a/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs
+++ b/Jellyfin.Api/Models/SubtitleDtos/UploadSubtitleDto.cs
@@ -31,4 +31,4 @@ namespace Jellyfin.Api.Models.SubtitleDtos
[Required]
public string Data { get; set; } = string.Empty;
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Data/Enums/BaseItemKind.cs b/Jellyfin.Data/Enums/BaseItemKind.cs
index 875781746..6fac6c487 100644
--- a/Jellyfin.Data/Enums/BaseItemKind.cs
+++ b/Jellyfin.Data/Enums/BaseItemKind.cs
@@ -134,7 +134,7 @@
PlaylistsFolder,
/// <summary>
- /// Item is program
+ /// Item is program.
/// </summary>
Program,
diff --git a/Jellyfin.Data/Enums/UnratedItem.cs b/Jellyfin.Data/Enums/UnratedItem.cs
index 871794086..21ec65af5 100644
--- a/Jellyfin.Data/Enums/UnratedItem.cs
+++ b/Jellyfin.Data/Enums/UnratedItem.cs
@@ -31,7 +31,7 @@ namespace Jellyfin.Data.Enums
Book = 4,
/// <summary>
- /// A live TV channel
+ /// A live TV channel.
/// </summary>
LiveTvChannel = 5,
diff --git a/Jellyfin.Data/Jellyfin.Data.csproj b/Jellyfin.Data/Jellyfin.Data.csproj
index 87233d907..f2779d8f2 100644
--- a/Jellyfin.Data/Jellyfin.Data.csproj
+++ b/Jellyfin.Data/Jellyfin.Data.csproj
@@ -30,7 +30,7 @@
<!-- Code analysers-->
<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/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
index 5fa386eca..4cc215903 100644
--- a/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
+++ b/Jellyfin.Drawing.Skia/Jellyfin.Drawing.Skia.csproj
@@ -31,7 +31,7 @@
<!-- Code analysers-->
<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/Jellyfin.Networking/Jellyfin.Networking.csproj b/Jellyfin.Networking/Jellyfin.Networking.csproj
index 0cd9a5915..a6af8566c 100644
--- a/Jellyfin.Networking/Jellyfin.Networking.csproj
+++ b/Jellyfin.Networking/Jellyfin.Networking.csproj
@@ -12,7 +12,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/Jellyfin.Server.Implementations/Devices/DeviceManager.cs b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
index a55949df8..6c77421c7 100644
--- a/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
+++ b/Jellyfin.Server.Implementations/Devices/DeviceManager.cs
@@ -24,7 +24,7 @@ namespace Jellyfin.Server.Implementations.Devices
{
private readonly JellyfinDbProvider _dbProvider;
private readonly IUserManager _userManager;
- private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new ();
+ private readonly ConcurrentDictionary<string, ClientCapabilities> _capabilitiesMap = new();
/// <summary>
/// Initializes a new instance of the <see cref="DeviceManager"/> class.
@@ -173,8 +173,8 @@ namespace Jellyfin.Server.Implementations.Devices
var sessions = dbContext.Devices
.Include(d => d.User)
.AsQueryable()
- .OrderBy(d => d.DeviceId)
- .ThenByDescending(d => d.DateLastActivity)
+ .OrderByDescending(d => d.DateLastActivity)
+ .ThenBy(d => d.DeviceId)
.AsAsyncEnumerable();
if (supportsSync.HasValue)
diff --git a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
index d22757c03..86aec1399 100644
--- a/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
+++ b/Jellyfin.Server.Implementations/Jellyfin.Server.Implementations.csproj
@@ -6,10 +6,14 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<!-- Code analysers-->
<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/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs b/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs
index 80ad65a42..e73a90cff 100644
--- a/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs
+++ b/Jellyfin.Server.Implementations/ModelBuilderExtensions.cs
@@ -45,4 +45,4 @@ namespace Jellyfin.Server.Implementations
modelBuilder.UseValueConverterForType<DateTime?>(new DateTimeKindValueConverter(kind));
}
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server.Implementations/Users/UserManager.cs b/Jellyfin.Server.Implementations/Users/UserManager.cs
index 3d0a51ff6..c41b343c7 100644
--- a/Jellyfin.Server.Implementations/Users/UserManager.cs
+++ b/Jellyfin.Server.Implementations/Users/UserManager.cs
@@ -394,12 +394,12 @@ namespace Jellyfin.Server.Implementations.Users
var user = Users.FirstOrDefault(i => string.Equals(username, i.Username, StringComparison.OrdinalIgnoreCase));
var authResult = await AuthenticateLocalUser(username, password, user, remoteEndPoint)
.ConfigureAwait(false);
- var authenticationProvider = authResult.authenticationProvider;
- var success = authResult.success;
+ var authenticationProvider = authResult.AuthenticationProvider;
+ var success = authResult.Success;
if (user == null)
{
- string updatedUsername = authResult.username;
+ string updatedUsername = authResult.Username;
if (success
&& authenticationProvider != null
@@ -785,7 +785,7 @@ namespace Jellyfin.Server.Implementations.Users
return providers;
}
- private async Task<(IAuthenticationProvider? authenticationProvider, string username, bool success)> AuthenticateLocalUser(
+ private async Task<(IAuthenticationProvider? AuthenticationProvider, string Username, bool Success)> AuthenticateLocalUser(
string username,
string password,
User? user,
@@ -798,8 +798,8 @@ namespace Jellyfin.Server.Implementations.Users
{
var providerAuthResult =
await AuthenticateWithProvider(provider, username, password, user).ConfigureAwait(false);
- var updatedUsername = providerAuthResult.username;
- success = providerAuthResult.success;
+ var updatedUsername = providerAuthResult.Username;
+ success = providerAuthResult.Success;
if (success)
{
@@ -822,7 +822,7 @@ namespace Jellyfin.Server.Implementations.Users
return (authenticationProvider, username, success);
}
- private async Task<(string username, bool success)> AuthenticateWithProvider(
+ private async Task<(string Username, bool Success)> AuthenticateWithProvider(
IAuthenticationProvider provider,
string username,
string password,
diff --git a/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
index 802662ce2..077908895 100644
--- a/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
+++ b/Jellyfin.Server/Filters/SecurityRequirementsOperationFilter.cs
@@ -75,4 +75,4 @@ namespace Jellyfin.Server.Filters
}
}
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server/Jellyfin.Server.csproj b/Jellyfin.Server/Jellyfin.Server.csproj
index 1638310fd..12efa15dd 100644
--- a/Jellyfin.Server/Jellyfin.Server.csproj
+++ b/Jellyfin.Server/Jellyfin.Server.csproj
@@ -14,6 +14,10 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
@@ -25,7 +29,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>
@@ -43,7 +47,7 @@
<PackageReference Include="Serilog.Sinks.Async" Version="1.5.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.0.1" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
- <PackageReference Include="Serilog.Sinks.Graylog" Version="2.2.2" />
+ <PackageReference Include="Serilog.Sinks.Graylog" Version="2.3.0" />
<PackageReference Include="SQLitePCLRaw.bundle_e_sqlite3" Version="2.0.7" />
</ItemGroup>
diff --git a/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs b/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs
index fdd8974d2..b214299df 100644
--- a/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs
+++ b/Jellyfin.Server/Middleware/LegacyEmbyRouteRewriteMiddleware.cs
@@ -51,4 +51,4 @@ namespace Jellyfin.Server.Middleware
await _next(httpContext).ConfigureAwait(false);
}
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs b/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs
index 9d40d74fe..fabcd2da7 100644
--- a/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs
+++ b/Jellyfin.Server/Middleware/RobotsRedirectionMiddleware.cs
@@ -44,4 +44,4 @@ namespace Jellyfin.Server.Middleware
await _next(httpContext).ConfigureAwait(false);
}
}
-} \ No newline at end of file
+}
diff --git a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
index a951f751e..5e601ca84 100644
--- a/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
+++ b/Jellyfin.Server/Migrations/PreStartupRoutines/CreateNetworkConfiguration.cs
@@ -53,7 +53,7 @@ public class CreateNetworkConfiguration : IMigrationRoutine
networkConfigSerializer.Serialize(xmlWriter, networkSettings);
}
-#pragma warning disable CS1591
+#pragma warning disable
public sealed class OldNetworkConfiguration
{
public const int DefaultHttpPort = 8096;
@@ -134,5 +134,5 @@ public class CreateNetworkConfiguration : IMigrationRoutine
public string[] KnownProxies { get; set; } = Array.Empty<string>();
}
-#pragma warning restore CS1591
+#pragma warning restore
}
diff --git a/MediaBrowser.Common/MediaBrowser.Common.csproj b/MediaBrowser.Common/MediaBrowser.Common.csproj
index 4ed44baef..2a2fffce0 100644
--- a/MediaBrowser.Common/MediaBrowser.Common.csproj
+++ b/MediaBrowser.Common/MediaBrowser.Common.csproj
@@ -38,6 +38,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
@@ -46,7 +50,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.Common/Net/IPNetAddress.cs b/MediaBrowser.Common/Net/IPNetAddress.cs
index f6e3971bf..f1428d4be 100644
--- a/MediaBrowser.Common/Net/IPNetAddress.cs
+++ b/MediaBrowser.Common/Net/IPNetAddress.cs
@@ -195,7 +195,7 @@ namespace MediaBrowser.Common.Net
return NetworkAddress.PrefixLength <= netaddrObj.PrefixLength;
}
- var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).address;
+ var altAddress = NetworkAddressOf(netaddrObj.Address, PrefixLength).Address;
return NetworkAddress.Address.Equals(altAddress);
}
diff --git a/MediaBrowser.Common/Net/IPObject.cs b/MediaBrowser.Common/Net/IPObject.cs
index 2612268fd..bd5368882 100644
--- a/MediaBrowser.Common/Net/IPObject.cs
+++ b/MediaBrowser.Common/Net/IPObject.cs
@@ -53,7 +53,7 @@ namespace MediaBrowser.Common.Net
/// <param name="address">IP Address to convert.</param>
/// <param name="prefixLength">Subnet prefix.</param>
/// <returns>IPAddress.</returns>
- public static (IPAddress address, byte prefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
+ public static (IPAddress Address, byte PrefixLength) NetworkAddressOf(IPAddress address, byte prefixLength)
{
if (address == null)
{
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/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/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 9d0187c8c..29f7bf92b 100644
--- a/MediaBrowser.Controller/Entities/Audio/Audio.cs
+++ b/MediaBrowser.Controller/Entities/Audio/Audio.cs
@@ -135,7 +135,7 @@ namespace MediaBrowser.Controller.Entities.Audio
return info;
}
- protected override IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources()
+ protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
=> new[] { ((BaseItem)this, MediaSourceType.Default) };
}
}
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs
index 95d49508f..915971adc 100644
--- a/MediaBrowser.Controller/Entities/BaseItem.cs
+++ b/MediaBrowser.Controller/Entities/BaseItem.cs
@@ -1069,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())
{
@@ -1097,7 +1097,7 @@ namespace MediaBrowser.Controller.Entities
.ToList();
}
- protected virtual IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources()
+ protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
{
return Enumerable.Empty<(BaseItem, MediaSourceType)>();
}
@@ -2592,9 +2592,9 @@ namespace MediaBrowser.Controller.Entities
.Select(i => i.OfficialRating)
.Where(i => !string.IsNullOrEmpty(i))
.Distinct(StringComparer.OrdinalIgnoreCase)
- .Select(i => (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;
diff --git a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
index 33870e2fb..e0583e630 100644
--- a/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
+++ b/MediaBrowser.Controller/Entities/BaseItemExtensions.cs
@@ -47,11 +47,12 @@ namespace MediaBrowser.Controller.Entities
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/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/InternalItemsQuery.cs b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
index f06b5c787..db1697c79 100644
--- a/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
+++ b/MediaBrowser.Controller/Entities/InternalItemsQuery.cs
@@ -38,7 +38,7 @@ namespace MediaBrowser.Controller.Entities
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>();
@@ -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; }
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/Video.cs b/MediaBrowser.Controller/Entities/Video.cs
index 4f7614f96..3e125602a 100644
--- a/MediaBrowser.Controller/Entities/Video.cs
+++ b/MediaBrowser.Controller/Entities/Video.cs
@@ -517,7 +517,7 @@ namespace MediaBrowser.Controller.Entities
}).FirstOrDefault();
}
- protected override IEnumerable<(BaseItem, MediaSourceType)> GetAllItemsForMediaSources()
+ protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources()
{
var list = new List<(BaseItem, MediaSourceType)>
{
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs
index eba92695e..8db528330 100644
--- a/MediaBrowser.Controller/Library/ILibraryManager.cs
+++ b/MediaBrowser.Controller/Library/ILibraryManager.cs
@@ -212,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.
@@ -573,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);
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/MediaBrowser.Controller.csproj b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
index cf3b7bc7a..432159d5d 100644
--- a/MediaBrowser.Controller/MediaBrowser.Controller.csproj
+++ b/MediaBrowser.Controller/MediaBrowser.Controller.csproj
@@ -13,6 +13,10 @@
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<ItemGroup>
<PackageReference Include="Diacritics" Version="3.3.10" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="6.0.0" />
@@ -49,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 92b345f12..d6f69a150 100644
--- a/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
+++ b/MediaBrowser.Controller/MediaEncoding/EncodingHelper.cs
@@ -21,10 +21,17 @@ namespace MediaBrowser.Controller.MediaEncoding
{
public class EncodingHelper
{
+ 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",
@@ -32,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,
@@ -62,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)
@@ -91,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;
}
@@ -103,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>
@@ -463,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)
@@ -526,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));
}
- arg.Append("-i ")
+ 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 ")
.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;
@@ -693,7 +868,19 @@ 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)
@@ -814,6 +1001,26 @@ 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}");
}
@@ -850,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);
@@ -860,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;
@@ -881,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))
@@ -901,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);
}
@@ -912,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(CultureInfo.InvariantCulture),
+ alphaParam,
+ sub2videoParam,
// fallbackFontParam,
setPtsParam);
}
@@ -999,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;
@@ -1021,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)
@@ -1179,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";
@@ -1364,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)
@@ -1387,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))
{
@@ -1407,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))
@@ -1496,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)
{
@@ -1708,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;
}
@@ -1945,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>
@@ -2080,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;
+ int inputWidth = Convert.ToInt32(videoWidth ?? requestedWidth, CultureInfo.InvariantCulture);
+ int inputHeight = Convert.ToInt32(videoHeight ?? requestedHeight, CultureInfo.InvariantCulture);
+ int outputWidth = requestedWidth ?? inputWidth;
+ int outputHeight = requestedHeight ?? inputHeight;
- var subtitleStreamIndex = state.SubtitleStream.IsExternal
- ? 0
- : state.SubtitleStream.Index;
+ // 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);
- // 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\"";
-
- // 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,
@@ -2261,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,
@@ -2313,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(CultureInfo.InvariantCulture);
- var heightParam = requestedHeight.Value.ToString(CultureInfo.InvariantCulture);
-
- 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(CultureInfo.InvariantCulture);
- var maxHeightParam = requestedMaxHeight.Value.ToString(CultureInfo.InvariantCulture);
-
- 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(CultureInfo.InvariantCulture);
+ 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(CultureInfo.InvariantCulture);
+ // 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(CultureInfo.InvariantCulture);
+ // 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(CultureInfo.InvariantCulture);
+ // 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);
@@ -2634,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);
- var parameters = "tonemap_opencl=format=nv12:primaries=bt709:transfer=bt709:matrix=bt709:tonemap={0}:desat={1}:threshold={2}:peak={3}";
+ // sw => hw
+ if (doCuTonemap)
+ {
+ mainFilters.Add("hwupload");
+ }
+ }
- if (options.TonemappingParam != 0)
+ 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);
+ }
+ }
+
+ // OUTPUT cuda(yuv420p) surface(vram)
+
+ /* 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);
+ }
+
+ /// <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");
+ }
- if ((isDeinterlaceH264 || isDeinterlaceHevc) && isNvdecDecoder)
+ /* 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");
+ }
- // convert cuda device data to p010 host data.
- if (isNvdecDecoder)
+ // 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");
+ }
+
+ /* 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)
{
- filters.Add("format=nv12|vaapi");
- filters.Add("hwupload");
+ // 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)
+ {
+ // 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");
}
+ }
- // Assert 8-bit hardware VAAPI decodable
- else if (!isColorDepth10)
+ 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;
+
+ // 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)
{
- if (isVaapiEncoder
- || (isTonemappingSupportedOnQsv && isVppTonemappingSupported))
+ mainFilters.Add("hwmap");
+ mainFilters.Add("format=vaapi");
+ }
+ else if (memoryOutput && isVaapiEncoder)
+ {
+ 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");
+ }
+ }
+ }
+
+ 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)
+ {
+ // 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)
+ {
+ // -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);
}
}
- // Add software deinterlace filter before scaling filter.
- if ((isDeinterlaceH264 || isDeinterlaceHevc)
- && !isVaapiH264Encoder
- && !isVaapiHevcEncoder
- && !isQsvH264Encoder
- && !isQsvHevcEncoder
- && !isNvdecDecoder
- && !isCuvidH264Decoder)
+ 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 (string.Equals(options.DeinterlaceMethod, "bwdif", StringComparison.OrdinalIgnoreCase))
+ if (videoStream.BitDepth.HasValue)
{
- filters.Add(
- string.Format(
- CultureInfo.InvariantCulture,
- "bwdif={0}:-1:0",
- doubleRateDeinterlace ? "1" : "0"));
+ 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);
+ }
+
+ if (string.Equals(videoStream.Codec, "vp8", StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "qsv", "vp8", bitDepth);
+ }
- // Test passed on Intel and AMD gfx
- filters.Add("hwmap=mode=read+write");
- filters.Add("format=nv12");
+ 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))
{
- var subParam = GetTextSubtitleParam(state);
+ 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);
+ }
- filters.Add(subParam);
+ if (string.Equals("mpeg2video", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg2video", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg2", "cuvid", "mpeg2video", bitDepth);
+ }
- // 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("vc1", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- filters.Add("hwmap");
+ return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface) + GetHwDecoderName(options, "vc1", "cuvid", "vc1", bitDepth);
}
- if (isTonemappingSupportedOnQsv && isVppTonemappingSupported)
+ if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- filters.Add("hwmap,format=vaapi");
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface) + GetHwDecoderName(options, "mpeg4", "cuvid", "mpeg4", bitDepth);
}
- if (isNvdecDecoder && isNvencEncoder)
+ if (string.Equals("vp8", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
{
- isHwuploadCudaRequired = true;
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface) + GetHwDecoderName(options, "vp8", "cuvid", "vp8", bitDepth);
}
}
- // Interop the VAAPI data to QSV for hybrid tonemapping
- if (isTonemappingSupportedOnQsv && isVppTonemappingSupported && !hasGraphicalSubs)
+ if (is8_10bitSwFormatsNvdec)
{
- 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) + 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);
+ }
}
- if (isHwuploadCudaRequired && !hasGraphicalSubs)
+ return null;
+ }
+
+ public string GetAmfVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ if (!OperatingSystem.IsWindows()
+ || !string.Equals(options.HardwareAccelerationType, "amf", 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 = _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);
+
+ if (is8bitSwFormatsAmf)
{
- 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))
{
- filters.Add(outputSdrParams);
+ return GetHwaccelType(state, options, "vc1", bitDepth, hwSurface);
+ }
+
+ if (string.Equals("mpeg4", videoStream.Codec, StringComparison.OrdinalIgnoreCase))
+ {
+ return GetHwaccelType(state, options, "mpeg4", bitDepth, hwSurface);
}
}
- var output = string.Empty;
- if (filters.Count > 0)
+ if (is8_10bitSwFormatsAmf)
{
- 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 GetVaapiVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
- if (string.Equals(colorTransfer, "arib-std-b67", StringComparison.OrdinalIgnoreCase))
+ if (!OperatingSystem.IsLinux()
+ || !string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
{
- // HLG
- return "setparams=color_primaries=bt2020:color_trc=arib-std-b67:colorspace=bt2020nc";
+ return null;
}
- else
+
+ 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)
+ {
+ 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))
+ {
+ return GetHwaccelType(state, options, "vp8", bitDepth, hwSurface);
+ }
+ }
+
+ if (is8_10bitSwFormatsVaapi)
{
- // 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, 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 null;
}
- public static string GetOutputSdrParams(string tonemappingRange)
+ public string GetVideotoolboxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
{
- // SDR
- if (string.Equals(tonemappingRange, "tv", StringComparison.OrdinalIgnoreCase))
+ if (!OperatingSystem.IsMacOS()
+ || !string.Equals(options.HardwareAccelerationType, "videotoolbox", StringComparison.OrdinalIgnoreCase))
{
- return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709:range=tv";
+ return null;
}
- if (string.Equals(tonemappingRange, "pc", StringComparison.OrdinalIgnoreCase))
+ var is8bitSwFormatsVt = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+ var is8_10bitSwFormatsVt = is8bitSwFormatsVt || string.Equals("yuv420p10le", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+
+ if (is8bitSwFormatsVt)
{
- 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 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);
+ }
}
- return "setparams=color_primaries=bt709:color_trc=bt709:colorspace=bt709";
+ if (is8_10bitSwFormatsVt)
+ {
+ 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 string GetOmxVidDecoder(EncodingJobInfo state, EncodingOptions options, MediaStream videoStream, int bitDepth)
+ {
+ if (!OperatingSystem.IsLinux()
+ || !string.Equals(options.HardwareAccelerationType, "omx", StringComparison.OrdinalIgnoreCase))
+ {
+ return null;
+ }
+
+ var is8bitSwFormatsOmx = string.Equals("yuv420p", videoStream.PixelFormat, StringComparison.OrdinalIgnoreCase);
+
+ if (is8bitSwFormatsOmx)
+ {
+ 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 null;
}
/// <summary>
@@ -3302,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)
@@ -3356,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);
@@ -3623,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)
@@ -4056,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;
- }
+ // video processing filters.
+ var videoProcessParam = GetVideoProcessingFilterParam(state, encodingOptions, videoCodec);
- // This is for graphical subs
- if (hasGraphicalSubs)
- {
- var graphicalSubtitleParam = GetGraphicalSubtitleParam(state, encodingOptions, videoCodec);
+ args += videoProcessParam;
- args += graphicalSubtitleParam;
-
- hasCopyTs = graphicalSubtitleParam.IndexOf("copyts", StringComparison.OrdinalIgnoreCase) != -1;
- }
+ hasCopyTs = videoProcessParam.Contains("copyts", StringComparison.OrdinalIgnoreCase);
if (state.RunTimeTicks.HasValue && state.BaseRequest.CopyTimestamps)
{
@@ -4199,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 1418e583e..fd3eb8105 100644
--- a/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
+++ b/MediaBrowser.Controller/MediaEncoding/IMediaEncoder.cs
@@ -26,6 +26,30 @@ namespace MediaBrowser.Controller.MediaEncoding
string EncoderPath { 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>
@@ -61,12 +85,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>
diff --git a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
index 933f440ac..8b2837ee3 100644
--- a/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
+++ b/MediaBrowser.Controller/MediaEncoding/JobLogger.cs
@@ -41,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)
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/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/Providers/DirectoryService.cs b/MediaBrowser.Controller/Providers/DirectoryService.cs
index e6d975ffe..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)
{
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.LocalMetadata/MediaBrowser.LocalMetadata.csproj b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
index a3db717b9..41c79651d 100644
--- a/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
+++ b/MediaBrowser.LocalMetadata/MediaBrowser.LocalMetadata.csproj
@@ -23,7 +23,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.MediaEncoding/Attachments/AttachmentExtractor.cs b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
index 9ebc0d0cf..3fd4cd731 100644
--- a/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
+++ b/MediaBrowser.MediaEncoding/Attachments/AttachmentExtractor.cs
@@ -50,7 +50,7 @@ namespace MediaBrowser.MediaEncoding.Attachments
}
/// <inheritdoc />
- public async Task<(MediaAttachment attachment, Stream stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
+ public async Task<(MediaAttachment Attachment, Stream Stream)> GetAttachment(BaseItem item, string mediaSourceId, int attachmentStreamIndex, CancellationToken cancellationToken)
{
if (item == null)
{
diff --git a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
index 60a2d39e5..fe3069934 100644
--- a/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/EncoderValidator.cs
@@ -16,6 +16,12 @@ namespace MediaBrowser.MediaEncoding.Encoder
{
"h264",
"hevc",
+ "vp8",
+ "libvpx",
+ "vp9",
+ "libvpx-vp9",
+ "av1",
+ "libdav1d",
"mpeg2video",
"mpeg4",
"msmpeg4",
@@ -30,6 +36,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
"vc1_qsv",
"vp8_qsv",
"vp9_qsv",
+ "av1_qsv",
"h264_cuvid",
"hevc_cuvid",
"mpeg2_cuvid",
@@ -37,16 +44,11 @@ namespace MediaBrowser.MediaEncoding.Encoder
"mpeg4_cuvid",
"vp8_cuvid",
"vp9_cuvid",
+ "av1_cuvid",
"h264_mmal",
"mpeg2_mmal",
"mpeg4_mmal",
"vc1_mmal",
- "h264_mediacodec",
- "hevc_mediacodec",
- "mpeg2_mediacodec",
- "mpeg4_mediacodec",
- "vp8_mediacodec",
- "vp9_mediacodec",
"h264_opencl",
"hevc_opencl",
"mpeg2_opencl",
@@ -89,20 +91,39 @@ namespace MediaBrowser.MediaEncoding.Encoder
private static readonly string[] _requiredFilters = new[]
{
+ // sw
+ "alphasrc",
+ "zscale",
+ // qsv
+ "scale_qsv",
+ "vpp_qsv",
+ "deinterlace_qsv",
+ "overlay_qsv",
+ // cuda
"scale_cuda",
"yadif_cuda",
- "hwupload_cuda",
- "overlay_cuda",
"tonemap_cuda",
+ "overlay_cuda",
+ "hwupload_cuda",
+ // opencl
+ "scale_opencl",
"tonemap_opencl",
+ "overlay_opencl",
+ // vaapi
+ "scale_vaapi",
+ "deinterlace_vaapi",
"tonemap_vaapi",
+ "overlay_vaapi",
+ "hwupload_vaapi"
};
private static readonly IReadOnlyDictionary<int, string[]> _filterOptionsDict = new Dictionary<int, string[]>
{
{ 0, new string[] { "scale_cuda", "Output format (default \"same\")" } },
{ 1, new string[] { "tonemap_cuda", "GPU accelerated HDR to SDR tonemapping" } },
- { 2, new string[] { "tonemap_opencl", "bt2390" } }
+ { 2, new string[] { "tonemap_opencl", "bt2390" } },
+ { 3, new string[] { "overlay_opencl", "Action to take when encountering EOF from secondary input" } },
+ { 4, new string[] { "overlay_vaapi", "Action to take when encountering EOF from secondary input" } }
};
// These are the library versions that corresponds to our minimum ffmpeg version 4.x according to the version table below
@@ -144,7 +165,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-version");
+ output = GetProcessOutput(_encoderPath, "-version", false);
}
catch (Exception ex)
{
@@ -225,7 +246,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-version");
+ output = GetProcessOutput(_encoderPath, "-version", false);
}
catch (Exception ex)
{
@@ -318,12 +339,36 @@ namespace MediaBrowser.MediaEncoding.Encoder
return map;
}
+ public bool CheckVaapiDeviceByDriverName(string driverName, string renderNodePath)
+ {
+ if (!OperatingSystem.IsLinux())
+ {
+ return false;
+ }
+
+ if (string.IsNullOrEmpty(driverName) || string.IsNullOrEmpty(renderNodePath))
+ {
+ return false;
+ }
+
+ try
+ {
+ var output = GetProcessOutput(_encoderPath, "-v verbose -hide_banner -init_hw_device vaapi=va:" + renderNodePath, true);
+ return output.Contains(driverName, StringComparison.Ordinal);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error detecting the given vaapi render node path");
+ return false;
+ }
+ }
+
private IEnumerable<string> GetHwaccelTypes()
{
string? output = null;
try
{
- output = GetProcessOutput(_encoderPath, "-hwaccels");
+ output = GetProcessOutput(_encoderPath, "-hwaccels", false);
}
catch (Exception ex)
{
@@ -351,7 +396,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-h filter=" + filter);
+ output = GetProcessOutput(_encoderPath, "-h filter=" + filter, false);
}
catch (Exception ex)
{
@@ -375,7 +420,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-" + codecstr);
+ output = GetProcessOutput(_encoderPath, "-" + codecstr, false);
}
catch (Exception ex)
{
@@ -406,7 +451,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
string output;
try
{
- output = GetProcessOutput(_encoderPath, "-filters");
+ output = GetProcessOutput(_encoderPath, "-filters", false);
}
catch (Exception ex)
{
@@ -444,7 +489,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
return dict;
}
- private string GetProcessOutput(string path, string arguments)
+ private string GetProcessOutput(string path, string arguments, bool readStdErr)
{
using (var process = new Process()
{
@@ -455,7 +500,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
WindowStyle = ProcessWindowStyle.Hidden,
ErrorDialog = false,
RedirectStandardOutput = true,
- // ffmpeg uses stderr to log info, don't show this
RedirectStandardError = true
}
})
@@ -464,7 +508,7 @@ namespace MediaBrowser.MediaEncoding.Encoder
process.Start();
- return process.StandardOutput.ReadToEnd();
+ return readStdErr ? process.StandardError.ReadToEnd() : process.StandardOutput.ReadToEnd();
}
}
}
diff --git a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
index 1c97a1982..e1643ea43 100644
--- a/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Encoder/MediaEncoder.cs
@@ -65,6 +65,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
private List<string> _filters = new List<string>();
private IDictionary<int, bool> _filtersWithOption = new Dictionary<int, bool>();
+ private bool _isVaapiDeviceAmd = false;
+ private bool _isVaapiDeviceInteliHD = false;
+ private bool _isVaapiDeviceInteli965 = false;
+
private Version _ffmpegVersion = null;
private string _ffmpegPath = string.Empty;
private string _ffprobePath;
@@ -88,6 +92,14 @@ namespace MediaBrowser.MediaEncoding.Encoder
/// <inheritdoc />
public string EncoderPath => _ffmpegPath;
+ public Version EncoderVersion => _ffmpegVersion;
+
+ public bool IsVaapiDeviceAmd => _isVaapiDeviceAmd;
+
+ public bool IsVaapiDeviceInteliHD => _isVaapiDeviceInteliHD;
+
+ public bool IsVaapiDeviceInteli965 => _isVaapiDeviceInteli965;
+
/// <summary>
/// Run at startup or if the user removes a Custom path from transcode page.
/// Sets global variables FFmpegPath.
@@ -114,9 +126,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
// Write the FFmpeg path to the config/encoding.xml file as <EncoderAppPathDisplay> so it appears in UI
- var config = _configurationManager.GetEncodingOptions();
- config.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
- _configurationManager.SaveConfiguration("encoding", config);
+ var options = _configurationManager.GetEncodingOptions();
+ options.EncoderAppPathDisplay = _ffmpegPath ?? string.Empty;
+ _configurationManager.SaveConfiguration("encoding", options);
// Only if mpeg path is set, try and set path to probe
if (_ffmpegPath != null)
@@ -134,7 +146,30 @@ namespace MediaBrowser.MediaEncoding.Encoder
SetAvailableHwaccels(validator.GetHwaccels());
SetMediaEncoderVersion(validator);
- _threads = EncodingHelper.GetNumberOfThreads(null, _configurationManager.GetEncodingOptions(), null);
+ _threads = EncodingHelper.GetNumberOfThreads(null, options, null);
+
+ // Check the Vaapi device vendor
+ if (OperatingSystem.IsLinux()
+ && SupportsHwaccel("vaapi")
+ && !string.IsNullOrEmpty(options.VaapiDevice)
+ && string.Equals(options.HardwareAccelerationType, "vaapi", StringComparison.OrdinalIgnoreCase))
+ {
+ _isVaapiDeviceAmd = validator.CheckVaapiDeviceByDriverName("Mesa Gallium driver", options.VaapiDevice);
+ _isVaapiDeviceInteliHD = validator.CheckVaapiDeviceByDriverName("Intel iHD driver", options.VaapiDevice);
+ _isVaapiDeviceInteli965 = validator.CheckVaapiDeviceByDriverName("Intel i965 driver", options.VaapiDevice);
+ if (_isVaapiDeviceAmd)
+ {
+ _logger.LogInformation("VAAPI device {RenderNodePath} is AMD GPU", options.VaapiDevice);
+ }
+ else if (_isVaapiDeviceInteliHD)
+ {
+ _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (iHD)", options.VaapiDevice);
+ }
+ else if (_isVaapiDeviceInteli965)
+ {
+ _logger.LogInformation("VAAPI device {RenderNodePath} is Intel GPU (i965)", options.VaapiDevice);
+ }
+ }
}
_logger.LogInformation("FFmpeg: {FfmpegPath}", _ffmpegPath ?? string.Empty);
@@ -301,11 +336,6 @@ namespace MediaBrowser.MediaEncoding.Encoder
return false;
}
- public Version GetMediaEncoderVersion()
- {
- return _ffmpegVersion;
- }
-
public bool CanEncodeToAudioCodec(string codec)
{
if (string.Equals(codec, "opus", StringComparison.OrdinalIgnoreCase))
@@ -508,36 +538,9 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (!isAudio)
{
- // The failure of HDR extraction usually occurs when using custom ffmpeg that does not contain the zscale filter.
- try
- {
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, true, targetFormat, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "I-frame or HDR image extraction failed, will attempt with I-frame extraction disabled. Input: {Arguments}", inputArgument);
- }
-
try
{
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, true, targetFormat, cancellationToken).ConfigureAwait(false);
- }
- catch (ArgumentException)
- {
- throw;
- }
- catch (Exception ex)
- {
- _logger.LogError(ex, "HDR image extraction failed, will fallback to SDR image extraction. Input: {Arguments}", inputArgument);
- }
-
- try
- {
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, false, targetFormat, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, true, targetFormat, cancellationToken).ConfigureAwait(false);
}
catch (ArgumentException)
{
@@ -549,10 +552,10 @@ namespace MediaBrowser.MediaEncoding.Encoder
}
}
- return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, false, targetFormat, cancellationToken).ConfigureAwait(false);
+ return await ExtractImageInternal(inputArgument, container, videoStream, imageStreamIndex, threedFormat, offset, false, targetFormat, cancellationToken).ConfigureAwait(false);
}
- private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, bool allowTonemap, ImageFormat? targetFormat, CancellationToken cancellationToken)
+ private async Task<string> ExtractImageInternal(string inputPath, string container, MediaStream videoStream, int? imageStreamIndex, Video3DFormat? threedFormat, TimeSpan? offset, bool useIFrame, ImageFormat? targetFormat, CancellationToken cancellationToken)
{
if (string.IsNullOrEmpty(inputPath))
{
@@ -572,36 +575,32 @@ namespace MediaBrowser.MediaEncoding.Encoder
var tempExtractPath = Path.Combine(_configurationManager.ApplicationPaths.TempDirectory, Guid.NewGuid() + outputExtension);
Directory.CreateDirectory(Path.GetDirectoryName(tempExtractPath));
+ // deint -> scale -> thumbnail -> tonemap.
+ // put the SW tonemap right after the thumbnail to do it only once to reduce cpu usage.
+ var filters = new List<string>();
+
+ // deinterlace using bwdif algorithm for video stream.
+ if (videoStream != null && videoStream.IsInterlaced)
+ {
+ filters.Add("bwdif=0:-1:0");
+ }
+
// apply some filters to thumbnail extracted below (below) crop any black lines that we made and get the correct ar.
// This filter chain may have adverse effects on recorded tv thumbnails if ar changes during presentation ex. commercials @ diff ar
- var vf = threedFormat switch
+ var scaler = threedFormat switch
{
// hsbs crop width in half,scale to correct size, set the display aspect,crop out any black bars we may have made. Work out the correct height based on the display aspect it will maintain the aspect where -1 in this case (3d) may not.
- Video3DFormat.HalfSideBySide => "-vf crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ Video3DFormat.HalfSideBySide => "crop=iw/2:ih:0:0,scale=(iw*2):ih,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
// fsbs crop width in half,set the display aspect,crop out any black bars we may have made
- Video3DFormat.FullSideBySide => "-vf crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ Video3DFormat.FullSideBySide => "crop=iw/2:ih:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
// htab crop heigh in half,scale to correct size, set the display aspect,crop out any black bars we may have made
- Video3DFormat.HalfTopAndBottom => "-vf crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ Video3DFormat.HalfTopAndBottom => "crop=iw:ih/2:0:0,scale=(iw*2):ih),setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
// ftab crop heigt in half, set the display aspect,crop out any black bars we may have made
- Video3DFormat.FullTopAndBottom => "-vf crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
- _ => string.Empty
+ Video3DFormat.FullTopAndBottom => "crop=iw:ih/2:0:0,setdar=dar=a,crop=min(iw\\,ih*dar):min(ih\\,iw/dar):(iw-min(iw\\,iw*sar))/2:(ih - min (ih\\,ih/sar))/2,setsar=sar=1",
+ _ => "scale=trunc(iw*sar):ih"
};
- var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
-
- var enableHdrExtraction = allowTonemap && string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase);
- if (enableHdrExtraction)
- {
- string tonemapFilters = "zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p";
- if (vf.Length == 0)
- {
- vf = "-vf " + tonemapFilters;
- }
- else
- {
- vf += "," + tonemapFilters;
- }
- }
+ filters.Add(scaler);
// Use ffmpeg to sample 100 (we can drop this if required using thumbnail=50 for 50 frames) frames and pick the best thumbnail. Have a fall back just in case.
// mpegts need larger batch size otherwise the corrupted thumbnail will be created. Larger batch size will lower the processing speed.
@@ -609,18 +608,19 @@ namespace MediaBrowser.MediaEncoding.Encoder
if (enableThumbnail)
{
var useLargerBatchSize = string.Equals("mpegts", container, StringComparison.OrdinalIgnoreCase);
- var batchSize = useLargerBatchSize ? "50" : "24";
- if (string.IsNullOrEmpty(vf))
- {
- vf = "-vf thumbnail=" + batchSize;
- }
- else
- {
- vf += ",thumbnail=" + batchSize;
- }
+ filters.Add("thumbnail=n=" + (useLargerBatchSize ? "50" : "24"));
+ }
+
+ // Use SW tonemap on HDR video stream only when the zscale filter is available.
+ var enableHdrExtraction = string.Equals(videoStream?.VideoRange, "HDR", StringComparison.OrdinalIgnoreCase) && SupportsFilter("zscale");
+ if (enableHdrExtraction)
+ {
+ filters.Add("zscale=t=linear:npl=100,format=gbrpf32le,zscale=p=bt709,tonemap=tonemap=hable:desat=0:peak=100,zscale=t=bt709:m=bt709,format=yuv420p");
}
- var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
+ var vf = string.Join(',', filters);
+ var mapArg = imageStreamIndex.HasValue ? (" -map 0:" + imageStreamIndex.Value.ToString(CultureInfo.InvariantCulture)) : string.Empty;
+ var args = string.Format(CultureInfo.InvariantCulture, "-i {0}{3} -threads {4} -v quiet -vframes 1 -vf {2} -f image2 \"{1}\"", inputPath, tempExtractPath, vf, mapArg, _threads);
if (offset.HasValue)
{
diff --git a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
index 9f6d8e7fe..b60ccd2ca 100644
--- a/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
+++ b/MediaBrowser.MediaEncoding/MediaBrowser.MediaEncoding.csproj
@@ -11,6 +11,10 @@
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<ItemGroup>
<Compile Include="..\SharedVersion.cs" />
</ItemGroup>
@@ -32,7 +36,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.MediaEncoding/Probing/ProbeResultNormalizer.cs b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
index 9057a101a..750fd44eb 100644
--- a/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
+++ b/MediaBrowser.MediaEncoding/Probing/ProbeResultNormalizer.cs
@@ -28,7 +28,7 @@ namespace MediaBrowser.MediaEncoding.Probing
private readonly char[] _nameDelimiters = { '/', '|', ';', '\\' };
- private static readonly Regex _performerPattern = new (@"(?<name>.*) \((?<instrument>.*)\)");
+ private static readonly Regex _performerPattern = new(@"(?<name>.*) \((?<instrument>.*)\)");
private readonly ILogger _logger;
private readonly ILocalizationManager _localization;
@@ -777,18 +777,23 @@ namespace MediaBrowser.MediaEncoding.Probing
if (!stream.BitDepth.HasValue)
{
- if (!string.IsNullOrEmpty(streamInfo.PixelFormat)
- && streamInfo.PixelFormat.Contains("p10", StringComparison.OrdinalIgnoreCase))
+ if (!string.IsNullOrEmpty(streamInfo.PixelFormat))
{
- stream.BitDepth = 10;
- }
-
- if (!string.IsNullOrEmpty(streamInfo.Profile)
- && (streamInfo.Profile.Contains("Main 10", StringComparison.OrdinalIgnoreCase)
- || streamInfo.Profile.Contains("High 10", StringComparison.OrdinalIgnoreCase)
- || streamInfo.Profile.Contains("Profile 2", StringComparison.OrdinalIgnoreCase)))
- {
- stream.BitDepth = 10;
+ if (string.Equals(streamInfo.PixelFormat, "yuv420p", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(streamInfo.PixelFormat, "yuv444p", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 8;
+ }
+ else if (string.Equals(streamInfo.PixelFormat, "yuv420p10le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(streamInfo.PixelFormat, "yuv444p10le", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 10;
+ }
+ else if (string.Equals(streamInfo.PixelFormat, "yuv420p12le", StringComparison.OrdinalIgnoreCase)
+ || string.Equals(streamInfo.PixelFormat, "yuv444p12le", StringComparison.OrdinalIgnoreCase))
+ {
+ stream.BitDepth = 12;
+ }
}
}
diff --git a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
index 89365a516..5b1ec8041 100644
--- a/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
+++ b/MediaBrowser.MediaEncoding/Subtitles/SubtitleEncoder.cs
@@ -139,28 +139,28 @@ namespace MediaBrowser.MediaEncoding.Subtitles
var subtitle = await GetSubtitleStream(mediaSource, subtitleStream, cancellationToken)
.ConfigureAwait(false);
- var inputFormat = subtitle.format;
+ var inputFormat = subtitle.Format;
// Return the original if we don't have any way of converting it
if (!TryGetWriter(outputFormat, out var writer))
{
- return subtitle.stream;
+ return subtitle.Stream;
}
// Return the original if the same format is being requested
// Character encoding was already handled in GetSubtitleStream
if (string.Equals(inputFormat, outputFormat, StringComparison.OrdinalIgnoreCase))
{
- return subtitle.stream;
+ return subtitle.Stream;
}
- using (var stream = subtitle.stream)
+ using (var stream = subtitle.Stream)
{
return ConvertSubtitles(stream, inputFormat, outputFormat, startTimeTicks, endTimeTicks, preserveOriginalTimestamps, cancellationToken);
}
}
- private async Task<(Stream stream, string format)> GetSubtitleStream(
+ private async Task<(Stream Stream, string Format)> GetSubtitleStream(
MediaSourceInfo mediaSource,
MediaStream subtitleStream,
CancellationToken cancellationToken)
diff --git a/MediaBrowser.Model/Configuration/EncodingOptions.cs b/MediaBrowser.Model/Configuration/EncodingOptions.cs
index 365bbeef6..d0ded99ea 100644
--- a/MediaBrowser.Model/Configuration/EncodingOptions.cs
+++ b/MediaBrowser.Model/Configuration/EncodingOptions.cs
@@ -16,12 +16,9 @@ namespace MediaBrowser.Model.Configuration
// This is a DRM device that is almost guaranteed to be there on every intel platform,
// plus it's the default one in ffmpeg if you don't specify anything
VaapiDevice = "/dev/dri/renderD128";
- // This is the OpenCL device that is used for tonemapping.
- // The left side of the dot is the platform number, and the right side is the device number on the platform.
- OpenclDevice = "0.0";
EnableTonemapping = false;
EnableVppTonemapping = false;
- TonemappingAlgorithm = "hable";
+ TonemappingAlgorithm = "bt2390";
TonemappingRange = "auto";
TonemappingDesat = 0;
TonemappingThreshold = 0.8;
@@ -34,6 +31,9 @@ namespace MediaBrowser.Model.Configuration
EnableDecodingColorDepth10Hevc = true;
EnableDecodingColorDepth10Vp9 = true;
EnableEnhancedNvdecDecoder = true;
+ PreferSystemNativeHwDecoder = true;
+ EnableIntelLowPowerH264HwEncoder = false;
+ EnableIntelLowPowerHevcHwEncoder = false;
EnableHardwareEncoding = true;
AllowHevcEncoding = false;
EnableSubtitleExtraction = true;
@@ -70,8 +70,6 @@ namespace MediaBrowser.Model.Configuration
public string VaapiDevice { get; set; }
- public string OpenclDevice { get; set; }
-
public bool EnableTonemapping { get; set; }
public bool EnableVppTonemapping { get; set; }
@@ -104,6 +102,12 @@ namespace MediaBrowser.Model.Configuration
public bool EnableEnhancedNvdecDecoder { get; set; }
+ public bool PreferSystemNativeHwDecoder { get; set; }
+
+ public bool EnableIntelLowPowerH264HwEncoder { get; set; }
+
+ public bool EnableIntelLowPowerHevcHwEncoder { get; set; }
+
public bool EnableHardwareEncoding { get; set; }
public bool AllowHevcEncoding { get; set; }
diff --git a/MediaBrowser.Model/Dlna/StreamBuilder.cs b/MediaBrowser.Model/Dlna/StreamBuilder.cs
index c6ce45788..84a6f6abe 100644
--- a/MediaBrowser.Model/Dlna/StreamBuilder.cs
+++ b/MediaBrowser.Model/Dlna/StreamBuilder.cs
@@ -289,8 +289,8 @@ namespace MediaBrowser.Model.Dlna
var directPlayInfo = GetAudioDirectPlayMethods(item, audioStream, options);
- var directPlayMethods = directPlayInfo.Item1;
- var transcodeReasons = directPlayInfo.Item2.ToList();
+ var directPlayMethods = directPlayInfo.PlayMethods;
+ var transcodeReasons = directPlayInfo.TranscodeReasons.ToList();
int? inputAudioChannels = audioStream?.Channels;
int? inputAudioBitrate = audioStream?.BitDepth;
@@ -448,7 +448,7 @@ namespace MediaBrowser.Model.Dlna
return options.GetMaxBitrate(isAudio);
}
- private (IEnumerable<PlayMethod>, IEnumerable<TranscodeReason>) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
+ private (IEnumerable<PlayMethod> PlayMethods, IEnumerable<TranscodeReason> TranscodeReasons) GetAudioDirectPlayMethods(MediaSourceInfo item, MediaStream audioStream, AudioOptions options)
{
DirectPlayProfile directPlayProfile = options.Profile.DirectPlayProfiles
.FirstOrDefault(x => x.Type == DlnaProfileType.Audio && IsAudioDirectPlaySupported(x, item, audioStream));
@@ -679,8 +679,8 @@ namespace MediaBrowser.Model.Dlna
// TODO: This doesn't account for situations where the device is able to handle the media's bitrate, but the connection isn't fast enough
var directPlayEligibilityResult = IsEligibleForDirectPlay(item, GetBitrateForDirectPlayCheck(item, options, true) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectPlay);
var directStreamEligibilityResult = IsEligibleForDirectPlay(item, options.GetMaxBitrate(false) ?? 0, subtitleStream, audioStream, options, PlayMethod.DirectStream);
- bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.Item1);
- bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.Item1);
+ bool isEligibleForDirectPlay = options.EnableDirectPlay && (options.ForceDirectPlay || directPlayEligibilityResult.DirectPlay);
+ bool isEligibleForDirectStream = options.EnableDirectStream && (options.ForceDirectStream || directStreamEligibilityResult.DirectPlay);
_logger.LogDebug(
"Profile: {0}, Path: {1}, isEligibleForDirectPlay: {2}, isEligibleForDirectStream: {3}",
@@ -695,7 +695,7 @@ namespace MediaBrowser.Model.Dlna
{
// See if it can be direct played
var directPlayInfo = GetVideoDirectPlayProfile(options, item, videoStream, audioStream, isEligibleForDirectStream);
- var directPlay = directPlayInfo.Item1;
+ var directPlay = directPlayInfo.PlayMethod;
if (directPlay != null)
{
@@ -713,17 +713,17 @@ namespace MediaBrowser.Model.Dlna
return playlistItem;
}
- transcodeReasons.AddRange(directPlayInfo.Item2);
+ transcodeReasons.AddRange(directPlayInfo.TranscodeReasons);
}
- if (directPlayEligibilityResult.Item2.HasValue)
+ if (directPlayEligibilityResult.Reason.HasValue)
{
- transcodeReasons.Add(directPlayEligibilityResult.Item2.Value);
+ transcodeReasons.Add(directPlayEligibilityResult.Reason.Value);
}
- if (directStreamEligibilityResult.Item2.HasValue)
+ if (directStreamEligibilityResult.Reason.HasValue)
{
- transcodeReasons.Add(directStreamEligibilityResult.Item2.Value);
+ transcodeReasons.Add(directStreamEligibilityResult.Reason.Value);
}
// Can't direct play, find the transcoding profile
@@ -1000,7 +1000,7 @@ namespace MediaBrowser.Model.Dlna
return 7168000;
}
- private (PlayMethod?, List<TranscodeReason>) GetVideoDirectPlayProfile(
+ private (PlayMethod? PlayMethod, List<TranscodeReason> TranscodeReasons) GetVideoDirectPlayProfile(
VideoOptions options,
MediaSourceInfo mediaSource,
MediaStream videoStream,
@@ -1209,7 +1209,7 @@ namespace MediaBrowser.Model.Dlna
mediaSource.Path ?? "Unknown path");
}
- private (bool directPlay, TranscodeReason? reason) IsEligibleForDirectPlay(
+ private (bool DirectPlay, TranscodeReason? Reason) IsEligibleForDirectPlay(
MediaSourceInfo item,
long maxBitrate,
MediaStream subtitleStream,
diff --git a/MediaBrowser.Model/Entities/MetadataFields.cs b/MediaBrowser.Model/Entities/MetadataField.cs
index 2cc6c8e33..2cc6c8e33 100644
--- a/MediaBrowser.Model/Entities/MetadataFields.cs
+++ b/MediaBrowser.Model/Entities/MetadataField.cs
diff --git a/MediaBrowser.Model/MediaBrowser.Model.csproj b/MediaBrowser.Model/MediaBrowser.Model.csproj
index a161b99fd..63f7ada5c 100644
--- a/MediaBrowser.Model/MediaBrowser.Model.csproj
+++ b/MediaBrowser.Model/MediaBrowser.Model.csproj
@@ -23,6 +23,10 @@
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<PropertyGroup Condition=" '$(Stability)'=='Unstable'">
<!-- Include all symbols in the main nupkg until Azure Artifact Feed starts supporting ingesting NuGet symbol packages. -->
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
@@ -46,7 +50,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>
<ItemGroup>
diff --git a/MediaBrowser.Model/Plugins/PluginStatus.cs b/MediaBrowser.Model/Plugins/PluginStatus.cs
index 4b9b9bbee..bd420d7b4 100644
--- a/MediaBrowser.Model/Plugins/PluginStatus.cs
+++ b/MediaBrowser.Model/Plugins/PluginStatus.cs
@@ -29,7 +29,7 @@ namespace MediaBrowser.Model.Plugins
NotSupported = -2,
/// <summary>
- /// This plugin caused an error when instantiated. (Either DI loop, or exception)
+ /// This plugin caused an error when instantiated (either DI loop, or exception).
/// </summary>
Malfunctioned = -3,
diff --git a/MediaBrowser.Model/Session/GeneralCommandType.cs b/MediaBrowser.Model/Session/GeneralCommandType.cs
index c58fa9a6b..166a6b441 100644
--- a/MediaBrowser.Model/Session/GeneralCommandType.cs
+++ b/MediaBrowser.Model/Session/GeneralCommandType.cs
@@ -39,7 +39,6 @@ namespace MediaBrowser.Model.Session
SetRepeatMode = 29,
ChannelUp = 30,
ChannelDown = 31,
- SetMaxStreamingBitrate = 31,
Guide = 32,
ToggleStats = 33,
PlayMediaSource = 34,
@@ -48,6 +47,7 @@ namespace MediaBrowser.Model.Session
PlayState = 37,
PlayNext = 38,
ToggleOsdMenu = 39,
- Play = 40
+ Play = 40,
+ SetMaxStreamingBitrate = 41
}
}
diff --git a/MediaBrowser.Model/Session/HardwareEncodingType.cs b/MediaBrowser.Model/Session/HardwareEncodingType.cs
index 0e172f35f..0db5697d3 100644
--- a/MediaBrowser.Model/Session/HardwareEncodingType.cs
+++ b/MediaBrowser.Model/Session/HardwareEncodingType.cs
@@ -6,42 +6,42 @@
public enum HardwareEncodingType
{
/// <summary>
- /// AMD AMF
+ /// AMD AMF.
/// </summary>
AMF = 0,
/// <summary>
- /// Intel Quick Sync Video
+ /// Intel Quick Sync Video.
/// </summary>
QSV = 1,
/// <summary>
- /// NVIDIA NVENC
+ /// NVIDIA NVENC.
/// </summary>
NVENC = 2,
/// <summary>
- /// OpenMax OMX
+ /// OpenMax OMX.
/// </summary>
OMX = 3,
/// <summary>
- /// Exynos V4L2 MFC
+ /// Exynos V4L2 MFC.
/// </summary>
V4L2M2M = 4,
/// <summary>
- /// MediaCodec Android
+ /// MediaCodec Android.
/// </summary>
MediaCodec = 5,
/// <summary>
- /// Video Acceleration API (VAAPI)
+ /// Video Acceleration API (VAAPI).
/// </summary>
VAAPI = 6,
/// <summary>
- /// Video ToolBox
+ /// Video ToolBox.
/// </summary>
VideoToolBox = 7
}
diff --git a/MediaBrowser.Model/Tasks/ITaskTrigger.cs b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
index 999db9605..8c3ec6626 100644
--- a/MediaBrowser.Model/Tasks/ITaskTrigger.cs
+++ b/MediaBrowser.Model/Tasks/ITaskTrigger.cs
@@ -25,7 +25,7 @@ namespace MediaBrowser.Model.Tasks
/// <param name="logger">The <see cref="ILogger"/>.</param>
/// <param name="taskName">The name of the task.</param>
/// <param name="isApplicationStartup">Wheter or not this is is fired during startup.</param>
- void Start(TaskResult lastResult, ILogger logger, string taskName, bool isApplicationStartup);
+ void Start(TaskResult? lastResult, ILogger logger, string taskName, bool isApplicationStartup);
/// <summary>
/// Stops waiting for the trigger action.
diff --git a/MediaBrowser.Providers/Manager/MetadataService.cs b/MediaBrowser.Providers/Manager/MetadataService.cs
index 0af76f75a..94045b38b 100644
--- a/MediaBrowser.Providers/Manager/MetadataService.cs
+++ b/MediaBrowser.Providers/Manager/MetadataService.cs
@@ -682,12 +682,12 @@ namespace MediaBrowser.Providers.Manager
{
try
{
- await ProviderManager.SaveImage(item, remoteImage.url, remoteImage.type, null, cancellationToken).ConfigureAwait(false);
+ await ProviderManager.SaveImage(item, remoteImage.Url, remoteImage.Type, null, cancellationToken).ConfigureAwait(false);
refreshResult.UpdateType |= ItemUpdateType.ImageUpdate;
}
catch (HttpRequestException ex)
{
- Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.type), remoteImage.url);
+ Logger.LogError(ex, "Could not save {ImageType} image: {Url}", Enum.GetName(remoteImage.Type), remoteImage.Url);
}
}
diff --git a/MediaBrowser.Providers/MediaBrowser.Providers.csproj b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
index dac5aaf56..43cf621cd 100644
--- a/MediaBrowser.Providers/MediaBrowser.Providers.csproj
+++ b/MediaBrowser.Providers/MediaBrowser.Providers.csproj
@@ -32,10 +32,14 @@
<CodeAnalysisRuleSet>../jellyfin.ruleset</CodeAnalysisRuleSet>
</PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' ">
+ <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
+ </PropertyGroup>
+
<!-- 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.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
index 8a32cb07c..5ae5ff3be 100644
--- a/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
+++ b/MediaBrowser.Providers/Plugins/MusicBrainz/MusicBrainzAlbumProvider.cs
@@ -303,7 +303,7 @@ namespace MediaBrowser.Providers.Music
return ReleaseResult.Parse(reader).FirstOrDefault();
}
- private static (string, string) ParseArtistCredit(XmlReader reader)
+ private static (string Name, string ArtistId) ParseArtistCredit(XmlReader reader)
{
reader.MoveToContent();
reader.Read();
@@ -345,7 +345,7 @@ namespace MediaBrowser.Providers.Music
return default;
}
- private static (string, string) ParseArtistNameCredit(XmlReader reader)
+ private static (string Name, string ArtistId) ParseArtistNameCredit(XmlReader reader)
{
reader.MoveToContent();
reader.Read();
@@ -388,7 +388,7 @@ namespace MediaBrowser.Providers.Music
return (null, null);
}
- private static (string name, string id) ParseArtistArtistCredit(XmlReader reader, string artistId)
+ private static (string Name, string ArtistId) ParseArtistArtistCredit(XmlReader reader, string artistId)
{
reader.MoveToContent();
reader.Read();
@@ -628,7 +628,7 @@ namespace MediaBrowser.Providers.Music
public string Overview;
public int? Year;
- public List<ValueTuple<string, string>> Artists = new List<ValueTuple<string, string>>();
+ public List<(string, string)> Artists = new();
public static IEnumerable<ReleaseResult> Parse(XmlReader reader)
{
@@ -776,7 +776,7 @@ namespace MediaBrowser.Providers.Music
using var subReader = reader.ReadSubtree();
var artist = ParseArtistCredit(subReader);
- if (!string.IsNullOrEmpty(artist.Item1))
+ if (!string.IsNullOrEmpty(artist.Name))
{
result.Artists.Add(artist);
}
diff --git a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
index 6fa34b985..3a3048cec 100644
--- a/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
+++ b/MediaBrowser.Providers/Plugins/StudioImages/StudiosImageProvider.cs
@@ -27,7 +27,7 @@ namespace MediaBrowser.Providers.Studios
private readonly IServerConfigurationManager _config;
private readonly IHttpClientFactory _httpClientFactory;
private readonly IFileSystem _fileSystem;
- private readonly String repositoryUrl;
+ private readonly string repositoryUrl;
public StudiosImageProvider(IServerConfigurationManager config, IHttpClientFactory httpClientFactory, IFileSystem fileSystem)
{
diff --git a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
index a3a78103e..234d717bf 100644
--- a/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
+++ b/MediaBrowser.Providers/Plugins/Tmdb/TmdbUtils.cs
@@ -11,7 +11,7 @@ namespace MediaBrowser.Providers.Plugins.Tmdb
/// </summary>
public static class TmdbUtils
{
- private static readonly Regex _nonWords = new (@"[\W_]+", RegexOptions.Compiled);
+ private static readonly Regex _nonWords = new(@"[\W_]+", RegexOptions.Compiled);
/// <summary>
/// URL of the TMDB instance to use.
diff --git a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
index 926be5a92..ad06688fb 100644
--- a/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
+++ b/MediaBrowser.XbmcMetadata/MediaBrowser.XbmcMetadata.csproj
@@ -23,7 +23,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.XbmcMetadata/Parsers/BaseNfoParser.cs b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
index bcf9a8366..007101868 100644
--- a/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
+++ b/MediaBrowser.XbmcMetadata/Parsers/BaseNfoParser.cs
@@ -872,7 +872,7 @@ namespace MediaBrowser.XbmcMetadata.Parsers
else
{
// only allow one item of each type
- if (itemResult.RemoteImages.Any(x => x.type == imageType))
+ if (itemResult.RemoteImages.Any(x => x.Type == imageType))
{
return;
}
diff --git a/fedora/Makefile b/fedora/Makefile
index 22cc30448..261fd262d 100644
--- a/fedora/Makefile
+++ b/fedora/Makefile
@@ -3,9 +3,8 @@ INSTGIT := $(shell if [ "$$(id -u)" = "0" ]; then dnf -y install git; fi)
NAME := jellyfin-server
VERSION := $(shell sed -ne '/^Version:/s/.* *//p' $(DIR)/jellyfin.spec)
RELEASE := $(shell sed -ne '/^Release:/s/.* *\(.*\)%{.*}.*/\1/p' $(DIR)/jellyfin.spec)
-GIT_VER := $(shell git describe --tags | sed -e 's/^v//' -e 's/-[0-9]*-g.*$$//')
-SRPM := jellyfin-$(subst -,~,$(GIT_VER))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
-TARBALL :=$(NAME)-$(subst -,~,$(GIT_VER)).tar.gz
+SRPM := jellyfin-$(subst -,~,$(VERSION))-$(RELEASE)$(shell rpm --eval %dist).src.rpm
+TARBALL :=$(NAME)-$(subst -,~,$(VERSION)).tar.gz
epel-7-x86_64_repos := https://packages.microsoft.com/rhel/7/prod/
epel-8-x86_64_repos := https://download.copr.fedorainfracloud.org/results/@dotnet-sig/dotnet-preview/$(TARGET)/
@@ -24,9 +23,9 @@ $(DIR)/$(TARBALL):
cd $(DIR)/; \
SOURCE_DIR=.. \
WORKDIR="$${PWD}"; \
- version=$(GIT_VER); \
+ version=$(VERSION); \
tar \
- --transform "s,^\.,$(NAME)-$(subst -,~,$(GIT_VER))," \
+ --transform "s,^\.,$(NAME)-$(subst -,~,$(VERSION))," \
--exclude='.git*' \
--exclude='**/.git' \
--exclude='**/.hg' \
@@ -43,7 +42,6 @@ $(DIR)/$(TARBALL):
-C $${SOURCE_DIR} ./
$(DIR)/$(SRPM): $(DIR)/$(TARBALL) $(DIR)/jellyfin.spec
- ./bump_version $(GIT_VER)
cd $(DIR)/; \
rpmbuild -bs jellyfin.spec \
--define "_sourcedir $$PWD/" \
diff --git a/jellyfin.ruleset b/jellyfin.ruleset
index 7adc35087..e9293588c 100644
--- a/jellyfin.ruleset
+++ b/jellyfin.ruleset
@@ -1,6 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="Rules for Jellyfin.Server" Description="Code analysis rules for Jellyfin.Server.csproj" ToolsVersion="14.0">
<Rules AnalyzerId="StyleCop.Analyzers" RuleNamespace="StyleCop.Analyzers">
+ <!-- error on SA1000: The keyword 'new' should be followed by a space -->
+ <Rule Id="SA1000" Action="Error" />
+ <!-- error on SA1001: Commas should not be preceded by whitespace -->
+ <Rule Id="SA1001" Action="Error" />
+ <!-- error on SA1117: The parameters should all be placed on the same line or each parameter should be placed on its own line -->
+ <Rule Id="SA1117" Action="Error" />
+ <!-- error on SA1142: Refer to tuple fields by name -->
+ <Rule Id="SA1142" Action="Error" />
+ <!-- error on SA1210: Using directives should be ordered alphabetically by the namespaces -->
+ <Rule Id="SA1210" Action="Error" />
+ <!-- error on SA1518: File is required to end with a single newline character -->
+ <Rule Id="SA1518" Action="Error" />
+ <!-- error on SA1629: Documentation text should end with a period -->
+ <Rule Id="SA1629" Action="Error" />
+
<!-- disable warning SA1009: Closing parenthesis should be followed by a space. -->
<Rule Id="SA1009" Action="None" />
<!-- disable warning SA1011: Closing square bracket should be followed by a space. -->
diff --git a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
index 3d9538d1b..90d2a0da6 100644
--- a/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
+++ b/src/Jellyfin.Extensions/Jellyfin.Extensions.csproj
@@ -30,7 +30,7 @@
<!-- Code Analyzers-->
<ItemGroup>
<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/src/Jellyfin.Extensions/Json/JsonDefaults.cs b/src/Jellyfin.Extensions/Json/JsonDefaults.cs
index f4ec91123..2cd89dc3b 100644
--- a/src/Jellyfin.Extensions/Json/JsonDefaults.cs
+++ b/src/Jellyfin.Extensions/Json/JsonDefaults.cs
@@ -25,7 +25,7 @@ namespace Jellyfin.Extensions.Json
/// -> AddJellyfinApi
/// -> AddJsonOptions.
/// </summary>
- private static readonly JsonSerializerOptions _jsonSerializerOptions = new ()
+ private static readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
ReadCommentHandling = JsonCommentHandling.Disallow,
WriteIndented = false,
@@ -44,12 +44,12 @@ namespace Jellyfin.Extensions.Json
}
};
- private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new (_jsonSerializerOptions)
+ private static readonly JsonSerializerOptions _pascalCaseJsonSerializerOptions = new(_jsonSerializerOptions)
{
PropertyNamingPolicy = null
};
- private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new (_jsonSerializerOptions)
+ private static readonly JsonSerializerOptions _camelCaseJsonSerializerOptions = new(_jsonSerializerOptions)
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
diff --git a/src/Jellyfin.Extensions/SplitStringExtensions.cs b/src/Jellyfin.Extensions/SplitStringExtensions.cs
index 5fa5c0123..1d1c377f5 100644
--- a/src/Jellyfin.Extensions/SplitStringExtensions.cs
+++ b/src/Jellyfin.Extensions/SplitStringExtensions.cs
@@ -43,7 +43,7 @@ namespace Jellyfin.Extensions
/// <param name="separator">The separator to split on.</param>
/// <returns>The enumerator struct.</returns>
[Pure]
- public static Enumerator SpanSplit(this string str, char separator) => new (str.AsSpan(), separator);
+ public static Enumerator SpanSplit(this string str, char separator) => new(str.AsSpan(), separator);
/// <summary>
/// Creates a new span split enumerator.
@@ -52,7 +52,7 @@ namespace Jellyfin.Extensions
/// <param name="separator">The separator to split on.</param>
/// <returns>The enumerator struct.</returns>
[Pure]
- public static Enumerator Split(this ReadOnlySpan<char> str, char separator) => new (str, separator);
+ public static Enumerator Split(this ReadOnlySpan<char> str, char separator) => new(str, separator);
/// <summary>
/// Provides an enumerator for the substrings seperated by the separator.
diff --git a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
index bcbe9c1be..6e0474dbf 100644
--- a/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
+++ b/tests/Jellyfin.Api.Tests/Jellyfin.Api.Tests.csproj
@@ -27,7 +27,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/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
index ce607b2ec..8476c935e 100644
--- a/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
+++ b/tests/Jellyfin.Common.Tests/Jellyfin.Common.Tests.csproj
@@ -22,7 +22,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/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
index feffb50e8..46439aecb 100644
--- a/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
+++ b/tests/Jellyfin.Controller.Tests/DirectoryServiceTests.cs
@@ -13,22 +13,22 @@ namespace Jellyfin.Controller.Tests
private static readonly FileSystemMetadata[] _lowerCaseFileSystemMetadata =
{
- new ()
+ new()
{
FullName = LowerCasePath + "/Artwork",
IsDirectory = true
},
- new ()
+ new()
{
FullName = LowerCasePath + "/Some Other Folder",
IsDirectory = true
},
- new ()
+ new()
{
FullName = LowerCasePath + "/Song 2.mp3",
IsDirectory = false
},
- new ()
+ new()
{
FullName = LowerCasePath + "/Song 3.mp3",
IsDirectory = false
@@ -37,12 +37,12 @@ namespace Jellyfin.Controller.Tests
private static readonly FileSystemMetadata[] _upperCaseFileSystemMetadata =
{
- new ()
+ new()
{
FullName = UpperCasePath + "/Lyrics",
IsDirectory = true
},
- new ()
+ new()
{
FullName = UpperCasePath + "/Song 1.mp3",
IsDirectory = false
diff --git a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
index 0ffc19833..981c7e9c9 100644
--- a/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
+++ b/tests/Jellyfin.Controller.Tests/Jellyfin.Controller.Tests.csproj
@@ -22,7 +22,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/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
index 668bd8f87..78a956f5f 100644
--- a/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
+++ b/tests/Jellyfin.Dlna.Tests/DlnaManagerTests.cs
@@ -46,7 +46,7 @@ namespace Jellyfin.Dlna.Tests
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
- Identification = new ()
+ Identification = new()
{
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
@@ -92,7 +92,7 @@ namespace Jellyfin.Dlna.Tests
ModelDescription = "LG WebOSTV DMRplus",
ModelName = "LG TV",
ModelNumber = "1.0",
- Identification = new ()
+ Identification = new()
{
FriendlyName = "My Device",
Manufacturer = "LG Electronics",
@@ -120,7 +120,7 @@ namespace Jellyfin.Dlna.Tests
{
Name = "Test Profile",
FriendlyName = "My .*",
- Identification = new ()
+ Identification = new()
};
var deviceMatch = GetManager().IsMatch(device.ToDeviceIdentification(), profile.Identification);
diff --git a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
index 098166001..6200a148b 100644
--- a/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
+++ b/tests/Jellyfin.Dlna.Tests/Jellyfin.Dlna.Tests.csproj
@@ -17,7 +17,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/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
index ee3af7559..3dcc00ff0 100644
--- a/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
+++ b/tests/Jellyfin.Extensions.Tests/Jellyfin.Extensions.Tests.csproj
@@ -23,7 +23,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/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
index 655e07074..345f37cbe 100644
--- a/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
+++ b/tests/Jellyfin.Extensions.Tests/Json/Converters/JsonStringConverterTests.cs
@@ -6,7 +6,7 @@ namespace Jellyfin.Extensions.Tests.Json.Converters
{
public class JsonStringConverterTests
{
- private readonly JsonSerializerOptions _jsonSerializerOptions = new ()
+ private readonly JsonSerializerOptions _jsonSerializerOptions = new()
{
Converters =
{
diff --git a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
index dc4a42c19..f366f553a 100644
--- a/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
+++ b/tests/Jellyfin.MediaEncoding.Tests/Jellyfin.MediaEncoding.Tests.csproj
@@ -31,7 +31,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/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
index 7e8397d9f..d4a1a30c3 100644
--- a/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
+++ b/tests/Jellyfin.Model.Tests/Jellyfin.Model.Tests.csproj
@@ -17,7 +17,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/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
index 4096873a3..4c95e78b1 100644
--- a/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
+++ b/tests/Jellyfin.Naming.Tests/Jellyfin.Naming.Tests.csproj
@@ -25,7 +25,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/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
index 8dd637559..731580e0c 100644
--- a/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
+++ b/tests/Jellyfin.Naming.Tests/Video/ExtraTests.cs
@@ -53,6 +53,7 @@ namespace Jellyfin.Naming.Tests.Video
[InlineData(ExtraType.Sample, "samples")]
[InlineData(ExtraType.Clip, "shorts")]
[InlineData(ExtraType.Clip, "featurettes")]
+ [InlineData(ExtraType.ThemeVideo, "backdrops")]
[InlineData(ExtraType.Unknown, "extras")]
public void TestDirectories(ExtraType type, string dirName)
{
@@ -81,7 +82,7 @@ namespace Jellyfin.Naming.Tests.Video
private void Test(string input, ExtraType? expectedType)
{
- var extraType = ExtraResolver.GetExtraInfo(input, _videoOptions).ExtraType;
+ var extraType = ExtraRuleResolver.GetExtraInfo(input, _videoOptions).ExtraType;
Assert.Equal(expectedType, extraType);
}
@@ -91,7 +92,7 @@ namespace Jellyfin.Naming.Tests.Video
{
var rule = new ExtraRule(ExtraType.Unknown, ExtraRuleType.Regex, @"([eE]x(tra)?\.\w+)", MediaType.Video);
var options = new NamingOptions { VideoExtraRules = new[] { rule } };
- var res = ExtraResolver.GetExtraInfo("extra.mp4", options);
+ var res = ExtraRuleResolver.GetExtraInfo("extra.mp4", options);
Assert.Equal(rule, res.Rule);
}
diff --git a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
index 78556ee67..87acc7f68 100644
--- a/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
+++ b/tests/Jellyfin.Networking.Tests/Jellyfin.Networking.Tests.csproj
@@ -23,7 +23,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/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
index 10767ae23..4338c812d 100644
--- a/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
+++ b/tests/Jellyfin.Providers.Tests/Jellyfin.Providers.Tests.csproj
@@ -29,7 +29,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/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
index 558321810..98ac1dd64 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/EmbeddedImageProviderTests.cs
@@ -62,7 +62,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo
for (int i = 1; i <= targetIndex; i++)
{
var name = i == targetIndex ? filename : "unmatched";
- attachments.Add(new ()
+ attachments.Add(new()
{
FileName = name,
MimeType = mimetype,
@@ -107,7 +107,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo
for (int i = 1; i <= targetIndex; i++)
{
var comment = i == targetIndex ? label : "unmatched";
- streams.Add(new ()
+ streams.Add(new()
{
Type = MediaStreamType.EmbeddedImage,
Index = i,
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
index 33da277e3..040ea5d1d 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/SubtitleResolverTests.cs
@@ -113,7 +113,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo
private static MediaStream CreateMediaStream(string path, string codec, string? language, int index, bool isForced = false, bool isDefault = false)
{
- return new ()
+ return new()
{
Index = index,
Codec = codec,
diff --git a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
index 839925dd1..1503a3392 100644
--- a/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
+++ b/tests/Jellyfin.Providers.Tests/MediaInfo/VideoImageProviderTests.cs
@@ -21,7 +21,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo
{
private static TheoryData<Video> GetImage_UnsupportedInput_ReturnsNoImage_TestData()
{
- return new ()
+ return new()
{
new Movie { IsPlaceHolder = true },
@@ -82,7 +82,7 @@ namespace Jellyfin.Providers.Tests.MediaInfo
[InlineData(500, 50)] // calculated time
public async void GetImage_TimeSpan_SelectsCorrectTime(int? runTimeSeconds, long expectedSeconds)
{
- MediaStream targetStream = new () { Type = MediaStreamType.Video, Index = 0 };
+ MediaStream targetStream = new() { Type = MediaStreamType.Video, Index = 0 };
var input = new Movie
{
DefaultVideoStreamIndex = 0,
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
index 6337dea41..4c7c56311 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Data/SqliteItemRepositoryTests.cs
@@ -173,7 +173,7 @@ namespace Jellyfin.Server.Implementations.Tests.Data
"/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg*637452096478512963*Primary*1920*1080*WjQbtJtSO8nhNZ%L_Io#R/oaS6o}-;adXAoIn7j[%hW9s:WGw[nN|test|1234||ss",
new ItemImageInfo[]
{
- new ()
+ new()
{
Path = "/mnt/series/Family Guy/Season 1/Family Guy - S01E01-thumb.jpg",
Type = ImageType.Primary,
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
index 028ebdf55..f9228b1a7 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
+++ b/tests/Jellyfin.Server.Implementations.Tests/Jellyfin.Server.Implementations.Tests.csproj
@@ -32,7 +32,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/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
index 362c3216f..5c7c983c2 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/EpisodeResolverTest.cs
@@ -14,7 +14,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
public class EpisodeResolverTest
{
- private static readonly NamingOptions _namingOptions = new ();
+ private static readonly NamingOptions _namingOptions = new();
[Fact]
public void Resolve_GivenVideoInExtrasFolder_DoesNotResolveToEpisode()
@@ -65,7 +65,7 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
}
- protected override TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) => new ();
+ protected override TVideoType ResolveVideo<TVideoType>(ItemResolveArgs args, bool parseName) => new();
}
}
}
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
index b29426d85..de4421320 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/LibraryManager/FindExtrasTests.cs
@@ -1,16 +1,18 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
using System.IO;
using System.Linq;
using AutoFixture;
using AutoFixture.AutoMoq;
using Emby.Naming.Common;
-using Emby.Server.Implementations.Library.Resolvers;
using Emby.Server.Implementations.Library.Resolvers.Audio;
using MediaBrowser.Controller.Configuration;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Entities.Movies;
using MediaBrowser.Controller.Entities.TV;
using MediaBrowser.Controller.Library;
+using MediaBrowser.Controller.Persistence;
using MediaBrowser.Controller.Providers;
using MediaBrowser.Controller.Resolvers;
using MediaBrowser.Controller.Sorting;
@@ -32,11 +34,13 @@ public class FindExtrasTests
fixture.Register(() => new NamingOptions());
var configMock = fixture.Freeze<Mock<IServerConfigurationManager>>();
configMock.Setup(c => c.ApplicationPaths.ProgramDataPath).Returns("/data");
+ var itemRepository = fixture.Freeze<Mock<IItemRepository>>();
+ itemRepository.Setup(i => i.RetrieveItem(It.IsAny<Guid>())).Returns<BaseItem>(null);
_fileSystemMock = fixture.Freeze<Mock<IFileSystem>>();
_fileSystemMock.Setup(f => f.GetFileInfo(It.IsAny<string>())).Returns<string>(path => new FileSystemMetadata { FullName = path });
_libraryManager = fixture.Build<Emby.Server.Implementations.Library.LibraryManager>().Do(s => s.AddParts(
fixture.Create<IEnumerable<IResolverIgnoreRule>>(),
- new List<IItemResolver> { new GenericVideoResolver<Video>(fixture.Create<NamingOptions>()), new AudioResolver(fixture.Create<NamingOptions>()) },
+ new List<IItemResolver> { new AudioResolver(fixture.Create<NamingOptions>()) },
fixture.Create<IEnumerable<IIntroProvider>>(),
fixture.Create<IEnumerable<IBaseItemComparer>>(),
fixture.Create<IEnumerable<ILibraryPostScanTask>>()))
@@ -69,6 +73,7 @@ public class FindExtrasTests
Assert.Equal(2, extras.Count);
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[0].GetType());
Assert.Equal(ExtraType.Sample, extras[1].ExtraType);
}
@@ -97,13 +102,13 @@ public class FindExtrasTests
false))
.Returns(new List<FileSystemMetadata>
{
- new ()
+ new()
{
FullName = "/movies/Up/trailers/some trailer.mkv",
Name = "some trailer.mkv",
IsDirectory = false
}
- });
+ }).Verifiable();
_fileSystemMock.Setup(f => f.GetFiles(
"/movies/Up/behind the scenes",
@@ -112,13 +117,13 @@ public class FindExtrasTests
false))
.Returns(new List<FileSystemMetadata>
{
- new ()
+ new()
{
FullName = "/movies/Up/behind the scenes/the making of Up.mkv",
Name = "the making of Up.mkv",
IsDirectory = false
}
- });
+ }).Verifiable();
_fileSystemMock.Setup(f => f.GetFiles(
"/movies/Up/theme-music",
@@ -127,30 +132,35 @@ public class FindExtrasTests
false))
.Returns(new List<FileSystemMetadata>
{
- new ()
+ new()
{
FullName = "/movies/Up/theme-music/theme2.mp3",
Name = "theme2.mp3",
IsDirectory = false
}
- });
+ }).Verifiable();
var files = paths.Select(p => new FileSystemMetadata
{
FullName = p,
Name = Path.GetFileName(p),
- IsDirectory = string.IsNullOrEmpty(Path.GetExtension(p))
+ IsDirectory = !Path.HasExtension(p)
}).ToList();
var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+ _fileSystemMock.Verify();
Assert.Equal(6, extras.Count);
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[0].GetType());
Assert.Equal(ExtraType.Trailer, extras[1].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[1].GetType());
Assert.Equal(ExtraType.BehindTheScenes, extras[2].ExtraType);
Assert.Equal(ExtraType.Sample, extras[3].ExtraType);
Assert.Equal(ExtraType.ThemeSong, extras[4].ExtraType);
+ Assert.Equal(typeof(Audio), extras[4].GetType());
Assert.Equal(ExtraType.ThemeSong, extras[5].ExtraType);
+ Assert.Equal(typeof(Audio), extras[5].GetType());
}
[Fact]
@@ -174,6 +184,7 @@ public class FindExtrasTests
Assert.Single(extras);
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[0].GetType());
Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path);
}
@@ -200,11 +211,52 @@ public class FindExtrasTests
Assert.Single(extras);
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[0].GetType());
Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
Assert.Equal("/movies/Up/trailer.mkv", extras[0].Path);
}
[Fact]
+ public void FindExtras_WrongExtensions_FindsNoExtras()
+ {
+ var owner = new Movie { Name = "Up", Path = "/movies/Up/Up.mkv" };
+ var paths = new List<string>
+ {
+ "/movies/Up/Up.mkv",
+ "/movies/Up/trailer.noext",
+ "/movies/Up/theme.png",
+ "/movies/Up/trailers"
+ };
+
+ var files = paths.Select(p => new FileSystemMetadata
+ {
+ FullName = p,
+ Name = Path.GetFileName(p),
+ IsDirectory = !Path.HasExtension(p)
+ }).ToList();
+
+ _fileSystemMock.Setup(f => f.GetFiles(
+ "/movies/Up/trailers",
+ It.IsAny<string[]>(),
+ false,
+ false))
+ .Returns(new List<FileSystemMetadata>
+ {
+ new()
+ {
+ FullName = "/movies/Up/trailers/trailer.jpg",
+ Name = "trailer.jpg",
+ IsDirectory = false
+ }
+ }).Verifiable();
+
+ var extras = _libraryManager.FindExtras(owner, files, new DirectoryService(_fileSystemMock.Object)).OrderBy(e => e.ExtraType).ToList();
+
+ _fileSystemMock.Verify();
+ Assert.Empty(extras);
+ }
+
+ [Fact]
public void FindExtras_SeriesWithTrailers_FindsCorrectExtras()
{
var owner = new Series { Name = "Dexter", Path = "/series/Dexter" };
@@ -225,6 +277,7 @@ public class FindExtrasTests
Assert.Equal(2, extras.Count);
Assert.Equal(ExtraType.Trailer, extras[0].ExtraType);
+ Assert.Equal(typeof(Trailer), extras[0].GetType());
Assert.Equal("trailer", extras[0].FileNameWithoutExtension);
Assert.Equal("/series/Dexter/trailer.mkv", extras[0].Path);
Assert.Equal("/series/Dexter/trailers/trailer2.mkv", extras[1].Path);
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
index 54a63a5f2..be2dfe0a8 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/PathExtensionsTests.cs
@@ -8,13 +8,17 @@ namespace Jellyfin.Server.Implementations.Tests.Library
{
[Theory]
[InlineData("Superman: Red Son [imdbid=tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son [imdbid-tt10985510]", "imdbid", "tt10985510")]
[InlineData("Superman: Red Son - tt10985510", "imdbid", "tt10985510")]
[InlineData("Superman: Red Son", "imdbid", null)]
[InlineData("Superman: Red Son", "something", null)]
[InlineData("Superman: Red Son [imdbid1=tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son [imdbid1-tt11111111][imdbid=tt10985510]", "imdbid", "tt10985510")]
[InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "imdbid", "tt10985510")]
- [InlineData("Superman: Red Son [tmdbid=618355][imdbid=tt10985510]", "tmdbid", "618355")]
+ [InlineData("Superman: Red Son [tmdbid-618355][imdbid-tt10985510]", "imdbid", "tt10985510")]
+ [InlineData("Superman: Red Son [tmdbid-618355][imdbid-tt10985510]", "tmdbid", "618355")]
[InlineData("[tmdbid=618355]", "tmdbid", "618355")]
+ [InlineData("[tmdbid-618355]", "tmdbid", "618355")]
[InlineData("tmdbid=111111][tmdbid=618355]", "tmdbid", "618355")]
[InlineData("[tmdbid=618355]tmdbid=111111]", "tmdbid", "618355")]
[InlineData("tmdbid=618355]", "tmdbid", null)]
@@ -23,6 +27,8 @@ namespace Jellyfin.Server.Implementations.Tests.Library
[InlineData("tmdbid=", "tmdbid", null)]
[InlineData("tmdbid", "tmdbid", null)]
[InlineData("[tmdbid=][imdbid=tt10985510]", "tmdbid", null)]
+ [InlineData("[tmdbid-][imdbid-tt10985510]", "tmdbid", null)]
+ [InlineData("Superman: Red Son [tmdbid-618355][tmdbid=1234567]", "tmdbid", "618355")]
public void GetAttributeValue_ValidArgs_Correct(string input, string attribute, string? expectedResult)
{
Assert.Equal(expectedResult, PathExtensions.GetAttributeValue(input, attribute));
diff --git a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
index 21131eb97..79c11a865 100644
--- a/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/AuthHelper.cs
@@ -1,8 +1,8 @@
using System;
using System.Net;
using System.Net.Http;
-using System.Net.Http.Json;
using System.Net.Http.Headers;
+using System.Net.Http.Json;
using System.Text.Json;
using System.Threading.Tasks;
using Jellyfin.Api.Models.StartupDtos;
diff --git a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
index a59900b02..e8fc495f9 100644
--- a/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
+++ b/tests/Jellyfin.Server.Integration.Tests/Jellyfin.Server.Integration.Tests.csproj
@@ -29,7 +29,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/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
index ffdc04eba..1a1033443 100644
--- a/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
+++ b/tests/Jellyfin.Server.Integration.Tests/WebSocketTests.cs
@@ -26,7 +26,8 @@ namespace Jellyfin.Server.Integration.Tests
{
Scheme = "ws",
Path = "websocket"
- }.Uri, CancellationToken.None));
+ }.Uri,
+ CancellationToken.None));
}
}
}
diff --git a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
index ada9034df..b25b06c7b 100644
--- a/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
+++ b/tests/Jellyfin.Server.Tests/Jellyfin.Server.Tests.csproj
@@ -22,7 +22,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/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
index edf9e0fef..55cdfa2e0 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Jellyfin.XbmcMetadata.Tests.csproj
@@ -23,7 +23,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/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
index 7ea45d14d..7c9952030 100644
--- a/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
+++ b/tests/Jellyfin.XbmcMetadata.Tests/Parsers/MovieNfoParserTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Collections.Generic;
using System.Linq;
using System.Threading;
using Jellyfin.Data.Entities;
@@ -157,33 +158,33 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
// Images
Assert.Equal(7, result.RemoteImages.Count);
- var posters = result.RemoteImages.Where(x => x.type == ImageType.Primary).ToList();
+ var posters = result.RemoteImages.Where(x => x.Type == ImageType.Primary).ToList();
Assert.Single(posters);
- Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].url);
+ Assert.Equal("http://image.tmdb.org/t/p/original/9rtrRGeRnL0JKtu9IMBWsmlmmZz.jpg", posters[0].Url);
- var logos = result.RemoteImages.Where(x => x.type == ImageType.Logo).ToList();
+ var logos = result.RemoteImages.Where(x => x.Type == ImageType.Logo).ToList();
Assert.Single(logos);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovielogo/justice-league-5865bf95cbadb.png", logos[0].Url);
- var banners = result.RemoteImages.Where(x => x.type == ImageType.Banner).ToList();
+ var banners = result.RemoteImages.Where(x => x.Type == ImageType.Banner).ToList();
Assert.Single(banners);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebanner/justice-league-586017e95adbd.jpg", banners[0].Url);
- var thumbs = result.RemoteImages.Where(x => x.type == ImageType.Thumb).ToList();
+ var thumbs = result.RemoteImages.Where(x => x.Type == ImageType.Thumb).ToList();
Assert.Single(thumbs);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviethumb/justice-league-585fb155c3743.jpg", thumbs[0].Url);
- var art = result.RemoteImages.Where(x => x.type == ImageType.Art).ToList();
+ var art = result.RemoteImages.Where(x => x.Type == ImageType.Art).ToList();
Assert.Single(art);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/hdmovieclearart/justice-league-5865c23193041.png", art[0].Url);
- var discArt = result.RemoteImages.Where(x => x.type == ImageType.Disc).ToList();
+ var discArt = result.RemoteImages.Where(x => x.Type == ImageType.Disc).ToList();
Assert.Single(discArt);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviedisc/justice-league-5a3af26360617.png", discArt[0].Url);
- var backdrop = result.RemoteImages.Where(x => x.type == ImageType.Backdrop).ToList();
+ var backdrop = result.RemoteImages.Where(x => x.Type == ImageType.Backdrop).ToList();
Assert.Single(backdrop);
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].url);
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5793f518c6d6e.jpg", backdrop[0].Url);
// Local Image - contains only one item depending on operating system
Assert.Single(result.Images);
@@ -216,8 +217,8 @@ namespace Jellyfin.XbmcMetadata.Tests.Parsers
_parser.Fetch(result, "Test Data/Fanart.nfo", CancellationToken.None);
- Assert.Single(result.RemoteImages.Where(x => x.type == ImageType.Backdrop));
- Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.type == ImageType.Backdrop).url);
+ Assert.Single(result.RemoteImages.Where(x => x.Type == ImageType.Backdrop));
+ Assert.Equal("https://assets.fanart.tv/fanart/movies/141052/moviebackground/justice-league-5a5332c7b5e77.jpg", result.RemoteImages.First(x => x.Type == ImageType.Backdrop).Url);
}
[Fact]