From c18be5559bfd888e65b251a1347fe0cb61809418 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Thu, 4 Jan 2024 10:42:04 -0600 Subject: [PATCH] fix delete old segments (#1536) * code cleanup * ignore errors deleting old hls segments --- CHANGELOG.md | 1 + .../Channels/Queries/GetChannelPlaylist.cs | 9 ++++-- ...pportedHardwareAccelerationKindsHandler.cs | 2 +- .../CallJellyfinCollectionScannerHandler.cs | 4 ++- .../SynchronizeJellyfinCollections.cs | 3 +- .../Queries/GetExternalCollectionsHandler.cs | 4 +-- .../Commands/StartFFmpegSessionHandler.cs | 14 ++++----- .../Streaming/HlsSessionWorker.cs | 31 ++++++++++++------- .../PlayoutModeSchedulerDurationTests.cs | 8 ++--- .../Scheduling/SchedulerTestBase.cs | 4 +-- .../PlayoutScheduleItemFillGroupIndex.cs | 4 +-- ErsatzTV.Core/Domain/ProgramScheduleItem.cs | 2 ++ .../FFmpeg/FFmpegLibraryProcessService.cs | 2 +- .../Interfaces/Plex/IPlexServerApiClient.cs | 2 +- ErsatzTV.Core/Iptv/ChannelPlaylist.cs | 4 +-- ErsatzTV.Core/Scheduling/PlayoutBuilder.cs | 26 +++++++++------- .../Scheduling/PlayoutModeSchedulerBase.cs | 2 +- .../PlayoutModeSchedulerMultiple.cs | 4 +-- .../RandomizedMediaCollectionEnumerator.cs | 3 +- .../ShuffleInOrderCollectionEnumerator.cs | 3 +- .../ShuffledMediaCollectionEnumerator.cs | 3 +- .../Capabilities/FFmpegCapabilities.cs | 6 ++-- .../Capabilities/FFmpegKnownDecoder.cs | 11 +++---- .../Capabilities/FFmpegKnownEncoder.cs | 7 ++--- .../Capabilities/FFmpegKnownFilter.cs | 11 +++---- .../FFmpegKnownHardwareAcceleration.cs | 11 +++---- .../Capabilities/FFmpegKnownOption.cs | 11 +++---- .../HardwareCapabilitiesFactory.cs | 13 +++++--- .../Decoder/Cuvid/DecoderImplicitCuda.cs | 1 + ErsatzTV.FFmpeg/Filter/CropFilter.cs | 2 +- .../Filter/Cuda/ScaleCudaFilter.cs | 2 +- .../Filter/NormalizeLoudnessFilter.cs | 5 +-- ErsatzTV.FFmpeg/Filter/ScaleFilter.cs | 2 +- .../Filter/Vaapi/ScaleVaapiFilter.cs | 2 +- .../Vaapi/VaapiSubtitlePixelFormatFilter.cs | 3 +- ErsatzTV.FFmpeg/Filter/VideoFilter.cs | 7 ++--- ErsatzTV.FFmpeg/MediaStream.cs | 2 +- .../OutputFormat/OutputFormatHls.cs | 4 +-- .../Pipeline/NvidiaPipelineBuilder.cs | 10 +++--- .../Pipeline/PipelineBuilderBase.cs | 4 +-- .../Pipeline/PipelineBuilderFactory.cs | 23 +++++++------- .../Pipeline/VaapiPipelineBuilder.cs | 2 +- ...ScheduleItemFillGroupIndexConfiguration.cs | 5 +-- .../Repositories/EmbyTelevisionRepository.cs | 4 +-- .../JellyfinTelevisionRepository.cs | 6 ++-- .../Repositories/PlexTelevisionRepository.cs | 2 +- .../Data/Repositories/TelevisionRepository.cs | 2 +- .../Jellyfin/JellyfinApiClient.cs | 2 +- .../Locking/EntityLocker.cs | 6 ++-- .../Plex/IPlexServerApi.cs | 6 ++-- .../PlexCollectionItemMetadataResponse.cs | 2 +- .../Models/PlexCollectionMetadataResponse.cs | 6 ++-- .../Plex/Models/PlexMetadataResponse.cs | 2 +- ErsatzTV.Infrastructure/Plex/PlexEtag.cs | 6 ++-- .../Plex/PlexServerApiClient.cs | 10 ++++-- ...MultiEpisodeShuffleCollectionEnumerator.cs | 3 +- .../Search/ElasticSearchIndex.cs | 4 +-- .../Search/LuceneSearchIndex.cs | 7 +++-- .../Search/Models/ElasticSearchItem.cs | 4 +-- .../SynchronizeJellyfinCollections.cs | 3 +- .../SynchronizeJellyfinCollectionsHandler.cs | 6 ++-- .../SynchronizePlexCollectionsHandler.cs | 2 +- .../Core/Metadata/Nfo/ShowNfoReader.cs | 2 +- .../Core/Metadata/OtherVideoFolderScanner.cs | 3 +- .../Core/Metadata/SongFolderScanner.cs | 3 +- .../Core/Plex/PlexCollectionScanner.cs | 4 +-- ErsatzTV.Scanner/Worker.cs | 6 ++-- ErsatzTV/Pages/FFmpegEditor.razor | 2 +- ErsatzTV/Services/ScannerService.cs | 6 ++-- ErsatzTV/Services/SchedulerService.cs | 4 +-- .../ProgramScheduleItemEditViewModel.cs | 2 +- 71 files changed, 207 insertions(+), 187 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 799ad353e..74a7e7840 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix error loading path replacements when using MySql - Fix tray icon shortcut to open logs folder on Windows - Unlock playout when playout build fails +- Ignore errors deleting old HLS segments; this should improve stream reliability ### Changed - Upgrade from .NET 7 to .NET 8 diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs index faece3960..972bd0d5a 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylist.cs @@ -2,5 +2,10 @@ using ErsatzTV.Core.Iptv; namespace ErsatzTV.Application.Channels; -public record GetChannelPlaylist - (string Scheme, string Host, string BaseUrl, string Mode, string UserAgent, string AccessToken) : IRequest; +public record GetChannelPlaylist( + string Scheme, + string Host, + string BaseUrl, + string Mode, + string UserAgent, + string AccessToken) : IRequest; diff --git a/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs index 318bc3701..35923fcba 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Queries/GetSupportedHardwareAccelerationKindsHandler.cs @@ -22,7 +22,7 @@ public class _dbContextFactory = dbContextFactory; _hardwareCapabilitiesFactory = hardwareCapabilitiesFactory; } - + public async Task> Handle( GetSupportedHardwareAccelerationKinds request, CancellationToken cancellationToken) diff --git a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs index 8d2ccf586..eea784cda 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/CallJellyfinCollectionScannerHandler.cs @@ -40,7 +40,9 @@ public class CallJellyfinCollectionScannerHandler : CallLibraryScannerHandler GetLastScan(TvContext dbContext, SynchronizeJellyfinCollections request) + protected override async Task GetLastScan( + TvContext dbContext, + SynchronizeJellyfinCollections request) { DateTime minDateTime = await dbContext.JellyfinMediaSources .SelectOneAsync(l => l.Id, l => l.Id == request.JellyfinMediaSourceId) diff --git a/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs b/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs index a6737ea0b..2bb6a34a6 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs @@ -2,5 +2,6 @@ using ErsatzTV.Core; namespace ErsatzTV.Application.Jellyfin; -public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) : IRequest>, +public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) : + IRequest>, IScannerBackgroundServiceRequest; diff --git a/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs b/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs index eb4ecdfa7..7c36051d7 100644 --- a/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs +++ b/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs @@ -36,7 +36,7 @@ public class GetExternalCollectionsHandler : IRequestHandler new LibraryViewModel("Emby", 0, "Collections", 0, id, string.Empty)); } - + private static async Task> GetJellyfinExternalCollections( TvContext dbContext, CancellationToken cancellationToken) @@ -49,7 +49,7 @@ public class GetExternalCollectionsHandler : IRequestHandler new LibraryViewModel("Jellyfin", 0, "Collections", 0, id, string.Empty)); } - + private static async Task> GetPlexExternalCollections( TvContext dbContext, CancellationToken cancellationToken) diff --git a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs index 4d0c8959d..a86fe8174 100644 --- a/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs +++ b/ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs @@ -18,15 +18,15 @@ namespace ErsatzTV.Application.Streaming; public class StartFFmpegSessionHandler : IRequestHandler> { + private readonly IClient _client; private readonly IConfigElementRepository _configElementRepository; - private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly IFFmpegSegmenterService _ffmpegSegmenterService; private readonly IHlsPlaylistFilter _hlsPlaylistFilter; - private readonly IServiceScopeFactory _serviceScopeFactory; - private readonly IMediator _mediator; - private readonly IClient _client; + private readonly IHostApplicationLifetime _hostApplicationLifetime; private readonly ILocalFileSystem _localFileSystem; private readonly ILogger _logger; + private readonly IMediator _mediator; + private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger _sessionWorkerLogger; private readonly ChannelWriter _workerChannel; @@ -40,7 +40,7 @@ public class StartFFmpegSessionHandler : IRequestHandler sessionWorkerLogger, IFFmpegSegmenterService ffmpegSegmenterService, IConfigElementRepository configElementRepository, - IHostApplicationLifetime hostApplicationLifetime, + IHostApplicationLifetime hostApplicationLifetime, ChannelWriter workerChannel) { _hlsPlaylistFilter = hlsPlaylistFilter; @@ -73,7 +73,7 @@ public class StartFFmpegSessionHandler : IRequestHandler targetFramerate = await _mediator.Send( new GetChannelFramerate(request.ChannelNumber), cancellationToken); - + var worker = new HlsSessionWorker( _serviceScopeFactory, _client, @@ -92,7 +92,7 @@ public class StartFFmpegSessionHandler : IRequestHandler _logger; - private readonly Option _targetFramerate; + private readonly IMediator _mediator; private readonly object _sync = new(); + private readonly Option _targetFramerate; private string _channelNumber; private bool _disposedValue; private bool _hasWrittenSegments; private DateTimeOffset _lastAccess; private DateTimeOffset _lastDelete = DateTimeOffset.MinValue; + private IServiceScope _serviceScope; private HlsSessionState _state; private Timer _timer; private DateTimeOffset _transcodedUntil; - private IServiceScope _serviceScope; public HlsSessionWorker( IServiceScopeFactory serviceScopeFactory, @@ -223,7 +223,7 @@ public class HlsSessionWorker : IHlsSessionWorker { _timer.Dispose(); _timer = null; - + _serviceScope.Dispose(); _serviceScope = null; } @@ -278,14 +278,14 @@ public class HlsSessionWorker : IHlsSessionWorker { Interlocked.Increment(ref _workAheadCount); _logger.LogInformation("HLS segmenter will work ahead for channel {Channel}", _channelNumber); - + HlsSessionState nextState = _state switch { HlsSessionState.SeekAndRealtime => HlsSessionState.SeekAndWorkAhead, HlsSessionState.ZeroAndRealtime => HlsSessionState.ZeroAndWorkAhead, _ => _state }; - + if (nextState != _state) { _logger.LogDebug("HLS session state accelerating {Last} => {Next}", _state, nextState); @@ -512,7 +512,16 @@ public class HlsSessionWorker : IHlsSessionWorker foreach (Segment segment in toDelete) { - File.Delete(segment.File); + try + { + File.Delete(segment.File); + } + catch (IOException) + { + // work around lots of: + // The process cannot access the file '...' because it is being used by another process + _logger.LogDebug("Failed to delete old segment {File}", segment.File); + } } } @@ -551,11 +560,9 @@ public class HlsSessionWorker : IHlsSessionWorker } } - private async Task GetWorkAheadLimit() - { - return await _configElementRepository.GetValue(ConfigElementKey.FFmpegWorkAheadSegmenters) + private async Task GetWorkAheadLimit() => + await _configElementRepository.GetValue(ConfigElementKey.FFmpegWorkAheadSegmenters) .Map(maybeCount => maybeCount.Match(identity, () => 1)); - } private async Task> ReadPlaylistLines(CancellationToken cancellationToken) { diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs index 0290bcc92..e3f7d7404 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs @@ -17,7 +17,7 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase private CancellationToken _cancellationToken; private readonly ILogger _logger; - + public PlayoutModeSchedulerDurationTests() { Log.Logger = new LoggerConfiguration() @@ -725,7 +725,7 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase playoutItems[6].FillerKind.Should().Be(FillerKind.Fallback); playoutItems[6].GuideFinish.HasValue.Should().BeFalse(); } - + [Test] public void Should_Not_Have_Gap_With_Post_Roll_Pad_And_Fallback_Filler() { @@ -742,7 +742,7 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase { 10, TimeSpan.Parse("00:00:31.5791160") }, { 11, TimeSpan.Parse("00:00:31.2540360") }, { 12, TimeSpan.Parse("00:00:36.2231070") }, - { 13, TimeSpan.Parse("00:02:00.0471430") }, + { 13, TimeSpan.Parse("00:02:00.0471430") } }); Collection collectionThree = TwoItemCollection(14, 15, TimeSpan.Parse("00:00:55.6349890")); @@ -820,7 +820,7 @@ public class PlayoutModeSchedulerDurationTests : SchedulerTestBase playoutBuilderState.CurrentTime.Should().Be(startState.CurrentTime.AddMinutes(30)); playoutItems.Last().FinishOffset.Should().Be(playoutBuilderState.CurrentTime); - + // THIS IS THE KEY TEST - needs to be exactly 30 minutes (playoutItems.Last().FinishOffset - playoutItems.First().StartOffset).Should().Be(TimeSpan.FromMinutes(30)); diff --git a/ErsatzTV.Core.Tests/Scheduling/SchedulerTestBase.cs b/ErsatzTV.Core.Tests/Scheduling/SchedulerTestBase.cs index c8bd33784..0dded0354 100644 --- a/ErsatzTV.Core.Tests/Scheduling/SchedulerTestBase.cs +++ b/ErsatzTV.Core.Tests/Scheduling/SchedulerTestBase.cs @@ -45,7 +45,7 @@ public abstract class SchedulerTestBase { CollectionKey.ForFillerPreset(fillerPreset), enumerator2 }, { CollectionKey.ForFillerPreset(fillerPreset2), enumerator3 } }; - + protected static Dictionary CollectionEnumerators( ProgramScheduleItem scheduleItem, IMediaCollectionEnumerator enumerator1, @@ -98,7 +98,7 @@ public abstract class SchedulerTestBase TestMovie(id2, duration, new DateTime(2020, 1, 2), chapterCount) } }; - + protected static Collection CollectionOf(IDictionary idsAndDurations, int chapterCount = 0) { var mediaItems = new List(); diff --git a/ErsatzTV.Core/Domain/PlayoutScheduleItemFillGroupIndex.cs b/ErsatzTV.Core/Domain/PlayoutScheduleItemFillGroupIndex.cs index c90adadc6..6fc52ba06 100644 --- a/ErsatzTV.Core/Domain/PlayoutScheduleItemFillGroupIndex.cs +++ b/ErsatzTV.Core/Domain/PlayoutScheduleItemFillGroupIndex.cs @@ -10,9 +10,9 @@ public class PlayoutScheduleItemFillGroupIndex [NotLogged] public Playout Playout { get; set; } - + public int ProgramScheduleItemId { get; set; } - + [NotLogged] public ProgramScheduleItem ProgramScheduleItem { get; set; } diff --git a/ErsatzTV.Core/Domain/ProgramScheduleItem.cs b/ErsatzTV.Core/Domain/ProgramScheduleItem.cs index d9afc4887..23c81cdde 100644 --- a/ErsatzTV.Core/Domain/ProgramScheduleItem.cs +++ b/ErsatzTV.Core/Domain/ProgramScheduleItem.cs @@ -13,8 +13,10 @@ public abstract class ProgramScheduleItem public GuideMode GuideMode { get; set; } public string CustomTitle { get; set; } public int ProgramScheduleId { get; set; } + [JsonIgnore] public ProgramSchedule ProgramSchedule { get; set; } + public int? CollectionId { get; set; } public Collection Collection { get; set; } public int? MediaItemId { get; set; } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index ce69c60d1..a062e07ca 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -329,7 +329,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService channel.FFmpegProfile.Resolution.Width, channel.FFmpegProfile.Resolution.Height); } - + var desiredState = new FrameState( playbackSettings.RealtimeOutput, fillerKind == FillerKind.Fallback, diff --git a/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs b/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs index 3675422c7..b0be9b6d4 100644 --- a/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs +++ b/ErsatzTV.Core/Interfaces/Plex/IPlexServerApiClient.cs @@ -67,7 +67,7 @@ public interface IPlexServerApiClient PlexLibrary library, PlexConnection connection, PlexServerAuthToken token); - + IAsyncEnumerable GetAllCollections( PlexConnection connection, PlexServerAuthToken token, diff --git a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs index 91221d17a..fe5889d82 100644 --- a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs +++ b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs @@ -9,9 +9,9 @@ public class ChannelPlaylist private readonly string _accessToken; private readonly string _baseUrl; private readonly List _channels; - private readonly string _userAgent; private readonly string _host; private readonly string _scheme; + private readonly string _userAgent; public ChannelPlaylist( string scheme, @@ -58,7 +58,7 @@ public class ChannelPlaylist sb.AppendLine("#KODIPROP:inputstream.ffmpegdirect.open_mode=ffmpeg"); } - + string logo = Optional(channel.Artwork).Flatten() .Filter(a => a.ArtworkKind == ArtworkKind.Logo) .HeadOrNone() diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index 04c913c1a..299598dde 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -424,11 +424,12 @@ public class PlayoutBuilder : IPlayoutBuilder cancellationToken); collectionEnumerators.Add(collectionKey, enumerator); } - + var collectionItemCount = collectionMediaItems.Map((k, v) => (k, v.Count)).Values.ToDictionary(); - + var scheduleItemsFillGroupEnumerators = new Dictionary(); - foreach (ProgramScheduleItem scheduleItem in sortedScheduleItems.Where(si => si.FillWithGroupMode is not FillWithGroupMode.None)) + foreach (ProgramScheduleItem scheduleItem in sortedScheduleItems.Where( + si => si.FillWithGroupMode is not FillWithGroupMode.None)) { var collectionKey = CollectionKey.ForScheduleItem(scheduleItem); List mediaItems = await MediaItemsForCollection.Collect( @@ -439,14 +440,15 @@ public class PlayoutBuilder : IPlayoutBuilder var fakeCollections = _mediaCollectionRepository.GroupIntoFakeCollections(mediaItems) .Filter(c => c.ShowId > 0 || c.ArtistId > 0) .ToList(); - List fakeScheduleItems = []; - + List fakeScheduleItems = [] + ; + // this will be used to clone a schedule item MethodInfo generic = typeof(JsonConvert).GetMethods() .FirstOrDefault( x => x.Name.Equals("DeserializeObject", StringComparison.OrdinalIgnoreCase) && x.IsGenericMethod && x.GetParameters().Length == 1)?.MakeGenericMethod(scheduleItem.GetType()); - + foreach (CollectionWithItems fakeCollection in fakeCollections) { var key = new CollectionKey @@ -473,7 +475,7 @@ public class PlayoutBuilder : IPlayoutBuilder cancellationToken); collectionEnumerators.Add(key, enumerator); - + // this makes multiple (0) work - since it needs the number of items in the collection collectionItemCount.Add(key, fakeCollection.MediaItems.Count); } @@ -572,7 +574,7 @@ public class PlayoutBuilder : IPlayoutBuilder // get the schedule item out of the sorted list ProgramScheduleItem scheduleItem = playoutBuilderState.ScheduleItemsEnumerator.Current; - + // replace with the fake schedule item when filling with group if (scheduleItem.FillWithGroupMode is not FillWithGroupMode.None) { @@ -672,7 +674,7 @@ public class PlayoutBuilder : IPlayoutBuilder activeSchedule, collectionEnumerators, saveAnchorDate); - + // build fill group indices playout.FillGroupIndices = BuildFillGroupIndices(playout, scheduleItemsFillGroupEnumerators); @@ -684,7 +686,7 @@ public class PlayoutBuilder : IPlayoutBuilder Dictionary scheduleItemsFillGroupEnumerators) { var result = playout.FillGroupIndices.ToList(); - + foreach ((int programScheduleItemId, IScheduleItemsEnumerator enumerator) in scheduleItemsFillGroupEnumerators) { Option maybeFgi = Optional( @@ -694,7 +696,7 @@ public class PlayoutBuilder : IPlayoutBuilder { fgi.EnumeratorState = enumerator.State; } - + if (maybeFgi.IsNone) { var fgi = new PlayoutScheduleItemFillGroupIndex @@ -703,7 +705,7 @@ public class PlayoutBuilder : IPlayoutBuilder ProgramScheduleItemId = programScheduleItemId, EnumeratorState = enumerator.State }; - + result.Add(fgi); } } diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs index 0f5a4bfc3..2a1fa30ec 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs @@ -223,7 +223,7 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe Logger.LogError("Multiple pad-to-nearest-minute values are invalid; no filler will be used"); return new List { playoutItem }; } - + // missing pad-to-nearest-minute value is invalid; use no filler FillerPreset invalidPadFiller = allFiller .FirstOrDefault(f => f.FillerMode == FillerMode.Pad && f.PadToNearestMinute.HasValue == false); diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs index 8240b02b4..093f58e8e 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs @@ -11,10 +11,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase _collectionItemCount; public PlayoutModeSchedulerMultiple(Map collectionItemCount, ILogger logger) - : base(logger) - { + : base(logger) => _collectionItemCount = collectionItemCount; - } public override Tuple> Schedule( PlayoutBuilderState playoutBuilderState, diff --git a/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs index 6cbcd4928..69b16d3a5 100644 --- a/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs @@ -15,7 +15,8 @@ public class RandomizedMediaCollectionEnumerator : IMediaCollectionEnumerator { _mediaItems = mediaItems; _lazyMinimumDuration = - new Lazy>(() => _mediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + new Lazy>( + () => _mediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); _random = new Random(state.Seed); State = new CollectionEnumeratorState { Seed = state.Seed }; diff --git a/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs index 0112f077e..09a413da9 100644 --- a/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs @@ -34,7 +34,8 @@ public class ShuffleInOrderCollectionEnumerator : IMediaCollectionEnumerator _random = new Random(state.Seed); _shuffled = Shuffle(_collections, _random); _lazyMinimumDuration = - new Lazy>(() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + new Lazy>( + () => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); State = new CollectionEnumeratorState { Seed = state.Seed }; while (State.Index < state.Index) diff --git a/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs index 48a9a9454..0c3870610 100644 --- a/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs @@ -31,7 +31,8 @@ public class ShuffledMediaCollectionEnumerator : IMediaCollectionEnumerator _random = new CloneableRandom(state.Seed); _shuffled = Shuffle(_mediaItems, _random); _lazyMinimumDuration = - new Lazy>(() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + new Lazy>( + () => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); State = new CollectionEnumeratorState { Seed = state.Seed }; while (State.Index < state.Index) diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs index eeb5b60fd..c0fe272c6 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs @@ -5,10 +5,10 @@ namespace ErsatzTV.FFmpeg.Capabilities; public class FFmpegCapabilities : IFFmpegCapabilities { - private readonly IReadOnlySet _ffmpegHardwareAccelerations; private readonly IReadOnlySet _ffmpegDecoders; private readonly IReadOnlySet _ffmpegEncoders; private readonly IReadOnlySet _ffmpegFilters; + private readonly IReadOnlySet _ffmpegHardwareAccelerations; private readonly IReadOnlySet _ffmpegOptions; public FFmpegCapabilities( @@ -32,8 +32,8 @@ public class FFmpegCapabilities : IFFmpegCapabilities { return _ffmpegEncoders.Any( e => e.EndsWith($"_{FFmpegKnownHardwareAcceleration.Amf.Name}", StringComparison.OrdinalIgnoreCase)); - } - + } + Option maybeAccelToCheck = hardwareAccelerationMode switch { HardwareAccelerationMode.Nvenc => FFmpegKnownHardwareAcceleration.Cuda, diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs index 37ce9cb6f..aac3ed8e6 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownDecoder.cs @@ -2,13 +2,6 @@ namespace ErsatzTV.FFmpeg.Capabilities; public record FFmpegKnownDecoder { - public string Name { get; } - - private FFmpegKnownDecoder(string Name) - { - this.Name = Name; - } - public static readonly FFmpegKnownDecoder Av1Cuvid = new("av1_cuvid"); public static readonly FFmpegKnownDecoder H264Cuvid = new("h264_cuvid"); public static readonly FFmpegKnownDecoder HevcCuvid = new("hevc_cuvid"); @@ -17,6 +10,10 @@ public record FFmpegKnownDecoder public static readonly FFmpegKnownDecoder Vc1Cuvid = new("vc1_cuvid"); public static readonly FFmpegKnownDecoder Vp9Cuvid = new("vp9_cuvid"); + private FFmpegKnownDecoder(string Name) => this.Name = Name; + + public string Name { get; } + public static IList AllDecoders => new[] { diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs index cd7e1cc04..8163a6d00 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs @@ -2,12 +2,9 @@ namespace ErsatzTV.FFmpeg.Capabilities; public record FFmpegKnownEncoder { - public string Name { get; } + private FFmpegKnownEncoder(string Name) => this.Name = Name; - private FFmpegKnownEncoder(string Name) - { - this.Name = Name; - } + public string Name { get; } // only list the encoders that we actually check for public static IList AllEncoders => diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs index 5a2852ab0..66a159bb6 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs @@ -2,15 +2,12 @@ namespace ErsatzTV.FFmpeg.Capabilities; public record FFmpegKnownFilter { - public string Name { get; } - - private FFmpegKnownFilter(string Name) - { - this.Name = Name; - } - public static readonly FFmpegKnownFilter ScaleNpp = new("scale_npp"); + private FFmpegKnownFilter(string Name) => this.Name = Name; + + public string Name { get; } + public static IList AllFilters => new[] { diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs index 6cab957cd..477180b80 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownHardwareAcceleration.cs @@ -2,19 +2,16 @@ namespace ErsatzTV.FFmpeg.Capabilities; public record FFmpegKnownHardwareAcceleration { - public string Name { get; } - - private FFmpegKnownHardwareAcceleration(string Name) - { - this.Name = Name; - } - public static readonly FFmpegKnownHardwareAcceleration Amf = new("amf"); public static readonly FFmpegKnownHardwareAcceleration Cuda = new("cuda"); public static readonly FFmpegKnownHardwareAcceleration Qsv = new("qsv"); public static readonly FFmpegKnownHardwareAcceleration Vaapi = new("vaapi"); public static readonly FFmpegKnownHardwareAcceleration VideoToolbox = new("videotoolbox"); + private FFmpegKnownHardwareAcceleration(string Name) => this.Name = Name; + + public string Name { get; } + public static IList AllAccels => new[] { diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownOption.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownOption.cs index 6835a6666..26520002e 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownOption.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownOption.cs @@ -6,15 +6,12 @@ namespace ErsatzTV.FFmpeg.Capabilities; [SuppressMessage("ReSharper", "StringLiteralTypo")] public record FFmpegKnownOption { - public string Name { get; } - - private FFmpegKnownOption(string Name) - { - this.Name = Name; - } - public static readonly FFmpegKnownOption ReadrateInitialBurst = new("readrate_initial_burst"); + private FFmpegKnownOption(string Name) => this.Name = Name; + + public string Name { get; } + public static IList AllOptions => new[] { diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index 3d1c26036..016a8fb77 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -18,7 +18,10 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory { private const string ArchitectureCacheKey = "ffmpeg.hardware.nvidia.architecture"; private const string ModelCacheKey = "ffmpeg.hardware.nvidia.model"; - private static readonly CompositeFormat VaapiCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.vaapi.{0}.{1}"); + + private static readonly CompositeFormat + VaapiCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.vaapi.{0}.{1}"); + private static readonly CompositeFormat QsvCacheKeyFormat = CompositeFormat.Parse("ffmpeg.hardware.qsv.{0}"); private static readonly CompositeFormat FFmpegCapabilitiesCacheKeyFormat = CompositeFormat.Parse("ffmpeg.{0}"); @@ -86,7 +89,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory { return new NoHardwareCapabilities(); } - + if (!ffmpegCapabilities.HasHardwareAcceleration(hardwareAccelerationMode)) { _logger.LogWarning( @@ -95,7 +98,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory return new NoHardwareCapabilities(); } - + return hardwareAccelerationMode switch { HardwareAccelerationMode.Nvenc => await GetNvidiaCapabilities(ffmpegPath, ffmpegCapabilities), @@ -135,7 +138,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory var arguments = option.GlobalOptions.ToList(); arguments.AddRange(QsvArguments); - + BufferedCommandResult result = await Cli.Wrap(ffmpegPath) .WithArguments(arguments) .WithValidation(CommandResultValidation.None) @@ -360,7 +363,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory "Detected {Count} VAAPI profile entrypoints for using QSV device {Device}", profileEntrypoints.Count, device); - + _memoryCache.Set(cacheKey, profileEntrypoints); return new VaapiHardwareCapabilities(profileEntrypoints, _logger); } diff --git a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderImplicitCuda.cs b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderImplicitCuda.cs index 5e6ba3748..17a7a3407 100644 --- a/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderImplicitCuda.cs +++ b/ErsatzTV.FFmpeg/Decoder/Cuvid/DecoderImplicitCuda.cs @@ -4,6 +4,7 @@ public class DecoderImplicitCuda : DecoderBase { protected override FrameDataLocation OutputFrameDataLocation => FrameDataLocation.Hardware; public override string Name => string.Empty; + public override string[] InputOptions(InputFile inputFile) => new[] { diff --git a/ErsatzTV.FFmpeg/Filter/CropFilter.cs b/ErsatzTV.FFmpeg/Filter/CropFilter.cs index cf269c1bd..953e9a994 100644 --- a/ErsatzTV.FFmpeg/Filter/CropFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/CropFilter.cs @@ -4,8 +4,8 @@ namespace ErsatzTV.FFmpeg.Filter; public class CropFilter : BaseFilter { - private readonly FrameState _currentState; private readonly FrameSize _croppedSize; + private readonly FrameState _currentState; public CropFilter(FrameState currentState, FrameSize croppedSize) { diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs index 6f9fb3801..4e6035d16 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs @@ -4,10 +4,10 @@ namespace ErsatzTV.FFmpeg.Filter.Cuda; public class ScaleCudaFilter : BaseFilter { + private readonly Option _croppedSize; private readonly FrameState _currentState; private readonly bool _isAnamorphicEdgeCase; private readonly FrameSize _paddedSize; - private readonly Option _croppedSize; private readonly FrameSize _scaledSize; public ScaleCudaFilter( diff --git a/ErsatzTV.FFmpeg/Filter/NormalizeLoudnessFilter.cs b/ErsatzTV.FFmpeg/Filter/NormalizeLoudnessFilter.cs index e54d6d87a..6cc1ca987 100644 --- a/ErsatzTV.FFmpeg/Filter/NormalizeLoudnessFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/NormalizeLoudnessFilter.cs @@ -4,10 +4,7 @@ public class NormalizeLoudnessFilter : BaseFilter { private readonly AudioFilter _loudnessFilter; - public NormalizeLoudnessFilter(AudioFilter loudnessFilter) - { - _loudnessFilter = loudnessFilter; - } + public NormalizeLoudnessFilter(AudioFilter loudnessFilter) => _loudnessFilter = loudnessFilter; public override string Filter => _loudnessFilter switch { diff --git a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs index 2cf55a1d1..cb6fabe4b 100644 --- a/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ScaleFilter.cs @@ -4,10 +4,10 @@ namespace ErsatzTV.FFmpeg.Filter; public class ScaleFilter : BaseFilter { + private readonly Option _croppedSize; private readonly FrameState _currentState; private readonly bool _isAnamorphicEdgeCase; private readonly FrameSize _paddedSize; - private readonly Option _croppedSize; private readonly FrameSize _scaledSize; public ScaleFilter( diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs index ff374b3f2..89514c7fc 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/ScaleVaapiFilter.cs @@ -4,10 +4,10 @@ namespace ErsatzTV.FFmpeg.Filter.Vaapi; public class ScaleVaapiFilter : BaseFilter { + private readonly Option _croppedSize; private readonly FrameState _currentState; private readonly bool _isAnamorphicEdgeCase; private readonly FrameSize _paddedSize; - private readonly Option _croppedSize; private readonly FrameSize _scaledSize; public ScaleVaapiFilter( diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiSubtitlePixelFormatFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiSubtitlePixelFormatFilter.cs index 36d027b91..f52720370 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiSubtitlePixelFormatFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/VaapiSubtitlePixelFormatFilter.cs @@ -2,7 +2,6 @@ public class VaapiSubtitlePixelFormatFilter : BaseFilter { - public override FrameState NextState(FrameState currentState) => currentState; - public override string Filter => "format=vaapi|yuva420p|yuva444p|yuva422p|rgba|abgr|bgra|gbrap|ya8"; + public override FrameState NextState(FrameState currentState) => currentState; } diff --git a/ErsatzTV.FFmpeg/Filter/VideoFilter.cs b/ErsatzTV.FFmpeg/Filter/VideoFilter.cs index 2f7d0151c..9a8528085 100644 --- a/ErsatzTV.FFmpeg/Filter/VideoFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/VideoFilter.cs @@ -16,9 +16,8 @@ public class VideoFilter : IPipelineStep public FrameState NextState(FrameState currentState) => currentState; private string[] Arguments() => - new [] - { - "-vf", + [ + "-vf", string.Join(",", _filterSteps.Map(fs => fs.Filter)) - }; + ]; } diff --git a/ErsatzTV.FFmpeg/MediaStream.cs b/ErsatzTV.FFmpeg/MediaStream.cs index a490c9bc3..87e0b34e8 100644 --- a/ErsatzTV.FFmpeg/MediaStream.cs +++ b/ErsatzTV.FFmpeg/MediaStream.cs @@ -120,7 +120,7 @@ public record VideoStream( return result; } - + public FrameSize SquarePixelFrameSizeForCrop(FrameSize resolution) { int width = FrameSize.Width; diff --git a/ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs b/ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs index 44afb7fe5..2cecac218 100644 --- a/ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs +++ b/ErsatzTV.FFmpeg/OutputFormat/OutputFormatHls.cs @@ -6,8 +6,8 @@ public class OutputFormatHls : IPipelineStep { private readonly FrameState _desiredState; private readonly Option _mediaFrameRate; - private readonly string _playlistPath; private readonly bool _oneSecondGop; + private readonly string _playlistPath; private readonly string _segmentTemplate; public OutputFormatHls( @@ -37,7 +37,7 @@ public class OutputFormatHls : IPipelineStep int frameRate = _desiredState.FrameRate.IfNone(GetFrameRateFromMedia); int gop = _oneSecondGop ? frameRate : frameRate * SEGMENT_SECONDS; - + return new[] { "-g", $"{gop}", diff --git a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs index dddf34f54..338475371 100644 --- a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs @@ -475,7 +475,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder subtitle.FilterSteps.Add(subtitleHardwareUpload); // only scale if scaling or padding was used for main video stream - if (videoInputFile.FilterSteps.Any(s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter)) + if (videoInputFile.FilterSteps.Any( + s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter)) { var scaleFilter = new SubtitleScaleNppFilter(desiredState.PaddedSize); subtitle.FilterSteps.Add(scaleFilter); @@ -484,17 +485,18 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder else { // only scale if scaling or padding was used for main video stream - if (videoInputFile.FilterSteps.Any(s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter)) + if (videoInputFile.FilterSteps.Any( + s => s is ScaleFilter or ScaleCudaFilter { IsFormatOnly: false } or PadFilter)) { var scaleFilter = new ScaleImageFilter(desiredState.PaddedSize); subtitle.FilterSteps.Add(scaleFilter); } - + var subtitleHardwareUpload = new HardwareUploadCudaFilter( currentState with { FrameDataLocation = FrameDataLocation.Software }); subtitle.FilterSteps.Add(subtitleHardwareUpload); } - + var subtitlesFilter = new OverlaySubtitleCudaFilter(); subtitleOverlayFilterSteps.Add(subtitlesFilter); } diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 76a0aa13f..a43a27c0a 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -510,7 +510,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder FrameState desiredState, string fontsFolder, ICollection pipelineSteps); - + protected static FrameState SetCrop( VideoInputFile videoInputFile, FrameState desiredState, @@ -631,7 +631,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder AudioFilter filter = _audioInputFile .Map(a => a.DesiredState.NormalizeLoudnessFilter) .IfNone(AudioFilter.None); - + initialBurst = filter switch { AudioFilter.LoudNorm => 5, diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs index 501a20af0..9bf13b13e 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderFactory.cs @@ -72,17 +72,18 @@ public class PipelineBuilderFactory : IPipelineBuilderFactory reportsFolder, fontsFolder, _logger), - HardwareAccelerationMode.VideoToolbox when capabilities is not NoHardwareCapabilities => new VideoToolboxPipelineBuilder( - ffmpegCapabilities, - capabilities, - hardwareAccelerationMode, - videoInputFile, - audioInputFile, - watermarkInputFile, - subtitleInputFile, - reportsFolder, - fontsFolder, - _logger), + HardwareAccelerationMode.VideoToolbox when capabilities is not NoHardwareCapabilities => new + VideoToolboxPipelineBuilder( + ffmpegCapabilities, + capabilities, + hardwareAccelerationMode, + videoInputFile, + audioInputFile, + watermarkInputFile, + subtitleInputFile, + reportsFolder, + fontsFolder, + _logger), HardwareAccelerationMode.Amf when capabilities is not NoHardwareCapabilities => new AmfPipelineBuilder( ffmpegCapabilities, capabilities, diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs index ae6afc066..4ada60e91 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs @@ -87,7 +87,7 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder { pipelineSteps.Add(new NoAutoScaleOutputOption()); } - + // disable hw accel if decoder/encoder isn't supported return ffmpegState with { diff --git a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutScheduleItemFillGroupIndexConfiguration.cs b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutScheduleItemFillGroupIndexConfiguration.cs index 738337868..9cabb30db 100644 --- a/ErsatzTV.Infrastructure/Data/Configurations/PlayoutScheduleItemFillGroupIndexConfiguration.cs +++ b/ErsatzTV.Infrastructure/Data/Configurations/PlayoutScheduleItemFillGroupIndexConfiguration.cs @@ -4,12 +4,13 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders; namespace ErsatzTV.Infrastructure.Data.Configurations; -public class PlayoutScheduleItemFillGroupIndexConfiguration : IEntityTypeConfiguration +public class + PlayoutScheduleItemFillGroupIndexConfiguration : IEntityTypeConfiguration { public void Configure(EntityTypeBuilder builder) { builder.ToTable("PlayoutScheduleItemFillGroupIndex"); - + builder.OwnsOne(a => a.EnumeratorState).ToTable("FillGroupEnumeratorState"); builder.HasOne(i => i.ProgramScheduleItem) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs index 111a7a984..247f14787 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs @@ -210,7 +210,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Normal; - + Option maybeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT EmbyEpisode.Id FROM EmbyEpisode INNER JOIN MediaItem MI ON MI.Id = EmbyEpisode.Id @@ -256,7 +256,7 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); show.State = MediaItemState.Normal; - + Option maybeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT EmbyShow.Id FROM EmbyShow INNER JOIN MediaItem MI ON MI.Id = EmbyShow.Id diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs index 706c99263..b4e176809 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs @@ -214,7 +214,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); episode.State = MediaItemState.Normal; - + Option maybeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT JellyfinEpisode.Id FROM JellyfinEpisode INNER JOIN MediaItem MI ON MI.Id = JellyfinEpisode.Id @@ -237,7 +237,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); season.State = MediaItemState.Normal; - + Option maybeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT JellyfinSeason.Id FROM JellyfinSeason INNER JOIN MediaItem MI ON MI.Id = JellyfinSeason.Id @@ -260,7 +260,7 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); show.State = MediaItemState.Normal; - + Option maybeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT JellyfinShow.Id FROM JellyfinShow INNER JOIN MediaItem MI ON MI.Id = JellyfinShow.Id diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs index b7a5dc939..1cd1cfecc 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs @@ -75,7 +75,7 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); show.State = MediaItemState.Normal; - + Option maybeId = await dbContext.Connection.ExecuteScalarAsync( @"SELECT PlexShow.Id FROM PlexShow INNER JOIN MediaItem MI ON MI.Id = PlexShow.Id diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index 4a39e97be..5c60ec373 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -175,7 +175,7 @@ public class TelevisionRepository : ITelevisionRepository public async Task> GetPagedSeasons(int televisionShowId, int pageNumber, int pageSize) { var result = new List(); - + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); Option maybeShowMetadata = await dbContext.ShowMetadata diff --git a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs index ea7dccea4..4c1ec89e1 100644 --- a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs +++ b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs @@ -14,8 +14,8 @@ namespace ErsatzTV.Infrastructure.Jellyfin; public class JellyfinApiClient : IJellyfinApiClient { - private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly IFallbackMetadataProvider _fallbackMetadataProvider; + private readonly IJellyfinPathReplacementService _jellyfinPathReplacementService; private readonly ILogger _logger; private readonly IMemoryCache _memoryCache; diff --git a/ErsatzTV.Infrastructure/Locking/EntityLocker.cs b/ErsatzTV.Infrastructure/Locking/EntityLocker.cs index 7e7f04598..f4820831c 100644 --- a/ErsatzTV.Infrastructure/Locking/EntityLocker.cs +++ b/ErsatzTV.Infrastructure/Locking/EntityLocker.cs @@ -10,8 +10,8 @@ public class EntityLocker : IEntityLocker private readonly ConcurrentDictionary _lockedRemoteMediaSourceTypes = new(); private bool _embyCollections; private bool _jellyfinCollections; - private bool _plexCollections; private bool _plex; + private bool _plexCollections; private bool _trakt; public event EventHandler OnLibraryChanged; @@ -155,7 +155,7 @@ public class EntityLocker : IEntityLocker } public bool AreEmbyCollectionsLocked() => _embyCollections; - + public bool LockJellyfinCollections() { if (!_jellyfinCollections) @@ -181,7 +181,7 @@ public class EntityLocker : IEntityLocker } public bool AreJellyfinCollectionsLocked() => _jellyfinCollections; - + public bool LockPlexCollections() { if (!_plexCollections) diff --git a/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs b/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs index b2f0bee69..d892a8ffe 100644 --- a/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs +++ b/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs @@ -35,7 +35,7 @@ public interface IPlexServerApi int take, [Query] [AliasAs("X-Plex-Token")] string token); - + [Get("/library/all?type=18&X-Plex-Container-Start=0&X-Plex-Container-Size=0")] [Headers("Accept: text/xml")] public Task GetCollectionCount( @@ -52,14 +52,14 @@ public interface IPlexServerApi int take, [Query] [AliasAs("X-Plex-Token")] string token); - + [Get("/library/collections/{key}/children?X-Plex-Container-Start=0&X-Plex-Container-Size=0")] [Headers("Accept: text/xml")] public Task GetCollectionItemsCount( string key, [Query] [AliasAs("X-Plex-Token")] string token); - + [Get("/library/collections/{key}/children")] [Headers("Accept: application/json")] public Task>> diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionItemMetadataResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionItemMetadataResponse.cs index 0ed0d0fec..6022df723 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionItemMetadataResponse.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionItemMetadataResponse.cs @@ -18,7 +18,7 @@ public class PlexCollectionItemMetadataResponse [XmlAttribute("updatedAt")] public long UpdatedAt { get; set; } - + [XmlAttribute("type")] public string Type { get; set; } } diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionMetadataResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionMetadataResponse.cs index 98dd440da..25b1867b3 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionMetadataResponse.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexCollectionMetadataResponse.cs @@ -18,13 +18,13 @@ public class PlexCollectionMetadataResponse [XmlAttribute("updatedAt")] public long UpdatedAt { get; set; } - + [XmlAttribute("smart")] public string Smart { get; set; } - + [XmlAttribute("librarySectionId")] public int LibrarySectionId { get; set; } - + [XmlAttribute("childCount")] public string ChildCount { get; set; } } diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs index ea2290cce..7f2cdf574 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexMetadataResponse.cs @@ -36,7 +36,7 @@ public class PlexMetadataResponse [XmlAttribute("updatedAt")] public long UpdatedAt { get; set; } - + [XmlAttribute("index")] public int Index { get; set; } diff --git a/ErsatzTV.Infrastructure/Plex/PlexEtag.cs b/ErsatzTV.Infrastructure/Plex/PlexEtag.cs index 6971d754c..c195f7419 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexEtag.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexEtag.cs @@ -253,7 +253,7 @@ public class PlexEtag byte[] hash = SHA1.Create().ComputeHash(ms); return BitConverter.ToString(hash).Replace("-", string.Empty); } - + public string ForCollection(PlexCollectionMetadataResponse response) { using MemoryStream ms = _recyclableMemoryStreamManager.GetStream(); @@ -267,7 +267,7 @@ public class PlexEtag // collection updated at bw.Write(response.UpdatedAt); - + // collection child count bw.Write(response.ChildCount ?? "0"); @@ -296,7 +296,7 @@ public class PlexEtag Art = 21, File = 30, - + ChildCount = 40, Smart = 41 // smart collection bool } diff --git a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs index 99dfeca06..ad92d3f8f 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs @@ -53,7 +53,9 @@ public class PlexServerApiClient : IPlexServerApiClient return directory // .Filter(l => l.Hidden == 0) .Filter(l => l.Type.ToLowerInvariant() is "movie" or "show") - .Filter(l => l.Type.ToLowerInvariant() is not "movie" || (l.Agent ?? string.Empty).ToLowerInvariant() is not "com.plexapp.agents.none") + .Filter( + l => l.Type.ToLowerInvariant() is not "movie" || + (l.Agent ?? string.Empty).ToLowerInvariant() is not "com.plexapp.agents.none") .Map(Project) .Somes() .ToList(); @@ -407,7 +409,9 @@ public class PlexServerApiClient : IPlexServerApiClient _ => None }; - private Option ProjectToCollection(PlexMediaSource plexMediaSource, PlexCollectionMetadataResponse item) + private Option ProjectToCollection( + PlexMediaSource plexMediaSource, + PlexCollectionMetadataResponse item) { try { @@ -432,7 +436,7 @@ public class PlexServerApiClient : IPlexServerApiClient return None; } } - + private Option ProjectToCollectionMediaItem(PlexCollectionItemMetadataResponse item) { try diff --git a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs index 830cac9ad..407ae6b78 100644 --- a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs +++ b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs @@ -78,7 +78,8 @@ public class MultiEpisodeShuffleCollectionEnumerator : IMediaCollectionEnumerato _random = new CloneableRandom(state.Seed); _shuffled = Shuffle(_random); _lazyMinimumDuration = - new Lazy>(() => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + new Lazy>( + () => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); State = new CollectionEnumeratorState { Seed = state.Seed }; while (State.Index < state.Index) diff --git a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs index 5dd344484..2471041f5 100644 --- a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs @@ -741,7 +741,7 @@ public class ElasticSearchIndex : ISearchIndex return result; } - + private async Task> GetLanguages(ISearchRepository searchRepository, List mediaCodes) { var englishNames = new System.Collections.Generic.HashSet(); @@ -757,7 +757,7 @@ public class ElasticSearchIndex : ISearchIndex return englishNames.ToList(); } - + private static List GetLanguageTags(IEnumerable mediaVersions) => mediaVersions .Map(mv => mv.Streams.Filter(ms => ms.MediaStreamKind == MediaStreamKind.Audio).Map(ms => ms.Language)) diff --git a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs index d57022bd4..08935d91e 100644 --- a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs @@ -518,7 +518,7 @@ public sealed class LuceneSearchIndex : ISearchIndex { doc.Add(new TextField(LanguageTagField, code, Field.Store.NO)); } - + var englishNames = new System.Collections.Generic.HashSet(); foreach (string code in await searchRepository.GetAllLanguageCodes(mediaCodes)) { @@ -809,7 +809,10 @@ public sealed class LuceneSearchIndex : ISearchIndex new StringField(IdField, musicVideo.Id.ToString(CultureInfo.InvariantCulture), Field.Store.YES), new StringField(TypeField, MusicVideoType, Field.Store.YES), new TextField(TitleField, metadata.Title ?? string.Empty, Field.Store.NO), - new StringField(SortTitleField, (metadata.SortTitle ?? string.Empty).ToLowerInvariant(), Field.Store.NO), + new StringField( + SortTitleField, + (metadata.SortTitle ?? string.Empty).ToLowerInvariant(), + Field.Store.NO), new TextField(LibraryNameField, musicVideo.LibraryPath.Library.Name, Field.Store.NO), new StringField( LibraryIdField, diff --git a/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs b/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs index 5dd85dded..b084ef9ee 100644 --- a/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs +++ b/ErsatzTV.Infrastructure/Search/Models/ElasticSearchItem.cs @@ -27,13 +27,13 @@ public class ElasticSearchItem : MinimalElasticSearchItem [JsonPropertyName(LuceneSearchIndex.LanguageField)] public List Language { get; set; } - + [JsonPropertyName(LuceneSearchIndex.LanguageTagField)] public List LanguageTag { get; set; } [JsonPropertyName(LuceneSearchIndex.MinutesField)] public int Minutes { get; set; } - + [JsonPropertyName(LuceneSearchIndex.SecondsField)] public int Seconds { get; set; } diff --git a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs index d9e09331c..4c1cbb5a1 100644 --- a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs +++ b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollections.cs @@ -2,4 +2,5 @@ namespace ErsatzTV.Scanner.Application.Jellyfin; -public record SynchronizeJellyfinCollections(int JellyfinMediaSourceId, bool ForceScan) : IRequest>; +public record SynchronizeJellyfinCollections + (int JellyfinMediaSourceId, bool ForceScan) : IRequest>; diff --git a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs index e41eb03cd..85d62bd99 100644 --- a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs @@ -9,10 +9,10 @@ namespace ErsatzTV.Scanner.Application.Jellyfin; public class SynchronizeJellyfinCollectionsHandler : IRequestHandler> { + private readonly IConfigElementRepository _configElementRepository; private readonly IJellyfinSecretStore _jellyfinSecretStore; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IJellyfinCollectionScanner _scanner; - private readonly IConfigElementRepository _configElementRepository; public SynchronizeJellyfinCollectionsHandler( IMediaSourceRepository mediaSourceRepository, @@ -42,7 +42,7 @@ public class Task> mediaSource = MediaSourceMustExist(request) .BindT(MediaSourceMustHaveActiveConnection) .BindT(MediaSourceMustHaveApiKey); - + return (await mediaSource, await ValidateLibraryRefreshInterval()) .Apply( (connectionParameters, libraryRefreshInterval) => new RequestParameters( @@ -51,7 +51,7 @@ public class request.ForceScan, libraryRefreshInterval)); } - + private Task> ValidateLibraryRefreshInterval() => _configElementRepository.GetValue(ConfigElementKey.LibraryRefreshInterval) .FilterT(lri => lri is >= 0 and < 1_000_000) diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs index bd8644189..354434197 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs @@ -9,8 +9,8 @@ namespace ErsatzTV.Scanner.Application.Plex; public class SynchronizePlexCollectionsHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; - private readonly IPlexSecretStore _plexSecretStore; private readonly IMediaSourceRepository _mediaSourceRepository; + private readonly IPlexSecretStore _plexSecretStore; private readonly IPlexCollectionScanner _scanner; public SynchronizePlexCollectionsHandler( diff --git a/ErsatzTV.Scanner/Core/Metadata/Nfo/ShowNfoReader.cs b/ErsatzTV.Scanner/Core/Metadata/Nfo/ShowNfoReader.cs index 9b4372182..5d1c6d586 100644 --- a/ErsatzTV.Scanner/Core/Metadata/Nfo/ShowNfoReader.cs +++ b/ErsatzTV.Scanner/Core/Metadata/Nfo/ShowNfoReader.cs @@ -41,7 +41,7 @@ public class ShowNfoReader : NfoReader, IShowNfoReader var settings = new XmlReaderSettings { Async = true, ConformanceLevel = ConformanceLevel.Fragment }; using var reader = XmlReader.Create(input, settings); var done = false; - int showDepth = 0; + var showDepth = 0; while (!done && await reader.ReadAsync()) { diff --git a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs index d30fbb8ca..c45662814 100644 --- a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs @@ -131,7 +131,8 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan .HeadOrNone(); // skip folder if etag matches - if (allFiles.Count == 0 || await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) == + if (allFiles.Count == 0 || + await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) == etag) { continue; diff --git a/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs index e214c41ba..bd6d42039 100644 --- a/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs @@ -126,7 +126,8 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner .HeadOrNone(); // skip folder if etag matches - if (allFiles.Count == 0 || await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) == + if (allFiles.Count == 0 || + await knownFolder.Map(f => f.Etag ?? string.Empty).IfNoneAsync(string.Empty) == etag) { continue; diff --git a/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs index db2f508e4..98a617b84 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexCollectionScanner.cs @@ -10,10 +10,10 @@ namespace ErsatzTV.Scanner.Core.Plex; public class PlexCollectionScanner : IPlexCollectionScanner { - private readonly IPlexServerApiClient _plexServerApiClient; - private readonly IPlexCollectionRepository _plexCollectionRepository; private readonly ILogger _logger; private readonly IMediator _mediator; + private readonly IPlexCollectionRepository _plexCollectionRepository; + private readonly IPlexServerApiClient _plexServerApiClient; public PlexCollectionScanner( IMediator mediator, diff --git a/ErsatzTV.Scanner/Worker.cs b/ErsatzTV.Scanner/Worker.cs index c10c2d9d6..2ec3e9858 100644 --- a/ErsatzTV.Scanner/Worker.cs +++ b/ErsatzTV.Scanner/Worker.cs @@ -73,7 +73,7 @@ public class Worker : BackgroundService var scanPlexCollectionsCommand = new Command("scan-plex-collections", "Scan Plex collections"); scanPlexCollectionsCommand.AddArgument(mediaSourceIdArgument); scanPlexCollectionsCommand.AddOption(forceOption); - + var scanEmbyCommand = new Command("scan-emby", "Scan an Emby library"); scanEmbyCommand.AddArgument(libraryIdArgument); scanEmbyCommand.AddOption(forceOption); @@ -128,7 +128,7 @@ public class Worker : BackgroundService await mediator.Send(scan, context.GetCancellationToken()); } }); - + scanPlexCollectionsCommand.SetHandler( async context => { @@ -202,7 +202,7 @@ public class Worker : BackgroundService await mediator.Send(scan, context.GetCancellationToken()); } }); - + scanJellyfinCollectionsCommand.SetHandler( async context => { diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index e74563edc..bb1963d45 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -196,7 +196,7 @@ { _resolutions = restoredResolutions; } - + if (!ApplicationState.TryTakeFromJson("_hardwareAccelerationKinds", out List restoredHardwareAccelerationKinds)) { _hardwareAccelerationKinds = await _mediator.Send(new GetSupportedHardwareAccelerationKinds(), _cts.Token); diff --git a/ErsatzTV/Services/ScannerService.cs b/ErsatzTV/Services/ScannerService.cs index bdf012a85..d39f4a634 100644 --- a/ErsatzTV/Services/ScannerService.cs +++ b/ErsatzTV/Services/ScannerService.cs @@ -138,7 +138,7 @@ public class ScannerService : BackgroundService entityLocker.UnlockLibrary(request.LibraryId); } } - + private async Task SynchronizeLibraries(SynchronizePlexLibraries request, CancellationToken cancellationToken) { using IServiceScope scope = _serviceScopeFactory.CreateScope(); @@ -188,7 +188,7 @@ public class ScannerService : BackgroundService entityLocker.UnlockLibrary(request.PlexLibraryId); } } - + private async Task SynchronizePlexCollections( SynchronizePlexCollections request, CancellationToken cancellationToken) @@ -285,7 +285,7 @@ public class ScannerService : BackgroundService entityLocker.UnlockLibrary(request.JellyfinLibraryId); } } - + private async Task SynchronizeJellyfinCollections( SynchronizeJellyfinCollections request, CancellationToken cancellationToken) diff --git a/ErsatzTV/Services/SchedulerService.cs b/ErsatzTV/Services/SchedulerService.cs index b155cbe13..4cfd4fb5d 100644 --- a/ErsatzTV/Services/SchedulerService.cs +++ b/ErsatzTV/Services/SchedulerService.cs @@ -247,7 +247,7 @@ public class SchedulerService : BackgroundService cancellationToken); } } - + foreach (int mediaSourceId in mediaSourceIds) { await _scannerWorkerChannel.WriteAsync( @@ -274,7 +274,7 @@ public class SchedulerService : BackgroundService cancellationToken); } } - + foreach (int mediaSourceId in mediaSourceIds) { await _scannerWorkerChannel.WriteAsync( diff --git a/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs b/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs index 919f5297d..776780b07 100644 --- a/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs +++ b/ErsatzTV/ViewModels/ProgramScheduleItemEditViewModel.cs @@ -35,7 +35,7 @@ public class ProgramScheduleItemEditViewModel : INotifyPropertyChanged PlayoutMode is PlayoutMode.Multiple or PlayoutMode.Duration && CollectionType is ProgramScheduleItemCollectionType.Collection or ProgramScheduleItemCollectionType.MultiCollection or ProgramScheduleItemCollectionType.SmartCollection; - + public PlayoutMode PlayoutMode { get; set; } public ProgramScheduleItemCollectionType CollectionType