diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index bfbc2e133..3b7ed03ca 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,10 +3,11 @@ "isRoot": true, "tools": { "jetbrains.resharper.globaltools": { - "version": "2024.1.1", + "version": "2025.1.4", "commands": [ "jb" - ] + ], + "rollForward": false } } } \ No newline at end of file diff --git a/ErsatzTV.Application/Artists/Mapper.cs b/ErsatzTV.Application/Artists/Mapper.cs index ac6ea422c..f6c026c3e 100644 --- a/ErsatzTV.Application/Artists/Mapper.cs +++ b/ErsatzTV.Application/Artists/Mapper.cs @@ -29,9 +29,10 @@ internal static class Mapper CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); return languages - .Map( - lang => allCultures.Filter( - ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase))) + .Map(lang => allCultures.Filter(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + lang, + StringComparison.OrdinalIgnoreCase))) .Flatten() .Distinct() .ToList(); diff --git a/ErsatzTV.Application/Artworks/Queries/GetArtworkHandler.cs b/ErsatzTV.Application/Artworks/Queries/GetArtworkHandler.cs index 3bffd5035..f09acaa68 100644 --- a/ErsatzTV.Application/Artworks/Queries/GetArtworkHandler.cs +++ b/ErsatzTV.Application/Artworks/Queries/GetArtworkHandler.cs @@ -6,24 +6,25 @@ using Microsoft.EntityFrameworkCore; namespace ErsatzTV.Application.Artworks; -public class GetArtworkHandler(IDbContextFactory dbContextFactory) : IRequestHandler> +public class GetArtworkHandler(IDbContextFactory dbContextFactory) + : IRequestHandler> { private readonly IDbContextFactory _dbContextFactory = dbContextFactory; public async Task> Handle( - GetArtwork request, + GetArtwork request, CancellationToken cancellationToken) { - try { + try + { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(cancellationToken); Option artwork = await dbContext.Artwork .AsNoTracking() .SelectOneAsync(a => a.Id, a => a.Id == request.Id) .MapT(Project); - + return artwork.ToEither(BaseError.New("Artwork not found")); - } catch (Exception ex) { @@ -31,12 +32,11 @@ public class GetArtworkHandler(IDbContextFactory dbContextFactory) : } } - private static Artwork Project(Artwork artwork) - { - return new Artwork { + private static Artwork Project(Artwork artwork) => + new() + { Id = artwork.Id, Path = artwork.Path, ArtworkKind = artwork.ArtworkKind }; - } } diff --git a/ErsatzTV.Application/Channels/ChannelViewModel.cs b/ErsatzTV.Application/Channels/ChannelViewModel.cs index 31cdd3017..33e7a3a84 100644 --- a/ErsatzTV.Application/Channels/ChannelViewModel.cs +++ b/ErsatzTV.Application/Channels/ChannelViewModel.cs @@ -1,6 +1,6 @@ -using ErsatzTV.Core.Domain; using System.Net; using ErsatzTV.Application.Artworks; +using ErsatzTV.Core.Domain; namespace ErsatzTV.Application.Channels; diff --git a/ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs b/ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs index 39af5f5eb..dd3a06422 100644 --- a/ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/CreateChannelHandler.cs @@ -40,68 +40,69 @@ public class CreateChannelHandler( await FFmpegProfileMustExist(dbContext, request), await WatermarkMustExist(dbContext, request), await FillerPresetMustExist(dbContext, request)) - .Apply( - ( - name, - number, - ffmpegProfileId, - watermarkId, - fillerPresetId) => + .Apply(( + name, + number, + ffmpegProfileId, + watermarkId, + fillerPresetId) => + { + var artwork = new List(); + if (!string.IsNullOrWhiteSpace(request.Logo?.Path)) { - var artwork = new List(); - if (!string.IsNullOrWhiteSpace(request.Logo?.Path)) + string logo = request.Logo.Path; + if (logo.StartsWith("iptv/logos/", StringComparison.Ordinal)) { - string logo = request.Logo.Path; - if (logo.StartsWith("iptv/logos/", StringComparison.Ordinal)) + logo = logo.Replace("iptv/logos/", string.Empty); + } + + artwork.Add( + new Artwork { - logo = logo.Replace("iptv/logos/", string.Empty); - } + Path = logo, + ArtworkKind = ArtworkKind.Logo, + OriginalContentType = !string.IsNullOrEmpty(request.Logo.ContentType) + ? request.Logo.ContentType + : null, + DateAdded = DateTime.UtcNow, + DateUpdated = DateTime.UtcNow + }); + } - artwork.Add( - new Artwork - { - Path = logo, - ArtworkKind = ArtworkKind.Logo, - OriginalContentType = !string.IsNullOrEmpty(request.Logo.ContentType) ? request.Logo.ContentType : null, - DateAdded = DateTime.UtcNow, - DateUpdated = DateTime.UtcNow - }); - } + var channel = new Channel(Guid.NewGuid()) + { + Name = name, + Number = number, + Group = request.Group, + Categories = request.Categories, + FFmpegProfileId = ffmpegProfileId, + ProgressMode = request.ProgressMode, + StreamingMode = request.StreamingMode, + Artwork = artwork, + StreamSelectorMode = request.StreamSelectorMode, + StreamSelector = request.StreamSelector, + PreferredAudioLanguageCode = request.PreferredAudioLanguageCode, + PreferredAudioTitle = request.PreferredAudioTitle, + PreferredSubtitleLanguageCode = request.PreferredSubtitleLanguageCode, + SubtitleMode = request.SubtitleMode, + MusicVideoCreditsMode = request.MusicVideoCreditsMode, + MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate, + SongVideoMode = request.SongVideoMode, + ActiveMode = request.ActiveMode + }; - var channel = new Channel(Guid.NewGuid()) - { - Name = name, - Number = number, - Group = request.Group, - Categories = request.Categories, - FFmpegProfileId = ffmpegProfileId, - ProgressMode = request.ProgressMode, - StreamingMode = request.StreamingMode, - Artwork = artwork, - StreamSelectorMode = request.StreamSelectorMode, - StreamSelector = request.StreamSelector, - PreferredAudioLanguageCode = request.PreferredAudioLanguageCode, - PreferredAudioTitle = request.PreferredAudioTitle, - PreferredSubtitleLanguageCode = request.PreferredSubtitleLanguageCode, - SubtitleMode = request.SubtitleMode, - MusicVideoCreditsMode = request.MusicVideoCreditsMode, - MusicVideoCreditsTemplate = request.MusicVideoCreditsTemplate, - SongVideoMode = request.SongVideoMode, - ActiveMode = request.ActiveMode - }; + foreach (int id in watermarkId) + { + channel.WatermarkId = id; + } - foreach (int id in watermarkId) - { - channel.WatermarkId = id; - } + foreach (int id in fillerPresetId) + { + channel.FallbackFillerId = id; + } - foreach (int id in fillerPresetId) - { - channel.FallbackFillerId = id; - } - - return channel; - }); + return channel; + }); private static Validation ValidateName(CreateChannel createChannel) => createChannel.NotEmpty(c => c.Name) @@ -168,8 +169,7 @@ public class CreateChannelHandler( .Map(Optional) .Filter(c => c > 0) .MapT(_ => Optional(createChannel.FallbackFillerId)) - .Map( - o => o.ToValidation( - $"Fallback filler {createChannel.FallbackFillerId} does not exist.")); + .Map(o => o.ToValidation( + $"Fallback filler {createChannel.FallbackFillerId} does not exist.")); } } diff --git a/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs b/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs index afa02b579..463f7517b 100644 --- a/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs +++ b/ErsatzTV.Application/Channels/Commands/RefreshChannelDataHandler.cs @@ -296,7 +296,8 @@ public class RefreshChannelDataHandler : IRequestHandler int finishIndex = j; while (finishIndex + 1 < sorted.Count && (sorted[finishIndex + 1].GuideGroup == startItem.GuideGroup || sorted[finishIndex + 1].FillerKind is FillerKind.GuideMode - or FillerKind.PostRoll or FillerKind.Tail or FillerKind.Fallback or FillerKind.DecoDefault)) + or FillerKind.PostRoll or FillerKind.Tail + or FillerKind.Fallback or FillerKind.DecoDefault)) { finishIndex++; } diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelLineupHandler.cs b/ErsatzTV.Application/Channels/Queries/GetChannelLineupHandler.cs index 6a6cbf5a5..e3fa9d4a6 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelLineupHandler.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelLineupHandler.cs @@ -12,5 +12,6 @@ public class GetChannelLineupHandler : IRequestHandler> Handle(GetChannelLineup request, CancellationToken cancellationToken) => _channelRepository.GetAll() - .Map(channels => channels.Where(c => c.ActiveMode is ChannelActiveMode.Active).Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList()); + .Map(channels => channels.Where(c => c.ActiveMode is ChannelActiveMode.Active) + .Map(c => new LineupItem(request.Scheme, request.Host, c)).ToList()); } diff --git a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs index 18c9ece49..638038b5c 100644 --- a/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs +++ b/ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs @@ -14,14 +14,13 @@ public class GetChannelPlaylistHandler : IRequestHandler Handle(GetChannelPlaylist request, CancellationToken cancellationToken) => _channelRepository.GetAll() .Map(channels => EnsureMode(channels, request.Mode)) - .Map( - channels => new ChannelPlaylist( - request.Scheme, - request.Host, - request.BaseUrl, - channels, - request.UserAgent, - request.AccessToken)); + .Map(channels => new ChannelPlaylist( + request.Scheme, + request.Host, + request.BaseUrl, + channels, + request.UserAgent, + request.AccessToken)); private static List EnsureMode(IEnumerable channels, string mode) { diff --git a/ErsatzTV.Application/Configuration/Commands/UpdateLibraryRefreshIntervalHandler.cs b/ErsatzTV.Application/Configuration/Commands/UpdateLibraryRefreshIntervalHandler.cs index 390e2da42..3449103b6 100644 --- a/ErsatzTV.Application/Configuration/Commands/UpdateLibraryRefreshIntervalHandler.cs +++ b/ErsatzTV.Application/Configuration/Commands/UpdateLibraryRefreshIntervalHandler.cs @@ -16,10 +16,9 @@ public class UpdateLibraryRefreshIntervalHandler : UpdateLibraryRefreshInterval request, CancellationToken cancellationToken) => Validate(request) - .MapT( - _ => _configElementRepository.Upsert( - ConfigElementKey.LibraryRefreshInterval, - request.LibraryRefreshInterval)) + .MapT(_ => _configElementRepository.Upsert( + ConfigElementKey.LibraryRefreshInterval, + request.LibraryRefreshInterval)) .Bind(v => v.ToEitherAsync()); private static Task> Validate(UpdateLibraryRefreshInterval request) => diff --git a/ErsatzTV.Application/Emby/Commands/UpdateEmbyPathReplacementsHandler.cs b/ErsatzTV.Application/Emby/Commands/UpdateEmbyPathReplacementsHandler.cs index 4d40c9076..aecbcb66c 100644 --- a/ErsatzTV.Application/Emby/Commands/UpdateEmbyPathReplacementsHandler.cs +++ b/ErsatzTV.Application/Emby/Commands/UpdateEmbyPathReplacementsHandler.cs @@ -43,7 +43,6 @@ public class UpdateEmbyPathReplacementsHandler : IRequestHandler> EmbyMediaSourceMustExist( UpdateEmbyPathReplacements request) => _mediaSourceRepository.GetEmby(request.EmbyMediaSourceId) - .Map( - v => v.ToValidation( - $"Emby media source {request.EmbyMediaSourceId} does not exist.")); + .Map(v => v.ToValidation( + $"Emby media source {request.EmbyMediaSourceId} does not exist.")); } diff --git a/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs b/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs index 2a1c4e276..46d91b1e9 100644 --- a/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs +++ b/ErsatzTV.Application/Emby/Queries/GetEmbyConnectionParametersHandler.cs @@ -54,9 +54,8 @@ public class GetEmbyConnectionParametersHandler : IRequestHandler> EmbyMediaSourceMustExist() => _mediaSourceRepository.GetAllEmby().Map(list => list.HeadOrNone()) - .Map( - v => v.ToValidation( - "Emby media source does not exist.")); + .Map(v => v.ToValidation( + "Emby media source does not exist.")); private Validation MediaSourceMustHaveActiveConnection( EmbyMediaSource embyMediaSource) diff --git a/ErsatzTV.Application/ErsatzTV.Application.csproj b/ErsatzTV.Application/ErsatzTV.Application.csproj index ace50f56e..80a34e9d5 100644 --- a/ErsatzTV.Application/ErsatzTV.Application.csproj +++ b/ErsatzTV.Application/ErsatzTV.Application.csproj @@ -9,25 +9,25 @@ - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + - - + + - + \ No newline at end of file diff --git a/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs b/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs index 4f68c11d9..76863758b 100644 --- a/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs +++ b/ErsatzTV.Application/FFmpegProfiles/Commands/CreateFFmpegProfileHandler.cs @@ -42,34 +42,33 @@ public class CreateFFmpegProfileHandler : TvContext dbContext, CreateFFmpegProfile request) => (ValidateName(request), ValidateThreadCount(request), await ResolutionMustExist(dbContext, request)) - .Apply( - (name, threadCount, resolutionId) => new FFmpegProfile - { - Name = name, - ThreadCount = threadCount, - HardwareAcceleration = request.HardwareAcceleration, - VaapiDriver = request.VaapiDriver, - VaapiDevice = request.VaapiDevice, - QsvExtraHardwareFrames = request.QsvExtraHardwareFrames, - ResolutionId = resolutionId, - ScalingBehavior = request.ScalingBehavior, - VideoFormat = request.VideoFormat, - VideoProfile = request.VideoProfile, - VideoPreset = request.VideoPreset, - AllowBFrames = request.AllowBFrames, - BitDepth = request.BitDepth, - VideoBitrate = request.VideoBitrate, - VideoBufferSize = request.VideoBufferSize, - TonemapAlgorithm = request.TonemapAlgorithm, - AudioFormat = request.AudioFormat, - AudioBitrate = request.AudioBitrate, - AudioBufferSize = request.AudioBufferSize, - NormalizeLoudnessMode = request.NormalizeLoudnessMode, - AudioChannels = request.AudioChannels, - AudioSampleRate = request.AudioSampleRate, - NormalizeFramerate = request.NormalizeFramerate, - DeinterlaceVideo = request.DeinterlaceVideo - }); + .Apply((name, threadCount, resolutionId) => new FFmpegProfile + { + Name = name, + ThreadCount = threadCount, + HardwareAcceleration = request.HardwareAcceleration, + VaapiDriver = request.VaapiDriver, + VaapiDevice = request.VaapiDevice, + QsvExtraHardwareFrames = request.QsvExtraHardwareFrames, + ResolutionId = resolutionId, + ScalingBehavior = request.ScalingBehavior, + VideoFormat = request.VideoFormat, + VideoProfile = request.VideoProfile, + VideoPreset = request.VideoPreset, + AllowBFrames = request.AllowBFrames, + BitDepth = request.BitDepth, + VideoBitrate = request.VideoBitrate, + VideoBufferSize = request.VideoBufferSize, + TonemapAlgorithm = request.TonemapAlgorithm, + AudioFormat = request.AudioFormat, + AudioBitrate = request.AudioBitrate, + AudioBufferSize = request.AudioBufferSize, + NormalizeLoudnessMode = request.NormalizeLoudnessMode, + AudioChannels = request.AudioChannels, + AudioSampleRate = request.AudioSampleRate, + NormalizeFramerate = request.NormalizeFramerate, + DeinterlaceVideo = request.DeinterlaceVideo + }); private static Validation ValidateName(CreateFFmpegProfile createFFmpegProfile) => createFFmpegProfile.NotEmpty(x => x.Name) diff --git a/ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs b/ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs index 1e0fbc408..ec4aff717 100644 --- a/ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs +++ b/ErsatzTV.Application/HDHR/Commands/UpdateHDHRTunerCountHandler.cs @@ -16,10 +16,9 @@ public class UpdateHDHRTunerCountHandler : IRequestHandler Validate(request) - .MapT( - _ => _configElementRepository.Upsert( - ConfigElementKey.HDHRTunerCount, - request.TunerCount.ToString(CultureInfo.InvariantCulture))) + .MapT(_ => _configElementRepository.Upsert( + ConfigElementKey.HDHRTunerCount, + request.TunerCount.ToString(CultureInfo.InvariantCulture))) .Bind(v => v.ToEitherAsync()); private static Task> Validate(UpdateHDHRTunerCount request) => diff --git a/ErsatzTV.Application/HDHR/Queries/GetHDHRUUIDHandler.cs b/ErsatzTV.Application/HDHR/Queries/GetHDHRUUIDHandler.cs index a0382c99f..fefdbf34f 100644 --- a/ErsatzTV.Application/HDHR/Queries/GetHDHRUUIDHandler.cs +++ b/ErsatzTV.Application/HDHR/Queries/GetHDHRUUIDHandler.cs @@ -13,12 +13,11 @@ public class GetHDHRUUIDHandler : IRequestHandler public async Task Handle(GetHDHRUUID request, CancellationToken cancellationToken) { Option maybeGuid = await _configElementRepository.GetValue(ConfigElementKey.HDHRUUID); - return await maybeGuid.IfNoneAsync( - async () => - { - Guid guid = Guid.NewGuid(); - await _configElementRepository.Upsert(ConfigElementKey.HDHRUUID, guid); - return guid; - }); + return await maybeGuid.IfNoneAsync(async () => + { + var guid = Guid.NewGuid(); + await _configElementRepository.Upsert(ConfigElementKey.HDHRUUID, guid); + return guid; + }); } } diff --git a/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs b/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs index 880d6233a..a8e0acb50 100644 --- a/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs +++ b/ErsatzTV.Application/Images/Commands/SaveArtworkToDisk.cs @@ -4,4 +4,5 @@ using ErsatzTV.Core.Domain; namespace ErsatzTV.Application.Images; // ReSharper disable once SuggestBaseTypeForParameter -public record SaveArtworkToDisk(Stream Stream, ArtworkKind ArtworkKind, string ContentType) : IRequest>; +public record SaveArtworkToDisk(Stream Stream, ArtworkKind ArtworkKind, string ContentType) + : IRequest>; diff --git a/ErsatzTV.Application/Images/Commands/UpdateImageFolderDurationHandler.cs b/ErsatzTV.Application/Images/Commands/UpdateImageFolderDurationHandler.cs index 326f9df48..57d242918 100644 --- a/ErsatzTV.Application/Images/Commands/UpdateImageFolderDurationHandler.cs +++ b/ErsatzTV.Application/Images/Commands/UpdateImageFolderDurationHandler.cs @@ -97,9 +97,8 @@ public class UpdateImageFolderDurationHandler(IDbContextFactory dbCon // update all images in this folder await dbContext.ImageMetadata - .Filter( - im => im.Image.MediaVersions.Any( - mv => mv.MediaFiles.Any(mf => mf.LibraryFolderId == currentFolder.Id))) + .Filter(im => + im.Image.MediaVersions.Any(mv => mv.MediaFiles.Any(mf => mf.LibraryFolderId == currentFolder.Id))) .ExecuteUpdateAsync( setters => setters.SetProperty(im => im.DurationSeconds, effectiveDuration), cancellationToken); diff --git a/ErsatzTV.Application/Images/Queries/GetCachedImagePath.cs b/ErsatzTV.Application/Images/Queries/GetCachedImagePath.cs index fea12f3cf..1fa0d9774 100644 --- a/ErsatzTV.Application/Images/Queries/GetCachedImagePath.cs +++ b/ErsatzTV.Application/Images/Queries/GetCachedImagePath.cs @@ -3,5 +3,6 @@ using ErsatzTV.Core.Domain; namespace ErsatzTV.Application.Images; -public record GetCachedImagePath(string FileName, ArtworkKind ArtworkKind, string ContentType, int? MaxHeight = null) : IRequest< - Either>; +public record GetCachedImagePath(string FileName, ArtworkKind ArtworkKind, string ContentType, int? MaxHeight = null) + : IRequest< + Either>; diff --git a/ErsatzTV.Application/Jellyfin/Commands/UpdateJellyfinPathReplacementsHandler.cs b/ErsatzTV.Application/Jellyfin/Commands/UpdateJellyfinPathReplacementsHandler.cs index 0f4dd76de..9ac74f1eb 100644 --- a/ErsatzTV.Application/Jellyfin/Commands/UpdateJellyfinPathReplacementsHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Commands/UpdateJellyfinPathReplacementsHandler.cs @@ -43,7 +43,6 @@ public class UpdateJellyfinPathReplacementsHandler : IRequestHandler> JellyfinMediaSourceMustExist( UpdateJellyfinPathReplacements request) => _mediaSourceRepository.GetJellyfin(request.JellyfinMediaSourceId) - .Map( - v => v.ToValidation( - $"Jellyfin media source {request.JellyfinMediaSourceId} does not exist.")); + .Map(v => v.ToValidation( + $"Jellyfin media source {request.JellyfinMediaSourceId} does not exist.")); } diff --git a/ErsatzTV.Application/Jellyfin/Queries/GetJellyfinConnectionParametersHandler.cs b/ErsatzTV.Application/Jellyfin/Queries/GetJellyfinConnectionParametersHandler.cs index a22579bd2..426bc2dac 100644 --- a/ErsatzTV.Application/Jellyfin/Queries/GetJellyfinConnectionParametersHandler.cs +++ b/ErsatzTV.Application/Jellyfin/Queries/GetJellyfinConnectionParametersHandler.cs @@ -48,9 +48,8 @@ public class GetJellyfinConnectionParametersHandler : IRequestHandler> JellyfinMediaSourceMustExist() => _mediaSourceRepository.GetAllJellyfin().Map(list => list.HeadOrNone()) - .Map( - v => v.ToValidation( - "Jellyfin media source does not exist.")); + .Map(v => v.ToValidation( + "Jellyfin media source does not exist.")); private Validation MediaSourceMustHaveActiveConnection( JellyfinMediaSource jellyfinMediaSource) diff --git a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs index 54d16be61..999df9ff8 100644 --- a/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CallLibraryScannerHandler.cs @@ -54,9 +54,9 @@ public abstract class CallLibraryScannerHandler { using var forcefulCts = new CancellationTokenSource(); - await using CancellationTokenRegistration link = cancellationToken.Register( - () => forcefulCts.CancelAfter(TimeSpan.FromSeconds(10)) - ); + await using CancellationTokenRegistration link = + cancellationToken.Register(() => forcefulCts.CancelAfter(TimeSpan.FromSeconds(10)) + ); CommandResult process = await Cli.Wrap(scanner) .WithArguments(arguments) diff --git a/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs b/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs index fbaca3444..4a575af29 100644 --- a/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/CreateLocalLibraryHandler.cs @@ -64,13 +64,12 @@ public class CreateLocalLibraryHandler : LocalLibraryHandlerBase, .OrderBy(lms => lms.Id) .FirstOrDefaultAsync() .Map(Optional) - .MapT( - lms => new LocalLibrary - { - Name = request.Name, - Paths = request.Paths.Map(p => new LibraryPath { Path = p }).ToList(), - MediaKind = request.MediaKind, - MediaSourceId = lms.Id - }) + .MapT(lms => new LocalLibrary + { + Name = request.Name, + Paths = request.Paths.Map(p => new LibraryPath { Path = p }).ToList(), + MediaKind = request.MediaKind, + MediaSourceId = lms.Id + }) .Map(o => o.ToValidation("LocalMediaSource does not exist.")); } diff --git a/ErsatzTV.Application/Libraries/Commands/LocalLibraryHandlerBase.cs b/ErsatzTV.Application/Libraries/Commands/LocalLibraryHandlerBase.cs index 42ce0c08d..795d430fd 100644 --- a/ErsatzTV.Application/Libraries/Commands/LocalLibraryHandlerBase.cs +++ b/ErsatzTV.Application/Libraries/Commands/LocalLibraryHandlerBase.cs @@ -44,10 +44,10 @@ public abstract class LocalLibraryHandlerBase // Images and OtherVideos do not conflict if (isConflict) { - bool imagesAndOtherVideos = (path1.MediaKind is LibraryMediaKind.Images && - path2.MediaKind is LibraryMediaKind.OtherVideos) - || (path2.MediaKind is LibraryMediaKind.Images && - path1.MediaKind is LibraryMediaKind.OtherVideos); + bool imagesAndOtherVideos = path1.MediaKind is LibraryMediaKind.Images && + path2.MediaKind is LibraryMediaKind.OtherVideos + || path2.MediaKind is LibraryMediaKind.Images && + path1.MediaKind is LibraryMediaKind.OtherVideos; if (imagesAndOtherVideos) { diff --git a/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs b/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs index e43db01dc..63399de1c 100644 --- a/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs +++ b/ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs @@ -102,9 +102,8 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase, UpdateLocalLibrary request) => LocalLibraryMustExist(dbContext, request) .BindT(parameters => NameMustBeValid(request, parameters.Incoming).MapT(_ => parameters)) - .BindT( - parameters => PathsMustBeValid(dbContext, parameters.Incoming, parameters.Existing.Id) - .MapT(_ => parameters)); + .BindT(parameters => PathsMustBeValid(dbContext, parameters.Incoming, parameters.Existing.Id) + .MapT(_ => parameters)); private static Task> LocalLibraryMustExist( TvContext dbContext, @@ -112,19 +111,18 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase, dbContext.LocalLibraries .Include(ll => ll.Paths) .SelectOneAsync(ll => ll.Id, ll => ll.Id == request.Id) - .MapT( - existing => + .MapT(existing => + { + var incoming = new LocalLibrary { - var incoming = new LocalLibrary - { - Name = request.Name, - Paths = request.Paths.Map(p => new LibraryPath { Id = p.Id, Path = p.Path }).ToList(), - MediaKind = existing.MediaKind, - MediaSourceId = existing.Id - }; + Name = request.Name, + Paths = request.Paths.Map(p => new LibraryPath { Id = p.Id, Path = p.Path }).ToList(), + MediaKind = existing.MediaKind, + MediaSourceId = existing.Id + }; - return new Parameters(existing, incoming); - }) + return new Parameters(existing, incoming); + }) .Map(o => o.ToValidation("LocalLibrary does not exist.")); private static string NormalizePath(string path) => diff --git a/ErsatzTV.Application/Libraries/Queries/GetAllLocalLibrariesHandler.cs b/ErsatzTV.Application/Libraries/Queries/GetAllLocalLibrariesHandler.cs index 78116098f..737e7a8e2 100644 --- a/ErsatzTV.Application/Libraries/Queries/GetAllLocalLibrariesHandler.cs +++ b/ErsatzTV.Application/Libraries/Queries/GetAllLocalLibrariesHandler.cs @@ -14,10 +14,9 @@ public class GetAllLocalLibrariesHandler : IRequestHandler _libraryRepository.GetAll() - .Map( - list => list - .OfType() - .OrderBy(l => l.MediaKind) - .Map(ProjectToViewModel) - .ToList()); + .Map(list => list + .OfType() + .OrderBy(l => l.MediaKind) + .Map(ProjectToViewModel) + .ToList()); } diff --git a/ErsatzTV.Application/Libraries/Queries/GetConfiguredLibrariesHandler.cs b/ErsatzTV.Application/Libraries/Queries/GetConfiguredLibrariesHandler.cs index 554ef80a2..03e5bc5a7 100644 --- a/ErsatzTV.Application/Libraries/Queries/GetConfiguredLibrariesHandler.cs +++ b/ErsatzTV.Application/Libraries/Queries/GetConfiguredLibrariesHandler.cs @@ -15,12 +15,11 @@ public class GetConfiguredLibrariesHandler : IRequestHandler _libraryRepository.GetAll() - .Map( - list => list.Filter(ShouldIncludeLibrary) - .OrderBy(l => l.MediaSource is LocalMediaSource ? 0 : 1) - .ThenBy(l => l.GetType().Name) - .ThenBy(l => l.MediaKind) - .Map(ProjectToViewModel).ToList()); + .Map(list => list.Filter(ShouldIncludeLibrary) + .OrderBy(l => l.MediaSource is LocalMediaSource ? 0 : 1) + .ThenBy(l => l.GetType().Name) + .ThenBy(l => l.MediaKind) + .Map(ProjectToViewModel).ToList()); private static bool ShouldIncludeLibrary(Library library) => library switch diff --git a/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs b/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs index 7c36051d7..d872d8d5c 100644 --- a/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs +++ b/ErsatzTV.Application/Libraries/Queries/GetExternalCollectionsHandler.cs @@ -46,8 +46,13 @@ public class GetExternalCollectionsHandler : IRequestHandler jms.Id) .ToListAsync(cancellationToken); - return jellyfinMediaSourceIds.Map( - id => new LibraryViewModel("Jellyfin", 0, "Collections", 0, id, string.Empty)); + return jellyfinMediaSourceIds.Map(id => new LibraryViewModel( + "Jellyfin", + 0, + "Collections", + 0, + id, + string.Empty)); } private static async Task> GetPlexExternalCollections( @@ -59,7 +64,6 @@ public class GetExternalCollectionsHandler : IRequestHandler pms.Id) .ToListAsync(cancellationToken); - return plexMediaSourceIds.Map( - id => new LibraryViewModel("Plex", 0, "Collections", 0, id, string.Empty)); + return plexMediaSourceIds.Map(id => new LibraryViewModel("Plex", 0, "Collections", 0, id, string.Empty)); } } diff --git a/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs index 4bc0b2957..b83e4b4fd 100644 --- a/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs +++ b/ErsatzTV.Application/Logs/Queries/GetRecentLogEntriesHandler.cs @@ -27,9 +27,9 @@ public class GetRecentLogEntriesHandler : IRequestHandler le.Level.ToString().Contains(request.Filter, StringComparison.OrdinalIgnoreCase) || - le.Message.Contains(request.Filter, StringComparison.OrdinalIgnoreCase)); + entries = entries.Filter(le => + le.Level.ToString().Contains(request.Filter, StringComparison.OrdinalIgnoreCase) || + le.Message.Contains(request.Filter, StringComparison.OrdinalIgnoreCase)); } int count = entries.Count(); diff --git a/ErsatzTV.Application/MediaCards/Mapper.cs b/ErsatzTV.Application/MediaCards/Mapper.cs index f79c2e50c..e30e4d54f 100644 --- a/ErsatzTV.Application/MediaCards/Mapper.cs +++ b/ErsatzTV.Application/MediaCards/Mapper.cs @@ -171,8 +171,8 @@ internal static class Mapper Option maybeEmby) => new( collection.Name, - collection.MediaItems.OfType().Map( - m => ProjectToViewModel(m.MovieMetadata.Head(), maybeJellyfin, maybeEmby) with + collection.MediaItems.OfType().Map(m => + ProjectToViewModel(m.MovieMetadata.Head(), maybeJellyfin, maybeEmby) with { CustomIndex = GetCustomIndex(collection, m.Id) }).ToList(), @@ -183,13 +183,12 @@ internal static class Mapper .ToList(), // collection view doesn't use local paths collection.MediaItems.OfType() - .Map( - e => ProjectToViewModel( - e.EpisodeMetadata.Head(), - maybeJellyfin, - maybeEmby, - false, - string.Empty)) + .Map(e => ProjectToViewModel( + e.EpisodeMetadata.Head(), + maybeJellyfin, + maybeEmby, + false, + string.Empty)) .ToList(), collection.MediaItems.OfType().Map(a => ProjectToViewModel(a.ArtistMetadata.Head())).ToList(), // collection view doesn't use local paths diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddArtistToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddArtistToCollectionHandler.cs index 57ac37771..c6ca07646 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddArtistToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddArtistToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddArtistToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddArtistToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs index c20b88d54..b8751c628 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddEpisodeToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddEpisodeToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddEpisodeToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddImageToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddImageToCollectionHandler.cs index 44a2bb1e6..fae0bbf50 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddImageToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddImageToCollectionHandler.cs @@ -14,9 +14,9 @@ namespace ErsatzTV.Application.MediaCollections; public class AddImageToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddImageToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddItemsToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddItemsToCollectionHandler.cs index b15c44c80..1cf0ec36c 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddItemsToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddItemsToCollectionHandler.cs @@ -15,10 +15,10 @@ public class AddItemsToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMovieRepository _movieRepository; + private readonly ChannelWriter _searchChannel; private readonly ITelevisionRepository _televisionRepository; public AddItemsToCollectionHandler( diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs index 85dc32bda..f00ac8ef6 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddMovieToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddMovieToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddMovieToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs index bc58455cb..e82e41209 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddMusicVideoToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddMusicVideoToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddMusicVideoToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs index e2e160c2b..6a40c9bd7 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddOtherVideoToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddOtherVideoToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddOtherVideoToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs index 9366077cb..0c923218b 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddSeasonToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddSeasonToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddSeasonToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs index d811042a7..80afe1cb4 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddShowToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddShowToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddShowToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddSongToCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddSongToCollectionHandler.cs index fb210308c..1d23dc585 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddSongToCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddSongToCollectionHandler.cs @@ -15,9 +15,9 @@ public class AddSongToCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public AddSongToCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs index 4f8a0f5b3..7c02c027a 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/AddTraktListHandler.cs @@ -89,11 +89,11 @@ public partial class AddTraktListHandler : TraktCommandBase, IRequestHandler Unit.Default); } - private sealed record Parameters(string User, string List); - [GeneratedRegex(@"https:\/\/trakt\.tv\/users\/([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")] private static partial Regex UriTraktListRegex(); [GeneratedRegex(@"([\w\-_]+)\/(?:lists\/)?([\w\-_]+)")] private static partial Regex ShorthandTraktListRegex(); + + private sealed record Parameters(string User, string List); } diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs index ccd54d772..0ab8b1ed2 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateCollectionHandler.cs @@ -39,12 +39,11 @@ public class CreateCollectionHandler : private static Task> Validate( TvContext dbContext, CreateCollection request) => - ValidateName(dbContext, request).MapT( - name => new Collection - { - Name = name, - MediaItems = new List() - }); + ValidateName(dbContext, request).MapT(name => new Collection + { + Name = name, + MediaItems = new List() + }); private static async Task> ValidateName( TvContext dbContext, diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs index 25eae6b85..14e2e42af 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateMultiCollectionHandler.cs @@ -51,45 +51,42 @@ public class CreateMultiCollectionHandler : private static Task> Validate( TvContext dbContext, CreateMultiCollection request) => - ValidateName(dbContext, request).MapT( - name => new MultiCollection - { - Name = name, - MultiCollectionItems = request.Items.Bind( - i => - { - if (i.CollectionId.HasValue) + ValidateName(dbContext, request).MapT(name => new MultiCollection + { + Name = name, + MultiCollectionItems = request.Items.Bind(i => + { + if (i.CollectionId.HasValue) + { + return Some( + new MultiCollectionItem { - return Some( - new MultiCollectionItem - { - CollectionId = i.CollectionId.Value, - ScheduleAsGroup = i.ScheduleAsGroup, - PlaybackOrder = i.PlaybackOrder - }); - } + CollectionId = i.CollectionId.Value, + ScheduleAsGroup = i.ScheduleAsGroup, + PlaybackOrder = i.PlaybackOrder + }); + } - return Option.None; - }) - .ToList(), - MultiCollectionSmartItems = request.Items.Bind( - i => - { - if (i.SmartCollectionId.HasValue) + return Option.None; + }) + .ToList(), + MultiCollectionSmartItems = request.Items.Bind(i => + { + if (i.SmartCollectionId.HasValue) + { + return Some( + new MultiCollectionSmartItem { - return Some( - new MultiCollectionSmartItem - { - SmartCollectionId = i.SmartCollectionId.Value, - ScheduleAsGroup = i.ScheduleAsGroup, - PlaybackOrder = i.PlaybackOrder - }); - } + SmartCollectionId = i.SmartCollectionId.Value, + ScheduleAsGroup = i.ScheduleAsGroup, + PlaybackOrder = i.PlaybackOrder + }); + } - return Option.None; - }) - .ToList() - }); + return Option.None; + }) + .ToList() + }); private static async Task> ValidateName( TvContext dbContext, diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreatePlaylistHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreatePlaylistHandler.cs index 9465e987a..36bf0e706 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreatePlaylistHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreatePlaylistHandler.cs @@ -25,12 +25,11 @@ public class CreatePlaylistHandler(IDbContextFactory dbContextFactory } private static async Task> Validate(TvContext dbContext, CreatePlaylist request) => - await ValidatePlaylistName(dbContext, request).MapT( - name => new Playlist - { - PlaylistGroupId = request.PlaylistGroupId, - Name = name - }); + await ValidatePlaylistName(dbContext, request).MapT(name => new Playlist + { + PlaylistGroupId = request.PlaylistGroupId, + Name = name + }); private static async Task> ValidatePlaylistName( TvContext dbContext, diff --git a/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs index 53a9e9d37..8309cb6b1 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/CreateSmartCollectionHandler.cs @@ -48,12 +48,11 @@ public class CreateSmartCollectionHandler : private static Task> Validate( TvContext dbContext, CreateSmartCollection request) => - ValidateName(dbContext, request).MapT( - name => new SmartCollection - { - Name = name, - Query = request.Query - }); + ValidateName(dbContext, request).MapT(name => new SmartCollection + { + Name = name, + Query = request.Query + }); private static async Task> ValidateName( TvContext dbContext, diff --git a/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollectionHandler.cs index df8714035..b9a64cc72 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/RemoveItemsFromCollectionHandler.cs @@ -14,9 +14,9 @@ namespace ErsatzTV.Application.MediaCollections; public class RemoveItemsFromCollectionHandler : IRequestHandler> { private readonly ChannelWriter _channel; - private readonly ChannelWriter _searchChannel; private readonly IDbContextFactory _dbContextFactory; private readonly IMediaCollectionRepository _mediaCollectionRepository; + private readonly ChannelWriter _searchChannel; public RemoveItemsFromCollectionHandler( IDbContextFactory dbContextFactory, diff --git a/ErsatzTV.Application/MediaCollections/Commands/UpdateMultiCollectionHandler.cs b/ErsatzTV.Application/MediaCollections/Commands/UpdateMultiCollectionHandler.cs index 1fe93f6ab..f261dde06 100644 --- a/ErsatzTV.Application/MediaCollections/Commands/UpdateMultiCollectionHandler.cs +++ b/ErsatzTV.Application/MediaCollections/Commands/UpdateMultiCollectionHandler.cs @@ -50,15 +50,14 @@ public class UpdateMultiCollectionHandler : IRequestHandler i.CollectionId.HasValue) // ReSharper disable once PossibleInvalidOperationException .Filter(i => c.MultiCollectionItems.All(i2 => i2.CollectionId != i.CollectionId.Value)) - .Map( - i => new MultiCollectionItem - { - // ReSharper disable once PossibleInvalidOperationException - CollectionId = i.CollectionId.Value, - MultiCollectionId = c.Id, - ScheduleAsGroup = i.ScheduleAsGroup, - PlaybackOrder = i.PlaybackOrder - }) + .Map(i => new MultiCollectionItem + { + // ReSharper disable once PossibleInvalidOperationException + CollectionId = i.CollectionId.Value, + MultiCollectionId = c.Id, + ScheduleAsGroup = i.ScheduleAsGroup, + PlaybackOrder = i.PlaybackOrder + }) .ToList(); var toRemove = c.MultiCollectionItems .Filter(i => request.Items.All(i2 => i2.CollectionId != i.CollectionId)) @@ -70,8 +69,8 @@ public class UpdateMultiCollectionHandler : IRequestHandler i.CollectionId == item.CollectionId)) + foreach (UpdateMultiCollectionItem incoming in + request.Items.Filter(i => i.CollectionId == item.CollectionId)) { item.ScheduleAsGroup = incoming.ScheduleAsGroup; item.PlaybackOrder = incoming.PlaybackOrder; @@ -85,15 +84,14 @@ public class UpdateMultiCollectionHandler : IRequestHandler i.SmartCollectionId.HasValue) // ReSharper disable once PossibleInvalidOperationException .Filter(i => c.MultiCollectionSmartItems.All(i2 => i2.SmartCollectionId != i.SmartCollectionId.Value)) - .Map( - i => new MultiCollectionSmartItem - { - // ReSharper disable once PossibleInvalidOperationException - SmartCollectionId = i.SmartCollectionId.Value, - MultiCollectionId = c.Id, - ScheduleAsGroup = i.ScheduleAsGroup, - PlaybackOrder = i.PlaybackOrder - }) + .Map(i => new MultiCollectionSmartItem + { + // ReSharper disable once PossibleInvalidOperationException + SmartCollectionId = i.SmartCollectionId.Value, + MultiCollectionId = c.Id, + ScheduleAsGroup = i.ScheduleAsGroup, + PlaybackOrder = i.PlaybackOrder + }) .ToList(); var toRemoveSmart = c.MultiCollectionSmartItems .Filter(i => request.Items.All(i2 => i2.SmartCollectionId != i.SmartCollectionId)) @@ -105,8 +103,8 @@ public class UpdateMultiCollectionHandler : IRequestHandler i.SmartCollectionId == item.SmartCollectionId)) + foreach (UpdateMultiCollectionItem incoming in request.Items.Filter(i => + i.SmartCollectionId == item.SmartCollectionId)) { item.ScheduleAsGroup = incoming.ScheduleAsGroup; item.PlaybackOrder = incoming.PlaybackOrder; diff --git a/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs b/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs index 395a55aa3..449b7c55d 100644 --- a/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs +++ b/ErsatzTV.Application/MediaItems/Queries/GetAllLanguageCodesHandler.cs @@ -1,6 +1,4 @@ -using System.Globalization; -using ErsatzTV.Core.Domain; -using ErsatzTV.Core.Interfaces.Repositories; +using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; namespace ErsatzTV.Application.MediaItems; diff --git a/ErsatzTV.Application/Movies/Mapper.cs b/ErsatzTV.Application/Movies/Mapper.cs index 3f8d9db9c..461b66bb1 100644 --- a/ErsatzTV.Application/Movies/Mapper.cs +++ b/ErsatzTV.Application/Movies/Mapper.cs @@ -46,9 +46,10 @@ internal static class Mapper CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); return languageCodes - .Map( - lang => allCultures.Filter( - ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase))) + .Map(lang => allCultures.Filter(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + lang, + StringComparison.OrdinalIgnoreCase))) .Flatten() .Map(ci => ci.EnglishName) .Distinct() diff --git a/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs index 6d105e13b..40ec59efe 100644 --- a/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs @@ -19,7 +19,6 @@ public class BuildPlayoutHandler : IRequestHandler _dbContextFactory; private readonly IEntityLocker _entityLocker; @@ -27,6 +26,7 @@ public class BuildPlayoutHandler : IRequestHandler _workerChannel; + private readonly IYamlPlayoutBuilder _yamlPlayoutBuilder; public BuildPlayoutHandler( IClient client, diff --git a/ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs index 322aec87f..b8c4d9551 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs @@ -37,13 +37,12 @@ public class CreateBlockPlayoutHandler( TvContext dbContext, CreateBlockPlayout request) => (await ValidateChannel(dbContext, request), ValidatePlayoutType(request)) - .Apply( - (channel, playoutType) => new Playout - { - ChannelId = channel.Id, - ProgramSchedulePlayoutType = playoutType, - Seed = new Random().Next() - }); + .Apply((channel, playoutType) => new Playout + { + ChannelId = channel.Id, + ProgramSchedulePlayoutType = playoutType, + Seed = new Random().Next() + }); private static Task> ValidateChannel( TvContext dbContext, diff --git a/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs index a8872d7d9..7140dbd86 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateExternalJsonPlayoutHandler.cs @@ -50,13 +50,12 @@ public class CreateExternalJsonPlayoutHandler TvContext dbContext, CreateExternalJsonPlayout request) => (await ValidateChannel(dbContext, request), ValidateExternalJsonFile(request), ValidatePlayoutType(request)) - .Apply( - (channel, externalJsonFile, playoutType) => new Playout - { - ChannelId = channel.Id, - ExternalJsonFile = externalJsonFile, - ProgramSchedulePlayoutType = playoutType - }); + .Apply((channel, externalJsonFile, playoutType) => new Playout + { + ChannelId = channel.Id, + ExternalJsonFile = externalJsonFile, + ProgramSchedulePlayoutType = playoutType + }); private static Task> ValidateChannel( TvContext dbContext, diff --git a/ErsatzTV.Application/Playouts/Commands/CreateFloodPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateFloodPlayoutHandler.cs index ca8e2a34c..664e8d12a 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateFloodPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateFloodPlayoutHandler.cs @@ -41,6 +41,7 @@ public class CreateFloodPlayoutHandler : IRequestHandler (await ValidateChannel(dbContext, request), await ValidateProgramSchedule(dbContext, request), ValidatePlayoutType(request)) - .Apply( - (channel, programSchedule, playoutType) => new Playout - { - ChannelId = channel.Id, - ProgramScheduleId = programSchedule.Id, - ProgramSchedulePlayoutType = playoutType - }); + .Apply((channel, programSchedule, playoutType) => new Playout + { + ChannelId = channel.Id, + ProgramScheduleId = programSchedule.Id, + ProgramSchedulePlayoutType = playoutType + }); private static Task> ValidateChannel( TvContext dbContext, diff --git a/ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs index 26fe44f04..aacd3035a 100644 --- a/ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs @@ -50,14 +50,13 @@ public class CreateYamlPlayoutHandler TvContext dbContext, CreateYamlPlayout request) => (await ValidateChannel(dbContext, request), ValidateYamlFile(request), ValidatePlayoutType(request)) - .Apply( - (channel, externalJsonFile, playoutType) => new Playout - { - ChannelId = channel.Id, - TemplateFile = externalJsonFile, - ProgramSchedulePlayoutType = playoutType, - Seed = new Random().Next() - }); + .Apply((channel, externalJsonFile, playoutType) => new Playout + { + ChannelId = channel.Id, + TemplateFile = externalJsonFile, + ProgramSchedulePlayoutType = playoutType, + Seed = new Random().Next() + }); private static Task> ValidateChannel( TvContext dbContext, diff --git a/ErsatzTV.Application/Playouts/Commands/ResetAllPlayoutsHandler.cs b/ErsatzTV.Application/Playouts/Commands/ResetAllPlayoutsHandler.cs index 8ccca54e7..c4c8fe27b 100644 --- a/ErsatzTV.Application/Playouts/Commands/ResetAllPlayoutsHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/ResetAllPlayoutsHandler.cs @@ -26,8 +26,11 @@ public class ResetAllPlayoutsHandler( case ProgramSchedulePlayoutType.Yaml: if (!locker.IsPlayoutLocked(playout.Id)) { - await channel.WriteAsync(new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), cancellationToken); + await channel.WriteAsync( + new BuildPlayout(playout.Id, PlayoutBuildMode.Reset), + cancellationToken); } + break; case ProgramSchedulePlayoutType.ExternalJson: case ProgramSchedulePlayoutType.None: diff --git a/ErsatzTV.Application/Playouts/Mapper.cs b/ErsatzTV.Application/Playouts/Mapper.cs index d078fe3ad..e534e140c 100644 --- a/ErsatzTV.Application/Playouts/Mapper.cs +++ b/ErsatzTV.Application/Playouts/Mapper.cs @@ -1,5 +1,4 @@ -using System.Globalization; -using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Domain; namespace ErsatzTV.Application.Playouts; @@ -51,18 +50,16 @@ internal static class Mapper .Map(am => $"{am.Title} - ").IfNone(string.Empty); return mv.MusicVideoMetadata.HeadOrNone() .Map(mvm => $"{artistName}{mvm.Title}") - .Map( - s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle) - ? s - : $"{s} ({playoutItem.ChapterTitle})") + .Map(s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle) + ? s + : $"{s} ({playoutItem.ChapterTitle})") .IfNone("[unknown music video]"); case OtherVideo ov: return ov.OtherVideoMetadata.HeadOrNone() .Map(ovm => ovm.Title ?? string.Empty) - .Map( - s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle) - ? s - : $"{s} ({playoutItem.ChapterTitle})") + .Map(s => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle) + ? s + : $"{s} ({playoutItem.ChapterTitle})") .IfNone("[unknown video]"); case Song s: string songArtist = s.SongMetadata.HeadOrNone() @@ -70,10 +67,9 @@ internal static class Mapper .IfNone(string.Empty); return s.SongMetadata.HeadOrNone() .Map(sm => $"{songArtist}{sm.Title ?? string.Empty}") - .Map( - t => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle) - ? t - : $"{s} ({playoutItem.ChapterTitle})") + .Map(t => string.IsNullOrWhiteSpace(playoutItem.ChapterTitle) + ? t + : $"{s} ({playoutItem.ChapterTitle})") .IfNone("[unknown song]"); case Image i: return i.ImageMetadata.HeadOrNone().Map(im => im.Title ?? string.Empty).IfNone("[unknown image]"); diff --git a/ErsatzTV.Application/Playouts/Queries/GetAllPlayoutsHandler.cs b/ErsatzTV.Application/Playouts/Queries/GetAllPlayoutsHandler.cs index 389cde59d..d953614e9 100644 --- a/ErsatzTV.Application/Playouts/Queries/GetAllPlayoutsHandler.cs +++ b/ErsatzTV.Application/Playouts/Queries/GetAllPlayoutsHandler.cs @@ -19,17 +19,16 @@ public class GetAllPlayoutsHandler : IRequestHandler p.ProgramSchedule) .Filter(p => p.Channel != null) - .Map( - p => new PlayoutNameViewModel( - p.Id, - p.ProgramSchedulePlayoutType, - p.Channel.Name, - p.Channel.Number, - p.Channel.ProgressMode, - p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name, - p.TemplateFile, - p.ExternalJsonFile, - p.DailyRebuildTime)) + .Map(p => new PlayoutNameViewModel( + p.Id, + p.ProgramSchedulePlayoutType, + p.Channel.Name, + p.Channel.Number, + p.Channel.ProgressMode, + p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name, + p.TemplateFile, + p.ExternalJsonFile, + p.DailyRebuildTime)) .ToListAsync(cancellationToken); } } diff --git a/ErsatzTV.Application/Playouts/Queries/GetPlayoutByIdHandler.cs b/ErsatzTV.Application/Playouts/Queries/GetPlayoutByIdHandler.cs index eca211000..e7cf3ae91 100644 --- a/ErsatzTV.Application/Playouts/Queries/GetPlayoutByIdHandler.cs +++ b/ErsatzTV.Application/Playouts/Queries/GetPlayoutByIdHandler.cs @@ -17,16 +17,15 @@ public class GetPlayoutByIdHandler(IDbContextFactory dbContextFactory .Include(p => p.ProgramSchedule) .Include(p => p.Channel) .SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId) - .MapT( - p => new PlayoutNameViewModel( - p.Id, - p.ProgramSchedulePlayoutType, - p.Channel.Name, - p.Channel.Number, - p.Channel.ProgressMode, - p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name, - p.TemplateFile, - p.ExternalJsonFile, - p.DailyRebuildTime)); + .MapT(p => new PlayoutNameViewModel( + p.Id, + p.ProgramSchedulePlayoutType, + p.Channel.Name, + p.Channel.Number, + p.Channel.ProgressMode, + p.ProgramScheduleId == null ? string.Empty : p.ProgramSchedule.Name, + p.TemplateFile, + p.ExternalJsonFile, + p.DailyRebuildTime)); } } diff --git a/ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs b/ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs index b6c2225a2..e39db8cb4 100644 --- a/ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/StartPlexPinFlowHandler.cs @@ -20,13 +20,12 @@ public class StartPlexPinFlowHandler : IRequestHandler> Handle( StartPlexPinFlow request, CancellationToken cancellationToken) => - _plexTvApiClient.StartPinFlow().Bind( - result => result.Match( - Left: error => Task.FromResult(Left(error)), - Right: async pin => - { - await _channel.WriteAsync(new TryCompletePlexPinFlow(pin), cancellationToken); - return Right(pin.Url); - }) + _plexTvApiClient.StartPinFlow().Bind(result => result.Match( + Left: error => Task.FromResult(Left(error)), + Right: async pin => + { + await _channel.WriteAsync(new TryCompletePlexPinFlow(pin), cancellationToken); + return Right(pin.Url); + }) ); } diff --git a/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs b/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs index c1cca092c..279b45407 100644 --- a/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs +++ b/ErsatzTV.Application/Plex/Commands/SynchronizePlexMediaSourcesHandler.cs @@ -10,8 +10,9 @@ using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Plex; -public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, IRequestHandler>> +public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, + IRequestHandler>> { private const string LocalhostUri = "http://localhost:32400"; @@ -56,8 +57,8 @@ public class SynchronizePlexMediaSourcesHandler : PlexBaseConnectionHandler, IRe } // delete removed servers - foreach (PlexMediaSource removed in allExisting.Filter( - s => servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier))) + foreach (PlexMediaSource removed in allExisting.Filter(s => + servers.All(pms => pms.ClientIdentifier != s.ClientIdentifier))) { _logger.LogWarning( "Deleting removed Plex server {ServerName}!", diff --git a/ErsatzTV.Application/Plex/PlexBaseConnectionHandler.cs b/ErsatzTV.Application/Plex/PlexBaseConnectionHandler.cs index 31ad68fca..848b15c2d 100644 --- a/ErsatzTV.Application/Plex/PlexBaseConnectionHandler.cs +++ b/ErsatzTV.Application/Plex/PlexBaseConnectionHandler.cs @@ -1,4 +1,5 @@ using System.Collections.Concurrent; +using System.Diagnostics; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; @@ -12,7 +13,9 @@ public abstract class PlexBaseConnectionHandler( IMediaSourceRepository mediaSourceRepository, ILogger logger) { - protected async Task> FindConnectionToActivate(PlexMediaSource server, PlexServerAuthToken token) + protected async Task> FindConnectionToActivate( + PlexMediaSource server, + PlexServerAuthToken token) { Option result = Option.None; @@ -43,7 +46,8 @@ public abstract class PlexBaseConnectionHandler( tasks.Remove(completed); } - Option maybeBest = successfulTimes.OrderByDescending(kv => kv.Value).Select(kvp => kvp.Key).HeadOrNone(); + Option maybeBest = + successfulTimes.OrderByDescending(kv => kv.Value).Select(kvp => kvp.Key).HeadOrNone(); foreach (PlexConnection connection in maybeBest) { connection.IsActive = true; @@ -60,12 +64,16 @@ public abstract class PlexBaseConnectionHandler( return result; } - private async Task PingPlexConnection(PlexConnection connection, PlexServerAuthToken token, ConcurrentDictionary successfulTimes, CancellationToken cancellationToken) + private async Task PingPlexConnection( + PlexConnection connection, + PlexServerAuthToken token, + ConcurrentDictionary successfulTimes, + CancellationToken cancellationToken) { try { logger.LogDebug("Attempting to locate to Plex at {Uri}", connection.Uri); - var sw = new System.Diagnostics.Stopwatch(); + var sw = new Stopwatch(); sw.Start(); bool pingResult = await plexServerApiClient.Ping(connection, token, cancellationToken); sw.Stop(); diff --git a/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs b/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs index b7f3d8dba..aca811499 100644 --- a/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs +++ b/ErsatzTV.Application/Plex/Queries/GetPlexConnectionParametersHandler.cs @@ -8,14 +8,15 @@ using Microsoft.Extensions.Logging; namespace ErsatzTV.Application.Plex; -public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, IRequestHandler> +public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, + IRequestHandler> { + private readonly ILogger _logger; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IMemoryCache _memoryCache; - private readonly IPlexServerApiClient _plexServerApiClient; private readonly IPlexSecretStore _plexSecretStore; - private readonly ILogger _logger; + private readonly IPlexServerApiClient _plexServerApiClient; public GetPlexConnectionParametersHandler( IMemoryCache memoryCache, @@ -49,7 +50,8 @@ public class GetPlexConnectionParametersHandler : PlexBaseConnectionHandler, IRe foreach (PlexServerAuthToken token in maybeToken) { // try to keep the same connection - Option maybeActiveConnection = mediaSource.Connections.Filter(c => c.IsActive).HeadOrNone(); + Option maybeActiveConnection = + mediaSource.Connections.Filter(c => c.IsActive).HeadOrNone(); foreach (PlexConnection activeConnection in maybeActiveConnection) { if (await _plexServerApiClient.Ping(activeConnection, token, cancellationToken)) diff --git a/ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs b/ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs index 62a42128b..eee851374 100644 --- a/ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs +++ b/ErsatzTV.Application/ProgramSchedules/Commands/CreateProgramScheduleHandler.cs @@ -30,20 +30,19 @@ public class CreateProgramScheduleHandler(IDbContextFactory dbContext private static Task> Validate( TvContext dbContext, CreateProgramSchedule request) => - ValidateName(dbContext, request).MapT( - name => + ValidateName(dbContext, request).MapT(name => + { + bool keepMultiPartEpisodesTogether = request.KeepMultiPartEpisodesTogether; + return new ProgramSchedule { - bool keepMultiPartEpisodesTogether = request.KeepMultiPartEpisodesTogether; - return new ProgramSchedule - { - Name = name, - KeepMultiPartEpisodesTogether = keepMultiPartEpisodesTogether, - TreatCollectionsAsShows = keepMultiPartEpisodesTogether && request.TreatCollectionsAsShows, - ShuffleScheduleItems = request.ShuffleScheduleItems, - RandomStartPoint = request.RandomStartPoint, - FixedStartTimeBehavior = request.FixedStartTimeBehavior - }; - }); + Name = name, + KeepMultiPartEpisodesTogether = keepMultiPartEpisodesTogether, + TreatCollectionsAsShows = keepMultiPartEpisodesTogether && request.TreatCollectionsAsShows, + ShuffleScheduleItems = request.ShuffleScheduleItems, + RandomStartPoint = request.RandomStartPoint, + FixedStartTimeBehavior = request.FixedStartTimeBehavior + }; + }); private static async Task> ValidateName( TvContext dbContext, diff --git a/ErsatzTV.Application/ProgramSchedules/Queries/GetAllProgramSchedulesHandler.cs b/ErsatzTV.Application/ProgramSchedules/Queries/GetAllProgramSchedulesHandler.cs index 7213e6b0a..c13f160eb 100644 --- a/ErsatzTV.Application/ProgramSchedules/Queries/GetAllProgramSchedulesHandler.cs +++ b/ErsatzTV.Application/ProgramSchedules/Queries/GetAllProgramSchedulesHandler.cs @@ -12,15 +12,14 @@ public class GetAllProgramSchedulesHandler(IDbContextFactory dbContex { await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.ProgramSchedules - .Map( - ps => new ProgramScheduleViewModel( - ps.Id, - ps.Name, - ps.KeepMultiPartEpisodesTogether, - ps.TreatCollectionsAsShows, - ps.ShuffleScheduleItems, - ps.RandomStartPoint, - ps.FixedStartTimeBehavior)) + .Map(ps => new ProgramScheduleViewModel( + ps.Id, + ps.Name, + ps.KeepMultiPartEpisodesTogether, + ps.TreatCollectionsAsShows, + ps.ShuffleScheduleItems, + ps.RandomStartPoint, + ps.FixedStartTimeBehavior)) .ToListAsync(cancellationToken); } } diff --git a/ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs b/ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs index 15e4343c6..76c94096e 100644 --- a/ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs +++ b/ErsatzTV.Application/ProgramSchedules/Queries/GetProgramScheduleItemsHandler.cs @@ -52,9 +52,8 @@ public class GetProgramScheduleItemsHandler : .Include(i => i.FallbackFiller) .Include(i => i.Watermark) .ToListAsync(cancellationToken) - .Map( - programScheduleItems => programScheduleItems.Map(ProjectToViewModel) - .Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList()); + .Map(programScheduleItems => programScheduleItems.Map(ProjectToViewModel) + .Map(psi => EnforceProperties(maybeProgramSchedule, psi)).ToList()); } // shuffled schedule items supports a limited set of property values diff --git a/ErsatzTV.Application/Resolutions/Commands/CreateCustomResolutionHandler.cs b/ErsatzTV.Application/Resolutions/Commands/CreateCustomResolutionHandler.cs index da3fe9d5e..5063f4967 100644 --- a/ErsatzTV.Application/Resolutions/Commands/CreateCustomResolutionHandler.cs +++ b/ErsatzTV.Application/Resolutions/Commands/CreateCustomResolutionHandler.cs @@ -42,14 +42,13 @@ public class CreateCustomResolutionHandler : IRequestHandler ResolutionMustBeUnique(dbContext, request) - .MapT( - _ => new Resolution - { - Name = $"{request.Width}x{request.Height}", - Width = request.Width, - Height = request.Height, - IsCustom = true - }); + .MapT(_ => new Resolution + { + Name = $"{request.Width}x{request.Height}", + Width = request.Width, + Height = request.Height, + IsCustom = true + }); private static async Task> ResolutionMustBeUnique( TvContext dbContext, diff --git a/ErsatzTV.Application/Scheduling/Commands/CreateBlockHandler.cs b/ErsatzTV.Application/Scheduling/Commands/CreateBlockHandler.cs index 78029d3e8..ba4afce38 100644 --- a/ErsatzTV.Application/Scheduling/Commands/CreateBlockHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/CreateBlockHandler.cs @@ -25,13 +25,12 @@ public class CreateBlockHandler(IDbContextFactory dbContextFactory) } private static async Task> Validate(TvContext dbContext, CreateBlock request) => - await ValidateBlockName(dbContext, request).MapT( - name => new Block - { - BlockGroupId = request.BlockGroupId, - Name = name, - Minutes = 30 - }); + await ValidateBlockName(dbContext, request).MapT(name => new Block + { + BlockGroupId = request.BlockGroupId, + Name = name, + Minutes = 30 + }); private static async Task> ValidateBlockName( TvContext dbContext, diff --git a/ErsatzTV.Application/Scheduling/Commands/CreateDecoHandler.cs b/ErsatzTV.Application/Scheduling/Commands/CreateDecoHandler.cs index 4df1b1345..d8dfccccf 100644 --- a/ErsatzTV.Application/Scheduling/Commands/CreateDecoHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/CreateDecoHandler.cs @@ -25,12 +25,11 @@ public class CreateDecoHandler(IDbContextFactory dbContextFactory) } private static async Task> Validate(TvContext dbContext, CreateDeco request) => - await ValidateDecoName(dbContext, request).MapT( - name => new Deco - { - DecoGroupId = request.DecoGroupId, - Name = name - }); + await ValidateDecoName(dbContext, request).MapT(name => new Deco + { + DecoGroupId = request.DecoGroupId, + Name = name + }); private static async Task> ValidateDecoName( TvContext dbContext, diff --git a/ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplateHandler.cs b/ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplateHandler.cs index 1d0af805a..93338a0e8 100644 --- a/ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplateHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplateHandler.cs @@ -26,12 +26,11 @@ public class CreateDecoTemplateHandler(IDbContextFactory dbContextFac private static Task> Validate(CreateDecoTemplate request) => Task.FromResult( - ValidateName(request).Map( - name => new DecoTemplate - { - DecoTemplateGroupId = request.DecoTemplateGroupId, - Name = name - })); + ValidateName(request).Map(name => new DecoTemplate + { + DecoTemplateGroupId = request.DecoTemplateGroupId, + Name = name + })); private static Validation ValidateName(CreateDecoTemplate createDecoTemplate) => createDecoTemplate.NotEmpty(x => x.Name) diff --git a/ErsatzTV.Application/Scheduling/Commands/CreateTemplateHandler.cs b/ErsatzTV.Application/Scheduling/Commands/CreateTemplateHandler.cs index 33f708d38..b0fce901a 100644 --- a/ErsatzTV.Application/Scheduling/Commands/CreateTemplateHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/CreateTemplateHandler.cs @@ -26,12 +26,11 @@ public class CreateTemplateHandler(IDbContextFactory dbContextFactory private static Task> Validate(CreateTemplate request) => Task.FromResult( - ValidateName(request).Map( - name => new Template - { - TemplateGroupId = request.TemplateGroupId, - Name = name - })); + ValidateName(request).Map(name => new Template + { + TemplateGroupId = request.TemplateGroupId, + Name = name + })); private static Validation ValidateName(CreateTemplate createTemplate) => createTemplate.NotEmpty(x => x.Name) diff --git a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs index ee0c9ab39..e967199f3 100644 --- a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutHistoryHandler.cs @@ -13,9 +13,8 @@ public class ErasePlayoutHistoryHandler(IDbContextFactory dbContextFa await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); Option maybePlayout = await dbContext.Playouts - .Filter( - p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block || - p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml) + .Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block || + p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml) .SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId); foreach (Playout playout in maybePlayout) diff --git a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs index 1a0e05bf0..69afe113e 100644 --- a/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/ErasePlayoutItemsHandler.cs @@ -16,9 +16,8 @@ public class ErasePlayoutItemsHandler(IDbContextFactory dbContextFact Option maybePlayout = await dbContext.Playouts .Include(p => p.Items) .Include(p => p.PlayoutHistory) - .Filter( - p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block || - p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml) + .Filter(p => p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Block || + p.ProgramSchedulePlayoutType == ProgramSchedulePlayoutType.Yaml) .SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId); foreach (Playout playout in maybePlayout) diff --git a/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs b/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs index b0965efbb..e7fe2a51d 100644 --- a/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/ReplaceTemplateItemsHandler.cs @@ -71,15 +71,14 @@ public class ReplaceTemplateItemsHandler(IDbContextFactory dbContextF .ToListAsync() .Map(list => list.ToDictionary(b => b.Id, b => b)); - var allTemplateItems = request.Items.Map( - i => - { - Block block = allBlocks[i.BlockId]; - return new BlockTemplateItem( - i.BlockId, - i.StartTime, - i.StartTime + TimeSpan.FromMinutes(block.Minutes)); - }) + var allTemplateItems = request.Items.Map(i => + { + Block block = allBlocks[i.BlockId]; + return new BlockTemplateItem( + i.BlockId, + i.StartTime, + i.StartTime + TimeSpan.FromMinutes(block.Minutes)); + }) .ToList(); foreach (BlockTemplateItem item in allTemplateItems) diff --git a/ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs b/ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs index b2bf7a221..ca90d2bd1 100644 --- a/ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs +++ b/ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs @@ -88,8 +88,8 @@ public class UpdateDecoHandler(IDbContextFactory dbContextFactory) } Option maybeExisting = await dbContext.Decos - .FirstOrDefaultAsync( - d => d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name) + .FirstOrDefaultAsync(d => + d.Id != request.DecoId && d.DecoGroupId == request.DecoGroupId && d.Name == request.Name) .Map(Optional); return maybeExisting.IsSome diff --git a/ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateItemsHandler.cs b/ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateItemsHandler.cs index fbfbc27ad..ac4037d28 100644 --- a/ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateItemsHandler.cs +++ b/ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateItemsHandler.cs @@ -18,10 +18,9 @@ public class GetDecoTemplateItemsHandler(IDbContextFactory dbContextF .Filter(i => i.DecoTemplateId == request.DecoTemplateId) .Include(i => i.Deco) .ToListAsync(cancellationToken) - .Map( - items => items - .Map(Mapper.ProjectToViewModel) - .Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero) - .ToList()); + .Map(items => items + .Map(Mapper.ProjectToViewModel) + .Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero) + .ToList()); } } diff --git a/ErsatzTV.Application/Scheduling/Queries/GetTemplateItemsHandler.cs b/ErsatzTV.Application/Scheduling/Queries/GetTemplateItemsHandler.cs index ea9c7f6d9..26d21e185 100644 --- a/ErsatzTV.Application/Scheduling/Queries/GetTemplateItemsHandler.cs +++ b/ErsatzTV.Application/Scheduling/Queries/GetTemplateItemsHandler.cs @@ -16,10 +16,9 @@ public class GetTemplateItemsHandler(IDbContextFactory dbContextFacto .Filter(i => i.TemplateId == request.TemplateId) .Include(i => i.Block) .ToListAsync(cancellationToken) - .Map( - items => items - .Map(Mapper.ProjectToViewModel) - .Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero) - .ToList()); + .Map(items => items + .Map(Mapper.ProjectToViewModel) + .Filter(i => i.StartTime < i.EndTime || i.EndTime.TimeOfDay == TimeSpan.Zero) + .ToList()); } } diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchIndexAllItemsHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchIndexAllItemsHandler.cs index b0b879e43..ea6706e25 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchIndexAllItemsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchIndexAllItemsHandler.cs @@ -30,5 +30,6 @@ public class QuerySearchIndexAllItemsHandler : IRequestHandler> GetIds(string type, string query) => - (await _searchIndex.Search(_client, $"type:{type} AND ({query})", string.Empty, 0, 0)).Items.Map(i => i.Id).ToList(); + (await _searchIndex.Search(_client, $"type:{type} AND ({query})", string.Empty, 0, 0)).Items.Map(i => i.Id) + .ToList(); } diff --git a/ErsatzTV.Application/Search/Queries/QuerySearchTargetsHandler.cs b/ErsatzTV.Application/Search/Queries/QuerySearchTargetsHandler.cs index 40107346f..11c545833 100644 --- a/ErsatzTV.Application/Search/Queries/QuerySearchTargetsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/QuerySearchTargetsHandler.cs @@ -42,12 +42,11 @@ public class QuerySearchTargetsHandler : IRequestHandler new[] - { - new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.Schedule), - new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.ScheduleItems) - })); + schedules.SelectMany(s => new[] + { + new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.Schedule), + new SearchTargetViewModel(s.Id, s.Name, SearchTargetKind.ScheduleItems) + })); return result; } diff --git a/ErsatzTV.Application/Search/Queries/SearchArtistsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchArtistsHandler.cs index 6f3f1f2a7..7913c7e78 100644 --- a/ErsatzTV.Application/Search/Queries/SearchArtistsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchArtistsHandler.cs @@ -17,10 +17,9 @@ public class SearchArtistsHandler : IRequestHandler EF.Functions.Like( - EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation), - $"%{request.Query}%")) + .Where(a => EF.Functions.Like( + EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation), + $"%{request.Query}%")) .OrderBy(a => EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation)) .Take(10) .ToListAsync(cancellationToken) diff --git a/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs index 37ddb3cbe..717803bc2 100644 --- a/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchCollectionsHandler.cs @@ -19,10 +19,9 @@ public class SearchCollectionsHandler : IRequestHandler EF.Functions.Like( - EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), - $"%{request.Query}%")) + .Where(c => EF.Functions.Like( + EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), + $"%{request.Query}%")) .OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation)) .Take(10) .ToListAsync(cancellationToken) diff --git a/ErsatzTV.Application/Search/Queries/SearchMoviesHandler.cs b/ErsatzTV.Application/Search/Queries/SearchMoviesHandler.cs index 1736c42b5..84e34a465 100644 --- a/ErsatzTV.Application/Search/Queries/SearchMoviesHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchMoviesHandler.cs @@ -14,10 +14,9 @@ public class SearchMoviesHandler(IDbContextFactory dbContextFactory) await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken); return await dbContext.MovieMetadata .AsNoTracking() - .Where( - s => EF.Functions.Like( - EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation), - $"%{request.Query}%")) + .Where(s => EF.Functions.Like( + EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation), + $"%{request.Query}%")) .OrderBy(a => EF.Functions.Collate(a.Title, TvContext.CaseInsensitiveCollation)) .ThenBy(s => s.Year) .Take(10) diff --git a/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs index e19d95fd0..34395c747 100644 --- a/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchMultiCollectionsHandler.cs @@ -19,10 +19,9 @@ public class SearchMultiCollectionsHandler : IRequestHandler EF.Functions.Like( - EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), - $"%{request.Query}%")) + .Where(c => EF.Functions.Like( + EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), + $"%{request.Query}%")) .OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation)) .Take(10) .ToListAsync(cancellationToken) diff --git a/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs index 6694f3419..5dd3365b6 100644 --- a/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchSmartCollectionsHandler.cs @@ -19,10 +19,9 @@ public class SearchSmartCollectionsHandler : IRequestHandler EF.Functions.Like( - EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), - $"%{request.Query}%")) + .Where(c => EF.Functions.Like( + EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation), + $"%{request.Query}%")) .OrderBy(c => EF.Functions.Collate(c.Name, TvContext.CaseInsensitiveCollation)) .Take(10) .ToListAsync(cancellationToken) diff --git a/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs b/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs index 914de0102..d0d6b9b09 100644 --- a/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs +++ b/ErsatzTV.Application/Search/Queries/SearchTelevisionShowsHandler.cs @@ -20,10 +20,9 @@ public class SearchTelevisionShowsHandler : IRequestHandler EF.Functions.Like( - EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation), - $"%{request.Query}%")) + .Where(s => EF.Functions.Like( + EF.Functions.Collate(s.Title + " " + s.Year, TvContext.CaseInsensitiveCollation), + $"%{request.Query}%")) .OrderBy(s => EF.Functions.Collate(s.Title, TvContext.CaseInsensitiveCollation)) .ThenBy(s => s.Year) .Take(10) diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs index 392226148..3497b5480 100644 --- a/ErsatzTV.Application/Streaming/HlsSessionWorker.cs +++ b/ErsatzTV.Application/Streaming/HlsSessionWorker.cs @@ -174,7 +174,9 @@ public class HlsSessionWorker : IHlsSessionWorker PlaylistStart = _transcodedUntil; // time shift on-demand playout if needed - await _mediator.Send(new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), cancellationToken); + await _mediator.Send( + new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), + cancellationToken); bool initialWorkAhead = Volatile.Read(ref _workAheadCount) < await GetWorkAheadLimit(); _state = initialWorkAhead ? HlsSessionState.SeekAndWorkAhead : HlsSessionState.SeekAndRealtime; @@ -583,15 +585,14 @@ public class HlsSessionWorker : IHlsSessionWorker Directory.GetFiles( Path.Combine(FileSystemLayout.TranscodeFolder, _channelNumber), "live*.mp4")) - .Map( - file => - { - string fileName = Path.GetFileName(file); - var sequenceNumber = int.Parse( - fileName.Replace("live", string.Empty).Split('.')[0], - CultureInfo.InvariantCulture); - return new Segment(file, sequenceNumber); - }) + .Map(file => + { + string fileName = Path.GetFileName(file); + var sequenceNumber = int.Parse( + fileName.Replace("live", string.Empty).Split('.')[0], + CultureInfo.InvariantCulture); + return new Segment(file, sequenceNumber); + }) .ToList(); var toDelete = allSegments.Filter(s => s.SequenceNumber < trimResult.Sequence).ToList(); diff --git a/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs b/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs index 7e9a7f1c1..1f8afea9c 100644 --- a/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs +++ b/ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs @@ -126,7 +126,9 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker PlaylistStart = _transcodedUntil; // time shift on-demand playout if needed - await _mediator.Send(new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), cancellationToken); + await _mediator.Send( + new TimeShiftOnDemandPlayout(_channelNumber, _transcodedUntil, true), + cancellationToken); // start concat/segmenter process // other transcode processes will be started by incoming requests from concat/segmenter process diff --git a/ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs b/ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs index 0c7a5b573..d8b3b4084 100644 --- a/ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/FFmpegProcessHandler.cs @@ -45,21 +45,20 @@ public abstract class FFmpegProcessHandler : IRequestHandler c.Artwork) .Include(c => c.Watermark) .SelectOneAsync(c => c.Number, c => c.Number == request.ChannelNumber) - .MapT( - channel => + .MapT(channel => + { + channel.StreamingMode = request.Mode.ToLowerInvariant() switch { - channel.StreamingMode = request.Mode.ToLowerInvariant() switch - { - "hls-direct" => StreamingMode.HttpLiveStreamingDirect, - "segmenter" => StreamingMode.HttpLiveStreamingSegmenter, - "segmenter-v2" => StreamingMode.HttpLiveStreamingSegmenterV2, - "ts" => StreamingMode.TransportStreamHybrid, - "ts-legacy" => StreamingMode.TransportStream, - _ => channel.StreamingMode - }; + "hls-direct" => StreamingMode.HttpLiveStreamingDirect, + "segmenter" => StreamingMode.HttpLiveStreamingSegmenter, + "segmenter-v2" => StreamingMode.HttpLiveStreamingSegmenterV2, + "ts" => StreamingMode.TransportStreamHybrid, + "ts-legacy" => StreamingMode.TransportStream, + _ => channel.StreamingMode + }; - return channel; - }) + return channel; + }) .Map(o => o.ToValidation($"Channel number {request.ChannelNumber} does not exist.")); private static Task> FFmpegPathMustExist(TvContext dbContext) => diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index d26a213ba..04996add4 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -227,9 +227,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< Option maybeGlobalWatermark = await dbContext.ConfigElements .GetValue(ConfigElementKey.FFmpegGlobalWatermarkId) - .BindT( - watermarkId => dbContext.ChannelWatermarks - .SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); + .BindT(watermarkId => dbContext.ChannelWatermarks + .SelectOneAsync(w => w.Id, w => w.Id == watermarkId)); Option playoutItemWatermark = Optional(playoutItemWithPath.PlayoutItem.Watermark); bool disableWatermarks = playoutItemWithPath.PlayoutItem.DisableWatermarks; @@ -261,7 +260,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< // override watermark as song_progress_overlay.png if (videoVersion is BackgroundImageMediaVersion { IsSongWithProgress: true }) { - double ratio = channel.FFmpegProfile.Resolution.Width / (double)channel.FFmpegProfile.Resolution.Height; + double ratio = channel.FFmpegProfile.Resolution.Width / + (double)channel.FFmpegProfile.Resolution.Height; bool is43 = Math.Abs(ratio - 4.0 / 3.0) < 0.01; string image = is43 ? "song_progress_overlay_43.png" : "song_progress_overlay.png"; diff --git a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs index 5fe97e5ba..6932a2912 100644 --- a/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs +++ b/ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs @@ -98,10 +98,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler requestedPlayout = await dbContext.Playouts .AsNoTracking() - .Filter( - p => p.Channel.SubtitleMode != ChannelSubtitleMode.None || - p.ProgramSchedule.Items.Any( - psi => psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None)) + .Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None || + p.ProgramSchedule.Items.Any(psi => + psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None)) .SelectOneAsync(p => p.Id, p => p.Id == request.PlayoutId.IfNone(-1)); playoutIdsToCheck.AddRange(requestedPlayout.Map(p => p.Id)); @@ -111,10 +110,9 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler p.Channel.SubtitleMode != ChannelSubtitleMode.None || - p.ProgramSchedule.Items.Any( - psi => psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None)) + .Filter(p => p.Channel.SubtitleMode != ChannelSubtitleMode.None || + p.ProgramSchedule.Items.Any(psi => + psi.SubtitleMode != null && psi.SubtitleMode != ChannelSubtitleMode.None)) .Map(p => p.Id) .ToList(); } @@ -216,11 +214,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler episodeIds = await dbContext.EpisodeMetadata .AsNoTracking() .Filter(em => mediaItemIds.Contains(em.EpisodeId)) - .Filter( - em => em.Subtitles.Any( - s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && - s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + .Filter(em => em.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded && + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && + s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(em => em.EpisodeId) .ToListAsync(cancellationToken); result.AddRange(episodeIds); @@ -228,11 +225,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler movieIds = await dbContext.MovieMetadata .AsNoTracking() .Filter(mm => mediaItemIds.Contains(mm.MovieId)) - .Filter( - mm => mm.Subtitles.Any( - s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && - s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + .Filter(mm => mm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded && + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && + s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(mm => mm.MovieId) .ToListAsync(cancellationToken); result.AddRange(movieIds); @@ -240,11 +236,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler musicVideoIds = await dbContext.MusicVideoMetadata .AsNoTracking() .Filter(mm => mediaItemIds.Contains(mm.MusicVideoId)) - .Filter( - mm => mm.Subtitles.Any( - s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && - s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + .Filter(mm => mm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded && + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && + s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(mm => mm.MusicVideoId) .ToListAsync(cancellationToken); result.AddRange(musicVideoIds); @@ -252,11 +247,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler otherVideoIds = await dbContext.OtherVideoMetadata .AsNoTracking() .Filter(ovm => mediaItemIds.Contains(ovm.OtherVideoId)) - .Filter( - ovm => ovm.Subtitles.Any( - s => s.SubtitleKind == SubtitleKind.Embedded && - s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && - s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) + .Filter(ovm => ovm.Subtitles.Any(s => s.SubtitleKind == SubtitleKind.Embedded && + s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && + s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs")) .Map(ovm => ovm.OtherVideoId) .ToListAsync(cancellationToken); result.AddRange(otherVideoIds); @@ -284,12 +278,10 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler subtitles = allSubtitles .Filter(s => s.SubtitleKind == SubtitleKind.Embedded) - .Filter( - s => s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && - s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs") - .Filter( - s => s.IsExtracted == false || string.IsNullOrWhiteSpace(s.Path) || - FileDoesntExist(mediaItem.Id, s)); + .Filter(s => s.Codec != "hdmv_pgs_subtitle" && s.Codec != "dvd_subtitle" && s.Codec != "dvdsub" && + s.Codec != "vobsub" && s.Codec != "pgssub" && s.Codec != "pgs") + .Filter(s => s.IsExtracted == false || string.IsNullOrWhiteSpace(s.Path) || + FileDoesntExist(mediaItem.Id, s)); // find cache paths for each subtitle foreach (Subtitle subtitle in subtitles) diff --git a/ErsatzTV.Application/Television/Mapper.cs b/ErsatzTV.Application/Television/Mapper.cs index 6c527e93e..25dfdeae6 100644 --- a/ErsatzTV.Application/Television/Mapper.cs +++ b/ErsatzTV.Application/Television/Mapper.cs @@ -1,5 +1,4 @@ using System.Globalization; -using ErsatzTV.Application.MediaCards; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Emby; using ErsatzTV.Core.Jellyfin; @@ -23,19 +22,19 @@ internal static class Mapper show.ShowMetadata.HeadOrNone().Map(m => GetPoster(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty), show.ShowMetadata.HeadOrNone().Map(m => GetFanArt(m, maybeJellyfin, maybeEmby)).IfNone(string.Empty), show.ShowMetadata.HeadOrNone().Map(m => m.Genres.Map(g => g.Name).ToList()).IfNone([]), - show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(g => g.Name).ToList()).IfNone([]), + show.ShowMetadata.HeadOrNone().Map(m => + m.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(g => g.Name).ToList()).IfNone([]), show.ShowMetadata.HeadOrNone().Map(m => m.Studios.Map(s => s.Name).ToList()).IfNone([]), - show.ShowMetadata.HeadOrNone().Map(m => m.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(g => g.Name).ToList()).IfNone([]), + show.ShowMetadata.HeadOrNone().Map(m => + m.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(g => g.Name).ToList()).IfNone([]), show.ShowMetadata.HeadOrNone() - .Map( - m => (m.ContentRating ?? string.Empty).Split("/").Map(s => s.Trim()) - .Where(x => !string.IsNullOrWhiteSpace(x)).ToList()).IfNone([]), + .Map(m => (m.ContentRating ?? string.Empty).Split("/").Map(s => s.Trim()) + .Where(x => !string.IsNullOrWhiteSpace(x)).ToList()).IfNone([]), LanguagesForShow(languages), show.ShowMetadata.HeadOrNone() - .Map( - m => m.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id) - .Map(a => MediaCards.Mapper.ProjectToViewModel(a, maybeJellyfin, maybeEmby)) - .ToList()) + .Map(m => m.Actors.OrderBy(a => a.Order).ThenBy(a => a.Id) + .Map(a => MediaCards.Mapper.ProjectToViewModel(a, maybeJellyfin, maybeEmby)) + .ToList()) .IfNone([])); internal static TelevisionSeasonViewModel ProjectToViewModel( @@ -104,9 +103,10 @@ internal static class Mapper CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); return languages - .Map( - lang => allCultures.Filter( - ci => string.Equals(ci.ThreeLetterISOLanguageName, lang, StringComparison.OrdinalIgnoreCase))) + .Map(lang => allCultures.Filter(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + lang, + StringComparison.OrdinalIgnoreCase))) .Flatten() .Distinct() .ToList(); diff --git a/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs b/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs index 511579c37..4be881273 100644 --- a/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs +++ b/ErsatzTV.Application/Watermarks/Commands/CreateWatermarkHandler.cs @@ -38,35 +38,34 @@ public class CreateWatermarkHandler : IRequestHandler Validate(CreateWatermark request) => ValidateName(request) - .Map( - _ => + .Map(_ => + { + var watermark = new ChannelWatermark { - var watermark = new ChannelWatermark - { - Name = request.Name, - Image = null, - OriginalContentType = null, - Mode = request.Mode, - ImageSource = request.ImageSource, - Location = request.Location, - Size = request.Size, - WidthPercent = request.Width, - HorizontalMarginPercent = request.HorizontalMargin, - VerticalMarginPercent = request.VerticalMargin, - FrequencyMinutes = request.FrequencyMinutes, - DurationSeconds = request.DurationSeconds, - Opacity = request.Opacity, - PlaceWithinSourceContent = request.PlaceWithinSourceContent - }; + Name = request.Name, + Image = null, + OriginalContentType = null, + Mode = request.Mode, + ImageSource = request.ImageSource, + Location = request.Location, + Size = request.Size, + WidthPercent = request.Width, + HorizontalMarginPercent = request.HorizontalMargin, + VerticalMarginPercent = request.VerticalMargin, + FrequencyMinutes = request.FrequencyMinutes, + DurationSeconds = request.DurationSeconds, + Opacity = request.Opacity, + PlaceWithinSourceContent = request.PlaceWithinSourceContent + }; - if (request.ImageSource == ChannelWatermarkImageSource.Custom) - { - watermark.Image = request.Image?.Path; - watermark.OriginalContentType = request.Image?.ContentType; - } + if (request.ImageSource == ChannelWatermarkImageSource.Custom) + { + watermark.Image = request.Image?.Path; + watermark.OriginalContentType = request.Image?.ContentType; + } - return watermark; - }); + return watermark; + }); private static Validation ValidateName(CreateWatermark request) => request.NotEmpty(x => x.Name) diff --git a/ErsatzTV.Core.Tests/Domain/PlayoutItemTests.cs b/ErsatzTV.Core.Tests/Domain/PlayoutItemTests.cs index 61b56df12..0568c570c 100644 --- a/ErsatzTV.Core.Tests/Domain/PlayoutItemTests.cs +++ b/ErsatzTV.Core.Tests/Domain/PlayoutItemTests.cs @@ -1,6 +1,6 @@ using ErsatzTV.Core.Domain; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Domain; diff --git a/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs index 89a0e4fc2..2ef9cad5d 100644 --- a/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Emby/EmbyPathReplacementServiceTests.cs @@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Emby; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.FFmpeg.Runtime; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Emby; diff --git a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj index 5f0464a07..2e2a3e431 100644 --- a/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj +++ b/ErsatzTV.Core.Tests/ErsatzTV.Core.Tests.csproj @@ -7,32 +7,32 @@ - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + - - - + + + - + \ No newline at end of file diff --git a/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs index 5f1f0009e..1c5106d1b 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs @@ -14,12 +14,6 @@ public class CustomStreamSelectorTests [TestFixture] public class SelectStreams { - private static readonly string TestFileName = Path.Combine(FileSystemLayout.ChannelStreamSelectorsFolder, "test.yml"); - - private Channel _channel; - private MediaItemAudioVersion _audioVersion; - private List _subtitles; - [SetUp] public void SetUp() { @@ -41,16 +35,24 @@ public class CustomStreamSelectorTests ]; } + private static readonly string TestFileName = Path.Combine( + FileSystemLayout.ChannelStreamSelectorsFolder, + "test.yml"); + + private Channel _channel; + private MediaItemAudioVersion _audioVersion; + private List _subtitles; + [Test] public async Task Should_Select_eng_Audio_Exact_Match() { const string YAML = -""" ---- -items: - - audio_language: - - "eng" -"""; + """ + --- + items: + - audio_language: + - "eng" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -71,11 +73,11 @@ items: public async Task Should_Select_und_Audio_Missing_Language() { const string YAML = -""" ---- -items: - - audio_language: ["und"] -"""; + """ + --- + items: + - audio_language: ["und"] + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -96,11 +98,11 @@ items: public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Audio_Languages() { const string YAML = -""" ---- -items: - - audio_language: ["en", "eng"] -"""; + """ + --- + items: + - audio_language: ["en", "eng"] + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -121,17 +123,17 @@ items: public async Task Should_Select_eng_Audio_Exact_Match_Multiple_Items() { const string YAML = -""" ---- -items: - - audio_language: - - "de" - subtitle_language: - - "eng" - - audio_language: - - "eng" - disable_subtitles: true -"""; + """ + --- + items: + - audio_language: + - "de" + subtitle_language: + - "eng" + - audio_language: + - "eng" + disable_subtitles: true + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -152,12 +154,12 @@ items: public async Task Should_Select_eng_Audio_Pattern_Match() { const string YAML = -""" ---- -items: - - audio_language: - - "en*" -"""; + """ + --- + items: + - audio_language: + - "en*" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -178,12 +180,12 @@ items: public async Task Should_Select_en_Audio_Pattern_Match() { const string YAML = -""" ---- -items: - - audio_language: - - "en*" -"""; + """ + --- + items: + - audio_language: + - "en*" + """; _audioVersion = GetTestAudioVersion("en"); var streamSelector = new CustomStreamSelector( @@ -205,13 +207,13 @@ items: public async Task disable_subtitles_Should_Select_No_Subtitles() { const string YAML = -""" ---- -items: - - audio_language: - - "eng" - disable_subtitles: true -"""; + """ + --- + items: + - audio_language: + - "eng" + disable_subtitles: true + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -226,14 +228,14 @@ items: public async Task Should_Select_eng_Subtitle_Exact_Match() { const string YAML = -""" ---- -items: - - audio_language: - - "ja" - subtitle_language: - - "eng" -"""; + """ + --- + items: + - audio_language: + - "ja" + subtitle_language: + - "eng" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -254,14 +256,14 @@ items: public async Task Should_Select_eng_Subtitle_Pattern_Match() { const string YAML = -""" ---- -items: - - audio_language: - - "ja" - subtitle_language: - - "en*" -"""; + """ + --- + items: + - audio_language: + - "ja" + subtitle_language: + - "en*" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -282,14 +284,14 @@ items: public async Task Should_Select_en_Subtitle_Pattern_Match() { const string YAML = -""" ---- -items: - - audio_language: - - "ja" - subtitle_language: - - "en*" -"""; + """ + --- + items: + - audio_language: + - "ja" + subtitle_language: + - "en*" + """; _audioVersion = GetTestAudioVersion("en"); _subtitles = @@ -316,17 +318,17 @@ items: public async Task Should_Select_No_Subtitle_Exact_Match_Multiple_Items() { const string YAML = -""" ---- -items: - - audio_language: - - "de" - subtitle_language: - - "eng" - - audio_language: - - "eng" - disable_subtitles: true -"""; + """ + --- + items: + - audio_language: + - "de" + subtitle_language: + - "eng" + - audio_language: + - "eng" + disable_subtitles: true + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -349,17 +351,17 @@ items: public async Task Should_Select_Foreign_Audio_And_English_Subtitle_Multiple_Items() { const string YAML = -""" ---- -items: - - audio_language: - - "ja" - subtitle_language: - - "eng" - - audio_language: - - "eng" - disable_subtitles: true -"""; + """ + --- + items: + - audio_language: + - "ja" + subtitle_language: + - "eng" + - audio_language: + - "eng" + disable_subtitles: true + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -388,14 +390,14 @@ items: public async Task Should_Ignore_Blocked_Audio_Title() { const string YAML = -""" ---- -items: - - audio_language: - - "en*" - audio_title_blocklist: - - "riff" -"""; + """ + --- + items: + - audio_language: + - "en*" + audio_title_blocklist: + - "riff" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -416,14 +418,14 @@ items: public async Task Should_Select_Allowed_Audio_Title() { const string YAML = -""" ---- -items: - - audio_language: - - "en*" - audio_title_allowlist: - - "movie" -"""; + """ + --- + items: + - audio_language: + - "en*" + audio_title_allowlist: + - "movie" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -444,16 +446,16 @@ items: public async Task Should_Ignore_Blocked_Subtitle_Title() { const string YAML = -""" ---- -items: - - audio_language: - - "*" - subtitle_language: - - "en" - subtitle_title_blocklist: - - "signs" -"""; + """ + --- + items: + - audio_language: + - "*" + subtitle_language: + - "en" + subtitle_title_blocklist: + - "signs" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -474,16 +476,16 @@ items: public async Task Should_Select_Allowed_Subtitle_Title() { const string YAML = -""" ---- -items: - - audio_language: - - "*" - subtitle_language: - - "en" - subtitle_title_allowlist: - - "songs" -"""; + """ + --- + items: + - audio_language: + - "*" + subtitle_language: + - "en" + subtitle_title_allowlist: + - "songs" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -504,13 +506,13 @@ items: public async Task Should_Select_Condition_Forced_Subtitle() { const string YAML = -""" ---- -items: - - audio_language: - - "*" - subtitle_condition: "forced" -"""; + """ + --- + items: + - audio_language: + - "*" + subtitle_condition: "forced" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -531,13 +533,13 @@ items: public async Task Should_Select_Condition_External_Subtitle() { const string YAML = -""" ---- -items: - - audio_language: - - "*" - subtitle_condition: "lang like 'en%' and external" -"""; + """ + --- + items: + - audio_language: + - "*" + subtitle_condition: "lang like 'en%' and external" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -558,13 +560,13 @@ items: public async Task Should_Select_Condition_Audio_Title() { const string YAML = -""" ---- -items: - - audio_language: - - "en*" - audio_condition: "title like '%movie%'" -"""; + """ + --- + items: + - audio_language: + - "en*" + audio_condition: "title like '%movie%'" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -585,13 +587,13 @@ items: public async Task Should_Select_Condition_Audio_Channels() { const string YAML = -""" ---- -items: - - audio_language: - - "en*" - audio_condition: "channels > 2" -"""; + """ + --- + items: + - audio_language: + - "en*" + audio_condition: "channels > 2" + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -612,12 +614,12 @@ items: public async Task Should_Select_Prioritized_Audio_Language() { const string YAML = -""" ---- -items: - - audio_language: ["en*","ja"] - audio_title_blocklist: ["riff"] -"""; + """ + --- + items: + - audio_language: ["en*","ja"] + audio_title_blocklist: ["riff"] + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -638,13 +640,13 @@ items: public async Task Should_Select_Prioritized_Subtitle_Language() { const string YAML = -""" ---- -items: - - audio_language: - - "*" - subtitle_language: ["jp","en*"] -"""; + """ + --- + items: + - audio_language: + - "*" + subtitle_language: ["jp","en*"] + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -665,14 +667,14 @@ items: public async Task Should_Select_No_Streams_When_Languages_Do_Not_Match() { const string YAML = -""" ---- -items: - - audio_language: ["en"] - subtitle_language: ["es*","de*"] - - audio_language: ["ja"] - subtitle_language: ["es*","de*"] -"""; + """ + --- + items: + - audio_language: ["en"] + subtitle_language: ["es*","de*"] + - audio_language: ["ja"] + subtitle_language: ["es*","de*"] + """; var streamSelector = new CustomStreamSelector( new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = YAML }]), @@ -697,7 +699,7 @@ items: MediaStreamKind = MediaStreamKind.Audio, Channels = 2, Language = "ja", - Title = "Some Title", + Title = "Some Title" }, new MediaStream { @@ -714,14 +716,14 @@ items: MediaStreamKind = MediaStreamKind.Audio, Channels = 6, Language = englishLanguage, - Title = "Movie Title", + Title = "Movie Title" }, new MediaStream { Index = 3, MediaStreamKind = MediaStreamKind.Audio, Channels = 2, - Title = "Who Knows", + Title = "Who Knows" } ] }; diff --git a/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs index 7df0c5a8d..050682e05 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/FFmpegPlaybackSettingsCalculatorTests.cs @@ -1,8 +1,8 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.FFmpeg; diff --git a/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs index 942470742..8660d682b 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/FFmpegStreamSelectorTests.cs @@ -3,10 +3,10 @@ using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Scripting; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.FFmpeg; @@ -31,7 +31,7 @@ public class FFmpegStreamSelectorTests MediaStreamKind = MediaStreamKind.Audio, Channels = 2, Language = "ja", - Title = "Some Title", + Title = "Some Title" }, new MediaStream { @@ -63,7 +63,12 @@ public class FFmpegStreamSelectorTests Substitute.For(), Substitute.For>()); - Option selectedStream = await selector.SelectAudioStream(audioVersion, StreamingMode.TransportStream, channel, "jpn", "Whatever"); + Option selectedStream = await selector.SelectAudioStream( + audioVersion, + StreamingMode.TransportStream, + channel, + "jpn", + "Whatever"); selectedStream.IsSome.ShouldBeTrue(); foreach (MediaStream stream in selectedStream) { @@ -86,7 +91,7 @@ public class FFmpegStreamSelectorTests MediaStreamKind = MediaStreamKind.Audio, Channels = 2, Language = "ja", - Title = "Some Title", + Title = "Some Title" }, new MediaStream { @@ -118,7 +123,12 @@ public class FFmpegStreamSelectorTests Substitute.For(), Substitute.For>()); - Option selectedStream = await selector.SelectAudioStream(audioVersion, StreamingMode.TransportStream, channel, null, channel.PreferredAudioTitle); + Option selectedStream = await selector.SelectAudioStream( + audioVersion, + StreamingMode.TransportStream, + channel, + null, + channel.PreferredAudioTitle); selectedStream.IsSome.ShouldBeTrue(); foreach (MediaStream stream in selectedStream) { @@ -143,8 +153,8 @@ public class FFmpegStreamSelectorTests { StreamIndex = 1, SubtitleKind = SubtitleKind.Sidecar, - Language = "he", - }, + Language = "he" + } }; var channel = new Channel(Guid.NewGuid()); diff --git a/ErsatzTV.Core.Tests/FFmpeg/HlsPlaylistFilterTests.cs b/ErsatzTV.Core.Tests/FFmpeg/HlsPlaylistFilterTests.cs index 7be63fe44..ece9b624e 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/HlsPlaylistFilterTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/HlsPlaylistFilterTests.cs @@ -1,9 +1,9 @@ using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.FFmpeg; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.FFmpeg; diff --git a/ErsatzTV.Core.Tests/FFmpeg/WatermarkCalculatorTests.cs b/ErsatzTV.Core.Tests/FFmpeg/WatermarkCalculatorTests.cs index ed967a790..636a72b47 100644 --- a/ErsatzTV.Core.Tests/FFmpeg/WatermarkCalculatorTests.cs +++ b/ErsatzTV.Core.Tests/FFmpeg/WatermarkCalculatorTests.cs @@ -1,6 +1,6 @@ using ErsatzTV.Core.FFmpeg; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.FFmpeg; diff --git a/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs b/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs index 90b869535..7afc39c2a 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeMediaCollectionRepository.cs @@ -28,7 +28,10 @@ public class FakeMediaCollectionRepository : IMediaCollectionRepository public Task> GetMultiCollectionItemsByName(string name) => throw new NotSupportedException(); public Task> GetSmartCollectionItems(int id) => _data[id].ToList().AsTask(); public Task> GetSmartCollectionItemsByName(string name) => throw new NotSupportedException(); - public Task> GetSmartCollectionItems(string query, string smartCollectionName) => throw new NotSupportedException(); + + public Task> GetSmartCollectionItems(string query, string smartCollectionName) => + throw new NotSupportedException(); + public Task> GetShowItemsByShowGuids(List guids) => throw new NotSupportedException(); public Task> GetPlaylistItems(int id) => throw new NotSupportedException(); public Task> GetMovie(int id) => throw new NotSupportedException(); diff --git a/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs b/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs index 4e26bf53c..3bbccb52b 100644 --- a/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs +++ b/ErsatzTV.Core.Tests/Fakes/FakeTelevisionRepository.cs @@ -66,7 +66,7 @@ public class FakeTelevisionRepository : ITelevisionRepository public Task AddGenre(ShowMetadata metadata, Genre genre) => throw new NotSupportedException(); public Task AddGenre(EpisodeMetadata metadata, Genre genre) => throw new NotSupportedException(); - public Task AddTag(ErsatzTV.Core.Domain.Metadata metadata, Tag tag) => throw new NotSupportedException(); + public Task AddTag(Core.Domain.Metadata metadata, Tag tag) => throw new NotSupportedException(); public Task AddStudio(ShowMetadata metadata, Studio studio) => throw new NotSupportedException(); public Task AddActor(ShowMetadata metadata, Actor actor) => throw new NotSupportedException(); diff --git a/ErsatzTV.Core.Tests/Iptv/ChannelIdentifierTests.cs b/ErsatzTV.Core.Tests/Iptv/ChannelIdentifierTests.cs index 8d28f23c8..28df3b2e9 100644 --- a/ErsatzTV.Core.Tests/Iptv/ChannelIdentifierTests.cs +++ b/ErsatzTV.Core.Tests/Iptv/ChannelIdentifierTests.cs @@ -1,6 +1,6 @@ using ErsatzTV.Core.Iptv; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Iptv; @@ -27,7 +27,7 @@ public class ChannelIdentifierTests [TestCase("124", "C124.247.ersatztv.org")] public void TestNew(string channelNumber, string expected) { - string actual = ChannelIdentifier.FromNumber(channelNumber); - actual.ShouldBe(expected); + string actual = ChannelIdentifier.FromNumber(channelNumber); + actual.ShouldBe(expected); } } diff --git a/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs index 29e32d9e3..28b35aa1a 100644 --- a/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Jellyfin/JellyfinPathReplacementServiceTests.cs @@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Jellyfin; using ErsatzTV.FFmpeg.Runtime; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Jellyfin; diff --git a/ErsatzTV.Core.Tests/Jellyfin/JellyfinUrlTests.cs b/ErsatzTV.Core.Tests/Jellyfin/JellyfinUrlTests.cs index 31a0f9d0b..a13dfa0f6 100644 --- a/ErsatzTV.Core.Tests/Jellyfin/JellyfinUrlTests.cs +++ b/ErsatzTV.Core.Tests/Jellyfin/JellyfinUrlTests.cs @@ -1,8 +1,8 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Jellyfin; -using Shouldly; using Flurl; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Jellyfin; diff --git a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs index 0f2353f17..2a1594f9f 100644 --- a/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs +++ b/ErsatzTV.Core.Tests/Metadata/FallbackMetadataProviderTests.cs @@ -1,9 +1,9 @@ using Bugsnag; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Metadata; -using Shouldly; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Metadata; diff --git a/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs b/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs index b8ef15ad6..6f639a1bf 100644 --- a/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs +++ b/ErsatzTV.Core.Tests/Plex/PlexPathReplacementServiceTests.cs @@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Plex; using ErsatzTV.FFmpeg.Runtime; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Plex; diff --git a/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutChangeDetectionTests.cs b/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutChangeDetectionTests.cs index bca4f07f8..e756dcc42 100644 --- a/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutChangeDetectionTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/BlockPlayoutChangeDetectionTests.cs @@ -2,9 +2,9 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling.BlockScheduling; -using Shouldly; using Newtonsoft.Json; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling.BlockScheduling; @@ -64,8 +64,8 @@ public static class BlockPlayoutChangeDetectionTests List effectiveBlocks = [ - new EffectiveBlock(block1, blockKey1, GetLocalDate(2024, 1, 17).AddHours(9), 1), - new EffectiveBlock(block2, blockKey2, GetLocalDate(2024, 1, 17).AddHours(13), 2) + new(block1, blockKey1, GetLocalDate(2024, 1, 17).AddHours(9), 1), + new(block2, blockKey2, GetLocalDate(2024, 1, 17).AddHours(13), 2) ]; Map collectionEtags = LanguageExt.Map.Empty; @@ -90,7 +90,7 @@ public static class BlockPlayoutChangeDetectionTests List blocks = [ // SHOW A - new Block + new() { Id = 1, Items = @@ -108,7 +108,7 @@ public static class BlockPlayoutChangeDetectionTests DateUpdated = dateUpdated.UtcDateTime }, // SHOW B - new Block + new() { Id = 2, Items = @@ -126,7 +126,7 @@ public static class BlockPlayoutChangeDetectionTests DateUpdated = dateUpdated.UtcDateTime }, // SHOW C - new Block + new() { Id = 3, Items = diff --git a/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/EffectiveBlockTests.cs b/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/EffectiveBlockTests.cs index c442e396f..fa5ec8c44 100644 --- a/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/EffectiveBlockTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/BlockScheduling/EffectiveBlockTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Scheduling.BlockScheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling.BlockScheduling; @@ -49,7 +49,7 @@ public static class EffectiveBlockTests List templates = [ - new PlayoutTemplate + new() { Index = 1, DaysOfWeek = [DayOfWeek.Sunday], @@ -74,7 +74,7 @@ public static class EffectiveBlockTests List templates = [ - new PlayoutTemplate + new() { Index = 1, DaysOfWeek = [DayOfWeek.Monday, DayOfWeek.Wednesday, DayOfWeek.Friday], diff --git a/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs index 4430b9f73..781697e68 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ChronologicalContentTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -74,19 +74,18 @@ public class ChronologicalContentTests } private static List Episodes(int count) => - Range(1, count).Map( - i => (MediaItem)new Episode + Range(1, count).Map(i => (MediaItem)new Episode + { + Id = i, + EpisodeMetadata = new List { - Id = i, - EpisodeMetadata = new List + new() { - new() - { - ReleaseDate = new DateTime(2020, 1, i), - EpisodeNumber = 20 - i - } + ReleaseDate = new DateTime(2020, 1, i), + EpisodeNumber = 20 - i } - }) + } + }) .Reverse() .ToList(); } diff --git a/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs index 7a8b7a71f..f7e7072e2 100644 --- a/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/CustomOrderContentTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -83,18 +83,17 @@ public class CustomOrderContentTests private static List Episodes(int count) => - Range(1, count).Map( - i => (MediaItem)new Episode + Range(1, count).Map(i => (MediaItem)new Episode + { + Id = i, + EpisodeMetadata = new List { - Id = i, - EpisodeMetadata = new List + new() { - new() - { - ReleaseDate = new DateTime(2020, 1, i) - } + ReleaseDate = new DateTime(2020, 1, i) } - }) + } + }) .Reverse() .ToList(); } diff --git a/ErsatzTV.Core.Tests/Scheduling/MultiPartEpisodeGrouperTests.cs b/ErsatzTV.Core.Tests/Scheduling/MultiPartEpisodeGrouperTests.cs index 55dc3246c..e614af0f3 100644 --- a/ErsatzTV.Core.Tests/Scheduling/MultiPartEpisodeGrouperTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/MultiPartEpisodeGrouperTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -326,8 +326,7 @@ public class MultiPartEpisodeGrouperTests IEnumerable result, MediaItem first, List additional) => - result.Filter( - g => g.First == first && g.Additional != null && g.Additional.Count == additional.Count && - additional.ForAll(g.Additional.Contains)) + result.Filter(g => g.First == first && g.Additional != null && g.Additional.Count == additional.Count && + additional.ForAll(g.Additional.Contains)) .Count().ShouldBe(1); } diff --git a/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs index 4897fc25d..c2a847add 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlaylistEnumeratorTests.cs @@ -1,9 +1,9 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Scheduling; -using Shouldly; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -60,7 +60,7 @@ public class PlaylistEnumeratorTests repo, playlistItemMap, new CollectionEnumeratorState(), - shufflePlaylistItems: false, + false, CancellationToken.None); var items = new List(); @@ -127,7 +127,7 @@ public class PlaylistEnumeratorTests repo, playlistItemMap, new CollectionEnumeratorState(), - shufflePlaylistItems: false, + false, CancellationToken.None); var items = new List(); diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs index ba82ff2da..7de0c4d01 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutBuilderTests.cs @@ -6,11 +6,11 @@ using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Tests.Fakes; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Serilog; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs index ea1da89e9..859e1cfad 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerBaseTests.cs @@ -2,11 +2,11 @@ using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Serilog; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs index 4855b6d9d..bfbfdc989 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerDurationTests.cs @@ -2,10 +2,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Scheduling; -using Shouldly; using Microsoft.Extensions.Logging; using NUnit.Framework; using Serilog; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs index 6d1c37c67..4564a568f 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerFloodTests.cs @@ -1,10 +1,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Scheduling; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs index d1e95766f..b050be922 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerMultipleTests.cs @@ -1,10 +1,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Scheduling; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs index f1b0d44f9..0774a5054 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutModeSchedulerOneTests.cs @@ -1,10 +1,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Scheduling; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs b/ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs index 7dd90d5c9..85f361534 100644 --- a/ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/PlayoutTemplateSelectorTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; diff --git a/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs index 222300465..4cbdabbd0 100644 --- a/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/RandomizedContentTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -89,18 +89,17 @@ public class RandomizedContentTests } private static List Episodes(int count) => - Range(1, count).Map( - i => (MediaItem)new Episode + Range(1, count).Map(i => (MediaItem)new Episode + { + Id = i, + EpisodeMetadata = new List { - Id = i, - EpisodeMetadata = new List + new() { - new() - { - ReleaseDate = new DateTime(2020, 1, i) - } + ReleaseDate = new DateTime(2020, 1, i) } - }) + } + }) .Reverse() .ToList(); } diff --git a/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs b/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs index c092de582..232e31ae4 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs @@ -70,14 +70,13 @@ public class ScheduleIntegrationTests ServiceLifetime.Scoped, ServiceLifetime.Singleton); - services.AddDbContextFactory( - options => options.UseSqlite( - connectionString, - o => - { - o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); - })); + services.AddDbContextFactory(options => options.UseSqlite( + connectionString, + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); + })); SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); SqlMapper.AddTypeHandler(new GuidHandler()); @@ -193,14 +192,13 @@ public class ScheduleIntegrationTests ServiceLifetime.Scoped, ServiceLifetime.Singleton); - services.AddDbContextFactory( - options => options.UseSqlite( - connectionString, - o => - { - o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); - })); + services.AddDbContextFactory(options => options.UseSqlite( + connectionString, + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); + })); SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); SqlMapper.AddTypeHandler(new GuidHandler()); diff --git a/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs index b0407d518..99200d5d5 100644 --- a/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/SeasonEpisodeContentTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -74,19 +74,18 @@ public class SeasonEpisodeContentTests } private static List Episodes(int count) => - Range(1, count).Map( - i => (MediaItem)new Episode + Range(1, count).Map(i => (MediaItem)new Episode + { + Id = i, + EpisodeMetadata = new List { - Id = i, - EpisodeMetadata = new List + new() { - new() - { - ReleaseDate = new DateTime(2020, 1, 20 - i), - EpisodeNumber = i - } + ReleaseDate = new DateTime(2020, 1, 20 - i), + EpisodeNumber = i } - }) + } + }) .Reverse() .ToList(); } diff --git a/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs b/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs index ebfe8a29a..9ed386047 100644 --- a/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs +++ b/ErsatzTV.Core.Tests/Scheduling/ShuffledContentTests.cs @@ -1,7 +1,7 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Scheduling; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Core.Tests.Scheduling; @@ -84,7 +84,7 @@ public class ShuffledContentTests } list.ShouldNotBe([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]); - list.ShouldBe([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], ignoreOrder: true); + list.ShouldBe([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], true); } [Test] @@ -135,18 +135,17 @@ public class ShuffledContentTests } private static List Episodes(int count) => - Range(1, count).Map( - i => (MediaItem)new Episode + Range(1, count).Map(i => (MediaItem)new Episode + { + Id = i, + EpisodeMetadata = new List { - Id = i, - EpisodeMetadata = new List + new() { - new() - { - ReleaseDate = new DateTime(2020, 1, i) - } + ReleaseDate = new DateTime(2020, 1, i) } - }) + } + }) .Reverse() .ToList(); } diff --git a/ErsatzTV.Core/Domain/Channel.cs b/ErsatzTV.Core/Domain/Channel.cs index fa88a54c8..4d9a594e9 100644 --- a/ErsatzTV.Core/Domain/Channel.cs +++ b/ErsatzTV.Core/Domain/Channel.cs @@ -1,5 +1,5 @@ -using ErsatzTV.Core.Domain.Filler; using System.Net; +using ErsatzTV.Core.Domain.Filler; namespace ErsatzTV.Core.Domain; diff --git a/ErsatzTV.Core/Domain/MediaItem/BackgroundImageMediaVersion.cs b/ErsatzTV.Core/Domain/MediaItem/BackgroundImageMediaVersion.cs index ccfa906a8..b4e1b5d93 100644 --- a/ErsatzTV.Core/Domain/MediaItem/BackgroundImageMediaVersion.cs +++ b/ErsatzTV.Core/Domain/MediaItem/BackgroundImageMediaVersion.cs @@ -5,7 +5,12 @@ namespace ErsatzTV.Core.Domain; public class BackgroundImageMediaVersion : MediaVersion { - public static BackgroundImageMediaVersion ForPath(string path, IDisplaySize resolution, bool isSongWithProgress = false) => + public bool IsSongWithProgress { get; private set; } + + public static BackgroundImageMediaVersion ForPath( + string path, + IDisplaySize resolution, + bool isSongWithProgress = false) => new() { Chapters = [], @@ -26,6 +31,4 @@ public class BackgroundImageMediaVersion : MediaVersion MediaFiles = [new MediaFile { Path = path }], IsSongWithProgress = isSongWithProgress }; - - public bool IsSongWithProgress { get; private set; } } diff --git a/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs b/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs index 66d692819..fe2ff2c28 100644 --- a/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs +++ b/ErsatzTV.Core/Emby/EmbyPathReplacementService.cs @@ -69,20 +69,19 @@ public class EmbyPathReplacementService : IEmbyPathReplacementService bool log) { Option maybeReplacement = pathReplacements - .SingleOrDefault( - r => + .SingleOrDefault(r => + { + if (string.IsNullOrWhiteSpace(r.EmbyPath)) { - if (string.IsNullOrWhiteSpace(r.EmbyPath)) - { - return false; - } + return false; + } - string separatorChar = IsWindows(r.EmbyMediaSource, path) ? @"\" : @"/"; - string prefix = r.EmbyPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase) - ? r.EmbyPath - : r.EmbyPath + separatorChar; - return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); - }); + string separatorChar = IsWindows(r.EmbyMediaSource, path) ? @"\" : @"/"; + string prefix = r.EmbyPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase) + ? r.EmbyPath + : r.EmbyPath + separatorChar; + return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); + }); foreach (EmbyPathReplacement replacement in maybeReplacement) { diff --git a/ErsatzTV.Core/ErsatzTV.Core.csproj b/ErsatzTV.Core/ErsatzTV.Core.csproj index b9bf628ac..7f1ccc8da 100644 --- a/ErsatzTV.Core/ErsatzTV.Core.csproj +++ b/ErsatzTV.Core/ErsatzTV.Core.csproj @@ -9,29 +9,29 @@ - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + @@ -41,7 +41,7 @@ - + - + \ No newline at end of file diff --git a/ErsatzTV.Core/Extensions/StringExtensions.cs b/ErsatzTV.Core/Extensions/StringExtensions.cs index b808bda15..f9fb9c035 100644 --- a/ErsatzTV.Core/Extensions/StringExtensions.cs +++ b/ErsatzTV.Core/Extensions/StringExtensions.cs @@ -6,18 +6,21 @@ public static class StringExtensions { unchecked { - int hash1 = 5381; + var hash1 = 5381; int hash2 = hash1; - for (int i = 0; i < str.Length && str[i] != '\0'; i += 2) + for (var i = 0; i < str.Length && str[i] != '\0'; i += 2) { hash1 = ((hash1 << 5) + hash1) ^ str[i]; if (i == str.Length - 1 || str[i + 1] == '\0') + { break; + } + hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; } - return hash1 + (hash2 * 1566083941); + return hash1 + hash2 * 1566083941; } } -} \ No newline at end of file +} diff --git a/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs b/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs index 13609f90f..627155ece 100644 --- a/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/CustomStreamSelector.cs @@ -4,14 +4,19 @@ using ErsatzTV.Core.FFmpeg.Selector; using ErsatzTV.Core.Interfaces.FFmpeg; using ErsatzTV.Core.Interfaces.Metadata; using Microsoft.Extensions.Logging; +using NCalc; using YamlDotNet.Serialization; using YamlDotNet.Serialization.NamingConventions; namespace ErsatzTV.Core.FFmpeg; -public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger logger) : ICustomStreamSelector +public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger logger) + : ICustomStreamSelector { - public async Task SelectStreams(Channel channel, MediaItemAudioVersion audioVersion, List allSubtitles) + public async Task SelectStreams( + Channel channel, + MediaItemAudioVersion audioVersion, + List allSubtitles) { try { @@ -235,7 +240,7 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger { e.Result = name switch @@ -256,7 +261,7 @@ public class CustomStreamSelector(ILocalFileSystem localFileSystem, ILogger { e.Result = name switch diff --git a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs index c741329a0..77883e881 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs @@ -184,21 +184,20 @@ public class FFmpegComplexFilterBuilder string outputPixelFormat = null; - _scaleToSize.IfSome( - size => + _scaleToSize.IfSome(size => + { + string filter = videoOnly switch { - string filter = videoOnly switch - { - true => - $"scale={size.Width}:{size.Height}:force_original_aspect_ratio=increase,crop={size.Width}:{size.Height}", - false => $"scale={size.Width}:{size.Height}:flags=fast_bilinear" - }; + true => + $"scale={size.Width}:{size.Height}:force_original_aspect_ratio=increase,crop={size.Width}:{size.Height}", + false => $"scale={size.Width}:{size.Height}:flags=fast_bilinear" + }; - if (!string.IsNullOrWhiteSpace(filter)) - { - videoFilterQueue.Add(filter); - } - }); + if (!string.IsNullOrWhiteSpace(filter)) + { + videoFilterQueue.Add(filter); + } + }); if (scaleOrPad && _boxBlur == false) { diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index 56fb60ff3..6d6574749 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -18,9 +18,9 @@ namespace ErsatzTV.Core.FFmpeg; public class FFmpegLibraryProcessService : IFFmpegProcessService { private readonly IConfigElementRepository _configElementRepository; + private readonly ICustomStreamSelector _customStreamSelector; private readonly FFmpegProcessService _ffmpegProcessService; private readonly IFFmpegStreamSelector _ffmpegStreamSelector; - private readonly ICustomStreamSelector _customStreamSelector; private readonly ILogger _logger; private readonly IPipelineBuilderFactory _pipelineBuilderFactory; private readonly ITempFilePool _tempFilePool; @@ -92,11 +92,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService List allSubtitles = await getSubtitles(playbackSettings); Option maybeAudioStream = Option.None; - Option maybeSubtitle = Option.None; + Option maybeSubtitle = Option.None; if (channel.StreamSelectorMode is ChannelStreamSelectorMode.Custom) { - StreamSelectorResult result = await _customStreamSelector.SelectStreams(channel, audioVersion, allSubtitles); + StreamSelectorResult result = await _customStreamSelector.SelectStreams( + channel, + audioVersion, + allSubtitles); maybeAudioStream = result.AudioStream; maybeSubtitle = result.Subtitle; @@ -150,15 +153,14 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService .Map(o => o.Watermark) .Flatten() .Where(wm => wm.Mode == ChannelWatermarkMode.Intermittent) - .Map( - wm => - WatermarkCalculator.CalculateFadePoints( - start, - inPoint, - outPoint, - playbackSettings.StreamSeek, - wm.FrequencyMinutes, - wm.DurationSeconds)); + .Map(wm => + WatermarkCalculator.CalculateFadePoints( + start, + inPoint, + outPoint, + playbackSettings.StreamSeek, + wm.FrequencyMinutes, + wm.DurationSeconds)); string audioFormat = playbackSettings.AudioFormat switch { @@ -188,16 +190,15 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService IPixelFormat pixelFormat = await AvailablePixelFormats .ForPixelFormat(videoStream.PixelFormat, pixelFormatLogger) - .IfNoneAsync( - () => + .IfNoneAsync(() => + { + return videoStream.BitsPerRawSample switch { - return videoStream.BitsPerRawSample switch - { - 8 => new PixelFormatYuv420P(), - 10 => new PixelFormatYuv420P10Le(), - _ => new PixelFormatUnknown(videoStream.BitsPerRawSample) - }; - }); + 8 => new PixelFormatYuv420P(), + 10 => new PixelFormatYuv420P10Le(), + _ => new PixelFormatUnknown(videoStream.BitsPerRawSample) + }; + }); var ffmpegVideoStream = new VideoStream( videoStream.Index, @@ -218,12 +219,11 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService var videoInputFile = new VideoInputFile(videoPath, new List { ffmpegVideoStream }); - Option audioInputFile = maybeAudioStream.Map( - audioStream => - { - var ffmpegAudioStream = new AudioStream(audioStream.Index, audioStream.Codec, audioStream.Channels); - return new AudioInputFile(audioPath, new List { ffmpegAudioStream }, audioState); - }); + Option audioInputFile = maybeAudioStream.Map(audioStream => + { + var ffmpegAudioStream = new AudioStream(audioStream.Index, audioStream.Codec, audioStream.Channels); + return new AudioInputFile(audioPath, new List { ffmpegAudioStream }, audioState); + }); // when no audio streams are available, use null audio source if (!audioVersion.MediaVersion.Streams.Any(s => s.MediaStreamKind is MediaStreamKind.Audio)) @@ -260,72 +260,71 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService Option subtitleLanguage = Option.None; Option subtitleTitle = Option.None; - Option subtitleInputFile = maybeSubtitle.Map>( - subtitle => + Option subtitleInputFile = maybeSubtitle.Map>(subtitle => + { + if (!subtitle.IsImage && subtitle.SubtitleKind == SubtitleKind.Embedded && + (!subtitle.IsExtracted || string.IsNullOrWhiteSpace(subtitle.Path))) { - if (!subtitle.IsImage && subtitle.SubtitleKind == SubtitleKind.Embedded && - (!subtitle.IsExtracted || string.IsNullOrWhiteSpace(subtitle.Path))) + _logger.LogWarning("Subtitles are not yet available for this item"); + return None; + } + + var ffmpegSubtitleStream = new ErsatzTV.FFmpeg.MediaStream( + subtitle.IsImage ? subtitle.StreamIndex : 0, + subtitle.Codec, + StreamKind.Video); + + string path = subtitle.IsImage switch + { + true => videoPath, + false when subtitle.SubtitleKind == SubtitleKind.Sidecar => subtitle.Path, + _ => Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.Path) + }; + + SubtitleMethod method = SubtitleMethod.Burn; + if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect) + { + method = (outputFormat, subtitle.SubtitleKind, subtitle.Codec) switch + { + // mkv supports all subtitle codecs, maybe? + (OutputFormatKind.Mkv, SubtitleKind.Embedded, _) => SubtitleMethod.Copy, + + // MP4 supports vobsub + (OutputFormatKind.Mp4, SubtitleKind.Embedded, "dvdsub" or "dvd_subtitle" or "vobsub") => + SubtitleMethod.Copy, + + // MP4 does not support PGS + (OutputFormatKind.Mp4, SubtitleKind.Embedded, "pgs" or "pgssub" or "hdmv_pgs_subtitle") => + SubtitleMethod.None, + + // ignore text subtitles for now + _ => SubtitleMethod.None + }; + + if (method == SubtitleMethod.None) { - _logger.LogWarning("Subtitles are not yet available for this item"); return None; } - var ffmpegSubtitleStream = new ErsatzTV.FFmpeg.MediaStream( - subtitle.IsImage ? subtitle.StreamIndex : 0, - subtitle.Codec, - StreamKind.Video); - - string path = subtitle.IsImage switch + // hls direct won't use extracted embedded subtitles + if (subtitle.SubtitleKind == SubtitleKind.Embedded) { - true => videoPath, - false when subtitle.SubtitleKind == SubtitleKind.Sidecar => subtitle.Path, - _ => Path.Combine(FileSystemLayout.SubtitleCacheFolder, subtitle.Path) - }; - - SubtitleMethod method = SubtitleMethod.Burn; - if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect) - { - method = (outputFormat, subtitle.SubtitleKind, subtitle.Codec) switch - { - // mkv supports all subtitle codecs, maybe? - (OutputFormatKind.Mkv, SubtitleKind.Embedded, _) => SubtitleMethod.Copy, - - // MP4 supports vobsub - (OutputFormatKind.Mp4, SubtitleKind.Embedded, "dvdsub" or "dvd_subtitle" or "vobsub") => - SubtitleMethod.Copy, - - // MP4 does not support PGS - (OutputFormatKind.Mp4, SubtitleKind.Embedded, "pgs" or "pgssub" or "hdmv_pgs_subtitle") => - SubtitleMethod.None, - - // ignore text subtitles for now - _ => SubtitleMethod.None - }; - - if (method == SubtitleMethod.None) - { - return None; - } - - // hls direct won't use extracted embedded subtitles - if (subtitle.SubtitleKind == SubtitleKind.Embedded) - { - path = videoPath; - ffmpegSubtitleStream = ffmpegSubtitleStream with { Index = subtitle.StreamIndex }; - } + path = videoPath; + ffmpegSubtitleStream = ffmpegSubtitleStream with { Index = subtitle.StreamIndex }; } + } - if (method == SubtitleMethod.Copy) - { - subtitleLanguage = Optional(subtitle.Language); - subtitleTitle = Optional(subtitle.Title); - } + if (method == SubtitleMethod.Copy) + { + subtitleLanguage = Optional(subtitle.Language); + subtitleTitle = Optional(subtitle.Title); + } - return new SubtitleInputFile( - path, - new List { ffmpegSubtitleStream }, - method); - }).Flatten(); + return new SubtitleInputFile( + path, + new List { ffmpegSubtitleStream }, + method); + }).Flatten(); Option watermarkInputFile = GetWatermarkInputFile(watermarkOptions, maybeFadePoints); @@ -422,7 +421,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService playbackSettings.ThreadCount, qsvExtraHardwareFrames, videoVersion is BackgroundImageMediaVersion { IsSongWithProgress: true }, - IsHdrTonemap: false, + false, GetTonemapAlgorithm(playbackSettings)); _logger.LogDebug("FFmpeg desired state {FrameState}", desiredState); @@ -577,8 +576,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService ptsOffset, Option.None, qsvExtraHardwareFrames, - IsSongWithProgress: false, - IsHdrTonemap: false, + false, + false, GetTonemapAlgorithm(playbackSettings)); var ffmpegSubtitleStream = new ErsatzTV.FFmpeg.MediaStream(0, "ass", StreamKind.Video); @@ -773,8 +772,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService 0, playbackSettings.ThreadCount, Optional(channel.FFmpegProfile.QsvExtraHardwareFrames), - IsSongWithProgress: false, - IsHdrTonemap: false, + false, + false, GetTonemapAlgorithm(playbackSettings)); _logger.LogDebug("FFmpeg desired state {FrameState}", desiredState); @@ -945,23 +944,21 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService ScanKind.Progressive) }, new WatermarkState( - maybeFadePoints.Map( - lst => lst.Map( - fp => - { - return fp switch - { - FadeInPoint fip => (WatermarkFadePoint)new WatermarkFadeIn( - fip.Time, - fip.EnableStart, - fip.EnableFinish), - FadeOutPoint fop => new WatermarkFadeOut( - fop.Time, - fop.EnableStart, - fop.EnableFinish), - _ => throw new NotSupportedException() // this will never happen - }; - }).ToList()), + maybeFadePoints.Map(lst => lst.Map(fp => + { + return fp switch + { + FadeInPoint fip => (WatermarkFadePoint)new WatermarkFadeIn( + fip.Time, + fip.EnableStart, + fip.EnableFinish), + FadeOutPoint fop => new WatermarkFadeOut( + fop.Time, + fop.EnableStart, + fop.EnableFinish), + _ => throw new NotSupportedException() // this will never happen + }; + }).ToList()), watermark.Location, watermark.Size, watermark.WidthPercent, @@ -1074,7 +1071,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService FFmpegProfileTonemapAlgorithm.Reinhard => TonemapAlgorithm.Reinhard, FFmpegProfileTonemapAlgorithm.Mobius => TonemapAlgorithm.Mobius, FFmpegProfileTonemapAlgorithm.Hable => TonemapAlgorithm.Hable, - _ => throw new ArgumentOutOfRangeException($"unexpected tonemap algorithm {playbackSettings.TonemapAlgorithm}") + _ => throw new ArgumentOutOfRangeException( + $"unexpected tonemap algorithm {playbackSettings.TonemapAlgorithm}") }; private static Option GetVideoProfile(string videoFormat, string videoProfile) => diff --git a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs index 10e07898c..441e564ca 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs @@ -98,7 +98,8 @@ public static class FFmpegPlaybackSettingsCalculator } IDisplaySize sizeAfterScaling = result.ScaledSize.IfNone(videoVersion); - if (!sizeAfterScaling.IsSameSizeAs(ffmpegProfile.Resolution) && ffmpegProfile.ScalingBehavior is not ScalingBehavior.Crop) + if (!sizeAfterScaling.IsSameSizeAs(ffmpegProfile.Resolution) && + ffmpegProfile.ScalingBehavior is not ScalingBehavior.Crop) { result.PadToDesiredResolution = true; } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs index 063b8d313..d901fad39 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessBuilder.cs @@ -177,14 +177,13 @@ internal class FFmpegProcessBuilder maybeIndex, audioPath.IsSome && videoPath != audioPath.IfNone("NotARealPath")); - maybeFilter.IfSome( - filter => - { - _arguments.Add("-filter_complex"); - _arguments.Add(filter.ComplexFilter); - videoLabel = filter.VideoLabel; - audioLabel = filter.AudioLabel; - }); + maybeFilter.IfSome(filter => + { + _arguments.Add("-filter_complex"); + _arguments.Add(filter.ComplexFilter); + videoLabel = filter.VideoLabel; + audioLabel = filter.AudioLabel; + }); _arguments.Add("-map"); _arguments.Add(videoLabel); diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs index 7503be513..753f71d09 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegProcessService.cs @@ -1,6 +1,5 @@ using System.Diagnostics; using System.Text; -using System.Text.Encodings.Web; using Bugsnag; using CliWrap; using CliWrap.Buffered; @@ -198,14 +197,18 @@ public class FFmpegProcessService None, await IsAnimated(ffprobePath, customPath)); case ChannelWatermarkImageSource.ChannelLogo: - Option maybeChannelPath = (channel.Artwork.Count == 0) ? + Option maybeChannelPath = channel.Artwork.Count == 0 + ? //We have to generate the logo on the fly and save it to a local temp path - ChannelLogoGenerator.GenerateChannelLogoUrl(channel) : + ChannelLogoGenerator.GenerateChannelLogoUrl(channel) + : //We have an artwork attached to the channel, let's use it :) channel.Artwork .Filter(a => a.ArtworkKind == ArtworkKind.Logo) .HeadOrNone() - .Map(a => Artwork.IsExternalUrl(a.Path) ? a.Path : _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + .Map(a => Artwork.IsExternalUrl(a.Path) + ? a.Path + : _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); return new WatermarkOptions( await watermarkOverride.IfNoneAsync(watermark), @@ -235,14 +238,18 @@ public class FFmpegProcessService None, await IsAnimated(ffprobePath, customPath)); case ChannelWatermarkImageSource.ChannelLogo: - Option maybeChannelPath = (channel.Artwork.Count == 0) ? + Option maybeChannelPath = channel.Artwork.Count == 0 + ? //We have to generate the logo on the fly and save it to a local temp path - ChannelLogoGenerator.GenerateChannelLogoUrl(channel) : + ChannelLogoGenerator.GenerateChannelLogoUrl(channel) + : //We have an artwork attached to the channel, let's use it :) channel.Artwork .Filter(a => a.ArtworkKind == ArtworkKind.Logo) .HeadOrNone() - .Map(a => Artwork.IsExternalUrl(a.Path) ? a.Path : _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + .Map(a => Artwork.IsExternalUrl(a.Path) + ? a.Path + : _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); return new WatermarkOptions( await watermarkOverride.IfNoneAsync(channel.Watermark), maybeChannelPath, @@ -271,14 +278,18 @@ public class FFmpegProcessService None, await IsAnimated(ffprobePath, customPath)); case ChannelWatermarkImageSource.ChannelLogo: - Option maybeChannelPath = (channel.Artwork.Count == 0) ? + Option maybeChannelPath = channel.Artwork.Count == 0 + ? //We have to generate the logo on the fly and save it to a local temp path - ChannelLogoGenerator.GenerateChannelLogoUrl(channel) : + ChannelLogoGenerator.GenerateChannelLogoUrl(channel) + : //We have an artwork attached to the channel, let's use it :) channel.Artwork .Filter(a => a.ArtworkKind == ArtworkKind.Logo) .HeadOrNone() - .Map(a => Artwork.IsExternalUrl(a.Path) ? a.Path : _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); + .Map(a => Artwork.IsExternalUrl(a.Path) + ? a.Path + : _imageCache.GetPathForImage(a.Path, ArtworkKind.Logo, Option.None)); return new WatermarkOptions( await watermarkOverride.IfNoneAsync(watermark), maybeChannelPath, diff --git a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs index 49c87dd51..13559be29 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs @@ -236,8 +236,8 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector { var audioStreams = version.Streams.Filter(s => s.MediaStreamKind == MediaStreamKind.Audio).ToList(); - var correctLanguage = audioStreams.Filter( - s => preferredLanguageCodes.Any(c => string.Equals(s.Language, c, StringComparison.OrdinalIgnoreCase))) + var correctLanguage = audioStreams.Filter(s => + preferredLanguageCodes.Any(c => string.Equals(s.Language, c, StringComparison.OrdinalIgnoreCase))) .ToList(); if (correctLanguage.Count != 0) diff --git a/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs b/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs index 1637758ff..519c7759c 100644 --- a/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs +++ b/ErsatzTV.Core/FFmpeg/SongVideoGenerator.cs @@ -165,7 +165,7 @@ public class SongVideoGenerator : ISongVideoGenerator { Id = 0, ArtworkKind = ArtworkKind.Thumbnail, - Path = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "song_album_cover_512.png"), + Path = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "song_album_cover_512.png") }); // signal that we want to use cover art as watermark @@ -247,7 +247,7 @@ public class SongVideoGenerator : ISongVideoGenerator videoVersion = BackgroundImageMediaVersion.ForPath( si, channel.FFmpegProfile.Resolution, - isSongWithProgress: channel.SongVideoMode is ChannelSongVideoMode.WithProgress); + channel.SongVideoMode is ChannelSongVideoMode.WithProgress); } return Tuple(videoPath, videoVersion); diff --git a/ErsatzTV.Core/FileSystemLayout.cs b/ErsatzTV.Core/FileSystemLayout.cs index 85a385770..3f639bb14 100644 --- a/ErsatzTV.Core/FileSystemLayout.cs +++ b/ErsatzTV.Core/FileSystemLayout.cs @@ -1,9 +1,67 @@ using System.Reflection; +using Serilog; namespace ErsatzTV.Core; public static class FileSystemLayout { + public static readonly string AppDataFolder; + + public static readonly string TranscodeFolder; + + public static readonly string DataProtectionFolder; + public static readonly string LogsFolder; + + public static readonly string DatabasePath; + public static readonly string LogFilePath; + + public static readonly string LegacyImageCacheFolder; + public static readonly string ResourcesCacheFolder; + public static readonly string ChannelGuideCacheFolder; + + public static readonly string PlexSecretsPath; + public static readonly string JellyfinSecretsPath; + public static readonly string EmbySecretsPath; + + public static readonly string FFmpegReportsFolder; + public static readonly string SearchIndexFolder; + public static readonly string TempFilePoolFolder; + + public static readonly string ArtworkCacheFolder; + + public static readonly string PosterCacheFolder; + public static readonly string ThumbnailCacheFolder; + public static readonly string LogoCacheFolder; + public static readonly string FanArtCacheFolder; + public static readonly string WatermarkCacheFolder; + + public static readonly string StreamsCacheFolder; + + public static readonly string SubtitleCacheFolder; + public static readonly string FontsCacheFolder; + + public static readonly string TemplatesFolder; + + public static readonly string MusicVideoCreditsTemplatesFolder; + + public static readonly string ChannelGuideTemplatesFolder; + + public static readonly string ScriptsFolder; + + public static readonly string MultiEpisodeShuffleTemplatesFolder; + + public static readonly string AudioStreamSelectorScriptsFolder; + + public static readonly string ChannelStreamSelectorsFolder; + + public static readonly string MacOsOldAppDataFolder = Path.Combine( + Environment.GetEnvironmentVariable("HOME") ?? string.Empty, + ".local", + "share", + "ersatztv"); + + public static readonly string MacOsOldDatabasePath = Path.Combine(MacOsOldAppDataFolder, "ersatztv.sqlite3"); + static FileSystemLayout() { string version = Assembly.GetEntryAssembly()?.GetCustomAttribute() @@ -25,7 +83,7 @@ public static class FileSystemLayout // check for config at old location if (Directory.Exists(defaultConfigFolder)) { - Serilog.Log.Logger.Warning( + Log.Logger.Warning( "Ignoring ETV_CONFIG_FOLDER {Folder} and using default {Default}", customConfigFolder, defaultConfigFolder); @@ -51,7 +109,7 @@ public static class FileSystemLayout // check for config at old location if (Directory.Exists(defaultTranscodeFolder)) { - Serilog.Log.Logger.Warning( + Log.Logger.Warning( "Ignoring ETV_TRANSCODE_FOLDER {Folder} and using default {Default}", customTranscodeFolder, defaultTranscodeFolder); @@ -110,61 +168,4 @@ public static class FileSystemLayout ChannelStreamSelectorsFolder = Path.Combine(ScriptsFolder, "channel-stream-selectors"); } - - public static readonly string AppDataFolder; - - public static readonly string TranscodeFolder; - - public static readonly string DataProtectionFolder; - public static readonly string LogsFolder; - - public static readonly string DatabasePath; - public static readonly string LogFilePath; - - public static readonly string LegacyImageCacheFolder; - public static readonly string ResourcesCacheFolder; - public static readonly string ChannelGuideCacheFolder; - - public static readonly string PlexSecretsPath; - public static readonly string JellyfinSecretsPath; - public static readonly string EmbySecretsPath; - - public static readonly string FFmpegReportsFolder; - public static readonly string SearchIndexFolder; - public static readonly string TempFilePoolFolder; - - public static readonly string ArtworkCacheFolder; - - public static readonly string PosterCacheFolder; - public static readonly string ThumbnailCacheFolder; - public static readonly string LogoCacheFolder; - public static readonly string FanArtCacheFolder; - public static readonly string WatermarkCacheFolder; - - public static readonly string StreamsCacheFolder; - - public static readonly string SubtitleCacheFolder; - public static readonly string FontsCacheFolder; - - public static readonly string TemplatesFolder; - - public static readonly string MusicVideoCreditsTemplatesFolder; - - public static readonly string ChannelGuideTemplatesFolder; - - public static readonly string ScriptsFolder; - - public static readonly string MultiEpisodeShuffleTemplatesFolder; - - public static readonly string AudioStreamSelectorScriptsFolder; - - public static readonly string ChannelStreamSelectorsFolder; - - public static readonly string MacOsOldAppDataFolder = Path.Combine( - Environment.GetEnvironmentVariable("HOME") ?? string.Empty, - ".local", - "share", - "ersatztv"); - - public static readonly string MacOsOldDatabasePath = Path.Combine(MacOsOldAppDataFolder, "ersatztv.sqlite3"); } diff --git a/ErsatzTV.Core/Hdhr/Discover.cs b/ErsatzTV.Core/Hdhr/Discover.cs index 4f7a11b1e..bb673dc01 100644 --- a/ErsatzTV.Core/Hdhr/Discover.cs +++ b/ErsatzTV.Core/Hdhr/Discover.cs @@ -6,9 +6,9 @@ namespace ErsatzTV.Core.Hdhr; [SuppressMessage("Performance", "CA1822:Mark members as static")] public class Discover { + private readonly Guid _UUID; private readonly string _host; private readonly string _scheme; - private readonly Guid _UUID; public Discover(string scheme, string host, int tunerCount, Guid uuid) { diff --git a/ErsatzTV.Core/Images/ChannelLogoGenerator.cs b/ErsatzTV.Core/Images/ChannelLogoGenerator.cs index 12d892ce9..193995920 100644 --- a/ErsatzTV.Core/Images/ChannelLogoGenerator.cs +++ b/ErsatzTV.Core/Images/ChannelLogoGenerator.cs @@ -13,13 +13,8 @@ public class ChannelLogoGenerator : IChannelLogoGenerator private readonly ILogger _logger; public ChannelLogoGenerator( - ILogger logger) - { + ILogger logger) => _logger = logger; - } - - public static Option GenerateChannelLogoUrl(Channel channel) => - $"http://localhost:{Settings.StreamingPort}{GetRoute}?{GetRouteQueryParamName}={channel.WebEncodedName}"; public Either GenerateChannelLogo( string text, @@ -35,12 +30,12 @@ public class ChannelLogoGenerator : IChannelLogoGenerator //etv logo string overlayImagePath = Path.Combine("wwwroot", "images", "ersatztv-500.png"); - using SKBitmap overlayImage = SKBitmap.Decode(overlayImagePath); + using var overlayImage = SKBitmap.Decode(overlayImagePath); canvas.DrawBitmap(overlayImage, new SKRect(155, 60, 205, 110)); //Custom Font string fontPath = Path.Combine(FileSystemLayout.ResourcesCacheFolder, "Sen.ttf"); - using SKTypeface fontTypeface = SKTypeface.FromFile(fontPath); + using var fontTypeface = SKTypeface.FromFile(fontPath); var fontSize = 30; var font = new SKFont { @@ -71,7 +66,7 @@ public class ChannelLogoGenerator : IChannelLogoGenerator canvas.DrawText(text, x, y, SKTextAlign.Center, font, paint); using SKImage image = surface.Snapshot(); - using MemoryStream ms = new MemoryStream(); + using var ms = new MemoryStream(); image.Encode(SKEncodedImageFormat.Png, 100).SaveTo(ms); ms.Seek(0, SeekOrigin.Begin); return ms.ToArray(); @@ -82,4 +77,7 @@ public class ChannelLogoGenerator : IChannelLogoGenerator return BaseError.New("Can't generate Channel Logo " + ex.Message); } } + + public static Option GenerateChannelLogoUrl(Channel channel) => + $"http://localhost:{Settings.StreamingPort}{GetRoute}?{GetRouteQueryParamName}={channel.WebEncodedName}"; } diff --git a/ErsatzTV.Core/Interfaces/FFmpeg/ICustomStreamSelector.cs b/ErsatzTV.Core/Interfaces/FFmpeg/ICustomStreamSelector.cs index 752637529..80097f5f0 100644 --- a/ErsatzTV.Core/Interfaces/FFmpeg/ICustomStreamSelector.cs +++ b/ErsatzTV.Core/Interfaces/FFmpeg/ICustomStreamSelector.cs @@ -5,5 +5,8 @@ namespace ErsatzTV.Core.Interfaces.FFmpeg; public interface ICustomStreamSelector { - Task SelectStreams(Channel channel, MediaItemAudioVersion audioVersion, List allSubtitles); + Task SelectStreams( + Channel channel, + MediaItemAudioVersion audioVersion, + List allSubtitles); } diff --git a/ErsatzTV.Core/Interfaces/Images/IChannelLogoGenerator.cs b/ErsatzTV.Core/Interfaces/Images/IChannelLogoGenerator.cs index d603914af..051f6455a 100644 --- a/ErsatzTV.Core/Interfaces/Images/IChannelLogoGenerator.cs +++ b/ErsatzTV.Core/Interfaces/Images/IChannelLogoGenerator.cs @@ -1,5 +1,3 @@ -using ErsatzTV.Core.Domain; - namespace ErsatzTV.Core.Interfaces.Images; public interface IChannelLogoGenerator diff --git a/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs b/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs index 27de55e0e..9635e63ac 100644 --- a/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs +++ b/ErsatzTV.Core/Interfaces/Jellyfin/IJellyfinApiClient.cs @@ -8,9 +8,15 @@ public interface IJellyfinApiClient Task> GetServerInformation(string address, string apiKey); Task>> GetLibraries(string address, string apiKey); - IAsyncEnumerable> GetMovieLibraryItems(string address, string apiKey, JellyfinLibrary library); + IAsyncEnumerable> GetMovieLibraryItems( + string address, + string apiKey, + JellyfinLibrary library); - IAsyncEnumerable> GetShowLibraryItems(string address, string apiKey, JellyfinLibrary library); + IAsyncEnumerable> GetShowLibraryItems( + string address, + string apiKey, + JellyfinLibrary library); IAsyncEnumerable> GetSeasonLibraryItems( string address, @@ -24,7 +30,10 @@ public interface IJellyfinApiClient JellyfinLibrary library, string seasonId); - IAsyncEnumerable> GetCollectionLibraryItems(string address, string apiKey, int mediaSourceId); + IAsyncEnumerable> GetCollectionLibraryItems( + string address, + string apiKey, + int mediaSourceId); IAsyncEnumerable> GetCollectionItems( string address, diff --git a/ErsatzTV.Core/Interfaces/Repositories/IMediaServerOtherVideoRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IMediaServerOtherVideoRepository.cs index 78ff5fac3..3af5febb1 100644 --- a/ErsatzTV.Core/Interfaces/Repositories/IMediaServerOtherVideoRepository.cs +++ b/ErsatzTV.Core/Interfaces/Repositories/IMediaServerOtherVideoRepository.cs @@ -12,6 +12,11 @@ public interface IMediaServerOtherVideoRepository> FlagUnavailable(TLibrary library, TOtherVideo otherVideo); Task> FlagRemoteOnly(TLibrary library, TOtherVideo otherVideo); Task> FlagFileNotFound(TLibrary library, List movieItemIds); - Task>> GetOrAdd(TLibrary library, TOtherVideo item, bool deepScan); + + Task>> GetOrAdd( + TLibrary library, + TOtherVideo item, + bool deepScan); + Task SetEtag(TOtherVideo otherVideo, string etag); } diff --git a/ErsatzTV.Core/Iptv/ChannelGuide.cs b/ErsatzTV.Core/Iptv/ChannelGuide.cs index 93837daf8..0924c3740 100644 --- a/ErsatzTV.Core/Iptv/ChannelGuide.cs +++ b/ErsatzTV.Core/Iptv/ChannelGuide.cs @@ -32,8 +32,8 @@ public class ChannelGuide xml.WriteRaw(_channelsFragment); - foreach ((string channelNumber, string channelDataFragment) in _channelDataFragments.OrderBy( - kvp => decimal.Parse(kvp.Key, CultureInfo.InvariantCulture))) + foreach ((string channelNumber, string channelDataFragment) in _channelDataFragments.OrderBy(kvp => + decimal.Parse(kvp.Key, CultureInfo.InvariantCulture))) { xml.WriteRaw(channelDataFragment); } diff --git a/ErsatzTV.Core/Iptv/ChannelIdentifier.cs b/ErsatzTV.Core/Iptv/ChannelIdentifier.cs index 67044fecc..caa2c733e 100644 --- a/ErsatzTV.Core/Iptv/ChannelIdentifier.cs +++ b/ErsatzTV.Core/Iptv/ChannelIdentifier.cs @@ -4,16 +4,13 @@ namespace ErsatzTV.Core.Iptv; public static class ChannelIdentifier { - public static string LegacyFromNumber(string channelNumber) - { - return $"{channelNumber}.etv"; - } + public static string LegacyFromNumber(string channelNumber) => $"{channelNumber}.etv"; public static string FromNumber(string channelNumber) { // get rid of any decimal (only two are allowed) - int number = (int)(decimal.Parse(channelNumber, CultureInfo.InvariantCulture) * 100); - int id = 0; + var number = (int)(decimal.Parse(channelNumber, CultureInfo.InvariantCulture) * 100); + var id = 0; while (number != 0) { id += number % 10 + 48; diff --git a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs index f63655c41..15849c61e 100644 --- a/ErsatzTV.Core/Iptv/ChannelPlaylist.cs +++ b/ErsatzTV.Core/Iptv/ChannelPlaylist.cs @@ -1,5 +1,4 @@ using System.Globalization; -using System.Net; using System.Text; using ErsatzTV.Core.Domain; diff --git a/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs b/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs index c0b8a453c..6e34294ab 100644 --- a/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs +++ b/ErsatzTV.Core/Jellyfin/JellyfinPathReplacementService.cs @@ -69,20 +69,19 @@ public class JellyfinPathReplacementService : IJellyfinPathReplacementService bool log) { Option maybeReplacement = pathReplacements - .SingleOrDefault( - r => + .SingleOrDefault(r => + { + if (string.IsNullOrWhiteSpace(r.JellyfinPath)) { - if (string.IsNullOrWhiteSpace(r.JellyfinPath)) - { - return false; - } + return false; + } - string separatorChar = IsWindows(r.JellyfinMediaSource, path) ? @"\" : @"/"; - string prefix = r.JellyfinPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase) - ? r.JellyfinPath - : r.JellyfinPath + separatorChar; - return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); - }); + string separatorChar = IsWindows(r.JellyfinMediaSource, path) ? @"\" : @"/"; + string prefix = r.JellyfinPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase) + ? r.JellyfinPath + : r.JellyfinPath + separatorChar; + return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); + }); foreach (JellyfinPathReplacement replacement in maybeReplacement) { diff --git a/ErsatzTV.Core/LanguageExtensions.cs b/ErsatzTV.Core/LanguageExtensions.cs index 9db3815b6..dd82903ba 100644 --- a/ErsatzTV.Core/LanguageExtensions.cs +++ b/ErsatzTV.Core/LanguageExtensions.cs @@ -28,12 +28,11 @@ public static class LanguageExtensions ILogger logger, string message, params object[] args) => - tryAsync.IfFail( - ex => - { - logger.LogError(ex, message, args); - return defaultValue; - }); + tryAsync.IfFail(ex => + { + logger.LogError(ex, message, args); + return defaultValue; + }); public static Task LogFailure( this TryAsync tryAsync, diff --git a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs index 1d25edd93..4574576d0 100644 --- a/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs +++ b/ErsatzTV.Core/Metadata/FallbackMetadataProvider.cs @@ -192,11 +192,10 @@ public partial class FallbackMetadataProvider : IFallbackMetadataProvider if (matches.Count > 0) { - var episodeNumbers = matches.Bind( - m => m.Groups[1].Value - .Replace('e', '-') - .Split('-') - .Bind(ep => int.TryParse(ep, out int num) ? Some(num) : Option.None)) + var episodeNumbers = matches.Bind(m => m.Groups[1].Value + .Replace('e', '-') + .Split('-') + .Bind(ep => int.TryParse(ep, out int num) ? Some(num) : Option.None)) .ToList(); switch (episodeNumbers.Count) diff --git a/ErsatzTV.Core/Plex/PlexPathReplacementService.cs b/ErsatzTV.Core/Plex/PlexPathReplacementService.cs index 71123b821..85903d90c 100644 --- a/ErsatzTV.Core/Plex/PlexPathReplacementService.cs +++ b/ErsatzTV.Core/Plex/PlexPathReplacementService.cs @@ -35,20 +35,19 @@ public class PlexPathReplacementService : IPlexPathReplacementService public string GetReplacementPlexPath(List pathReplacements, string path, bool log = true) { Option maybeReplacement = pathReplacements - .SingleOrDefault( - r => + .SingleOrDefault(r => + { + if (string.IsNullOrWhiteSpace(r.PlexPath)) { - if (string.IsNullOrWhiteSpace(r.PlexPath)) - { - return false; - } + return false; + } - string separatorChar = IsWindows(r.PlexMediaSource) ? @"\" : @"/"; - string prefix = r.PlexPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase) - ? r.PlexPath - : r.PlexPath + separatorChar; - return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); - }); + string separatorChar = IsWindows(r.PlexMediaSource) ? @"\" : @"/"; + string prefix = r.PlexPath.EndsWith(separatorChar, StringComparison.OrdinalIgnoreCase) + ? r.PlexPath + : r.PlexPath + separatorChar; + return path.StartsWith(prefix, StringComparison.OrdinalIgnoreCase); + }); foreach (PlexPathReplacement replacement in maybeReplacement) { diff --git a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs index 32b116b3b..f44f3905b 100644 --- a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs @@ -337,8 +337,8 @@ public class BlockPlayoutBuilder( .Distinct() .ToList(); - IEnumerable>> tuples = await collectionKeys.Map( - async collectionKey => Tuple( + IEnumerable>> tuples = await collectionKeys.Map(async collectionKey => + Tuple( collectionKey, await MediaItemsForCollection.Collect( mediaCollectionRepository, diff --git a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs index 9e7e7acc9..248c1cd08 100644 --- a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs +++ b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs @@ -56,7 +56,9 @@ public class BlockPlayoutFillerBuilder( foreach (Deco deco in GetDecoFor(playout, start)) { if (!HasDefaultFiller(deco)) + { continue; + } var collectionKey = CollectionKey.ForDecoDefaultFiller(deco); string historyKey = HistoryDetails.ForDefaultFiller(deco); @@ -89,7 +91,9 @@ public class BlockPlayoutFillerBuilder( // skip this deco if the collection has no items if (enumerator.Count == 0) + { continue; + } DateTimeOffset current = start; var pastTime = false; @@ -171,7 +175,8 @@ public class BlockPlayoutFillerBuilder( { foreach (DecoTemplateItem decoTemplateItem in template.DecoTemplate.Items) { - if (decoTemplateItem.StartTime <= start.TimeOfDay && decoTemplateItem.EndTime == TimeSpan.Zero || decoTemplateItem.EndTime > start.TimeOfDay) + if (decoTemplateItem.StartTime <= start.TimeOfDay && decoTemplateItem.EndTime == TimeSpan.Zero || + decoTemplateItem.EndTime > start.TimeOfDay) { switch (decoTemplateItem.Deco.DefaultFillerMode) { diff --git a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs index 963bddc22..ee4bbbf56 100644 --- a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutShuffledMediaCollectionEnumerator.cs @@ -24,8 +24,8 @@ public class BlockPlayoutShuffledMediaCollectionEnumerator : IMediaCollectionEnu _shuffled = Shuffle(_mediaItems); _lazyMinimumDuration = - new Lazy>( - () => _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + new Lazy>(() => + _shuffled.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); } public void ResetState(CollectionEnumeratorState state) diff --git a/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs index 0d600e33f..caacffd83 100644 --- a/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs @@ -16,8 +16,8 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu CurrentIncludeInProgramGuide = Option.None; _sortedMediaItems = mediaItems.OrderBy(identity, new ChronologicalMediaComparer()).ToList(); - _lazyMinimumDuration = new Lazy>( - () => _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + _lazyMinimumDuration = new Lazy>(() => + _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); State = new CollectionEnumeratorState { Seed = state.Seed }; diff --git a/ErsatzTV.Core/Scheduling/CustomOrderCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/CustomOrderCollectionEnumerator.cs index 05c289c07..e4687d788 100644 --- a/ErsatzTV.Core/Scheduling/CustomOrderCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/CustomOrderCollectionEnumerator.cs @@ -22,8 +22,8 @@ public class CustomOrderCollectionEnumerator : IMediaCollectionEnumerator .OrderBy(ci => ci.CustomIndex) .Map(ci => mediaItems.First(mi => mi.Id == ci.MediaItemId)) .ToList(); - _lazyMinimumDuration = new Lazy>( - () => _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + _lazyMinimumDuration = new Lazy>(() => + _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); State = new CollectionEnumeratorState { Seed = state.Seed }; while (State.Index < state.Index) diff --git a/ErsatzTV.Core/Scheduling/HistoryDetails.cs b/ErsatzTV.Core/Scheduling/HistoryDetails.cs index 8cd9ff8b7..33e7a33e4 100644 --- a/ErsatzTV.Core/Scheduling/HistoryDetails.cs +++ b/ErsatzTV.Core/Scheduling/HistoryDetails.cs @@ -59,7 +59,7 @@ internal static class HistoryDetails collectionKey.SmartCollectionId, collectionKey.MediaItemId, collectionKey.PlaylistId, - collectionKey.FakeCollectionKey, + collectionKey.FakeCollectionKey }; return JsonConvert.SerializeObject(key, Formatting.None, JsonSettings); @@ -74,7 +74,7 @@ internal static class HistoryDetails CollectionType = deco.DefaultFillerCollectionType, CollectionId = deco.DefaultFillerCollectionId, MultiCollectionId = deco.DefaultFillerMultiCollectionId, - SmartCollectionId = deco.DefaultFillerSmartCollectionId, + SmartCollectionId = deco.DefaultFillerSmartCollectionId }; return JsonConvert.SerializeObject(key, Formatting.None, JsonSettings); @@ -141,7 +141,8 @@ internal static class HistoryDetails maybeMatchedItem = fakeItem; } } - else if (maybeMatchedItem.IsNone && playbackOrder is PlaybackOrder.Chronological && details.ReleaseDate.HasValue) + else if (maybeMatchedItem.IsNone && playbackOrder is PlaybackOrder.Chronological && + details.ReleaseDate.HasValue) { maybeMatchedItem = Optional(collectionItems.Find(ci => MatchReleaseDate(ci, details.ReleaseDate.Value))); diff --git a/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs b/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs index 50679c947..51568293b 100644 --- a/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/PlaylistEnumerator.cs @@ -10,17 +10,22 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator { private readonly System.Collections.Generic.HashSet _remainingMediaItemIds = []; private System.Collections.Generic.HashSet _allMediaItemIds; - private int _enumeratorIndex; private System.Collections.Generic.HashSet _idsToIncludeInEPG; private IList _playAll; - private List _sortedEnumerators; - private bool _shufflePlaylistItems; private CloneableRandom _random; + private bool _shufflePlaylistItems; + private List _sortedEnumerators; private PlaylistEnumerator() { } + public int CountForRandom => _allMediaItemIds.Count; + + public ImmutableList ChildEnumerators { get; private set; } + + public int EnumeratorIndex { get; private set; } + public void ResetState(CollectionEnumeratorState state) => // seed doesn't matter here State.Index = state.Index; @@ -28,7 +33,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator public CollectionEnumeratorState State { get; private set; } public Option Current => _sortedEnumerators.Count > 0 - ? _sortedEnumerators[_enumeratorIndex].Current + ? _sortedEnumerators[EnumeratorIndex].Current : Option.None; public Option CurrentIncludeInProgramGuide @@ -46,27 +51,25 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator public int Count => throw new NotSupportedException("Count isn't used for playlist enumeration"); - public int CountForRandom => _allMediaItemIds.Count; - public Option MinimumDuration { get; private set; } public void MoveNext() { - foreach (MediaItem maybeMediaItem in _sortedEnumerators[_enumeratorIndex].Current) + foreach (MediaItem maybeMediaItem in _sortedEnumerators[EnumeratorIndex].Current) { _remainingMediaItemIds.Remove(maybeMediaItem.Id); } - _sortedEnumerators[_enumeratorIndex].MoveNext(); + _sortedEnumerators[EnumeratorIndex].MoveNext(); // if we aren't playing all, or if we just finished playing all, move to the next enumerator - if (!_playAll[_enumeratorIndex] || _sortedEnumerators[_enumeratorIndex].State.Index == 0) + if (!_playAll[EnumeratorIndex] || _sortedEnumerators[EnumeratorIndex].State.Index == 0) { - _enumeratorIndex = (_enumeratorIndex + 1) % _sortedEnumerators.Count; + EnumeratorIndex = (EnumeratorIndex + 1) % _sortedEnumerators.Count; } State.Index += 1; - if (_remainingMediaItemIds.Count == 0 && _enumeratorIndex == 0 && _sortedEnumerators[0].State.Index == 0) + if (_remainingMediaItemIds.Count == 0 && EnumeratorIndex == 0 && _sortedEnumerators[0].State.Index == 0) { State.Index = 0; _remainingMediaItemIds.UnionWith(_allMediaItemIds); @@ -80,14 +83,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator } } - public ImmutableList ChildEnumerators { get; private set; } - - public int EnumeratorIndex => _enumeratorIndex; - - public void SetEnumeratorIndex(int enumeratorIndex) - { - _enumeratorIndex = enumeratorIndex % _sortedEnumerators.Count; - } + public void SetEnumeratorIndex(int enumeratorIndex) => EnumeratorIndex = enumeratorIndex % _sortedEnumerators.Count; public static async Task Create( IMediaCollectionRepository mediaCollectionRepository, @@ -196,7 +192,7 @@ public class PlaylistEnumerator : IMediaCollectionEnumerator } result.State = new CollectionEnumeratorState { Seed = state.Seed }; - result._enumeratorIndex = 0; + result.EnumeratorIndex = 0; // this was a bug when playlist enumerators were first added; shouldn't happen anymore if (state.Index < 0) diff --git a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs index ae4c79414..234e7860e 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutBuilder.cs @@ -17,11 +17,11 @@ public class PlayoutBuilder : IPlayoutBuilder { private static readonly Random Random = new(); private readonly IArtistRepository _artistRepository; - private readonly IPlayoutTimeShifter _playoutTimeShifter; private readonly IConfigElementRepository _configElementRepository; private readonly ILocalFileSystem _localFileSystem; private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMultiEpisodeShuffleCollectionEnumeratorFactory _multiEpisodeFactory; + private readonly IPlayoutTimeShifter _playoutTimeShifter; private readonly ITelevisionRepository _televisionRepository; private Playlist _debugPlaylist; private ILogger _logger; @@ -150,12 +150,12 @@ public class PlayoutBuilder : IPlayoutBuilder // _logger.LogDebug("Checkpoint anchors: {@Anchors}", playout.ProgramScheduleAnchors); // remove old checkpoints - playout.ProgramScheduleAnchors.RemoveAll( - a => a.AnchorDateOffset.IfNone(SystemTime.MaxValueUtc) < parameters.Start.Date); + playout.ProgramScheduleAnchors.RemoveAll(a => + a.AnchorDateOffset.IfNone(SystemTime.MaxValueUtc) < parameters.Start.Date); // remove new checkpoints - playout.ProgramScheduleAnchors.RemoveAll( - a => a.AnchorDateOffset.IfNone(SystemTime.MinValueUtc).Date > parameters.Start.Date); + playout.ProgramScheduleAnchors.RemoveAll(a => + a.AnchorDateOffset.IfNone(SystemTime.MinValueUtc).Date > parameters.Start.Date); // _logger.LogDebug("Remaining anchors: {@Anchors}", playout.ProgramScheduleAnchors); @@ -284,8 +284,8 @@ public class PlayoutBuilder : IPlayoutBuilder playout.Channel.Name); // remove old checkpoints - playout.ProgramScheduleAnchors.RemoveAll( - a => a.AnchorDateOffset.IfNone(SystemTime.MaxValueUtc) < parameters.Start.Date); + playout.ProgramScheduleAnchors.RemoveAll(a => + a.AnchorDateOffset.IfNone(SystemTime.MaxValueUtc) < parameters.Start.Date); // _logger.LogDebug("Remaining anchors: {@Anchors}", playout.ProgramScheduleAnchors); @@ -493,8 +493,8 @@ public class PlayoutBuilder : IPlayoutBuilder 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( @@ -517,9 +517,9 @@ public class PlayoutBuilder : IPlayoutBuilder // 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()); + .FirstOrDefault(x => x.Name.Equals("DeserializeObject", StringComparison.OrdinalIgnoreCase) && + x.IsGenericMethod && + x.GetParameters().Length == 1)?.MakeGenericMethod(scheduleItem.GetType()); foreach (CollectionWithItems fakeCollection in fakeCollections) { @@ -824,8 +824,8 @@ public class PlayoutBuilder : IPlayoutBuilder .Distinct() .ToList(); - IEnumerable>> tuples = await collectionKeys.Map( - async collectionKey => Tuple( + IEnumerable>> tuples = await collectionKeys.Map(async collectionKey => + Tuple( collectionKey, await MediaItemsForCollection.Collect( _mediaCollectionRepository, @@ -895,28 +895,27 @@ public class PlayoutBuilder : IPlayoutBuilder Playout playout, DateTimeOffset start, IScheduleItemsEnumerator enumerator) => - Optional(playout.Anchor).IfNone( - () => + Optional(playout.Anchor).IfNone(() => + { + ProgramScheduleItem schedule = enumerator.Current; + switch (schedule.StartType) { - ProgramScheduleItem schedule = enumerator.Current; - switch (schedule.StartType) - { - case StartType.Fixed: - return new PlayoutAnchor - { - ScheduleItemsEnumeratorState = enumerator.State, - NextStart = (start - start.TimeOfDay).UtcDateTime + - schedule.StartTime.GetValueOrDefault() - }; - case StartType.Dynamic: - default: - return new PlayoutAnchor - { - ScheduleItemsEnumeratorState = enumerator.State, - NextStart = (start - start.TimeOfDay).UtcDateTime - }; - } - }); + case StartType.Fixed: + return new PlayoutAnchor + { + ScheduleItemsEnumeratorState = enumerator.State, + NextStart = (start - start.TimeOfDay).UtcDateTime + + schedule.StartTime.GetValueOrDefault() + }; + case StartType.Dynamic: + default: + return new PlayoutAnchor + { + ScheduleItemsEnumeratorState = enumerator.State, + NextStart = (start - start.TimeOfDay).UtcDateTime + }; + } + }); private static List BuildProgramScheduleAnchors( Playout playout, @@ -927,15 +926,15 @@ public class PlayoutBuilder : IPlayoutBuilder foreach (CollectionKey collectionKey in collectionEnumerators.Keys) { - Option maybeExisting = playout.ProgramScheduleAnchors.FirstOrDefault( - a => a.CollectionType == collectionKey.CollectionType - && a.CollectionId == collectionKey.CollectionId - && a.MediaItemId == collectionKey.MediaItemId - && a.FakeCollectionKey == collectionKey.FakeCollectionKey - && a.SmartCollectionId == collectionKey.SmartCollectionId - && a.MultiCollectionId == collectionKey.MultiCollectionId - && a.PlaylistId == collectionKey.PlaylistId - && a.AnchorDate is null); + Option maybeExisting = playout.ProgramScheduleAnchors.FirstOrDefault(a => + a.CollectionType == collectionKey.CollectionType + && a.CollectionId == collectionKey.CollectionId + && a.MediaItemId == collectionKey.MediaItemId + && a.FakeCollectionKey == collectionKey.FakeCollectionKey + && a.SmartCollectionId == collectionKey.SmartCollectionId + && a.MultiCollectionId == collectionKey.MultiCollectionId + && a.PlaylistId == collectionKey.PlaylistId + && a.AnchorDate is null); var maybeEnumeratorState = collectionEnumerators.ToDictionary(e => e.Key, e => e.Value.State); @@ -967,8 +966,8 @@ public class PlayoutBuilder : IPlayoutBuilder result.Add(scheduleAnchor); } - foreach (PlayoutProgramScheduleAnchor checkpointAnchor in playout.ProgramScheduleAnchors.Where( - a => a.AnchorDate is not null)) + foreach (PlayoutProgramScheduleAnchor checkpointAnchor in playout.ProgramScheduleAnchors.Where(a => + a.AnchorDate is not null)) { result.Add(checkpointAnchor); } @@ -987,13 +986,12 @@ public class PlayoutBuilder : IPlayoutBuilder { Option maybeAnchor = playout.ProgramScheduleAnchors .OrderByDescending(a => a.AnchorDate ?? DateTime.MaxValue) - .FirstOrDefault( - a => a.CollectionType == collectionKey.CollectionType - && a.CollectionId == collectionKey.CollectionId - && a.MultiCollectionId == collectionKey.MultiCollectionId - && a.SmartCollectionId == collectionKey.SmartCollectionId - && a.MediaItemId == collectionKey.MediaItemId - && a.PlaylistId == collectionKey.PlaylistId); + .FirstOrDefault(a => a.CollectionType == collectionKey.CollectionType + && a.CollectionId == collectionKey.CollectionId + && a.MultiCollectionId == collectionKey.MultiCollectionId + && a.SmartCollectionId == collectionKey.SmartCollectionId + && a.MediaItemId == collectionKey.MediaItemId + && a.PlaylistId == collectionKey.PlaylistId); CollectionEnumeratorState state = null; @@ -1020,7 +1018,7 @@ public class PlayoutBuilder : IPlayoutBuilder _mediaCollectionRepository, playlistItemMap, state, - shufflePlaylistItems: false, + false, cancellationToken); } } diff --git a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs index bce727292..246594dfb 100644 --- a/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs +++ b/ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs @@ -73,13 +73,19 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe // need to wrap to the next day if appropriate FixedStartTimeBehavior? fixedStartTimeBehavior = scheduleItem.FixedStartTimeBehavior; if (fixedStartTimeBehavior is null && scheduleItem.ProgramSchedule is not null) + { fixedStartTimeBehavior = scheduleItem.ProgramSchedule.FixedStartTimeBehavior; + } + switch (fixedStartTimeBehavior) { case FixedStartTimeBehavior.Flexible: // only wait for times on the same day if (result.Day == startTime.Day && result.TimeOfDay > startTime.TimeOfDay) + { startTime = result; + } + break; case FixedStartTimeBehavior.Strict: default: @@ -296,8 +302,8 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe } } - foreach (FillerPreset filler in allFiller.Filter( - f => f.FillerKind == FillerKind.PreRoll && f.FillerMode != FillerMode.Pad)) + foreach (FillerPreset filler in allFiller.Filter(f => + f.FillerKind == FillerKind.PreRoll && f.FillerMode != FillerMode.Pad)) { switch (filler.FillerMode) { @@ -344,8 +350,8 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe } else { - foreach (FillerPreset filler in allFiller.Filter( - f => f.FillerKind == FillerKind.MidRoll && f.FillerMode != FillerMode.Pad)) + foreach (FillerPreset filler in allFiller.Filter(f => + f.FillerKind == FillerKind.MidRoll && f.FillerMode != FillerMode.Pad)) { switch (filler.FillerMode) { @@ -361,7 +367,9 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe playoutBuilderState, e1, filler.Duration.Value, - scheduleItem.GuideMode == GuideMode.Filler ? FillerKind.GuideMode : FillerKind.MidRoll, + scheduleItem.GuideMode == GuideMode.Filler + ? FillerKind.GuideMode + : FillerKind.MidRoll, filler.AllowWatermarks, log, cancellationToken)); @@ -381,7 +389,9 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe playoutBuilderState, e2, filler.Count.Value, - scheduleItem.GuideMode == GuideMode.Filler ? FillerKind.GuideMode : FillerKind.MidRoll, + scheduleItem.GuideMode == GuideMode.Filler + ? FillerKind.GuideMode + : FillerKind.MidRoll, filler.AllowWatermarks, cancellationToken)); } @@ -400,7 +410,9 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe playoutBuilderState, e3, filler.Count.Value, - scheduleItem.GuideMode == GuideMode.Filler ? FillerKind.GuideMode : FillerKind.MidRoll, + scheduleItem.GuideMode == GuideMode.Filler + ? FillerKind.GuideMode + : FillerKind.MidRoll, filler.AllowWatermarks, cancellationToken)); } @@ -411,8 +423,8 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe } } - foreach (FillerPreset filler in allFiller.Filter( - f => f.FillerKind == FillerKind.PostRoll && f.FillerMode != FillerMode.Pad)) + foreach (FillerPreset filler in allFiller.Filter(f => + f.FillerKind == FillerKind.PostRoll && f.FillerMode != FillerMode.Pad)) { switch (filler.FillerMode) { @@ -485,7 +497,9 @@ public abstract class PlayoutModeSchedulerBase : IPlayoutModeScheduler whe // ensure filler works for content less than one minute if (targetTime <= playoutItem.StartOffset + totalDuration) + { targetTime = targetTime.AddMinutes(padFiller.PadToNearestMinute.Value); + } TimeSpan remainingToFill = targetTime - totalDuration - playoutItem.StartOffset; diff --git a/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs index d9f255d48..d971d51ab 100644 --- a/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/RandomizedMediaCollectionEnumerator.cs @@ -17,8 +17,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/RandomizedRotatingMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/RandomizedRotatingMediaCollectionEnumerator.cs index 1d0c7d3f5..bb53bd66f 100644 --- a/ErsatzTV.Core/Scheduling/RandomizedRotatingMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/RandomizedRotatingMediaCollectionEnumerator.cs @@ -6,12 +6,12 @@ namespace ErsatzTV.Core.Scheduling; public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnumerator { + private readonly Dictionary> _groupMedia; private readonly Lazy> _lazyMinimumDuration; private readonly IList _mediaItems; private readonly Random _random; - private readonly Dictionary> _groupMedia; - private int _index; private int _groupNumber; + private int _index; public RandomizedRotatingMediaCollectionEnumerator(IList mediaItems, CollectionEnumeratorState state) { @@ -19,12 +19,12 @@ public class RandomizedRotatingMediaCollectionEnumerator : IMediaCollectionEnume _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); _groupMedia = new Dictionary>(); - for (int i = 0; i < mediaItems.Count; i++) + for (var i = 0; i < mediaItems.Count; i++) { int id = mediaItems[i] switch { diff --git a/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs index cb381a6f8..8f32b20b7 100644 --- a/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/SeasonEpisodeMediaCollectionEnumerator.cs @@ -16,8 +16,8 @@ public sealed class SeasonEpisodeMediaCollectionEnumerator : IMediaCollectionEnu CurrentIncludeInProgramGuide = Option.None; _sortedMediaItems = mediaItems.OrderBy(identity, new SeasonEpisodeMediaComparer()).ToList(); - _lazyMinimumDuration = new Lazy>( - () => _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); + _lazyMinimumDuration = new Lazy>(() => + _sortedMediaItems.Bind(i => i.GetNonZeroDuration()).OrderBy(identity).HeadOrNone()); State = new CollectionEnumeratorState { Seed = state.Seed }; diff --git a/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs b/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs index 8c1ab3b5d..ca6705da5 100644 --- a/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ShuffleInOrderCollectionEnumerator.cs @@ -36,8 +36,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 8ae39145f..f7fe43924 100644 --- a/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs +++ b/ErsatzTV.Core/Scheduling/ShuffledMediaCollectionEnumerator.cs @@ -33,8 +33,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.Core/Scheduling/YamlScheduling/EnumeratorCache.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs index fc03871cc..e9a60d24e 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/EnumeratorCache.cs @@ -9,9 +9,9 @@ namespace ErsatzTV.Core.Scheduling.YamlScheduling; public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepository, ILogger logger) { + private readonly Dictionary _enumerators = new(); private readonly Dictionary> _mediaItems = new(); private readonly Dictionary> _playlistMediaItems = new(); - private readonly Dictionary _enumerators = new(); public System.Collections.Generic.HashSet MissingContentKeys { get; } = []; @@ -97,7 +97,10 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor if (content is YamlPlayoutContentMarathonItem marathon) { var helper = new YamlPlayoutMarathonHelper(mediaCollectionRepository); - Option maybeResult = await helper.GetEnumerator(marathon, state, cancellationToken); + Option maybeResult = await helper.GetEnumerator( + marathon, + state, + cancellationToken); foreach (YamlMarathonContentResult result in maybeResult) { foreach ((CollectionKey collectionKey, List mediaItems) in result.Content) @@ -112,7 +115,10 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor // playlist is a special case that needs to be handled on its own if (content is YamlPlayoutContentPlaylistItem playlist) { - if (!string.IsNullOrWhiteSpace(playlist.Order) && !string.Equals(playlist.Order, "none", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrWhiteSpace(playlist.Order) && !string.Equals( + playlist.Order, + "none", + StringComparison.OrdinalIgnoreCase)) { logger.LogWarning( "Ignoring playback order {Order} for playlist {Playlist}", @@ -134,7 +140,7 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor mediaCollectionRepository, itemMap, state, - shufflePlaylistItems: false, + false, cancellationToken); } @@ -145,7 +151,7 @@ public class EnumeratorCache(IMediaCollectionRepository mediaCollectionRepositor case PlaybackOrder.Shuffle: bool keepMultiPartEpisodesTogether = content.MultiPart; List groupedMediaItems = keepMultiPartEpisodesTogether - ? MultiPartEpisodeGrouper.GroupMediaItems(items, treatCollectionsAsShows: false) + ? MultiPartEpisodeGrouper.GroupMediaItems(items, false) : items.Map(mi => new GroupedMediaItem(mi, null)).ToList(); return new BlockPlayoutShuffledMediaCollectionEnumerator(groupedMediaItems, state); } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutApplyHistoryHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutApplyHistoryHandler.cs index eddf663d9..3428e4d89 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutApplyHistoryHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutApplyHistoryHandler.cs @@ -64,7 +64,8 @@ public class YamlPlayoutApplyHistoryHandler(EnumeratorCache enumeratorCache) var hasSetEnumeratorIndex = false; var childEnumeratorKeys = playlistEnumerator.ChildEnumerators.Map(x => x.CollectionKey).ToList(); - foreach ((IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) in playlistEnumerator.ChildEnumerators) + foreach ((IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) in + playlistEnumerator.ChildEnumerators) { PlaybackOrder itemPlaybackOrder = childEnumerator switch { @@ -90,11 +91,12 @@ public class YamlPlayoutApplyHistoryHandler(EnumeratorCache enumeratorCache) // h.Details, // h.IsCurrentChild); - enumerator.ResetState(new CollectionEnumeratorState - { - Seed = enumerator.State.Seed, - Index = h.Index + (h.IsCurrentChild ? 1 : 0) - }); + enumerator.ResetState( + new CollectionEnumeratorState + { + Seed = enumerator.State.Seed, + Index = h.Index + (h.IsCurrentChild ? 1 : 0) + }); if (itemPlaybackOrder is PlaybackOrder.Chronological) { diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs index bf9f3790c..d95b66204 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs @@ -1,5 +1,3 @@ -using System.Collections.Immutable; -using System.Globalization; using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain.Filler; using ErsatzTV.Core.Domain.Scheduling; @@ -67,7 +65,9 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache) YamlPlayoutContentItem contentItem = context.Definition.Content[index]; if (!Enum.TryParse(contentItem.Order, true, out PlaybackOrder playbackOrder)) { - logger.LogDebug("Unable to find history for content matching playback order {PlaybackOrder}", contentItem.Order); + logger.LogDebug( + "Unable to find history for content matching playback order {PlaybackOrder}", + contentItem.Order); return []; } @@ -93,7 +93,8 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache) for (var i = 0; i < playlistEnumerator.ChildEnumerators.Count; i++) { - (IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) = playlistEnumerator.ChildEnumerators[i]; + (IMediaCollectionEnumerator childEnumerator, CollectionKey collectionKey) = + playlistEnumerator.ChildEnumerators[i]; bool isCurrentChild = i == playlistEnumerator.EnumeratorIndex; foreach (MediaItem currentMediaItem in childEnumerator.Current) { @@ -153,7 +154,7 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache) return FillerKind.None; } - return Enum.TryParse(instruction.FillerKind, ignoreCase: true, out FillerKind result) + return Enum.TryParse(instruction.FillerKind, true, out FillerKind result) ? result : FillerKind.None; } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs index b334f8ddc..c4aebc93e 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs @@ -3,6 +3,7 @@ using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using Microsoft.Extensions.Logging; +using NCalc; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; @@ -33,7 +34,7 @@ public class YamlPlayoutCountHandler(EnumeratorCache enumeratorCache) : YamlPlay int enumeratorCount = enumerator is PlaylistEnumerator playlistEnumerator ? playlistEnumerator.CountForRandom : enumerator.Count; - var expression = new NCalc.Expression(count.Count); + var expression = new Expression(count.Count); expression.EvaluateParameter += (name, e) => { e.Result = name switch diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs index 91fea214b..4128ec3b8 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs @@ -86,7 +86,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP Option fallbackEnumerator, ILogger logger) { - bool done = false; + var done = false; TimeSpan remainingToFill = targetTime - context.CurrentTime; while (!done && enumerator.Current.IsSome && remainingToFill > TimeSpan.Zero) { diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs index eab56262f..2f71af2f4 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs @@ -36,7 +36,9 @@ public class YamlPlayoutPadToNextHandler(EnumeratorCache enumeratorCache) : Yaml // ensure filler works for content less than one minute if (targetTime <= context.CurrentTime) + { targetTime = targetTime.AddMinutes(padToNext.PadToNext); + } Option maybeEnumerator = await GetContentEnumerator( context, @@ -60,7 +62,7 @@ public class YamlPlayoutPadToNextHandler(EnumeratorCache enumeratorCache) : Yaml false, padToNext.DiscardAttempts, padToNext.Trim, - offlineTail: true, + true, GetFillerKind(padToNext), padToNext.CustomTitle, enumerator, diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs index 33d3ea0cd..e3e88f9c0 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using Microsoft.Extensions.Logging; +using NCalc; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; @@ -29,7 +30,7 @@ public class YamlPlayoutPadUntilHandler(EnumeratorCache enumeratorCache) : YamlP if (timeOnly > result) { - var expression = new NCalc.Expression(padUntil.Tomorrow); + var expression = new Expression(padUntil.Tomorrow); expression.EvaluateParameter += (name, e) => { e.Result = name switch diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutSkipItemsHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutSkipItemsHandler.cs index 5c6356a60..02a0d68ee 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutSkipItemsHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutSkipItemsHandler.cs @@ -1,6 +1,7 @@ using ErsatzTV.Core.Interfaces.Scheduling; using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using Microsoft.Extensions.Logging; +using NCalc; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; @@ -32,7 +33,7 @@ public class YamlPlayoutSkipItemsHandler(EnumeratorCache enumeratorCache) : IYam int enumeratorCount = enumerator is PlaylistEnumerator playlistEnumerator ? playlistEnumerator.CountForRandom : enumerator.Count; - var expression = new NCalc.Expression(skipItems.SkipItems); + var expression = new Expression(skipItems.SkipItems); expression.EvaluateParameter += (name, e) => { e.Result = name switch diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs index 0c96f3f37..9e0dc3a3c 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs @@ -38,7 +38,7 @@ public class YamlPlayoutBuilder( Dictionary handlers = new(); var enumeratorCache = new EnumeratorCache(mediaCollectionRepository, logger); - var context = new YamlPlayoutContext(playout, playoutDefinition, guideGroup: 1) + var context = new YamlPlayoutContext(playout, playoutDefinition, 1) { CurrentTime = start @@ -82,7 +82,9 @@ public class YamlPlayoutBuilder( // remove any future or "currently active" history items // this prevents "walking" the playout forward by repeatedly resetting var toRemove = new List(); - toRemove.AddRange(playout.PlayoutHistory.Filter(h => h.When > start.UtcDateTime || h.When <= start.UtcDateTime && h.Finish >= start.UtcDateTime)); + toRemove.AddRange( + playout.PlayoutHistory.Filter(h => + h.When > start.UtcDateTime || h.When <= start.UtcDateTime && h.Finish >= start.UtcDateTime)); foreach (PlayoutHistory history in toRemove) { playout.PlayoutHistory.Remove(history); @@ -210,7 +212,8 @@ public class YamlPlayoutBuilder( foreach (YamlPlayoutSequenceItem sequence in definition.Sequence) { - foreach (YamlPlayoutSequenceInstruction instruction in sequence.Items.OfType()) + foreach (YamlPlayoutSequenceInstruction instruction in + sequence.Items.OfType()) { graph.AddEdge(sequence.Key, instruction.Sequence); } @@ -317,40 +320,39 @@ public class YamlPlayoutBuilder( IDeserializer deserializer = new DeserializerBuilder() .WithNamingConvention(CamelCaseNamingConvention.Instance) - .WithTypeDiscriminatingNodeDeserializer( - o => + .WithTypeDiscriminatingNodeDeserializer(o => + { + var contentKeyMappings = new Dictionary { - var contentKeyMappings = new Dictionary - { - { "collection", typeof(YamlPlayoutContentCollectionItem) }, - { "marathon", typeof(YamlPlayoutContentMarathonItem) }, - { "multi_collection", typeof(YamlPlayoutContentMultiCollectionItem) }, - { "playlist", typeof(YamlPlayoutContentPlaylistItem) }, - { "search", typeof(YamlPlayoutContentSearchItem) }, - { "show", typeof(YamlPlayoutContentShowItem) }, - { "smart_collection", typeof(YamlPlayoutContentSmartCollectionItem) } - }; + { "collection", typeof(YamlPlayoutContentCollectionItem) }, + { "marathon", typeof(YamlPlayoutContentMarathonItem) }, + { "multi_collection", typeof(YamlPlayoutContentMultiCollectionItem) }, + { "playlist", typeof(YamlPlayoutContentPlaylistItem) }, + { "search", typeof(YamlPlayoutContentSearchItem) }, + { "show", typeof(YamlPlayoutContentShowItem) }, + { "smart_collection", typeof(YamlPlayoutContentSmartCollectionItem) } + }; - o.AddUniqueKeyTypeDiscriminator(contentKeyMappings); + o.AddUniqueKeyTypeDiscriminator(contentKeyMappings); - var instructionKeyMappings = new Dictionary - { - { "all", typeof(YamlPlayoutAllInstruction) }, - { "count", typeof(YamlPlayoutCountInstruction) }, - { "duration", typeof(YamlPlayoutDurationInstruction) }, - { "epg_group", typeof(YamlPlayoutEpgGroupInstruction) }, - { "pad_to_next", typeof(YamlPlayoutPadToNextInstruction) }, - { "pad_until", typeof(YamlPlayoutPadUntilInstruction) }, - { "repeat", typeof(YamlPlayoutRepeatInstruction) }, - { "sequence", typeof(YamlPlayoutSequenceInstruction) }, - { "shuffle_sequence", typeof(YamlPlayoutShuffleSequenceInstruction) }, - { "skip_items", typeof(YamlPlayoutSkipItemsInstruction) }, - { "skip_to_item", typeof(YamlPlayoutSkipToItemInstruction) }, - { "wait_until", typeof(YamlPlayoutWaitUntilInstruction) } - }; + var instructionKeyMappings = new Dictionary + { + { "all", typeof(YamlPlayoutAllInstruction) }, + { "count", typeof(YamlPlayoutCountInstruction) }, + { "duration", typeof(YamlPlayoutDurationInstruction) }, + { "epg_group", typeof(YamlPlayoutEpgGroupInstruction) }, + { "pad_to_next", typeof(YamlPlayoutPadToNextInstruction) }, + { "pad_until", typeof(YamlPlayoutPadUntilInstruction) }, + { "repeat", typeof(YamlPlayoutRepeatInstruction) }, + { "sequence", typeof(YamlPlayoutSequenceInstruction) }, + { "shuffle_sequence", typeof(YamlPlayoutShuffleSequenceInstruction) }, + { "skip_items", typeof(YamlPlayoutSkipItemsInstruction) }, + { "skip_to_item", typeof(YamlPlayoutSkipToItemInstruction) }, + { "wait_until", typeof(YamlPlayoutWaitUntilInstruction) } + }; - o.AddUniqueKeyTypeDiscriminator(instructionKeyMappings); - }) + o.AddUniqueKeyTypeDiscriminator(instructionKeyMappings); + }) .Build(); return deserializer.Deserialize(yaml); diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs index 987bcc367..bb672ff2e 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs @@ -6,9 +6,9 @@ namespace ErsatzTV.Core.Scheduling.YamlScheduling; public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definition, int guideGroup) { private readonly System.Collections.Generic.HashSet _visitedInstructions = []; - private int _instructionIndex; - private bool _guideGroupLocked; private int _guideGroup = guideGroup; + private bool _guideGroupLocked; + private int _instructionIndex; public Playout Playout { get; } = playout; @@ -68,8 +68,5 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio _guideGroupLocked = true; } - public void UnlockGuideGroup() - { - _guideGroupLocked = false; - } + public void UnlockGuideGroup() => _guideGroupLocked = false; } diff --git a/ErsatzTV.Core/Search/AdjGraph.cs b/ErsatzTV.Core/Search/AdjGraph.cs index fa15b2b6c..30b6be19d 100644 --- a/ErsatzTV.Core/Search/AdjGraph.cs +++ b/ErsatzTV.Core/Search/AdjGraph.cs @@ -4,15 +4,9 @@ public class AdjGraph { private readonly List _edges = []; - public void Clear() - { - _edges.Clear(); - } + public void Clear() => _edges.Clear(); - public void AddEdge(string from, string to) - { - _edges.Add(new Edge(from.ToLowerInvariant(), to.ToLowerInvariant())); - } + public void AddEdge(string from, string to) => _edges.Add(new Edge(from.ToLowerInvariant(), to.ToLowerInvariant())); public bool HasCycle(string from) { @@ -21,10 +15,7 @@ public class AdjGraph return HasCycleImpl(from.ToLowerInvariant(), visited, stack); } - public bool HasAnyCycle() - { - return _edges.Any(edge => HasCycle(edge.From)); - } + public bool HasAnyCycle() => _edges.Any(edge => HasCycle(edge.From)); private bool HasCycleImpl(string node, ISet visited, ISet stack) { diff --git a/ErsatzTV.Core/SystemStartup.cs b/ErsatzTV.Core/SystemStartup.cs index 34c12a49d..11564cd17 100644 --- a/ErsatzTV.Core/SystemStartup.cs +++ b/ErsatzTV.Core/SystemStartup.cs @@ -2,8 +2,8 @@ namespace ErsatzTV.Core; public class SystemStartup : IDisposable { - private readonly SemaphoreSlim _databaseStartup = new(0, 100); private readonly SemaphoreSlim _databaseCleaned = new(0, 100); + private readonly SemaphoreSlim _databaseStartup = new(0, 100); private readonly SemaphoreSlim _searchIndexStartup = new(0, 100); private bool _disposedValue; diff --git a/ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs b/ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs index 49ee11226..5128a8008 100644 --- a/ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs +++ b/ErsatzTV.FFmpeg.Tests/Capabilities/Vaapi/VaapiCapabilityParserTests.cs @@ -1,8 +1,8 @@ using System.Collections.Generic; using System.Linq; using ErsatzTV.FFmpeg.Capabilities.Vaapi; -using Shouldly; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.FFmpeg.Tests.Capabilities.Vaapi; diff --git a/ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj b/ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj index e63810486..6fddd381d 100644 --- a/ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj +++ b/ErsatzTV.FFmpeg.Tests/ErsatzTV.FFmpeg.Tests.csproj @@ -17,13 +17,13 @@ - all - runtime; build; native; contentfiles; analyzers; buildtransitive + all + runtime; build; native; contentfiles; analyzers; buildtransitive - + - + \ No newline at end of file diff --git a/ErsatzTV.FFmpeg.Tests/Filter/WatermarkOpacityFilterTests.cs b/ErsatzTV.FFmpeg.Tests/Filter/WatermarkOpacityFilterTests.cs index c8afde2eb..e3ec2d9c4 100644 --- a/ErsatzTV.FFmpeg.Tests/Filter/WatermarkOpacityFilterTests.cs +++ b/ErsatzTV.FFmpeg.Tests/Filter/WatermarkOpacityFilterTests.cs @@ -1,9 +1,9 @@ using System.Collections.Generic; using ErsatzTV.FFmpeg.Filter; using ErsatzTV.FFmpeg.State; -using Shouldly; using LanguageExt; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.FFmpeg.Tests.Filter; diff --git a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs index 95f7a684a..236fb15cd 100644 --- a/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs +++ b/ErsatzTV.FFmpeg.Tests/PipelineBuilderBaseTests.cs @@ -8,11 +8,11 @@ using ErsatzTV.FFmpeg.OutputFormat; using ErsatzTV.FFmpeg.Pipeline; using ErsatzTV.FFmpeg.Preset; using ErsatzTV.FFmpeg.State; -using Shouldly; using LanguageExt; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; using static LanguageExt.Prelude; namespace ErsatzTV.FFmpeg.Tests; diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs index 41e9c8418..a7cec3bd3 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegCapabilities.cs @@ -30,8 +30,9 @@ public class FFmpegCapabilities : IFFmpegCapabilities // AMF isn't a "hwaccel" in ffmpeg, so check for presence of encoders if (hardwareAccelerationMode is HardwareAccelerationMode.Amf) { - return _ffmpegEncoders.Any( - e => e.EndsWith($"_{FFmpegKnownHardwareAcceleration.Amf.Name}", StringComparison.OrdinalIgnoreCase)); + return _ffmpegEncoders.Any(e => e.EndsWith( + $"_{FFmpegKnownHardwareAcceleration.Amf.Name}", + StringComparison.OrdinalIgnoreCase)); } Option maybeAccelToCheck = hardwareAccelerationMode switch @@ -40,7 +41,7 @@ public class FFmpegCapabilities : IFFmpegCapabilities HardwareAccelerationMode.Qsv => FFmpegKnownHardwareAcceleration.Qsv, HardwareAccelerationMode.Vaapi => FFmpegKnownHardwareAcceleration.Vaapi, HardwareAccelerationMode.VideoToolbox => FFmpegKnownHardwareAcceleration.VideoToolbox, - HardwareAccelerationMode.OpenCL => FFmpegKnownHardwareAcceleration.OpenCL, + HardwareAccelerationMode.OpenCL => FFmpegKnownHardwareAcceleration.OpenCL, HardwareAccelerationMode.Vulkan => FFmpegKnownHardwareAcceleration.Vulkan, _ => Option.None }; diff --git a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs index 5130d7188..7c08015c6 100644 --- a/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs +++ b/ErsatzTV.FFmpeg/Capabilities/FFmpegKnownFilter.cs @@ -14,6 +14,6 @@ public record FFmpegKnownFilter new[] { ScaleNpp.Name, - TonemapOpenCL.Name, + TonemapOpenCL.Name }; } diff --git a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs index 2065033ef..e2ea3f7fc 100644 --- a/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs +++ b/ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs @@ -342,6 +342,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory display, driver); } + _memoryCache.Set(cacheKey, profileEntrypoints); return new VaapiHardwareCapabilities(profileEntrypoints, _logger); } diff --git a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs index 0375f4833..11f0f857b 100644 --- a/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs +++ b/ErsatzTV.FFmpeg/Capabilities/VaapiHardwareCapabilities.cs @@ -34,132 +34,116 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities (VideoFormat.H264, "baseline" or "66") => false, (VideoFormat.H264, "main" or "77") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.H264Main, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.H264Main, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.H264, "high" or "100") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.H264High, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.H264High, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.H264, "high 10" or "110") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.H264High, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.H264High, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.H264, "baseline constrained" or "578") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.H264ConstrainedBaseline, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.H264ConstrainedBaseline, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Mpeg2Video, "main" or "4") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Mpeg2Main, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Mpeg2Main, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Mpeg2Video, "simple" or "5") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Mpeg2Simple, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Mpeg2Simple, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vc1, "simple" or "0") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vc1Simple, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vc1Simple, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vc1, "main" or "1") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vc1Main, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vc1Main, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vc1, "advanced" or "3") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vc1Advanced, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vc1Advanced, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Hevc, "main" or "1") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.HevcMain, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.HevcMain, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Hevc, "main 10" or "2") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.HevcMain10, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.HevcMain10, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vp9, "profile 0" or "0") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vp9Profile0, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vp9Profile0, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vp9, "profile 1" or "1") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vp9Profile1, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vp9Profile1, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vp9, "profile 2" or "2") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vp9Profile2, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vp9Profile2, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Vp9, "profile 3" or "3") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Vp9Profile3, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Vp9Profile3, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), (VideoFormat.Av1, "main" or "0") => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Av1Profile0, - VaapiEntrypoint: VaapiEntrypoint.Decode - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Av1Profile0, + VaapiEntrypoint: VaapiEntrypoint.Decode + }), // fall back to software decoder _ => false @@ -189,36 +173,32 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities VideoFormat.H264 when bitDepth == 10 => false, VideoFormat.H264 => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.H264Main, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.H264Main, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }), VideoFormat.Hevc when bitDepth == 10 => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.HevcMain10, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.HevcMain10, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }), VideoFormat.Hevc => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.HevcMain, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.HevcMain, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }), VideoFormat.Mpeg2Video => - _profileEntrypoints.Any( - e => e is - { - VaapiProfile: VaapiProfile.Mpeg2Main, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }), + _profileEntrypoints.Any(e => e is + { + VaapiProfile: VaapiProfile.Mpeg2Main, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }), _ => false }; @@ -243,39 +223,35 @@ public class VaapiHardwareCapabilities : IHardwareCapabilities VideoFormat.H264 when bitDepth == 10 => None, VideoFormat.H264 => - _profileEntrypoints.Where( - e => e is - { - VaapiProfile: VaapiProfile.H264Main, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }) + _profileEntrypoints.Where(e => e is + { + VaapiProfile: VaapiProfile.H264Main, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }) .HeadOrNone(), VideoFormat.Hevc when bitDepth == 10 => - _profileEntrypoints.Where( - e => e is - { - VaapiProfile: VaapiProfile.HevcMain10, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }) + _profileEntrypoints.Where(e => e is + { + VaapiProfile: VaapiProfile.HevcMain10, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }) .HeadOrNone(), VideoFormat.Hevc => - _profileEntrypoints.Where( - e => e is - { - VaapiProfile: VaapiProfile.HevcMain, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }) + _profileEntrypoints.Where(e => e is + { + VaapiProfile: VaapiProfile.HevcMain, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }) .HeadOrNone(), VideoFormat.Mpeg2Video => - _profileEntrypoints.Where( - e => e is - { - VaapiProfile: VaapiProfile.Mpeg2Main, - VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower - }) + _profileEntrypoints.Where(e => e is + { + VaapiProfile: VaapiProfile.Mpeg2Main, + VaapiEntrypoint: VaapiEntrypoint.Encode or VaapiEntrypoint.EncodeLowPower + }) .HeadOrNone(), _ => None diff --git a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj index e9e8f9375..5f06017cc 100644 --- a/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj +++ b/ErsatzTV.FFmpeg/ErsatzTV.FFmpeg.csproj @@ -9,12 +9,11 @@ - - - - + + + + - - + \ No newline at end of file diff --git a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs index fa7a6ccbe..b0ac30df5 100644 --- a/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/ComplexFilter.cs @@ -134,8 +134,8 @@ public class ComplexFilter : IPipelineStep } } - foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter( - s => s.Method == SubtitleMethod.Burn)) + foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter(s => + s.Method == SubtitleMethod.Burn)) { int inputIndex = distinctPaths.IndexOf(subtitleInputFile.Path); foreach ((int index, _, _) in subtitleInputFile.Streams) @@ -232,12 +232,12 @@ public class ComplexFilter : IPipelineStep result.AddRange(new[] { "-map", videoLabel, "-map", audioLabel }); - foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter( - s => s.Method == SubtitleMethod.Copy || - s is - { - IsImageBased: true, Method: SubtitleMethod.Convert - })) // TODO: support converting text subtitles? + foreach (SubtitleInputFile subtitleInputFile in _maybeSubtitleInputFile.Filter(s => + s.Method == SubtitleMethod.Copy || + s is + { + IsImageBased: true, Method: SubtitleMethod.Convert + })) // TODO: support converting text subtitles? { if (subtitleInputFile.Streams.Any()) { diff --git a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs index dc8ea04de..c315bfd1b 100644 --- a/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Cuda/ScaleCudaFilter.cs @@ -7,8 +7,8 @@ public class ScaleCudaFilter : BaseFilter private readonly Option _croppedSize; private readonly FrameState _currentState; private readonly bool _isAnamorphicEdgeCase; - private readonly bool _passthrough; private readonly FrameSize _paddedSize; + private readonly bool _passthrough; private readonly FrameSize _scaledSize; public ScaleCudaFilter( diff --git a/ErsatzTV.FFmpeg/Filter/SongProgressFilter.cs b/ErsatzTV.FFmpeg/Filter/SongProgressFilter.cs index ce0dae73c..123a33784 100644 --- a/ErsatzTV.FFmpeg/Filter/SongProgressFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/SongProgressFilter.cs @@ -1,6 +1,7 @@ namespace ErsatzTV.FFmpeg.Filter; -public class SongProgressFilter(FrameSize frameSize, Option maybeStart, Option maybeDuration) : BaseFilter +public class SongProgressFilter(FrameSize frameSize, Option maybeStart, Option maybeDuration) + : BaseFilter { public override string Filter { @@ -22,7 +23,8 @@ public class SongProgressFilter(FrameSize frameSize, Option maybeStart var scaleToFullWidth = $"scale=iw*{alreadyPlayed}+iw*(t/{seconds})*{scale}:ih:eval=frame"; var overlayBar = "overlay=W*0.05:H-h-H*0.05:shortest=1:enable='gt(t,0.1)'"; - return $"loop=-1:1[si],{generateGrayBar}[gray];{generateWhiteBar},format=rgba,colorchannelmixer=aa=0.9,{scaleToFullWidth}[sbar];[si][gray]{overlayBar}[sgray];[sgray][sbar]{overlayBar}"; + return + $"loop=-1:1[si],{generateGrayBar}[gray];{generateWhiteBar},format=rgba,colorchannelmixer=aa=0.9,{scaleToFullWidth}[sbar];[si][gray]{overlayBar}[sgray];[sgray][sbar]{overlayBar}"; } return string.Empty; diff --git a/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs b/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs index 20bfc66a6..6f80ef034 100644 --- a/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/TonemapFilter.cs @@ -4,9 +4,9 @@ namespace ErsatzTV.FFmpeg.Filter; public class TonemapFilter : BaseFilter { - private readonly FFmpegState _ffmpegState; private readonly FrameState _currentState; private readonly IPixelFormat _desiredPixelFormat; + private readonly FFmpegState _ffmpegState; public TonemapFilter(FFmpegState ffmpegState, FrameState currentState, IPixelFormat desiredPixelFormat) { diff --git a/ErsatzTV.FFmpeg/Filter/Vaapi/TonemapVaapiFilter.cs b/ErsatzTV.FFmpeg/Filter/Vaapi/TonemapVaapiFilter.cs index 2bd74e21b..ae3bacf97 100644 --- a/ErsatzTV.FFmpeg/Filter/Vaapi/TonemapVaapiFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/Vaapi/TonemapVaapiFilter.cs @@ -2,7 +2,8 @@ namespace ErsatzTV.FFmpeg.Filter.Vaapi; public class TonemapVaapiFilter(FFmpegState ffmpegState) : BaseFilter { - public override string Filter => $"hwupload=derive_device=vaapi,hwmap=derive_device=opencl,tonemap_opencl=tonemap={ffmpegState.TonemapAlgorithm},hwmap=derive_device=vaapi:reverse=1"; + public override string Filter => + $"hwupload=derive_device=vaapi,hwmap=derive_device=opencl,tonemap_opencl=tonemap={ffmpegState.TonemapAlgorithm},hwmap=derive_device=vaapi:reverse=1"; public override FrameState NextState(FrameState currentState) => currentState with diff --git a/ErsatzTV.FFmpeg/Filter/WatermarkOpacityFilter.cs b/ErsatzTV.FFmpeg/Filter/WatermarkOpacityFilter.cs index 5a66cb51e..e667e62c5 100644 --- a/ErsatzTV.FFmpeg/Filter/WatermarkOpacityFilter.cs +++ b/ErsatzTV.FFmpeg/Filter/WatermarkOpacityFilter.cs @@ -14,7 +14,8 @@ public class WatermarkOpacityFilter : BaseFilter get { double opacity = _desiredState.Opacity / 100.0; - return $"format=yuva420p|yuva444p|yuva422p|rgba|abgr|bgra|gbrap|ya8,colorchannelmixer=aa={opacity.ToString("F2", NumberFormatInfo.InvariantInfo)}"; + return + $"format=yuva420p|yuva444p|yuva422p|rgba|abgr|bgra|gbrap|ya8,colorchannelmixer=aa={opacity.ToString("F2", NumberFormatInfo.InvariantInfo)}"; } } diff --git a/ErsatzTV.FFmpeg/InputFile.cs b/ErsatzTV.FFmpeg/InputFile.cs index bea82439f..9663c18b6 100644 --- a/ErsatzTV.FFmpeg/InputFile.cs +++ b/ErsatzTV.FFmpeg/InputFile.cs @@ -81,6 +81,6 @@ public record SubtitleInputFile(string Path, IList SubtitleStreams, Path, SubtitleStreams) { - public bool IsImageBased => SubtitleStreams.All( - s => s.Codec is "hdmv_pgs_subtitle" or "dvd_subtitle" or "dvdsub" or "vobsub" or "pgssub" or "pgs"); + public bool IsImageBased => SubtitleStreams.All(s => + s.Codec is "hdmv_pgs_subtitle" or "dvd_subtitle" or "dvdsub" or "vobsub" or "pgssub" or "pgs"); } diff --git a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs index adba490d1..ea7c119e6 100644 --- a/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/NvidiaPipelineBuilder.cs @@ -81,7 +81,8 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder bool isHdrTonemap = decodeCapability == FFmpegCapability.Hardware && _ffmpegCapabilities.HasHardwareAcceleration(HardwareAccelerationMode.Vulkan) && videoStream.ColorParams.IsHdr - && string.IsNullOrWhiteSpace(System.Environment.GetEnvironmentVariable("ETV_DISABLE_VULKAN")); + && string.IsNullOrWhiteSpace( + System.Environment.GetEnvironmentVariable("ETV_DISABLE_VULKAN")); if (decodeCapability == FFmpegCapability.Hardware) { @@ -173,7 +174,7 @@ public class NvidiaPipelineBuilder : SoftwarePipelineBuilder videoStream.FrameSize, Option.None, false, - passthrough: true); + true); currentState = filter.NextState(currentState); videoInputFile.FilterSteps.Add(filter); } @@ -581,8 +582,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); @@ -591,8 +592,8 @@ 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); diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 1d5920c5a..815e38e0b 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -743,7 +743,8 @@ public abstract class PipelineBuilderBase : IPipelineBuilder { if (ffmpegState.IsSongWithProgress) { - videoInputFile.FilterSteps.Add(new SongProgressFilter(videoStream.FrameSize, ffmpegState.Start, ffmpegState.Finish)); + videoInputFile.FilterSteps.Add( + new SongProgressFilter(videoStream.FrameSize, ffmpegState.Start, ffmpegState.Finish)); } else { diff --git a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs index e2b0bf13a..c2351bf83 100644 --- a/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/QsvPipelineBuilder.cs @@ -279,7 +279,8 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder IPixelFormat formatForDownload = pixelFormat; bool usesVppQsv = - videoInputFile.FilterSteps.Any(f => f is QsvFormatFilter or ScaleQsvFilter or DeinterlaceQsvFilter or TonemapQsvFilter); + videoInputFile.FilterSteps.Any(f => + f is QsvFormatFilter or ScaleQsvFilter or DeinterlaceQsvFilter or TonemapQsvFilter); // if we have no filters, check whether we need to convert pixel format // since qsv doesn't seem to like doing that at the encoder @@ -588,8 +589,8 @@ public class QsvPipelineBuilder : SoftwarePipelineBuilder // auto_scale filter seems to muck up 10-bit software decode => hardware scale, so use software scale in that case useSoftwareFilter = useSoftwareFilter || - (ffmpegState is { DecoderHardwareAccelerationMode: HardwareAccelerationMode.None } && - OperatingSystem.IsWindows() && currentState.BitDepth == 10); + ffmpegState is { DecoderHardwareAccelerationMode: HardwareAccelerationMode.None } && + OperatingSystem.IsWindows() && currentState.BitDepth == 10; if (currentState.ScaledSize != desiredState.ScaledSize && useSoftwareFilter) { diff --git a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs index 3c0ef703c..cbf6d3b02 100644 --- a/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs +++ b/ErsatzTV.FFmpeg/Pipeline/VaapiPipelineBuilder.cs @@ -636,7 +636,8 @@ public class VaapiPipelineBuilder : SoftwarePipelineBuilder { foreach (IPixelFormat pixelFormat in desiredState.PixelFormat) { - if (ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi && _ffmpegCapabilities.HasFilter(FFmpegKnownFilter.TonemapOpenCL)) + if (ffmpegState.DecoderHardwareAccelerationMode == HardwareAccelerationMode.Vaapi && + _ffmpegCapabilities.HasFilter(FFmpegKnownFilter.TonemapOpenCL)) { var filter = new TonemapVaapiFilter(ffmpegState); currentState = filter.NextState(currentState); diff --git a/ErsatzTV.Infrastructure.Tests/Data/Repositories/Caching/CachingSearchRepositoryTests.cs b/ErsatzTV.Infrastructure.Tests/Data/Repositories/Caching/CachingSearchRepositoryTests.cs index 714304d4c..78137ccac 100644 --- a/ErsatzTV.Infrastructure.Tests/Data/Repositories/Caching/CachingSearchRepositoryTests.cs +++ b/ErsatzTV.Infrastructure.Tests/Data/Repositories/Caching/CachingSearchRepositoryTests.cs @@ -1,9 +1,9 @@ using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Data.Repositories.Caching; -using Shouldly; using LanguageExt; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Infrastructure.Tests.Data.Repositories.Caching; diff --git a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj index f81d2be5e..56cebb4d9 100644 --- a/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj +++ b/ErsatzTV.Infrastructure.Tests/ErsatzTV.Infrastructure.Tests.csproj @@ -14,18 +14,18 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive - all + runtime; build; native; contentfiles; analyzers; buildtransitive + all - runtime; build; native; contentfiles; analyzers; buildtransitive - all + runtime; build; native; contentfiles; analyzers; buildtransitive + all - + - + \ No newline at end of file diff --git a/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs b/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs index 70330334d..f41a637b0 100644 --- a/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs +++ b/ErsatzTV.Infrastructure.Tests/Metadata/LocalStatisticsProviderTests.cs @@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Metadata; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Infrastructure.Metadata; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Infrastructure.Tests.Metadata; diff --git a/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs b/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs index e9f340b10..74d0fe343 100644 --- a/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs +++ b/ErsatzTV.Infrastructure.Tests/Search/SearchQueryParserTests.cs @@ -1,9 +1,9 @@ using ErsatzTV.Core.Search; using ErsatzTV.Infrastructure.Search; -using Shouldly; using Lucene.Net.Search; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Infrastructure.Tests.Search; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/ArtistRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/ArtistRepository.cs index d8e333a8b..66376bea2 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/ArtistRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/ArtistRepository.cs @@ -17,9 +17,8 @@ public class ArtistRepository : IArtistRepository { await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); Option maybeId = await dbContext.ArtistMetadata - .Where( - s => s.Title == metadata.Title && (metadata.MetadataKind == MetadataKind.Fallback || - s.Disambiguation == metadata.Disambiguation)) + .Where(s => s.Title == metadata.Title && (metadata.MetadataKind == MetadataKind.Fallback || + s.Disambiguation == metadata.Disambiguation)) .Where(s => s.Artist.LibraryPathId == libraryPathId) .SingleOrDefaultAsync() .Map(Optional) diff --git a/ErsatzTV.Infrastructure/Data/Repositories/ConfigElementRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/ConfigElementRepository.cs index 964fe9458..ff2df5fac 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/ConfigElementRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/ConfigElementRepository.cs @@ -51,21 +51,20 @@ public class ConfigElementRepository : IConfigElementRepository } public Task> GetValue(ConfigElementKey key) => - GetConfigElement(key).MapT( - ce => + GetConfigElement(key).MapT(ce => + { + if (typeof(T).Name == "Guid") { - if (typeof(T).Name == "Guid") - { - return (T)Convert.ChangeType(Guid.Parse(ce.Value), typeof(T), CultureInfo.InvariantCulture); - } + return (T)Convert.ChangeType(Guid.Parse(ce.Value), typeof(T), CultureInfo.InvariantCulture); + } - if (typeof(T).IsEnum) - { - return (T)Enum.Parse(typeof(T), ce.Value); - } + if (typeof(T).IsEnum) + { + return (T)Enum.Parse(typeof(T), ce.Value); + } - return (T)Convert.ChangeType(ce.Value, typeof(T), CultureInfo.InvariantCulture); - }); + return (T)Convert.ChangeType(ce.Value, typeof(T), CultureInfo.InvariantCulture); + }); public async Task Delete(ConfigElement configElement) { diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs index 1531acb52..3aadc25ed 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyMovieRepository.cs @@ -303,9 +303,9 @@ public class EmbyMovieRepository : IEmbyMovieRepository // actors foreach (Actor actor in metadata.Actors - .Filter( - a => incomingMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + incomingMetadata.Actors.All(a2 => + a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { metadata.Actors.Remove(actor); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs index e2917bcad..0a61be2c4 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/EmbyTelevisionRepository.cs @@ -487,9 +487,9 @@ public class EmbyTelevisionRepository : IEmbyTelevisionRepository // actors foreach (Actor actor in metadata.Actors - .Filter( - a => incomingMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + incomingMetadata.Actors.All(a2 => + a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { metadata.Actors.Remove(actor); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs index 8edf04ac0..e6ddfe671 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinMovieRepository.cs @@ -271,9 +271,9 @@ public class JellyfinMovieRepository : IJellyfinMovieRepository // actors foreach (Actor actor in metadata.Actors - .Filter( - a => incomingMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + incomingMetadata.Actors.All(a2 => + a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { metadata.Actors.Remove(actor); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs index 9b2cbb9f5..6d9fac2f3 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/JellyfinTelevisionRepository.cs @@ -491,9 +491,9 @@ public class JellyfinTelevisionRepository : IJellyfinTelevisionRepository // actors foreach (Actor actor in metadata.Actors - .Filter( - a => incomingMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + incomingMetadata.Actors.All(a2 => + a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { metadata.Actors.Remove(actor); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs index 96f6e21f2..b204fc683 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaCollectionRepository.cs @@ -503,8 +503,8 @@ public class MediaCollectionRepository : IMediaCollectionRepository } List nextIds = await dbContext.ShowMetadata - .Filter( - sm => sm.Guids.Any(g => EF.Functions.Collate(g.Guid, TvContext.CaseInsensitiveCollation) == guid)) + .Filter(sm => + sm.Guids.Any(g => EF.Functions.Collate(g.Guid, TvContext.CaseInsensitiveCollation) == guid)) .Map(sm => sm.ShowId) .ToListAsync(); diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs index 21409bfa4..cabab5cf1 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MediaItemRepository.cs @@ -27,12 +27,12 @@ public class MediaItemRepository : IMediaItemRepository CultureInfo[] allCultures = CultureInfo.GetCultures(CultureTypes.NeutralCultures); foreach (LanguageCode code in await dbContext.LanguageCodes.ToListAsync()) { - Option maybeCulture = allCultures.Find( - c => string.Equals(code.ThreeCode1, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase) - || string.Equals( - code.ThreeCode2, - c.ThreeLetterISOLanguageName, - StringComparison.OrdinalIgnoreCase)); + Option maybeCulture = allCultures.Find(c => + string.Equals(code.ThreeCode1, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase) + || string.Equals( + code.ThreeCode2, + c.ThreeLetterISOLanguageName, + StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo culture in maybeCulture) { result.Add(culture); @@ -55,8 +55,10 @@ public class MediaItemRepository : IMediaItemRepository { foreach (string code in await dbContext.LanguageCodes.GetAllLanguageCodes(mediaCode)) { - Option maybeCulture = allCultures.Find( - c => string.Equals(code, c.ThreeLetterISOLanguageName, StringComparison.OrdinalIgnoreCase)); + Option maybeCulture = allCultures.Find(c => string.Equals( + code, + c.ThreeLetterISOLanguageName, + StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo culture in maybeCulture) { diff --git a/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs index 268a88fb3..c3a3b9674 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/MetadataRepository.cs @@ -332,8 +332,7 @@ public class MetadataRepository : IMetadataRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); Option maybeExisting = await dbContext.Artwork .AsNoTracking() - .Filter( - a => a.SourcePath == sourcePath && a.ArtworkKind == artworkKind && a.DateUpdated == lastWriteTime) + .Filter(a => a.SourcePath == sourcePath && a.ArtworkKind == artworkKind && a.DateUpdated == lastWriteTime) .FirstOrDefaultAsync() .Map(Optional); foreach (Artwork existing in maybeExisting) @@ -461,7 +460,8 @@ public class MetadataRepository : IMetadataRepository await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); return await dbContext.Connection.ExecuteAsync( @"UPDATE MovieMetadata SET Plot = @Plot WHERE Id = @Id", - new { metadata.Id, Plot = plot }).ToUnit(); } + new { metadata.Id, Plot = plot }).ToUnit(); + } public async Task SetPlot(OtherVideoMetadata metadata, string plot) { @@ -642,8 +642,7 @@ public class MetadataRepository : IMetadataRepository foreach (Subtitle incomingSubtitle in toUpdate) { Subtitle existingSubtitle = - existing.Subtitles.First( - s => s.StreamIndex == incomingSubtitle.StreamIndex); + existing.Subtitles.First(s => s.StreamIndex == incomingSubtitle.StreamIndex); existingSubtitle.Codec = incomingSubtitle.Codec; existingSubtitle.Default = incomingSubtitle.Default; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexOtherVideoRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexOtherVideoRepository.cs index 82502f1a2..613571f95 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexOtherVideoRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexOtherVideoRepository.cs @@ -16,7 +16,9 @@ public class PlexOtherVideoRepository : IPlexOtherVideoRepository private readonly IDbContextFactory _dbContextFactory; private readonly ILogger _logger; - public PlexOtherVideoRepository(IDbContextFactory dbContextFactory, ILogger logger) + public PlexOtherVideoRepository( + IDbContextFactory dbContextFactory, + ILogger logger) { _dbContextFactory = dbContextFactory; _logger = logger; @@ -236,7 +238,10 @@ public class PlexOtherVideoRepository : IPlexOtherVideoRepository } } - private static async Task UpdateOtherVideoPath(TvContext dbContext, PlexOtherVideo existing, PlexOtherVideo incoming) + private static async Task UpdateOtherVideoPath( + TvContext dbContext, + PlexOtherVideo existing, + PlexOtherVideo incoming) { // library path is used for search indexing later incoming.LibraryPath = existing.LibraryPath; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs index 233c85aac..7a4bfff11 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/PlexTelevisionRepository.cs @@ -416,6 +416,78 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository return ids; } + public async Task> RemoveAllTags( + PlexLibrary library, + PlexTag tag, + System.Collections.Generic.HashSet keep) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + + var tagType = tag.TagType.ToString(CultureInfo.InvariantCulture); + + List result = await dbContext.ShowMetadata + .Where(sm => !keep.Contains(sm.ShowId)) + .Where(sm => sm.Show.LibraryPath.LibraryId == library.Id) + .Where(sm => sm.Tags.Any(t => t.Name == tag.Tag && t.ExternalTypeId == tagType)) + .Select(sm => sm.ShowId) + .ToListAsync(); + + if (result.Count > 0) + { + List tagIds = await dbContext.ShowMetadata + .Where(sm => result.Contains(sm.ShowId)) + .Where(sm => sm.Tags.Any(t => t.Name == tag.Tag && t.ExternalTypeId == tagType)) + .SelectMany(sm => sm.Tags.Select(t => t.Id)) + .ToListAsync(); + + // delete all tags + await dbContext.Connection.ExecuteAsync("DELETE FROM Tag WHERE Id IN @TagIds", new { TagIds = tagIds }); + } + + // show ids to refresh + return result; + } + + public async Task AddTag(PlexShow show, PlexTag tag) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + + int existingShowId = await dbContext.Connection.ExecuteScalarAsync( + @"SELECT PS.Id FROM Tag + INNER JOIN ShowMetadata SM on SM.Id = Tag.ShowMetadataId + INNER JOIN PlexShow PS on PS.Id = SM.ShowId + WHERE PS.Key = @Key AND Tag.Name = @Tag AND Tag.ExternalTypeId = @TagType", + new { show.Key, tag.Tag, tag.TagType }); + + // already exists + if (existingShowId > 0) + { + return new PlexShowAddTagResult(existingShowId, Option.None); + } + + int showId = await dbContext.PlexShows + .Where(s => s.Key == show.Key) + .Select(s => s.Id) + .FirstOrDefaultAsync(); + + await dbContext.Connection.ExecuteAsync( + @"INSERT INTO Tag (Name, ExternalTypeId, ShowMetadataId) + SELECT @Tag, @TagType, Id FROM + (SELECT Id FROM ShowMetadata WHERE ShowId = @ShowId) AS A", + new { tag.Tag, tag.TagType, ShowId = showId }); + + // show id to refresh + return new PlexShowAddTagResult(Option.None, showId); + } + + public async Task UpdateLastNetworksScan(PlexLibrary library) + { + await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); + await dbContext.Connection.ExecuteAsync( + "UPDATE PlexLibrary SET LastNetworksScan = @LastNetworksScan WHERE Id = @Id", + new { library.LastNetworksScan, library.Id }); + } + private static async Task>> AddShow( TvContext dbContext, PlexLibrary library, @@ -571,76 +643,4 @@ public class PlexTelevisionRepository : IPlexTelevisionRepository return BaseError.New("Failed to update episode path"); } } - - public async Task> RemoveAllTags( - PlexLibrary library, - PlexTag tag, - System.Collections.Generic.HashSet keep) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - - var tagType = tag.TagType.ToString(CultureInfo.InvariantCulture); - - List result = await dbContext.ShowMetadata - .Where(sm => !keep.Contains(sm.ShowId)) - .Where(sm => sm.Show.LibraryPath.LibraryId == library.Id) - .Where(sm => sm.Tags.Any(t => t.Name == tag.Tag && t.ExternalTypeId == tagType)) - .Select(sm => sm.ShowId) - .ToListAsync(); - - if (result.Count > 0) - { - List tagIds = await dbContext.ShowMetadata - .Where(sm => result.Contains(sm.ShowId)) - .Where(sm => sm.Tags.Any(t => t.Name == tag.Tag && t.ExternalTypeId == tagType)) - .SelectMany(sm => sm.Tags.Select(t => t.Id)) - .ToListAsync(); - - // delete all tags - await dbContext.Connection.ExecuteAsync("DELETE FROM Tag WHERE Id IN @TagIds", new { TagIds = tagIds }); - } - - // show ids to refresh - return result; - } - - public async Task AddTag(PlexShow show, PlexTag tag) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - - int existingShowId = await dbContext.Connection.ExecuteScalarAsync( - @"SELECT PS.Id FROM Tag - INNER JOIN ShowMetadata SM on SM.Id = Tag.ShowMetadataId - INNER JOIN PlexShow PS on PS.Id = SM.ShowId - WHERE PS.Key = @Key AND Tag.Name = @Tag AND Tag.ExternalTypeId = @TagType", - new { show.Key, tag.Tag, tag.TagType }); - - // already exists - if (existingShowId > 0) - { - return new PlexShowAddTagResult(existingShowId, Option.None); - } - - int showId = await dbContext.PlexShows - .Where(s => s.Key == show.Key) - .Select(s => s.Id) - .FirstOrDefaultAsync(); - - await dbContext.Connection.ExecuteAsync( - @"INSERT INTO Tag (Name, ExternalTypeId, ShowMetadataId) - SELECT @Tag, @TagType, Id FROM - (SELECT Id FROM ShowMetadata WHERE ShowId = @ShowId) AS A", - new { tag.Tag, tag.TagType, ShowId = showId }); - - // show id to refresh - return new PlexShowAddTagResult(Option.None, showId); - } - - public async Task UpdateLastNetworksScan(PlexLibrary library) - { - await using TvContext dbContext = await _dbContextFactory.CreateDbContextAsync(); - await dbContext.Connection.ExecuteAsync( - "UPDATE PlexLibrary SET LastNetworksScan = @LastNetworksScan WHERE Id = @Id", - new { library.LastNetworksScan, library.Id }); - } } diff --git a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs index ea0df7887..281f72017 100644 --- a/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs +++ b/ErsatzTV.Infrastructure/Data/Repositories/TelevisionRepository.cs @@ -104,11 +104,10 @@ public class TelevisionRepository : ITelevisionRepository .ThenInclude(s => s.ShowMetadata) .Include(sm => sm.Artwork) .ToListAsync() - .Map( - list => list - .OrderBy(s => s.Season.Show.ShowMetadata.HeadOrNone().Match(sm => sm.SortTitle, () => string.Empty)) - .ThenBy(s => s.Season.SeasonNumber) - .ToList()); + .Map(list => list + .OrderBy(s => s.Season.Show.ShowMetadata.HeadOrNone().Match(sm => sm.SortTitle, () => string.Empty)) + .ThenBy(s => s.Season.SeasonNumber) + .ToList()); } public async Task> GetEpisodesForCards(List ids) @@ -261,12 +260,9 @@ public class TelevisionRepository : ITelevisionRepository if (maybeId.IsNone) { List maybeShowIds = await dbContext.Episodes - .Where( - e => e.MediaVersions.Any( - mv => mv.MediaFiles.Any( - mf => EF.Functions.Like( - EF.Functions.Collate(mf.Path, TvContext.CaseInsensitiveCollation), - $"{showFolder}%")))) + .Where(e => e.MediaVersions.Any(mv => mv.MediaFiles.Any(mf => EF.Functions.Like( + EF.Functions.Collate(mf.Path, TvContext.CaseInsensitiveCollation), + $"{showFolder}%")))) .Map(e => e.Season.ShowId) .Distinct() .ToListAsync(); diff --git a/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs b/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs index 9d1d3fbef..ab4e0ec1e 100644 --- a/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs +++ b/ErsatzTV.Infrastructure/Emby/EmbyApiClient.cs @@ -69,7 +69,10 @@ public class EmbyApiClient : IEmbyApiClient } } - public IAsyncEnumerable> GetMovieLibraryItems(string address, string apiKey, EmbyLibrary library) + public IAsyncEnumerable> GetMovieLibraryItems( + string address, + string apiKey, + EmbyLibrary library) => GetPagedLibraryContents( address, library, @@ -81,7 +84,10 @@ public class EmbyApiClient : IEmbyApiClient limit: pageSize), (maybeLibrary, item) => maybeLibrary.Map(lib => ProjectToMovie(lib, item)).Flatten()); - public IAsyncEnumerable> GetShowLibraryItems(string address, string apiKey, EmbyLibrary library) + public IAsyncEnumerable> GetShowLibraryItems( + string address, + string apiKey, + EmbyLibrary library) => GetPagedLibraryContents( address, library, @@ -277,12 +283,11 @@ public class EmbyApiClient : IEmbyApiClient private static List GetPathInfos(EmbyLibraryResponse response) => response.LibraryOptions.PathInfos .Filter(pi => !string.IsNullOrWhiteSpace(pi.NetworkPath)) - .Map( - pi => new EmbyPathInfo - { - Path = pi.Path, - NetworkPath = pi.NetworkPath - }).ToList(); + .Map(pi => new EmbyPathInfo + { + Path = pi.Path, + NetworkPath = pi.NetworkPath + }).ToList(); private Option CacheCollectionLibraryId(string itemId) { @@ -777,115 +782,113 @@ public class EmbyApiClient : IEmbyApiClient IList streams = mediaSource.MediaStreams; Option maybeVideoStream = streams.Find(s => s.Type == EmbyMediaStreamType.Video); - return maybeVideoStream.Map( - videoStream => + return maybeVideoStream.Map(videoStream => + { + int width = videoStream.Width ?? 1; + int height = videoStream.Height ?? 1; + + var isAnamorphic = false; + if (!string.IsNullOrWhiteSpace(videoStream.AspectRatio) && videoStream.AspectRatio.Contains(':')) { - int width = videoStream.Width ?? 1; - int height = videoStream.Height ?? 1; + // if width/height != aspect ratio, is anamorphic + double resolutionRatio = width / (double)height; - var isAnamorphic = false; - if (!string.IsNullOrWhiteSpace(videoStream.AspectRatio) && videoStream.AspectRatio.Contains(':')) + string[] split = videoStream.AspectRatio.Split(":"); + var num = double.Parse(split[0], CultureInfo.InvariantCulture); + var den = double.Parse(split[1], CultureInfo.InvariantCulture); + double aspectRatio = num / den; + + isAnamorphic = Math.Abs(resolutionRatio - aspectRatio) > 0.01d; + } + else if (videoStream.IsAnamorphic.HasValue) + { + isAnamorphic = videoStream.IsAnamorphic.Value; + } + + var version = new MediaVersion + { + Duration = TimeSpan.FromTicks(mediaSource.RunTimeTicks), + SampleAspectRatio = isAnamorphic ? "0:0" : "1:1", + DisplayAspectRatio = string.IsNullOrWhiteSpace(videoStream.AspectRatio) + ? string.Empty + : videoStream.AspectRatio, + VideoScanKind = videoStream.IsInterlaced switch { - // if width/height != aspect ratio, is anamorphic - double resolutionRatio = width / (double)height; + true => VideoScanKind.Interlaced, + false => VideoScanKind.Progressive + }, + Streams = new List(), + Width = videoStream.Width ?? 1, + Height = videoStream.Height ?? 1, + RFrameRate = videoStream.RealFrameRate.HasValue + ? videoStream.RealFrameRate.Value.ToString("0.00###", CultureInfo.InvariantCulture) + : string.Empty, + Chapters = new List() + }; - string[] split = videoStream.AspectRatio.Split(":"); - var num = double.Parse(split[0], CultureInfo.InvariantCulture); - var den = double.Parse(split[1], CultureInfo.InvariantCulture); - double aspectRatio = num / den; - - isAnamorphic = Math.Abs(resolutionRatio - aspectRatio) > 0.01d; - } - else if (videoStream.IsAnamorphic.HasValue) + version.Streams.Add( + new MediaStream { - isAnamorphic = videoStream.IsAnamorphic.Value; - } + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Video, + Index = videoStream.Index, + Codec = videoStream.Codec, + Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), + Default = videoStream.IsDefault, + Language = videoStream.Language, + Forced = videoStream.IsForced, + PixelFormat = videoStream.PixelFormat, + ColorRange = (videoStream.ColorRange ?? string.Empty).ToLowerInvariant(), + ColorSpace = (videoStream.ColorSpace ?? string.Empty).ToLowerInvariant(), + ColorTransfer = (videoStream.ColorTransfer ?? string.Empty).ToLowerInvariant(), + ColorPrimaries = (videoStream.ColorPrimaries ?? string.Empty).ToLowerInvariant() + }); - var version = new MediaVersion + foreach (EmbyMediaStreamResponse audioStream in streams.Filter(s => s.Type == EmbyMediaStreamType.Audio)) + { + var stream = new MediaStream { - Duration = TimeSpan.FromTicks(mediaSource.RunTimeTicks), - SampleAspectRatio = isAnamorphic ? "0:0" : "1:1", - DisplayAspectRatio = string.IsNullOrWhiteSpace(videoStream.AspectRatio) - ? string.Empty - : videoStream.AspectRatio, - VideoScanKind = videoStream.IsInterlaced switch - { - true => VideoScanKind.Interlaced, - false => VideoScanKind.Progressive - }, - Streams = new List(), - Width = videoStream.Width ?? 1, - Height = videoStream.Height ?? 1, - RFrameRate = videoStream.RealFrameRate.HasValue - ? videoStream.RealFrameRate.Value.ToString("0.00###", CultureInfo.InvariantCulture) - : string.Empty, - Chapters = new List() + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Audio, + Index = audioStream.Index, + Codec = audioStream.Codec, + Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), + Channels = audioStream.Channels ?? 2, + Default = audioStream.IsDefault, + Forced = audioStream.IsForced, + Language = audioStream.Language, + Title = audioStream.DisplayTitle ?? string.Empty }; - version.Streams.Add( - new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Video, - Index = videoStream.Index, - Codec = videoStream.Codec, - Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), - Default = videoStream.IsDefault, - Language = videoStream.Language, - Forced = videoStream.IsForced, - PixelFormat = videoStream.PixelFormat, - ColorRange = (videoStream.ColorRange ?? string.Empty).ToLowerInvariant(), - ColorSpace = (videoStream.ColorSpace ?? string.Empty).ToLowerInvariant(), - ColorTransfer = (videoStream.ColorTransfer ?? string.Empty).ToLowerInvariant(), - ColorPrimaries = (videoStream.ColorPrimaries ?? string.Empty).ToLowerInvariant() - }); + version.Streams.Add(stream); + } - foreach (EmbyMediaStreamResponse audioStream in streams.Filter( - s => s.Type == EmbyMediaStreamType.Audio)) + foreach (EmbyMediaStreamResponse subtitleStream in streams.Filter(s => + s.Type == EmbyMediaStreamType.Subtitle)) + { + var stream = new MediaStream { - var stream = new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Audio, - Index = audioStream.Index, - Codec = audioStream.Codec, - Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), - Channels = audioStream.Channels ?? 2, - Default = audioStream.IsDefault, - Forced = audioStream.IsForced, - Language = audioStream.Language, - Title = audioStream.DisplayTitle ?? string.Empty - }; + MediaVersionId = version.Id, + MediaStreamKind = subtitleStream.IsExternal == true + ? MediaStreamKind.ExternalSubtitle + : MediaStreamKind.Subtitle, + Index = subtitleStream.Index, + Codec = (subtitleStream.Codec ?? string.Empty).ToLowerInvariant(), + Default = subtitleStream.IsDefault, + Forced = subtitleStream.IsForced, + Language = subtitleStream.Language + }; - version.Streams.Add(stream); + // hacky, oh well + if (subtitleStream.IsExternal == true) + { + stream.FileName = mediaSource.Id; } - foreach (EmbyMediaStreamResponse subtitleStream in streams.Filter( - s => s.Type == EmbyMediaStreamType.Subtitle)) - { - var stream = new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = subtitleStream.IsExternal == true - ? MediaStreamKind.ExternalSubtitle - : MediaStreamKind.Subtitle, - Index = subtitleStream.Index, - Codec = (subtitleStream.Codec ?? string.Empty).ToLowerInvariant(), - Default = subtitleStream.IsDefault, - Forced = subtitleStream.IsForced, - Language = subtitleStream.Language - }; + version.Streams.Add(stream); + } - // hacky, oh well - if (subtitleStream.IsExternal == true) - { - stream.FileName = mediaSource.Id; - } - - version.Streams.Add(stream); - } - - return version; - }); + return version; + }); } } diff --git a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj index 32f18b0ca..cf932ffe4 100644 --- a/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj +++ b/ErsatzTV.Infrastructure/ErsatzTV.Infrastructure.csproj @@ -10,37 +10,37 @@ - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + - - + + - + @@ -49,4 +49,4 @@ - + \ No newline at end of file diff --git a/ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs index d17856142..9569a18ee 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/FileNotFoundHealthCheck.cs @@ -44,13 +44,12 @@ public class FileNotFoundHealthCheck : BaseHealthCheck, IFileNotFoundHealthCheck if (mediaItems.Any()) { - IEnumerable paths = five.Map( - mi => mi switch - { - Show s => s.ShowMetadata.Head().Title, - Season s => $"{s.Show.ShowMetadata.Head().Title} Season {s.SeasonNumber}", - _ => mi.GetHeadVersion().MediaFiles.Head().Path - }); + IEnumerable paths = five.Map(mi => mi switch + { + Show s => s.ShowMetadata.Head().Title, + Season s => $"{s.Show.ShowMetadata.Head().Title} Season {s.SeasonNumber}", + _ => mi.GetHeadVersion().MediaFiles.Head().Path + }); var files = string.Join(", ", paths); diff --git a/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs index 4a0f076e2..4060b02b1 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/UnifiedDockerHealthCheck.cs @@ -6,7 +6,9 @@ namespace ErsatzTV.Infrastructure.Health.Checks; public class UnifiedDockerHealthCheck : BaseHealthCheck, IUnifiedDockerHealthCheck { - private static readonly string InfoVersion = Assembly.GetEntryAssembly()!.GetCustomAttribute()?.InformationalVersion ?? "unknown"; + private static readonly string InfoVersion = + Assembly.GetEntryAssembly()!.GetCustomAttribute() + ?.InformationalVersion ?? "unknown"; public override string Title => "Unified Docker"; @@ -14,7 +16,8 @@ public class UnifiedDockerHealthCheck : BaseHealthCheck, IUnifiedDockerHealthChe { if (InfoVersion.Contains("docker-vaapi") || InfoVersion.Contains("docker-nvidia")) { - return WarningResult("VAAPI and NVIDIA docker tag suffixes are deprecated; please remove `-vaapi` or `-nvidia` and pull the default image.") + return WarningResult( + "VAAPI and NVIDIA docker tag suffixes are deprecated; please remove `-vaapi` or `-nvidia` and pull the default image.") .AsTask(); } diff --git a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs index 418a4b034..d7593b657 100644 --- a/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs +++ b/ErsatzTV.Infrastructure/Health/Checks/VaapiDriverHealthCheck.cs @@ -53,7 +53,8 @@ public class VaapiDriverHealthCheck( foreach (string ffmpegPath in maybeFFmpegPath) { - IFFmpegCapabilities ffmpegCapabilities = await hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath); + IFFmpegCapabilities ffmpegCapabilities = + await hardwareCapabilitiesFactory.GetFFmpegCapabilities(ffmpegPath); foreach (FFmpegProfile profile in activeFFmpegProfiles) { Option vaapiDriver = VaapiDriverName(profile.VaapiDriver); diff --git a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs index 42a1d349e..6a562ef60 100644 --- a/ErsatzTV.Infrastructure/Health/HealthCheckService.cs +++ b/ErsatzTV.Infrastructure/Health/HealthCheckService.cs @@ -48,7 +48,7 @@ public class HealthCheckService : IHealthCheckService fileNotFoundHealthCheck, unavailableHealthCheck, vaapiDriverHealthCheck, - errorReportsHealthCheck, + errorReportsHealthCheck ]; } @@ -77,10 +77,8 @@ public class HealthCheckService : IHealthCheckService return result; } - public HealthCheckSummary GetHealthCheckSummary() - { - return _memoryCache.Get(CacheKey) ?? new HealthCheckSummary(0, 0); - } + public HealthCheckSummary GetHealthCheckSummary() => + _memoryCache.Get(CacheKey) ?? new HealthCheckSummary(0, 0); private HealthCheckResult LogAndReturn(Exception ex, HealthCheckResult failedResult) { diff --git a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs index 3ba4ef260..d1e13a69a 100644 --- a/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs +++ b/ErsatzTV.Infrastructure/Jellyfin/JellyfinApiClient.cs @@ -296,12 +296,11 @@ public class JellyfinApiClient : IJellyfinApiClient result.AddRange( response.LibraryOptions.PathInfos .Filter(pi => !string.IsNullOrWhiteSpace(pi.NetworkPath)) - .Map( - pi => new JellyfinPathInfo - { - Path = pi.Path, - NetworkPath = pi.NetworkPath - })); + .Map(pi => new JellyfinPathInfo + { + Path = pi.Path, + NetworkPath = pi.NetworkPath + })); } return result; @@ -329,8 +328,8 @@ public class JellyfinApiClient : IJellyfinApiClient } string path = item.Path ?? string.Empty; - foreach (JellyfinPathInfo pathInfo in library.PathInfos.Filter( - pi => !string.IsNullOrWhiteSpace(pi.NetworkPath))) + foreach (JellyfinPathInfo pathInfo in library.PathInfos.Filter(pi => + !string.IsNullOrWhiteSpace(pi.NetworkPath))) { if (path.StartsWith(pathInfo.NetworkPath, StringComparison.Ordinal)) { @@ -705,8 +704,8 @@ public class JellyfinApiClient : IJellyfinApiClient } string path = item.Path ?? string.Empty; - foreach (JellyfinPathInfo pathInfo in library.PathInfos.Filter( - pi => !string.IsNullOrWhiteSpace(pi.NetworkPath))) + foreach (JellyfinPathInfo pathInfo in library.PathInfos.Filter(pi => + !string.IsNullOrWhiteSpace(pi.NetworkPath))) { if (path.StartsWith(pathInfo.NetworkPath, StringComparison.Ordinal)) { @@ -850,117 +849,116 @@ public class JellyfinApiClient : IJellyfinApiClient Option maybeVideoStream = streams.Find(s => s.Type == JellyfinMediaStreamType.Video); - return maybeVideoStream.Map( - videoStream => + return maybeVideoStream.Map(videoStream => + { + int width = videoStream.Width ?? 1; + int height = videoStream.Height ?? 1; + + var isAnamorphic = false; + if (videoStream.IsAnamorphic.HasValue) { - int width = videoStream.Width ?? 1; - int height = videoStream.Height ?? 1; + isAnamorphic = videoStream.IsAnamorphic.Value; + } + else if (!string.IsNullOrWhiteSpace(videoStream.AspectRatio) && videoStream.AspectRatio.Contains(':')) + { + // if width/height != aspect ratio, is anamorphic + double resolutionRatio = width / (double)height; - var isAnamorphic = false; - if (videoStream.IsAnamorphic.HasValue) + string[] split = videoStream.AspectRatio.Split(":"); + var num = double.Parse(split[0], CultureInfo.InvariantCulture); + var den = double.Parse(split[1], CultureInfo.InvariantCulture); + double aspectRatio = num / den; + + isAnamorphic = Math.Abs(resolutionRatio - aspectRatio) > 0.01d; + } + + var version = new MediaVersion + { + Duration = TimeSpan.FromTicks(mediaSource.RunTimeTicks), + SampleAspectRatio = isAnamorphic ? "0:0" : "1:1", + DisplayAspectRatio = string.IsNullOrWhiteSpace(videoStream.AspectRatio) + ? string.Empty + : videoStream.AspectRatio, + VideoScanKind = videoStream.IsInterlaced switch { - isAnamorphic = videoStream.IsAnamorphic.Value; - } - else if (!string.IsNullOrWhiteSpace(videoStream.AspectRatio) && videoStream.AspectRatio.Contains(':')) + true => VideoScanKind.Interlaced, + false => VideoScanKind.Progressive + }, + Streams = new List(), + Width = videoStream.Width ?? 1, + Height = videoStream.Height ?? 1, + RFrameRate = videoStream.RealFrameRate.HasValue + ? videoStream.RealFrameRate.Value.ToString("0.00###", CultureInfo.InvariantCulture) + : string.Empty, + Chapters = new List() + }; + + version.Streams.Add( + new MediaStream { - // if width/height != aspect ratio, is anamorphic - double resolutionRatio = width / (double)height; + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Video, + Index = videoStream.Index - streamIndexOffset, + Codec = videoStream.Codec, + Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), + Default = videoStream.IsDefault, + Language = videoStream.Language, + Forced = videoStream.IsForced, + PixelFormat = videoStream.PixelFormat, + ColorRange = (videoStream.ColorRange ?? string.Empty).ToLowerInvariant(), + ColorSpace = (videoStream.ColorSpace ?? string.Empty).ToLowerInvariant(), + ColorTransfer = (videoStream.ColorTransfer ?? string.Empty).ToLowerInvariant(), + ColorPrimaries = (videoStream.ColorPrimaries ?? string.Empty).ToLowerInvariant() + }); - string[] split = videoStream.AspectRatio.Split(":"); - var num = double.Parse(split[0], CultureInfo.InvariantCulture); - var den = double.Parse(split[1], CultureInfo.InvariantCulture); - double aspectRatio = num / den; - - isAnamorphic = Math.Abs(resolutionRatio - aspectRatio) > 0.01d; - } - - var version = new MediaVersion + foreach (JellyfinMediaStreamResponse audioStream in streams.Filter(s => + s.Type == JellyfinMediaStreamType.Audio)) + { + var stream = new MediaStream { - Duration = TimeSpan.FromTicks(mediaSource.RunTimeTicks), - SampleAspectRatio = isAnamorphic ? "0:0" : "1:1", - DisplayAspectRatio = string.IsNullOrWhiteSpace(videoStream.AspectRatio) - ? string.Empty - : videoStream.AspectRatio, - VideoScanKind = videoStream.IsInterlaced switch - { - true => VideoScanKind.Interlaced, - false => VideoScanKind.Progressive - }, - Streams = new List(), - Width = videoStream.Width ?? 1, - Height = videoStream.Height ?? 1, - RFrameRate = videoStream.RealFrameRate.HasValue - ? videoStream.RealFrameRate.Value.ToString("0.00###", CultureInfo.InvariantCulture) - : string.Empty, - Chapters = new List() + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Audio, + Index = audioStream.Index - streamIndexOffset, + Codec = audioStream.Codec, + Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), + Channels = audioStream.Channels ?? 2, + Default = audioStream.IsDefault, + Forced = audioStream.IsForced, + Language = audioStream.Language, + Title = audioStream.Title ?? string.Empty }; - version.Streams.Add( - new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Video, - Index = videoStream.Index - streamIndexOffset, - Codec = videoStream.Codec, - Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), - Default = videoStream.IsDefault, - Language = videoStream.Language, - Forced = videoStream.IsForced, - PixelFormat = videoStream.PixelFormat, - ColorRange = (videoStream.ColorRange ?? string.Empty).ToLowerInvariant(), - ColorSpace = (videoStream.ColorSpace ?? string.Empty).ToLowerInvariant(), - ColorTransfer = (videoStream.ColorTransfer ?? string.Empty).ToLowerInvariant(), - ColorPrimaries = (videoStream.ColorPrimaries ?? string.Empty).ToLowerInvariant() - }); + version.Streams.Add(stream); + } - foreach (JellyfinMediaStreamResponse audioStream in streams.Filter( - s => s.Type == JellyfinMediaStreamType.Audio)) + foreach (JellyfinMediaStreamResponse subtitleStream in streams.Filter(s => + s.Type == JellyfinMediaStreamType.Subtitle)) + { + var stream = new MediaStream { - var stream = new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Audio, - Index = audioStream.Index - streamIndexOffset, - Codec = audioStream.Codec, - Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), - Channels = audioStream.Channels ?? 2, - Default = audioStream.IsDefault, - Forced = audioStream.IsForced, - Language = audioStream.Language, - Title = audioStream.Title ?? string.Empty - }; + MediaVersionId = version.Id, + Codec = (subtitleStream.Codec ?? string.Empty).ToLowerInvariant(), + Default = subtitleStream.IsDefault, + Forced = subtitleStream.IsForced, + Language = subtitleStream.Language + }; - version.Streams.Add(stream); + if (subtitleStream.IsExternal) + { + stream.MediaStreamKind = MediaStreamKind.ExternalSubtitle; + // ensure these don't collide with real indexes from the source file + stream.Index = subtitleStream.Index + JellyfinStream.ExternalStreamOffset; + } + else + { + stream.MediaStreamKind = MediaStreamKind.Subtitle; + stream.Index = subtitleStream.Index - streamIndexOffset; } - foreach (JellyfinMediaStreamResponse subtitleStream in streams.Filter( - s => s.Type == JellyfinMediaStreamType.Subtitle)) - { - var stream = new MediaStream - { - MediaVersionId = version.Id, - Codec = (subtitleStream.Codec ?? string.Empty).ToLowerInvariant(), - Default = subtitleStream.IsDefault, - Forced = subtitleStream.IsForced, - Language = subtitleStream.Language - }; + version.Streams.Add(stream); + } - if (subtitleStream.IsExternal) - { - stream.MediaStreamKind = MediaStreamKind.ExternalSubtitle; - // ensure these don't collide with real indexes from the source file - stream.Index = subtitleStream.Index + JellyfinStream.ExternalStreamOffset; - } - else - { - stream.MediaStreamKind = MediaStreamKind.Subtitle; - stream.Index = subtitleStream.Index - streamIndexOffset; - } - - version.Streams.Add(stream); - } - - return version; - }); + return version; + }); } } diff --git a/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs b/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs index ddfc01a9b..5ac06e100 100644 --- a/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs +++ b/ErsatzTV.Infrastructure/Metadata/LocalStatisticsProvider.cs @@ -399,8 +399,8 @@ public class LocalStatisticsProvider : ILocalStatisticsProvider version.Streams.Add(stream); } - foreach (FFprobeStreamData attachmentStream in json.streams.Filter( - s => s.codec_type == "attachment")) + foreach (FFprobeStreamData attachmentStream in + json.streams.Filter(s => s.codec_type == "attachment")) { var stream = new MediaStream { diff --git a/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs b/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs index ab8f114f1..fb19c0757 100644 --- a/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs +++ b/ErsatzTV.Infrastructure/Plex/IPlexServerApi.cs @@ -1,7 +1,5 @@ -using System.Collections.Specialized; -using ErsatzTV.Infrastructure.Plex.Models; +using ErsatzTV.Infrastructure.Plex.Models; using Refit; -using CollectionFormat = Refit.CollectionFormat; namespace ErsatzTV.Infrastructure.Plex; diff --git a/ErsatzTV.Infrastructure/Plex/Models/PlexLocationResponse.cs b/ErsatzTV.Infrastructure/Plex/Models/PlexLocationResponse.cs index 6b032fb8b..ad6f7b38c 100644 --- a/ErsatzTV.Infrastructure/Plex/Models/PlexLocationResponse.cs +++ b/ErsatzTV.Infrastructure/Plex/Models/PlexLocationResponse.cs @@ -1,4 +1,5 @@ namespace ErsatzTV.Infrastructure.Plex.Models; + public class PlexLocationResponse { public int Id { get; set; } diff --git a/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs b/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs index 3fb0f07e1..738c1afed 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexSecretStore.cs @@ -8,45 +8,40 @@ namespace ErsatzTV.Infrastructure.Plex; public class PlexSecretStore : IPlexSecretStore { public Task GetClientIdentifier() => - ReadSecrets().Bind( - plexSecrets => Optional(plexSecrets.ClientIdentifier).Match( - Task.FromResult, - async () => - { - string identifier = GenerateClientIdentifier(); - plexSecrets.ClientIdentifier = identifier; - await SaveSecrets(plexSecrets); - return identifier; - })); + ReadSecrets().Bind(plexSecrets => Optional(plexSecrets.ClientIdentifier).Match( + Task.FromResult, + async () => + { + string identifier = GenerateClientIdentifier(); + plexSecrets.ClientIdentifier = identifier; + await SaveSecrets(plexSecrets); + return identifier; + })); public Task> GetUserAuthTokens() => - ReadSecrets().Map( - s => Optional(s.UserAuthTokens).Match( - tokens => tokens.Map(kvp => new PlexUserAuthToken(kvp.Key, kvp.Value)).ToList(), - () => new List())); + ReadSecrets().Map(s => Optional(s.UserAuthTokens).Match( + tokens => tokens.Map(kvp => new PlexUserAuthToken(kvp.Key, kvp.Value)).ToList(), + () => new List())); public Task> GetServerAuthToken(string clientIdentifier) => - ReadSecrets().Map( - s => Optional(s.ServerAuthTokens.SingleOrDefault(kvp => kvp.Key == clientIdentifier)) - .Map(kvp => new PlexServerAuthToken(kvp.Key, kvp.Value))); + ReadSecrets().Map(s => Optional(s.ServerAuthTokens.SingleOrDefault(kvp => kvp.Key == clientIdentifier)) + .Map(kvp => new PlexServerAuthToken(kvp.Key, kvp.Value))); public Task UpsertUserAuthToken(PlexUserAuthToken userAuthToken) => - ReadSecrets().Bind( - secrets => - { - secrets.UserAuthTokens ??= new Dictionary(); - secrets.UserAuthTokens[userAuthToken.Email] = userAuthToken.AuthToken; - return SaveSecrets(secrets); - }); + ReadSecrets().Bind(secrets => + { + secrets.UserAuthTokens ??= new Dictionary(); + secrets.UserAuthTokens[userAuthToken.Email] = userAuthToken.AuthToken; + return SaveSecrets(secrets); + }); public Task UpsertServerAuthToken(PlexServerAuthToken serverAuthToken) => - ReadSecrets().Bind( - secrets => - { - secrets.ServerAuthTokens ??= new Dictionary(); - secrets.ServerAuthTokens[serverAuthToken.ClientIdentifier] = serverAuthToken.AuthToken; - return SaveSecrets(secrets); - }); + ReadSecrets().Bind(secrets => + { + secrets.ServerAuthTokens ??= new Dictionary(); + secrets.ServerAuthTokens[serverAuthToken.ClientIdentifier] = serverAuthToken.AuthToken; + return SaveSecrets(secrets); + }); public Task DeleteAll() => ReadSecrets().Bind(secrets => SaveSecrets(new PlexSecrets { ClientIdentifier = secrets.ClientIdentifier })); @@ -55,13 +50,12 @@ public class PlexSecretStore : IPlexSecretStore File.ReadAllTextAsync(FileSystemLayout.PlexSecretsPath) .Map(JsonConvert.DeserializeObject) .Map(s => Optional(s).IfNone(new PlexSecrets())) - .Map( - s => - { - s.ServerAuthTokens ??= new Dictionary(); - s.UserAuthTokens ??= new Dictionary(); - return s; - }); + .Map(s => + { + s.ServerAuthTokens ??= new Dictionary(); + s.UserAuthTokens ??= new Dictionary(); + return s; + }); private static Task SaveSecrets(PlexSecrets plexSecrets) => Some(JsonConvert.SerializeObject(plexSecrets)).Match( diff --git a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs index 82e48d821..b7f942c25 100644 --- a/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs +++ b/ErsatzTV.Infrastructure/Plex/PlexServerApiClient.cs @@ -25,7 +25,10 @@ public class PlexServerApiClient : IPlexServerApiClient _logger = logger; } - public async Task Ping(PlexConnection connection, PlexServerAuthToken token, CancellationToken cancellationToken) + public async Task Ping( + PlexConnection connection, + PlexServerAuthToken token, + CancellationToken cancellationToken) { try { @@ -53,7 +56,7 @@ public class PlexServerApiClient : IPlexServerApiClient }); List directory = await service.GetLibraries(token.AuthToken).Map(r => r.MediaContainer.Directory); - List response = directory + var response = directory .Filter(l => l.Type.ToLowerInvariant() is "movie" or "show") .Map(Project) .Somes() @@ -80,9 +83,8 @@ public class PlexServerApiClient : IPlexServerApiClient { return jsonService .GetLibrarySectionContents(library.Key, skip, pageSize, token.AuthToken) - .Map( - r => r.MediaContainer.Metadata.Filter( - m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) + .Map(r => r.MediaContainer.Metadata.Filter(m => + m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) .Map(list => list.Map(metadata => ProjectToMovie(metadata, library.MediaSourceId))); } @@ -124,9 +126,8 @@ public class PlexServerApiClient : IPlexServerApiClient { return jsonService .GetLibrarySectionContents(library.Key, skip, pageSize, token.AuthToken) - .Map( - r => r.MediaContainer.Metadata.Filter( - m => m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) + .Map(r => r.MediaContainer.Metadata.Filter(m => + m.Media.Count > 0 && m.Media.Any(media => media.Part.Count > 0))) .Map(list => list.Map(metadata => ProjectToOtherVideo(metadata, library.MediaSourceId, library))); } @@ -211,9 +212,8 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map( - r => r.Filter( - m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); + .Map(r => r.Filter(m => + m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { @@ -245,9 +245,8 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map( - r => r.Filter( - m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); + .Map(r => r.Filter(m => + m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { @@ -278,9 +277,8 @@ public class PlexServerApiClient : IPlexServerApiClient Option maybeResponse = await service .GetVideoMetadata(key, token.AuthToken) .Map(Optional) - .Map( - r => r.Filter( - m => m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); + .Map(r => r.Filter(m => + m.Metadata.Media.Count > 0 && m.Metadata.Media.Any(media => media.Part.Count > 0))); return maybeResponse.Match( response => { @@ -455,7 +453,7 @@ public class PlexServerApiClient : IPlexServerApiClient { List paths = [ - new LibraryPath + new() { Path = JsonConvert.SerializeObject( new LibraryPaths { Paths = response.Location.Map(l => l.Path).ToList() }) @@ -494,9 +492,9 @@ public class PlexServerApiClient : IPlexServerApiClient try { // skip collections in libraries that are not synchronized - if (plexMediaSource.Libraries.OfType().Any( - l => l.Key == item.LibrarySectionId.ToString(CultureInfo.InvariantCulture) && - l.ShouldSyncItems == false)) + if (plexMediaSource.Libraries.OfType().Any(l => + l.Key == item.LibrarySectionId.ToString(CultureInfo.InvariantCulture) && + l.ShouldSyncItems == false)) { return Option.None; } @@ -709,112 +707,110 @@ public class PlexServerApiClient : IPlexServerApiClient List streams = media.Part.Head().Stream; DateTime dateUpdated = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; Option maybeVideoStream = streams.Find(s => s.StreamType == 1); - return maybeVideoStream.Map( - videoStream => + return maybeVideoStream.Map(videoStream => + { + var version = new MediaVersion { - var version = new MediaVersion + Duration = TimeSpan.FromMilliseconds(media.Duration), + SampleAspectRatio = string.IsNullOrWhiteSpace(videoStream.PixelAspectRatio) + ? "1:1" + : videoStream.PixelAspectRatio, + VideoScanKind = videoStream.ScanType switch { - Duration = TimeSpan.FromMilliseconds(media.Duration), - SampleAspectRatio = string.IsNullOrWhiteSpace(videoStream.PixelAspectRatio) - ? "1:1" - : videoStream.PixelAspectRatio, - VideoScanKind = videoStream.ScanType switch - { - "interlaced" => VideoScanKind.Interlaced, - "progressive" => VideoScanKind.Progressive, - _ => VideoScanKind.Unknown - }, - Streams = new List(), - DateUpdated = dateUpdated, - Width = videoStream.Width, - Height = videoStream.Height, - RFrameRate = videoStream.FrameRate, - DisplayAspectRatio = media.AspectRatio == 0 - ? string.Empty - : media.AspectRatio.ToString("0.00###", CultureInfo.InvariantCulture), - Chapters = Optional(response.Chapters).Flatten().Map(ProjectToModel).ToList() + "interlaced" => VideoScanKind.Interlaced, + "progressive" => VideoScanKind.Progressive, + _ => VideoScanKind.Unknown + }, + Streams = new List(), + DateUpdated = dateUpdated, + Width = videoStream.Width, + Height = videoStream.Height, + RFrameRate = videoStream.FrameRate, + DisplayAspectRatio = media.AspectRatio == 0 + ? string.Empty + : media.AspectRatio.ToString("0.00###", CultureInfo.InvariantCulture), + Chapters = Optional(response.Chapters).Flatten().Map(ProjectToModel).ToList() + }; + + version.Streams.Add( + new MediaStream + { + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Video, + Index = videoStream.Index!.Value, + Codec = videoStream.Codec, + Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), + Default = videoStream.Default, + Language = videoStream.LanguageCode, + Forced = videoStream.Forced, + BitsPerRawSample = videoStream.BitDepth, + ColorRange = (videoStream.ColorRange ?? string.Empty).ToLowerInvariant(), + ColorSpace = (videoStream.ColorSpace ?? string.Empty).ToLowerInvariant(), + ColorTransfer = (videoStream.ColorTrc ?? string.Empty).ToLowerInvariant(), + ColorPrimaries = (videoStream.ColorPrimaries ?? string.Empty).ToLowerInvariant() + }); + + foreach (PlexStreamResponse audioStream in streams.Filter(s => s.StreamType == 2 && s.Index.HasValue)) + { + var stream = new MediaStream + { + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Audio, + Index = audioStream.Index.Value, + Codec = audioStream.Codec, + Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), + Channels = audioStream.Channels, + Default = audioStream.Default, + Forced = audioStream.Forced, + Language = audioStream.LanguageCode, + Title = audioStream.Title ?? string.Empty }; - version.Streams.Add( - new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Video, - Index = videoStream.Index!.Value, - Codec = videoStream.Codec, - Profile = (videoStream.Profile ?? string.Empty).ToLowerInvariant(), - Default = videoStream.Default, - Language = videoStream.LanguageCode, - Forced = videoStream.Forced, - BitsPerRawSample = videoStream.BitDepth, - ColorRange = (videoStream.ColorRange ?? string.Empty).ToLowerInvariant(), - ColorSpace = (videoStream.ColorSpace ?? string.Empty).ToLowerInvariant(), - ColorTransfer = (videoStream.ColorTrc ?? string.Empty).ToLowerInvariant(), - ColorPrimaries = (videoStream.ColorPrimaries ?? string.Empty).ToLowerInvariant() - }); + version.Streams.Add(stream); + } - foreach (PlexStreamResponse audioStream in streams.Filter(s => s.StreamType == 2 && s.Index.HasValue)) + // filter to embedded subtitles, but ignore "embedded in video" closed-caption streams + foreach (PlexStreamResponse subtitleStream in + streams.Filter(s => s.StreamType == 3 && s.Index.HasValue && !s.EmbeddedInVideo)) + { + var stream = new MediaStream { - var stream = new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Audio, - Index = audioStream.Index.Value, - Codec = audioStream.Codec, - Profile = (audioStream.Profile ?? string.Empty).ToLowerInvariant(), - Channels = audioStream.Channels, - Default = audioStream.Default, - Forced = audioStream.Forced, - Language = audioStream.LanguageCode, - Title = audioStream.Title ?? string.Empty - }; + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.Subtitle, + Index = subtitleStream.Index.Value, + Codec = subtitleStream.Codec, + Default = subtitleStream.Default, + Forced = subtitleStream.Forced, + Language = subtitleStream.LanguageCode + }; - version.Streams.Add(stream); - } + version.Streams.Add(stream); + } - // filter to embedded subtitles, but ignore "embedded in video" closed-caption streams - foreach (PlexStreamResponse subtitleStream in - streams.Filter(s => s.StreamType == 3 && s.Index.HasValue && !s.EmbeddedInVideo)) + // also include external subtitles + foreach (PlexStreamResponse subtitleStream in + streams.Filter(s => s.StreamType == 3 && !s.Index.HasValue && !string.IsNullOrWhiteSpace(s.Key))) + { + var stream = new MediaStream { - var stream = new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.Subtitle, - Index = subtitleStream.Index.Value, - Codec = subtitleStream.Codec, - Default = subtitleStream.Default, - Forced = subtitleStream.Forced, - Language = subtitleStream.LanguageCode - }; + MediaVersionId = version.Id, + MediaStreamKind = MediaStreamKind.ExternalSubtitle, - version.Streams.Add(stream); - } + // hacky? maybe... + FileName = subtitleStream.Key, + Index = subtitleStream.Id, - // also include external subtitles - foreach (PlexStreamResponse subtitleStream in - streams.Filter( - s => s.StreamType == 3 && !s.Index.HasValue && !string.IsNullOrWhiteSpace(s.Key))) - { - var stream = new MediaStream - { - MediaVersionId = version.Id, - MediaStreamKind = MediaStreamKind.ExternalSubtitle, + Codec = subtitleStream.Codec, + Default = subtitleStream.Default, + Forced = subtitleStream.Forced, + Language = subtitleStream.LanguageCode + }; - // hacky? maybe... - FileName = subtitleStream.Key, - Index = subtitleStream.Id, + version.Streams.Add(stream); + } - Codec = subtitleStream.Codec, - Default = subtitleStream.Default, - Forced = subtitleStream.Forced, - Language = subtitleStream.LanguageCode - }; - - version.Streams.Add(stream); - } - - return version; - }); + return version; + }); } private PlexShow ProjectToShow(PlexMetadataResponse response, int mediaSourceId) @@ -1186,7 +1182,11 @@ public class PlexServerApiClient : IPlexServerApiClient return otherVideo; } - private OtherVideoMetadata ProjectToOtherVideoMetadata(MediaVersion version, PlexMetadataResponse response, int mediaSourceId, PlexLibrary library) + private OtherVideoMetadata ProjectToOtherVideoMetadata( + MediaVersion version, + PlexMetadataResponse response, + int mediaSourceId, + PlexLibrary library) { DateTime dateAdded = DateTimeOffset.FromUnixTimeSeconds(response.AddedAt).DateTime; DateTime lastWriteTime = DateTimeOffset.FromUnixTimeSeconds(response.UpdatedAt).DateTime; diff --git a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs index 42a4d9d94..28a9eee87 100644 --- a/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs +++ b/ErsatzTV.Infrastructure/Scheduling/MultiEpisodeShuffleCollectionEnumerator.cs @@ -80,8 +80,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/Scripting/ScriptEngine.cs b/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs index 58730aaf3..4d2db7ed3 100644 --- a/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs +++ b/ErsatzTV.Infrastructure/Scripting/ScriptEngine.cs @@ -10,14 +10,13 @@ public class ScriptEngine : IScriptEngine private Engine _engine; public ScriptEngine(ILogger logger) => - _engine = new Engine( - options => - { - options.AllowClr(); - options.LimitMemory(4_000_000); - options.TimeoutInterval(TimeSpan.FromSeconds(4)); - options.MaxStatements(1000); - }) + _engine = new Engine(options => + { + options.AllowClr(); + options.LimitMemory(4_000_000); + options.TimeoutInterval(TimeSpan.FromSeconds(4)); + options.MaxStatements(1000); + }) .SetValue("log", new Action(s => logger.LogDebug("JS Script: {Message}", s))); public void Load(string jsScriptPath) diff --git a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs index 778ac66f7..b75b54cf5 100644 --- a/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/ElasticSearchIndex.cs @@ -1,6 +1,5 @@ using System.Globalization; using Bugsnag; -using Elastic.Clients.Elasticsearch; using Elastic.Clients.Elasticsearch.Aggregations; using Elastic.Clients.Elasticsearch.Core.Bulk; using Elastic.Clients.Elasticsearch.IndexManagement; @@ -24,10 +23,10 @@ namespace ErsatzTV.Infrastructure.Search; public class ElasticSearchIndex : ISearchIndex { private readonly List _cultureInfos; + private readonly ILogger _logger; private readonly SearchQueryParser _searchQueryParser; - private readonly ILogger _logger; - private ElasticsearchClient _client; + private ES.ElasticsearchClient _client; public ElasticSearchIndex(SearchQueryParser searchQueryParser, ILogger logger) { @@ -152,27 +151,33 @@ public class ElasticSearchIndex : ISearchIndex public async Task RemoveItems(IEnumerable ids) { - var deleteBulkRequest = new BulkRequest { Operations = [] }; + var deleteBulkRequest = new ES.BulkRequest { Operations = [] }; foreach (int id in ids) { - var deleteOperation = new BulkDeleteOperation(new Id(id)) { Index = IndexName }; + var deleteOperation = new BulkDeleteOperation(new ES.Id(id)) { Index = IndexName }; deleteBulkRequest.Operations.Add(deleteOperation); } - BulkResponse deleteResponse = await _client.BulkAsync(deleteBulkRequest).ConfigureAwait(false); + ES.BulkResponse deleteResponse = await _client.BulkAsync(deleteBulkRequest).ConfigureAwait(false); return deleteResponse.IsValidResponse; } - public async Task Search(IClient client, string query, string smartCollectionName, int skip, int limit) + public async Task Search( + IClient client, + string query, + string smartCollectionName, + int skip, + int limit) { var items = new List(); var totalCount = 0; Query parsedQuery = await _searchQueryParser.ParseQuery(query, smartCollectionName); - SearchResponse response = await _client.SearchAsync( - s => s.Indices(IndexName) - .Sort(ss => ss.Field(f => f.SortTitle, fs => fs.Order(SortOrder.Asc))) + ES.SearchResponse response = await _client.SearchAsync(s => + s + .Indices(IndexName) + .Sort(ss => ss.Field(f => f.SortTitle, fs => fs.Order(ES.SortOrder.Asc))) .From(skip) .Size(limit) .QueryLuceneSyntax(parsedQuery.ToString())); @@ -198,63 +203,61 @@ public class ElasticSearchIndex : ISearchIndex // do nothing } - private static ElasticsearchClient CreateClient() + private static ES.ElasticsearchClient CreateClient() { - ElasticsearchClientSettings settings = new ElasticsearchClientSettings(Uri).DefaultIndex(IndexName); - return new ElasticsearchClient(settings); + ES.ElasticsearchClientSettings settings = new ES.ElasticsearchClientSettings(Uri).DefaultIndex(IndexName); + return new ES.ElasticsearchClient(settings); } private async Task CreateIndex() => await _client.Indices.CreateAsync( IndexName, - i => i.Mappings( - m => m.Properties( - p => p - .Keyword(t => t.Type, t => t.Store()) - .Text(t => t.Title, t => t.Store(false)) - .Keyword(t => t.SortTitle, t => t.Store(false)) - .Text(t => t.LibraryName, t => t.Store(false)) - .Keyword(t => t.LibraryId, t => t.Store(false)) - .Keyword(t => t.TitleAndYear, t => t.Store(false)) - .Keyword(t => t.JumpLetter, t => t.Store()) - .Keyword(t => t.State, t => t.Store(false)) - .Text(t => t.MetadataKind, t => t.Store(false)) - .Text(t => t.Language, t => t.Store(false)) - .Text(t => t.LanguageTag, t => t.Store(false)) - .Text(t => t.SubLanguage, t => t.Store(false)) - .Text(t => t.SubLanguageTag, t => t.Store(false)) - .IntegerNumber(t => t.Height, t => t.Store(false)) - .IntegerNumber(t => t.Width, t => t.Store(false)) - .Keyword(t => t.VideoCodec, t => t.Store(false)) - .IntegerNumber(t => t.VideoBitDepth, t => t.Store(false)) - .Keyword(t => t.VideoDynamicRange, t => t.Store(false)) - .Keyword(t => t.ContentRating, t => t.Store(false)) - .Keyword(t => t.ReleaseDate, t => t.Store(false)) - .Keyword(t => t.AddedDate, t => t.Store(false)) - .Text(t => t.Plot, t => t.Store(false)) - .Text(t => t.Genre, t => t.Store(false)) - .Text(t => t.Tag, t => t.Store(false)) - .Keyword(t => t.TagFull, t => t.Store(false)) - .Text(t => t.Studio, t => t.Store(false)) - .Text(t => t.Network, t => t.Store(false)) - .Text(t => t.Actor, t => t.Store(false)) - .Text(t => t.Director, t => t.Store(false)) - .Text(t => t.Writer, t => t.Store(false)) - .Keyword(t => t.TraktList, t => t.Store(false)) - .IntegerNumber(t => t.SeasonNumber, t => t.Store(false)) - .Text(t => t.ShowTitle, t => t.Store(false)) - .Text(t => t.ShowGenre, t => t.Store(false)) - .Text(t => t.ShowTag, t => t.Store(false)) - .Text(t => t.ShowStudio, t => t.Store(false)) - .Text(t => t.ShowNetwork, t => t.Store(false)) - .Keyword(t => t.ShowContentRating, t => t.Store(false)) - .Text(t => t.Style, t => t.Store(false)) - .Text(t => t.Mood, t => t.Store(false)) - .Text(t => t.Album, t => t.Store(false)) - .Text(t => t.Artist, t => t.Store(false)) - .IntegerNumber(t => t.EpisodeNumber, t => t.Store(false)) - .Text(t => t.AlbumArtist, t => t.Store(false)) - ))); + i => i.Mappings(m => m.Properties(p => p + .Keyword(t => t.Type, t => t.Store()) + .Text(t => t.Title, t => t.Store(false)) + .Keyword(t => t.SortTitle, t => t.Store(false)) + .Text(t => t.LibraryName, t => t.Store(false)) + .Keyword(t => t.LibraryId, t => t.Store(false)) + .Keyword(t => t.TitleAndYear, t => t.Store(false)) + .Keyword(t => t.JumpLetter, t => t.Store()) + .Keyword(t => t.State, t => t.Store(false)) + .Text(t => t.MetadataKind, t => t.Store(false)) + .Text(t => t.Language, t => t.Store(false)) + .Text(t => t.LanguageTag, t => t.Store(false)) + .Text(t => t.SubLanguage, t => t.Store(false)) + .Text(t => t.SubLanguageTag, t => t.Store(false)) + .IntegerNumber(t => t.Height, t => t.Store(false)) + .IntegerNumber(t => t.Width, t => t.Store(false)) + .Keyword(t => t.VideoCodec, t => t.Store(false)) + .IntegerNumber(t => t.VideoBitDepth, t => t.Store(false)) + .Keyword(t => t.VideoDynamicRange, t => t.Store(false)) + .Keyword(t => t.ContentRating, t => t.Store(false)) + .Keyword(t => t.ReleaseDate, t => t.Store(false)) + .Keyword(t => t.AddedDate, t => t.Store(false)) + .Text(t => t.Plot, t => t.Store(false)) + .Text(t => t.Genre, t => t.Store(false)) + .Text(t => t.Tag, t => t.Store(false)) + .Keyword(t => t.TagFull, t => t.Store(false)) + .Text(t => t.Studio, t => t.Store(false)) + .Text(t => t.Network, t => t.Store(false)) + .Text(t => t.Actor, t => t.Store(false)) + .Text(t => t.Director, t => t.Store(false)) + .Text(t => t.Writer, t => t.Store(false)) + .Keyword(t => t.TraktList, t => t.Store(false)) + .IntegerNumber(t => t.SeasonNumber, t => t.Store(false)) + .Text(t => t.ShowTitle, t => t.Store(false)) + .Text(t => t.ShowGenre, t => t.Store(false)) + .Text(t => t.ShowTag, t => t.Store(false)) + .Text(t => t.ShowStudio, t => t.Store(false)) + .Text(t => t.ShowNetwork, t => t.Store(false)) + .Keyword(t => t.ShowContentRating, t => t.Store(false)) + .Text(t => t.Style, t => t.Store(false)) + .Text(t => t.Mood, t => t.Store(false)) + .Text(t => t.Album, t => t.Store(false)) + .Text(t => t.Artist, t => t.Store(false)) + .IntegerNumber(t => t.EpisodeNumber, t => t.Store(false)) + .Text(t => t.AlbumArtist, t => t.Store(false)) + ))); private async Task RebuildItem( ISearchRepository searchRepository, @@ -374,10 +377,13 @@ public class ElasticSearchIndex : ISearchIndex AddedDate = GetAddedDate(metadata.DateAdded), Plot = metadata.Plot ?? string.Empty, Genre = metadata.Genres.Map(g => g.Name).ToList(), - Tag = metadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name).ToList(), - TagFull = metadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name).ToList(), + Tag = metadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name) + .ToList(), + TagFull = metadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name) + .ToList(), Studio = metadata.Studios.Map(s => s.Name).ToList(), - Network = metadata.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(t => t.Name).ToList(), + Network = metadata.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(t => t.Name) + .ToList(), Actor = metadata.Actors.Map(a => a.Name).ToList(), TraktList = show.TraktListItems.Map(t => t.TraktList.TraktId.ToString(CultureInfo.InvariantCulture)) .ToList() @@ -631,9 +637,11 @@ public class ElasticSearchIndex : ISearchIndex { doc.ShowTitle = showMetadata.Title; doc.ShowGenre = showMetadata.Genres.Map(g => g.Name).ToList(); - doc.ShowTag = showMetadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)).Map(t => t.Name).ToList(); + doc.ShowTag = showMetadata.Tags.Where(t => string.IsNullOrWhiteSpace(t.ExternalTypeId)) + .Map(t => t.Name).ToList(); doc.ShowStudio = showMetadata.Studios.Map(s => s.Name).ToList(); - doc.ShowNetwork = showMetadata.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId).Map(t => t.Name).ToList(); + doc.ShowNetwork = showMetadata.Tags.Where(t => t.ExternalTypeId == Tag.PlexNetworkTypeId) + .Map(t => t.Name).ToList(); doc.ShowContentRating = GetContentRatings(showMetadata.ContentRating); } @@ -879,8 +887,10 @@ public class ElasticSearchIndex : ISearchIndex var englishNames = new System.Collections.Generic.HashSet(); foreach (string code in await searchRepository.GetAllThreeLetterLanguageCodes(mediaCodes)) { - Option maybeCultureInfo = _cultureInfos.Find( - ci => string.Equals(ci.ThreeLetterISOLanguageName, code, StringComparison.OrdinalIgnoreCase)); + Option maybeCultureInfo = _cultureInfos.Find(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + code, + StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo cultureInfo in maybeCultureInfo) { englishNames.Add(cultureInfo.EnglishName); @@ -900,10 +910,9 @@ public class ElasticSearchIndex : ISearchIndex private static List GetSubLanguageTags(IEnumerable mediaVersions) => mediaVersions - .Map( - mv => mv.Streams - .Filter(ms => ms.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle) - .Map(ms => ms.Language)) + .Map(mv => mv.Streams + .Filter(ms => ms.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle) + .Map(ms => ms.Language)) .Flatten() .Filter(s => !string.IsNullOrWhiteSpace(s)) .Distinct() @@ -980,10 +989,11 @@ public class ElasticSearchIndex : ISearchIndex private async Task GetSearchPageMap(string query, int limit) { - SearchResponse response = await _client.SearchAsync( - s => s.Indices(IndexName) + ES.SearchResponse response = await _client.SearchAsync(s => + s + .Indices(IndexName) .Size(0) - .Sort(ss => ss.Field(f => f.SortTitle, fs => fs.Order(SortOrder.Asc))) + .Sort(ss => ss.Field(f => f.SortTitle, fs => fs.Order(ES.SortOrder.Asc))) .Aggregations(a => a.Add("count", agg => agg.Terms(v => v.Field(i => i.JumpLetter).Size(30)))) .QueryLuceneSyntax(query)); diff --git a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs index c06003145..0287ebcc9 100644 --- a/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs +++ b/ErsatzTV.Infrastructure/Search/LuceneSearchIndex.cs @@ -91,9 +91,9 @@ public sealed class LuceneSearchIndex : ISearchIndex private readonly string _cleanShutdownPath; private readonly List _cultureInfos; + private readonly ILogger _logger; private readonly SearchQueryParser _searchQueryParser; - private readonly ILogger _logger; private FSDirectory _directory; private bool _initialized; @@ -202,7 +202,12 @@ public sealed class LuceneSearchIndex : ISearchIndex return Task.FromResult(true); } - public async Task Search(IClient client, string query, string smartCollectionName, int skip, int limit) + public async Task Search( + IClient client, + string query, + string smartCollectionName, + int skip, + int limit) { var metadata = new Dictionary { @@ -528,10 +533,9 @@ public sealed class LuceneSearchIndex : ISearchIndex await AddLanguages(searchRepository, doc, mediaCodes); var subMediaCodes = mediaVersions - .Map( - mv => mv.Streams - .Filter(ms => ms.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle) - .Map(ms => ms.Language)) + .Map(mv => mv.Streams + .Filter(ms => ms.MediaStreamKind is MediaStreamKind.Subtitle or MediaStreamKind.ExternalSubtitle) + .Map(ms => ms.Language)) .Flatten() .Filter(c => !string.IsNullOrWhiteSpace(c)) .Distinct() @@ -550,8 +554,10 @@ public sealed class LuceneSearchIndex : ISearchIndex var englishNames = new System.Collections.Generic.HashSet(); foreach (string code in await searchRepository.GetAllThreeLetterLanguageCodes(mediaCodes)) { - Option maybeCultureInfo = _cultureInfos.Find( - ci => string.Equals(ci.ThreeLetterISOLanguageName, code, StringComparison.OrdinalIgnoreCase)); + Option maybeCultureInfo = _cultureInfos.Find(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + code, + StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo cultureInfo in maybeCultureInfo) { englishNames.Add(cultureInfo.EnglishName); @@ -574,8 +580,10 @@ public sealed class LuceneSearchIndex : ISearchIndex var englishNames = new System.Collections.Generic.HashSet(); foreach (string code in await searchRepository.GetAllThreeLetterLanguageCodes(mediaCodes)) { - Option maybeCultureInfo = _cultureInfos.Find( - ci => string.Equals(ci.ThreeLetterISOLanguageName, code, StringComparison.OrdinalIgnoreCase)); + Option maybeCultureInfo = _cultureInfos.Find(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + code, + StringComparison.OrdinalIgnoreCase)); foreach (CultureInfo cultureInfo in maybeCultureInfo) { englishNames.Add(cultureInfo.EnglishName); diff --git a/ErsatzTV.Infrastructure/Search/SearchQueryParser.cs b/ErsatzTV.Infrastructure/Search/SearchQueryParser.cs index 5148ae5de..56c41b398 100644 --- a/ErsatzTV.Infrastructure/Search/SearchQueryParser.cs +++ b/ErsatzTV.Infrastructure/Search/SearchQueryParser.cs @@ -73,7 +73,8 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach if (parsedQuery == replaceResult.Query) { - Log.Logger.Warning("Failed to replace smart_collection in query; is the syntax correct? Quotes are required. Giving up..."); + Log.Logger.Warning( + "Failed to replace smart_collection in query; is the syntax correct? Quotes are required. Giving up..."); break; } @@ -110,7 +111,9 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach string smartCollectionName = match.Groups[1].Value; if (await smartCollectionCache.HasCycle(smartCollectionName)) { - Log.Logger.Error("Smart collection {Name} contains a cycle; will not evaluate", smartCollectionName); + Log.Logger.Error( + "Smart collection {Name} contains a cycle; will not evaluate", + smartCollectionName); return new ReplaceResult(query, true); } @@ -145,9 +148,10 @@ public partial class SearchQueryParser(ISmartCollectionCache smartCollectionCach return query; } - [GeneratedRegex(""" - smart_collection:"([^"]+)" - """)] + [GeneratedRegex( + """ + smart_collection:"([^"]+)" + """)] internal static partial Regex SmartCollectionRegex(); private record ReplaceResult(string Query, bool Fatal); diff --git a/ErsatzTV.Infrastructure/Search/SmartCollectionCache.cs b/ErsatzTV.Infrastructure/Search/SmartCollectionCache.cs index 3090abc08..eeeb17835 100644 --- a/ErsatzTV.Infrastructure/Search/SmartCollectionCache.cs +++ b/ErsatzTV.Infrastructure/Search/SmartCollectionCache.cs @@ -10,8 +10,10 @@ public sealed class SmartCollectionCache(IDbContextFactory dbContextF : ISmartCollectionCache, IDisposable { private readonly Dictionary _data = new(StringComparer.OrdinalIgnoreCase); - private readonly SemaphoreSlim _semaphoreSlim = new(1, 1); private readonly AdjGraph _graph = new(); + private readonly SemaphoreSlim _semaphoreSlim = new(1, 1); + + public void Dispose() => _semaphoreSlim.Dispose(); public async Task Refresh() { @@ -79,11 +81,6 @@ public sealed class SmartCollectionCache(IDbContextFactory dbContextF } } - public void Dispose() - { - _semaphoreSlim.Dispose(); - } - private record SmartCollectionData(string Query) { public bool HasCycle { get; set; } diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index 139f5a051..419f85790 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -25,12 +25,12 @@ using ErsatzTV.FFmpeg.State; using ErsatzTV.Infrastructure.Images; using ErsatzTV.Infrastructure.Metadata; using ErsatzTV.Infrastructure.Runtime; -using Shouldly; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; using Serilog; +using Shouldly; using MediaStream = ErsatzTV.Core.Domain.MediaStream; namespace ErsatzTV.Scanner.Tests.Core.FFmpeg; @@ -159,7 +159,7 @@ public class TranscodingTests // new("mpeg2video", "yuv420p"), // // //new InputFormat("libx265", "yuv420p"), - new InputFormat("libx265", "yuv420p10le") + new("libx265", "yuv420p10le") // // new("mpeg4", "yuv420p"), // @@ -177,9 +177,9 @@ public class TranscodingTests public static Resolution[] Resolutions = [ - new Resolution { Width = 1920, Height = 1080 }, - new Resolution { Width = 1280, Height = 720 }, - new Resolution { Width = 640, Height = 480 } + new() { Width = 1920, Height = 1080 }, + new() { Width = 1280, Height = 720 }, + new() { Width = 640, Height = 480 } ]; public static FFmpegProfileBitDepth[] BitDepths = @@ -198,7 +198,7 @@ public class TranscodingTests public static HardwareAccelerationKind[] TestAccelerations = [ //HardwareAccelerationKind.None, - HardwareAccelerationKind.Nvenc, + HardwareAccelerationKind.Nvenc //HardwareAccelerationKind.Vaapi //HardwareAccelerationKind.Qsv, // HardwareAccelerationKind.VideoToolbox, @@ -207,7 +207,7 @@ public class TranscodingTests public static StreamingMode[] StreamingModes = [ - StreamingMode.TransportStream, + StreamingMode.TransportStream //StreamingMode.HttpLiveStreamingSegmenter, //StreamingMode.HttpLiveStreamingSegmenterV2 ]; @@ -324,21 +324,20 @@ public class TranscodingTests IMetadataRepository metadataRepository = Substitute.For(); metadataRepository.When(x => x.UpdateStatistics(Arg.Any(), Arg.Any(), Arg.Any())) - .Do( - x => + .Do(x => + { + MediaVersion version = x.Arg(); + if (version.Streams.Any(s => s.MediaStreamKind == MediaStreamKind.Video && s.AttachedPic == false)) { - MediaVersion version = x.Arg(); - if (version.Streams.Any(s => s.MediaStreamKind == MediaStreamKind.Video && s.AttachedPic == false)) - { - version.MediaFiles = videoVersion.MediaFiles; - videoVersion = version; - } - else - { - version.MediaFiles = songVersion.MediaFiles; - songVersion = version; - } - }); + version.MediaFiles = videoVersion.MediaFiles; + videoVersion = version; + } + else + { + version.MediaFiles = songVersion.MediaFiles; + songVersion = version; + } + }); var localStatisticsProvider = new LocalStatisticsProvider( metadataRepository, @@ -458,13 +457,12 @@ public class TranscodingTests IMetadataRepository? metadataRepository = Substitute.For(); metadataRepository .When(r => r.UpdateStatistics(Arg.Any(), Arg.Any(), Arg.Any())) - .Do( - args => - { - MediaVersion? version = args.Arg(); - version.MediaFiles = v.MediaFiles; - v = version; - }); + .Do(args => + { + MediaVersion? version = args.Arg(); + version.MediaFiles = v.MediaFiles; + v = version; + }); var localStatisticsProvider = new LocalStatisticsProvider( metadataRepository, @@ -563,13 +561,13 @@ public class TranscodingTests } } - bool hasDeinterlaceFilter = filterChain.VideoFilterSteps.Any( - s => s is YadifFilter or YadifCudaFilter or DeinterlaceQsvFilter or DeinterlaceVaapiFilter); + bool hasDeinterlaceFilter = filterChain.VideoFilterSteps.Any(s => + s is YadifFilter or YadifCudaFilter or DeinterlaceQsvFilter or DeinterlaceVaapiFilter); hasDeinterlaceFilter.ShouldBe(videoScanKind == VideoScanKind.Interlaced); - bool hasScaling = filterChain.VideoFilterSteps.Filter( - s => s is ScaleFilter or ScaleCudaFilter or ScaleQsvFilter or ScaleVaapiFilter) + bool hasScaling = filterChain.VideoFilterSteps + .Filter(s => s is ScaleFilter or ScaleCudaFilter or ScaleQsvFilter or ScaleVaapiFilter) .Filter(s => s is not ScaleCudaFilter cuda || !cuda.Filter.Contains("scale_cuda=format=")) .Any(); @@ -598,16 +596,15 @@ public class TranscodingTests bool hasSubtitleFilters = filterChain.VideoFilterSteps.Any(s => s is SubtitlesFilter) || - filterChain.SubtitleOverlayFilterSteps.Any( - s => s is OverlaySubtitleFilter - or OverlaySubtitleCudaFilter - or OverlaySubtitleQsvFilter - or OverlaySubtitleVaapiFilter); + filterChain.SubtitleOverlayFilterSteps.Any(s => s is OverlaySubtitleFilter + or OverlaySubtitleCudaFilter + or OverlaySubtitleQsvFilter + or OverlaySubtitleVaapiFilter); hasSubtitleFilters.ShouldBe(subtitle != Subtitle.None); - bool hasWatermarkFilters = filterChain.WatermarkOverlayFilterSteps.Any( - s => s is OverlayWatermarkFilter or OverlayWatermarkCudaFilter or OverlayWatermarkQsvFilter); + bool hasWatermarkFilters = filterChain.WatermarkOverlayFilterSteps.Any(s => + s is OverlayWatermarkFilter or OverlayWatermarkCudaFilter or OverlayWatermarkQsvFilter); hasWatermarkFilters.ShouldBe(watermark != Watermark.None); } diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/LocalSubtitlesProviderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/LocalSubtitlesProviderTests.cs index 526acdb91..c0c9ba2ed 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/LocalSubtitlesProviderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/LocalSubtitlesProviderTests.cs @@ -3,10 +3,10 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Scanner.Core.Metadata; using ErsatzTV.Scanner.Tests.Core.Fakes; -using Shouldly; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata; diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs index 05c61cbf6..99890a3bd 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/MovieFolderScannerTests.cs @@ -11,11 +11,11 @@ using ErsatzTV.Scanner.Core.Interfaces.FFmpeg; using ErsatzTV.Scanner.Core.Interfaces.Metadata; using ErsatzTV.Scanner.Core.Metadata; using ErsatzTV.Scanner.Tests.Core.Fakes; -using Shouldly; using MediatR; using Microsoft.Extensions.Logging; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata; @@ -46,10 +46,9 @@ public class MovieFolderScannerTests { _movieRepository = Substitute.For(); _movieRepository.GetOrAdd(Arg.Any(), Arg.Any(), Arg.Any()) - .Returns( - args => - Right>(new FakeMovieWithPath(args.Arg())) - .AsTask()); + .Returns(args => + Right>(new FakeMovieWithPath(args.Arg())) + .AsTask()); _movieRepository.FindMoviePaths(Arg.Any()) .Returns(new List().AsEnumerable().AsTask()); @@ -65,12 +64,11 @@ public class MovieFolderScannerTests // fallback metadata adds metadata to a movie, so we need to replicate that here _localMetadataProvider.RefreshFallbackMetadata(Arg.Any()) - .Returns( - arg => - { - ((Movie)arg.Arg()).MovieMetadata = new List { new() }; - return Task.FromResult(true); - }); + .Returns(arg => + { + ((Movie)arg.Arg()).MovieMetadata = new List { new() }; + return Task.FromResult(true); + }); _imageCache = Substitute.For(); diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ArtistNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ArtistNfoReaderTests.cs index f6da7b52e..96ec2ad42 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ArtistNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ArtistNfoReaderTests.cs @@ -2,12 +2,12 @@ using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Scanner.Core.Metadata.Nfo; -using Shouldly; using Microsoft.Extensions.Logging; using Microsoft.IO; using NSubstitute; using NUnit.Framework; using Serilog; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo; diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs index 152e8a492..2f9dfd5b3 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/EpisodeNfoReaderTests.cs @@ -2,12 +2,12 @@ using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Scanner.Core.Metadata.Nfo; -using Shouldly; using Microsoft.Extensions.Logging; using Microsoft.IO; using NSubstitute; using NUnit.Framework; using Serilog; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo; @@ -283,9 +283,8 @@ public class EpisodeNfoReaderTests { list.Count.ShouldBe(2); list.Count(nfo => nfo.Directors.Count == 1 && nfo.Directors[0] == "Director 1").ShouldBe(1); - list.Count( - nfo => nfo.Directors.Count == 2 && nfo.Directors[0] == "Director 2" && - nfo.Directors[1] == "Director 3") + list.Count(nfo => nfo.Directors.Count == 2 && nfo.Directors[0] == "Director 2" && + nfo.Directors[1] == "Director 3") .ShouldBe(1); } } diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MovieNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MovieNfoReaderTests.cs index 5319d8c43..b378971fa 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MovieNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MovieNfoReaderTests.cs @@ -2,11 +2,11 @@ using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Scanner.Core.Metadata.Nfo; -using Shouldly; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.IO; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo; diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs index df5d71278..a7071fcc0 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/MusicVideoNfoReaderTests.cs @@ -2,11 +2,11 @@ using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Scanner.Core.Metadata.Nfo; -using Shouldly; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.IO; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo; diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/OtherVideoNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/OtherVideoNfoReaderTests.cs index a1086b855..4a376ddcd 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/OtherVideoNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/OtherVideoNfoReaderTests.cs @@ -2,11 +2,11 @@ using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Scanner.Core.Metadata.Nfo; -using Shouldly; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.IO; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo; @@ -237,7 +237,8 @@ https://www.themoviedb.org/movie/11-star-wars")); [TestCase("musicvideo")] public async Task MetadataNfo_With_Tag_Should_Return_Nfo(string topLevel) { - await using var stream = new MemoryStream(Encoding.UTF8.GetBytes(@$"<{topLevel}>Test Tag")); + await using var stream = + new MemoryStream(Encoding.UTF8.GetBytes(@$"<{topLevel}>Test Tag")); Either result = await _otherVideoNfoReader.Read(stream); diff --git a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ShowNfoReaderTests.cs b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ShowNfoReaderTests.cs index 95e13749d..8a649a366 100644 --- a/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ShowNfoReaderTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/Metadata/Nfo/ShowNfoReaderTests.cs @@ -2,11 +2,11 @@ using Bugsnag; using ErsatzTV.Core; using ErsatzTV.Scanner.Core.Metadata.Nfo; -using Shouldly; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.IO; using NSubstitute; using NUnit.Framework; +using Shouldly; namespace ErsatzTV.Scanner.Tests.Core.Metadata.Nfo; diff --git a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj index d6bac833f..ab753bd65 100644 --- a/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj +++ b/ErsatzTV.Scanner.Tests/ErsatzTV.Scanner.Tests.csproj @@ -15,18 +15,18 @@ - runtime; build; native; contentfiles; analyzers; buildtransitive - all + runtime; build; native; contentfiles; analyzers; buildtransitive + all - runtime; build; native; contentfiles; analyzers; buildtransitive - all + runtime; build; native; contentfiles; analyzers; buildtransitive + all - + @@ -40,23 +40,23 @@ Always - Always + Always - Always + Always - Always + Always - Always + Always - - Always - + + Always + - + \ No newline at end of file diff --git a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs index 86027cc42..66df8f3e2 100644 --- a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyCollectionsHandler.cs @@ -42,12 +42,11 @@ public class SynchronizeEmbyCollectionsHandler : IRequestHandler new RequestParameters( - connectionParameters, - connectionParameters.MediaSource, - request.ForceScan, - libraryRefreshInterval)); + .Apply((connectionParameters, libraryRefreshInterval) => new RequestParameters( + connectionParameters, + connectionParameters.MediaSource, + request.ForceScan, + libraryRefreshInterval)); } private Task> ValidateLibraryRefreshInterval() => diff --git a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs index 1f2570622..12d57f457 100644 --- a/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs +++ b/ErsatzTV.Scanner/Application/Emby/Commands/SynchronizeEmbyLibraryByIdHandler.cs @@ -109,15 +109,14 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler> Validate( SynchronizeEmbyLibraryById request) => (await ValidateConnection(request), await EmbyLibraryMustExist(request), await ValidateLibraryRefreshInterval()) - .Apply( - (connectionParameters, embyLibrary, libraryRefreshInterval) => - new RequestParameters( - connectionParameters, - embyLibrary, - request.ForceScan, - libraryRefreshInterval, - request.DeepScan - )); + .Apply((connectionParameters, embyLibrary, libraryRefreshInterval) => + new RequestParameters( + connectionParameters, + embyLibrary, + request.ForceScan, + libraryRefreshInterval, + request.DeepScan + )); private Task> ValidateConnection( SynchronizeEmbyLibraryById request) => @@ -128,9 +127,8 @@ public class SynchronizeEmbyLibraryByIdHandler : IRequestHandler> EmbyMediaSourceMustExist( SynchronizeEmbyLibraryById request) => _mediaSourceRepository.GetEmbyByLibraryId(request.EmbyLibraryId) - .Map( - v => v.ToValidation( - $"Emby media source for library {request.EmbyLibraryId} does not exist.")); + .Map(v => v.ToValidation( + $"Emby media source for library {request.EmbyLibraryId} does not exist.")); private Validation MediaSourceMustHaveActiveConnection( EmbyMediaSource embyMediaSource) diff --git a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs index 85d62bd99..1d8f3bb96 100644 --- a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinCollectionsHandler.cs @@ -44,12 +44,11 @@ public class .BindT(MediaSourceMustHaveApiKey); return (await mediaSource, await ValidateLibraryRefreshInterval()) - .Apply( - (connectionParameters, libraryRefreshInterval) => new RequestParameters( - connectionParameters, - connectionParameters.MediaSource, - request.ForceScan, - libraryRefreshInterval)); + .Apply((connectionParameters, libraryRefreshInterval) => new RequestParameters( + connectionParameters, + connectionParameters.MediaSource, + request.ForceScan, + libraryRefreshInterval)); } private Task> ValidateLibraryRefreshInterval() => diff --git a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs index 914c26efd..f8492d12e 100644 --- a/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs +++ b/ErsatzTV.Scanner/Application/Jellyfin/Commands/SynchronizeJellyfinLibraryByIdHandler.cs @@ -111,15 +111,14 @@ public class SynchronizeJellyfinLibraryById request) => (await ValidateConnection(request), await JellyfinLibraryMustExist(request), await ValidateLibraryRefreshInterval()) - .Apply( - (connectionParameters, jellyfinLibrary, libraryRefreshInterval) => - new RequestParameters( - connectionParameters, - jellyfinLibrary, - request.ForceScan, - libraryRefreshInterval, - request.DeepScan - )); + .Apply((connectionParameters, jellyfinLibrary, libraryRefreshInterval) => + new RequestParameters( + connectionParameters, + jellyfinLibrary, + request.ForceScan, + libraryRefreshInterval, + request.DeepScan + )); private Task> ValidateConnection( SynchronizeJellyfinLibraryById request) => @@ -130,9 +129,8 @@ public class private Task> JellyfinMediaSourceMustExist( SynchronizeJellyfinLibraryById request) => _mediaSourceRepository.GetJellyfinByLibraryId(request.JellyfinLibraryId) - .Map( - v => v.ToValidation( - $"Jellyfin media source for library {request.JellyfinLibraryId} does not exist.")); + .Map(v => v.ToValidation( + $"Jellyfin media source for library {request.JellyfinLibraryId} does not exist.")); private Validation MediaSourceMustHaveActiveConnection( JellyfinMediaSource jellyfinMediaSource) diff --git a/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs b/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs index 82ac77dd2..17db2f608 100644 --- a/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs +++ b/ErsatzTV.Scanner/Application/MediaSources/Commands/ScanLocalLibraryHandler.cs @@ -175,13 +175,12 @@ public class ScanLocalLibraryHandler : IRequestHandler refreshIntervalResult = await ValidateLibraryRefreshInterval(); return (libraryResult, ffprobePathResult, ffmpegPathResult, refreshIntervalResult) - .Apply( - (library, ffprobePath, ffmpegPath, libraryRefreshInterval) => new RequestParameters( - library, - ffprobePath, - ffmpegPath, - request.ForceScan, - libraryRefreshInterval)); + .Apply((library, ffprobePath, ffmpegPath, libraryRefreshInterval) => new RequestParameters( + library, + ffprobePath, + ffmpegPath, + request.ForceScan, + libraryRefreshInterval)); } private Task> LocalLibraryMustExist(ScanLocalLibrary request) => @@ -192,16 +191,14 @@ public class ScanLocalLibraryHandler : IRequestHandler> ValidateFFprobePath() => _configElementRepository.GetValue(ConfigElementKey.FFprobePath) .FilterT(File.Exists) - .Map( - ffprobePath => - ffprobePath.ToValidation("FFprobe path does not exist on the file system")); + .Map(ffprobePath => + ffprobePath.ToValidation("FFprobe path does not exist on the file system")); private Task> ValidateFFmpegPath() => _configElementRepository.GetValue(ConfigElementKey.FFmpegPath) .FilterT(File.Exists) - .Map( - ffmpegPath => - ffmpegPath.ToValidation("FFmpeg path does not exist on the file system")); + .Map(ffmpegPath => + ffmpegPath.ToValidation("FFmpeg path does not exist on the file system")); private Task> ValidateLibraryRefreshInterval() => _configElementRepository.GetValue(ConfigElementKey.LibraryRefreshInterval) diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs index 354434197..fc4a901b1 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexCollectionsHandler.cs @@ -42,12 +42,11 @@ public class SynchronizePlexCollectionsHandler : IRequestHandler new RequestParameters( - connectionParameters, - connectionParameters.PlexMediaSource, - request.ForceScan, - libraryRefreshInterval)); + .Apply((connectionParameters, libraryRefreshInterval) => new RequestParameters( + connectionParameters, + connectionParameters.PlexMediaSource, + request.ForceScan, + libraryRefreshInterval)); } private Task> ValidateLibraryRefreshInterval() => diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs index 8284ef964..d87d76149 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexLibraryByIdHandler.cs @@ -119,15 +119,14 @@ public class SynchronizePlexLibraryByIdHandler : IRequestHandler> Validate(SynchronizePlexLibraryById request) => (await ValidateConnection(request), await PlexLibraryMustExist(request), await ValidateLibraryRefreshInterval()) - .Apply( - (connectionParameters, plexLibrary, libraryRefreshInterval) => - new RequestParameters( - connectionParameters, - plexLibrary, - request.ForceScan, - libraryRefreshInterval, - request.DeepScan - )); + .Apply((connectionParameters, plexLibrary, libraryRefreshInterval) => + new RequestParameters( + connectionParameters, + plexLibrary, + request.ForceScan, + libraryRefreshInterval, + request.DeepScan + )); private Task> ValidateConnection( SynchronizePlexLibraryById request) => @@ -138,9 +137,8 @@ public class SynchronizePlexLibraryByIdHandler : IRequestHandler> PlexMediaSourceMustExist( SynchronizePlexLibraryById request) => _mediaSourceRepository.GetPlexByLibraryId(request.PlexLibraryId) - .Map( - v => v.ToValidation( - $"Plex media source for library {request.PlexLibraryId} does not exist.")); + .Map(v => v.ToValidation( + $"Plex media source for library {request.PlexLibraryId} does not exist.")); private Validation MediaSourceMustHaveActiveConnection( PlexMediaSource plexMediaSource) diff --git a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexNetworksHandler.cs b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexNetworksHandler.cs index 6035fe7d7..6194e3b41 100644 --- a/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexNetworksHandler.cs +++ b/ErsatzTV.Scanner/Application/Plex/Commands/SynchronizePlexNetworksHandler.cs @@ -9,9 +9,9 @@ namespace ErsatzTV.Scanner.Application.Plex; public class SynchronizePlexNetworksHandler : IRequestHandler> { private readonly IConfigElementRepository _configElementRepository; - private readonly IPlexTelevisionRepository _plexTelevisionRepository; private readonly IMediaSourceRepository _mediaSourceRepository; private readonly IPlexSecretStore _plexSecretStore; + private readonly IPlexTelevisionRepository _plexTelevisionRepository; private readonly IPlexNetworkScanner _scanner; public SynchronizePlexNetworksHandler( @@ -45,12 +45,11 @@ public class SynchronizePlexNetworksHandler : IRequestHandler new RequestParameters( - connectionParameters, - plexLibrary, - request.ForceScan, - libraryRefreshInterval)); + .Apply((connectionParameters, plexLibrary, libraryRefreshInterval) => new RequestParameters( + connectionParameters, + plexLibrary, + request.ForceScan, + libraryRefreshInterval)); } private Task> PlexLibraryMustExist( @@ -66,7 +65,8 @@ public class SynchronizePlexNetworksHandler : IRequestHandler> MediaSourceMustExist( SynchronizePlexNetworks request) => _mediaSourceRepository.GetPlexByLibraryId(request.PlexLibraryId) - .Map(o => o.ToValidation($"Plex media source for library {request.PlexLibraryId} does not exist.")); + .Map(o => o.ToValidation( + $"Plex media source for library {request.PlexLibraryId} does not exist.")); private static Validation MediaSourceMustHaveActiveConnection( PlexMediaSource plexMediaSource) diff --git a/ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs b/ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs index af4ac3267..b2bd37fb2 100644 --- a/ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs +++ b/ErsatzTV.Scanner/Core/Emby/EmbyCollectionScanner.cs @@ -38,7 +38,9 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner // get all collections from db (item id, etag) List existingCollections = await _embyCollectionRepository.GetCollections(); - await foreach ((EmbyCollection collection, int _) in _embyApiClient.GetCollectionLibraryItems(address, apiKey)) + await foreach ((EmbyCollection collection, int _) in _embyApiClient.GetCollectionLibraryItems( + address, + apiKey)) { incomingItemIds.Add(collection.ItemId); @@ -88,7 +90,10 @@ public class EmbyCollectionScanner : IEmbyCollectionScanner try { // get collection items from Emby - IAsyncEnumerable> items = _embyApiClient.GetCollectionItems(address, apiKey, collection.ItemId); + IAsyncEnumerable> items = _embyApiClient.GetCollectionItems( + address, + apiKey, + collection.ItemId); List removedIds = await _embyCollectionRepository.RemoveAllTags(collection); diff --git a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs index 74cc8694e..20575d3f4 100644 --- a/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs +++ b/ErsatzTV.Scanner/Core/Jellyfin/JellyfinCollectionScanner.cs @@ -3,7 +3,6 @@ using ErsatzTV.Core.Domain; using ErsatzTV.Core.Interfaces.Jellyfin; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.MediaSources; -using ErsatzTV.Scanner.Application.Jellyfin; using Microsoft.Extensions.Logging; namespace ErsatzTV.Scanner.Core.Jellyfin; @@ -70,8 +69,8 @@ public class JellyfinCollectionScanner : IJellyfinCollectionScanner } // remove missing collections (and remove any lingering tags from those collections) - foreach (JellyfinCollection collection in existingCollections.Filter( - e => !incomingItemIds.Contains(e.ItemId))) + foreach (JellyfinCollection collection in existingCollections.Filter(e => + !incomingItemIds.Contains(e.ItemId))) { await _jellyfinCollectionRepository.RemoveCollection(collection); } diff --git a/ErsatzTV.Scanner/Core/Metadata/LocalSubtitlesProvider.cs b/ErsatzTV.Scanner/Core/Metadata/LocalSubtitlesProvider.cs index d0dc7a1b7..8b3e4aa16 100644 --- a/ErsatzTV.Scanner/Core/Metadata/LocalSubtitlesProvider.cs +++ b/ErsatzTV.Scanner/Core/Metadata/LocalSubtitlesProvider.cs @@ -157,8 +157,8 @@ public class LocalSubtitlesProvider : ILocalSubtitlesProvider .Replace($"{withoutExtension.ToLowerInvariant()}.", string.Empty)[..3] .Replace(".", string.Empty); - Option maybeCulture = languageCodes.Find( - ci => ci.TwoLetterISOLanguageName == language || ci.ThreeLetterISOLanguageName == language); + Option maybeCulture = languageCodes.Find(ci => + ci.TwoLetterISOLanguageName == language || ci.ThreeLetterISOLanguageName == language); if (maybeCulture.IsNone) { diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs index 74cce9b10..bbfb1e0f0 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerMovieLibraryScanner.cs @@ -108,40 +108,41 @@ public abstract class MediaServerMovieLibraryScanner - { - result.LocalPath = localPath; - return result; - }) - .BindT( - existing => UpdateMetadataAndStatistics( - connectionParameters, - library, - existing, - incoming, - deepScan)); + .MapT(result => + { + result.LocalPath = localPath; + return result; + }) + .BindT(existing => UpdateMetadataAndStatistics( + connectionParameters, + library, + existing, + incoming, + deepScan)); } else { maybeMovie = await movieRepository .GetOrAdd(library, incoming, deepScan) - .MapT( - result => - { - result.LocalPath = localPath; - return result; - }) - .BindT( - existing => UpdateMetadata(connectionParameters, library, existing, incoming, deepScan, None)) - .BindT( - existing => UpdateStatistics( - connectionParameters, - library, - existing, - incoming, - deepScan, - None)) + .MapT(result => + { + result.LocalPath = localPath; + return result; + }) + .BindT(existing => UpdateMetadata( + connectionParameters, + library, + existing, + incoming, + deepScan, + None)) + .BindT(existing => UpdateStatistics( + connectionParameters, + library, + existing, + incoming, + deepScan, + None)) .BindT(UpdateSubtitles); } diff --git a/ErsatzTV.Scanner/Core/Metadata/MediaServerOtherVideoLibraryScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MediaServerOtherVideoLibraryScanner.cs index e25e2999e..f845f495e 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MediaServerOtherVideoLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MediaServerOtherVideoLibraryScanner.cs @@ -76,7 +76,8 @@ public abstract class MediaServerOtherVideoLibraryScanner e.MediaServerItemId, e => e); - await foreach ((TOtherVideo incoming, int totalOtherVideoCount) in otherVideoEntries.WithCancellation(cancellationToken)) + await foreach ((TOtherVideo incoming, int totalOtherVideoCount) in otherVideoEntries.WithCancellation( + cancellationToken)) { if (cancellationToken.IsCancellationRequested) { @@ -97,7 +98,13 @@ public abstract class MediaServerOtherVideoLibraryScanner - { - result.LocalPath = localPath; - return result; - }) - .BindT( - existing => UpdateMetadataAndStatistics( - connectionParameters, - library, - existing, - incoming, - deepScan)); + .MapT(result => + { + result.LocalPath = localPath; + return result; + }) + .BindT(existing => UpdateMetadataAndStatistics( + connectionParameters, + library, + existing, + incoming, + deepScan)); } else { maybeOtherVideo = await otherVideoRepository .GetOrAdd(library, incoming, deepScan) - .MapT( - result => - { - result.LocalPath = localPath; - return result; - }) - .BindT( - existing => UpdateMetadata(connectionParameters, library, existing, incoming, deepScan, None)) - .BindT( - existing => UpdateStatistics( - connectionParameters, - library, - existing, - incoming, - deepScan, - None)) + .MapT(result => + { + result.LocalPath = localPath; + return result; + }) + .BindT(existing => UpdateMetadata( + connectionParameters, + library, + existing, + incoming, + deepScan, + None)) + .BindT(existing => UpdateStatistics( + connectionParameters, + library, + existing, + incoming, + deepScan, + None)) .BindT(UpdateSubtitles); } @@ -322,7 +330,9 @@ public abstract class MediaServerOtherVideoLibraryScanner - { - result.LocalPath = localPath; - return result; - }) - .BindT( - existing => UpdateMetadataAndStatistics( - connectionParameters, - library, - existing, - incoming, - deepScan)); + .MapT(result => + { + result.LocalPath = localPath; + return result; + }) + .BindT(existing => UpdateMetadataAndStatistics( + connectionParameters, + library, + existing, + incoming, + deepScan)); } else { maybeEpisode = await televisionRepository .GetOrAdd(library, incoming, deepScan) - .MapT( - result => - { - result.LocalPath = localPath; - return result; - }) - .BindT( - existing => UpdateMetadata(connectionParameters, library, existing, incoming, deepScan, None)) - .BindT( - existing => UpdateStatistics( - connectionParameters, - library, - existing, - incoming, - deepScan, - None)) + .MapT(result => + { + result.LocalPath = localPath; + return result; + }) + .BindT(existing => UpdateMetadata( + connectionParameters, + library, + existing, + incoming, + deepScan, + None)) + .BindT(existing => UpdateStatistics( + connectionParameters, + library, + existing, + incoming, + deepScan, + None)) .BindT(UpdateSubtitles); } diff --git a/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs index b98819b03..f58b647c3 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MovieFolderScanner.cs @@ -125,9 +125,8 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner var allFiles = filesForEtag .Filter(f => VideoFileExtensions.Contains(Path.GetExtension(f))) .Filter(f => !Path.GetFileName(f).StartsWith("._", StringComparison.OrdinalIgnoreCase)) - .Filter( - f => !ExtraFiles.Any( - e => Path.GetFileNameWithoutExtension(f).EndsWith(e, StringComparison.OrdinalIgnoreCase))) + .Filter(f => !ExtraFiles.Any(e => + Path.GetFileNameWithoutExtension(f).EndsWith(e, StringComparison.OrdinalIgnoreCase))) .ToList(); string etag = FolderEtag.Calculate(movieFolder, _localFileSystem); @@ -356,14 +355,13 @@ public class MovieFolderScanner : LocalFolderScanner, IMovieFolderScanner string path = movie.MediaVersions.Head().MediaFiles.Head().Path; string folder = Path.GetDirectoryName(path) ?? string.Empty; - IEnumerable possibleMoviePosters = ImageFileExtensions.Collect( - ext => new[] { $"{segment}.{ext}", Path.GetFileNameWithoutExtension(path) + $"-{segment}.{ext}" }) + IEnumerable possibleMoviePosters = ImageFileExtensions.Collect(ext => + new[] { $"{segment}.{ext}", Path.GetFileNameWithoutExtension(path) + $"-{segment}.{ext}" }) .Map(f => Path.Combine(folder, f)); Option result = possibleMoviePosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone(); if (result.IsNone && artworkKind == ArtworkKind.Poster) { - IEnumerable possibleFolderPosters = ImageFileExtensions.Collect( - ext => new[] { $"folder.{ext}" }) + IEnumerable possibleFolderPosters = ImageFileExtensions.Collect(ext => new[] { $"folder.{ext}" }) .Map(f => Path.Combine(folder, f)); result = possibleFolderPosters.Filter(p => _localFileSystem.FileExists(p)).HeadOrNone(); } diff --git a/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs index fe9256851..80f1d76e3 100644 --- a/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/MusicVideoFolderScanner.cs @@ -115,18 +115,16 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan Either> maybeArtist = await FindOrCreateArtist(libraryPath.Id, artistFolder) .BindT(artist => UpdateMetadataForArtist(artist, artistFolder)) - .BindT( - artist => UpdateArtworkForArtist( - artist, - artistFolder, - ArtworkKind.Thumbnail, - cancellationToken)) - .BindT( - artist => UpdateArtworkForArtist( - artist, - artistFolder, - ArtworkKind.FanArt, - cancellationToken)); + .BindT(artist => UpdateArtworkForArtist( + artist, + artistFolder, + ArtworkKind.Thumbnail, + cancellationToken)) + .BindT(artist => UpdateArtworkForArtist( + artist, + artistFolder, + ArtworkKind.FanArt, + cancellationToken)); foreach (BaseError error in maybeArtist.LeftToSeq()) { @@ -296,12 +294,11 @@ public class MusicVideoFolderScanner : LocalFolderScanner, IMusicVideoFolderScan try { Artist artist = result.Item; - await LocateArtworkForArtist(artistFolder, artworkKind).IfSomeAsync( - async artworkFile => - { - ArtistMetadata metadata = artist.ArtistMetadata.Head(); - await RefreshArtwork(artworkFile, metadata, artworkKind, None, None, cancellationToken); - }); + await LocateArtworkForArtist(artistFolder, artworkKind).IfSomeAsync(async artworkFile => + { + ArtistMetadata metadata = artist.ArtistMetadata.Head(); + await RefreshArtwork(artworkFile, metadata, artworkKind, None, None, cancellationToken); + }); return result; } diff --git a/ErsatzTV.Scanner/Core/Metadata/Nfo/OtherVideoNfoReader.cs b/ErsatzTV.Scanner/Core/Metadata/Nfo/OtherVideoNfoReader.cs index 9c24164f8..52f1d5717 100644 --- a/ErsatzTV.Scanner/Core/Metadata/Nfo/OtherVideoNfoReader.cs +++ b/ErsatzTV.Scanner/Core/Metadata/Nfo/OtherVideoNfoReader.cs @@ -56,6 +56,7 @@ public class OtherVideoNfoReader : NfoReader, IOtherVideoNfoReade { throw new InvalidOperationException("Cannot have multiple opening tags"); } + nfo = new OtherVideoNfo(); break; case "title": diff --git a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs index 754901217..7c40a5b8c 100644 --- a/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/OtherVideoFolderScanner.cs @@ -128,7 +128,8 @@ public class OtherVideoFolderScanner : LocalFolderScanner, IOtherVideoFolderScan cancellationToken); string otherVideoFolder = folderQueue.Dequeue(); - Option maybeParentFolder = await _libraryRepository.GetParentFolderId(libraryPath, otherVideoFolder); + Option maybeParentFolder = + await _libraryRepository.GetParentFolderId(libraryPath, otherVideoFolder); foldersCompleted++; diff --git a/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs index f67db240b..1bcac32a7 100644 --- a/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/SongFolderScanner.cs @@ -345,15 +345,14 @@ public class SongFolderScanner : LocalFolderScanner, ISongFolderScanner string path = song.MediaVersions.Head().MediaFiles.Head().Path; Option parent = Optional(Directory.GetParent(path)); - return parent.Map( - di => - { - string coverPath = Path.Combine(di.FullName, "cover.jpg"); - return ImageFileExtensions - .Map(ext => Path.ChangeExtension(coverPath, ext)) - .Filter(f => _localFileSystem.FileExists(f)) - .HeadOrNone(); - }).Flatten(); + return parent.Map(di => + { + string coverPath = Path.Combine(di.FullName, "cover.jpg"); + return ImageFileExtensions + .Map(ext => Path.ChangeExtension(coverPath, ext)) + .Filter(f => _localFileSystem.FileExists(f)) + .HeadOrNone(); + }).Flatten(); } private async Task ExtractEmbeddedArtwork(Song song, string ffmpegPath, CancellationToken cancellationToken) diff --git a/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs b/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs index 9c6f5d75f..2cf614c67 100644 --- a/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs +++ b/ErsatzTV.Scanner/Core/Metadata/TelevisionFolderScanner.cs @@ -126,8 +126,11 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan .BindT(show => UpdateMetadataForShow(show, showFolder)) .BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Poster, cancellationToken)) .BindT(show => UpdateArtworkForShow(show, showFolder, ArtworkKind.FanArt, cancellationToken)) - .BindT( - show => UpdateArtworkForShow(show, showFolder, ArtworkKind.Thumbnail, cancellationToken)); + .BindT(show => UpdateArtworkForShow( + show, + showFolder, + ArtworkKind.Thumbnail, + cancellationToken)); foreach (BaseError error in maybeShow.LeftToSeq()) { @@ -340,9 +343,8 @@ public class TelevisionFolderScanner : LocalFolderScanner, ITelevisionFolderScan // TODO: figure out how to rebuild playlists Either maybeEpisode = await _televisionRepository .GetOrAddEpisode(season, libraryPath, seasonFolder, file) - .BindT( - episode => UpdateStatistics(new MediaItemScanResult(episode), ffmpegPath, ffprobePath) - .MapT(_ => episode)) + .BindT(episode => UpdateStatistics(new MediaItemScanResult(episode), ffmpegPath, ffprobePath) + .MapT(_ => episode)) .BindT(video => UpdateLibraryFolderId(video, seasonFolder)) .BindT(UpdateMetadata) .BindT(e => UpdateThumbnail(e, cancellationToken)) diff --git a/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs index 3585c6a96..d99da0850 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexMovieLibraryScanner.cs @@ -226,9 +226,8 @@ public class PlexMovieLibraryScanner : } foreach (Actor actor in existingMetadata.Actors - .Filter( - a => fullMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + fullMetadata.Actors.All(a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { existingMetadata.Actors.Remove(actor); diff --git a/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs index 07fc6c3fd..d289fe5fe 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexOtherVideoLibraryScanner.cs @@ -6,7 +6,6 @@ using ErsatzTV.Core.Interfaces.Plex; using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Metadata; using ErsatzTV.Core.Plex; -using ErsatzTV.Infrastructure.Data.Repositories; using ErsatzTV.Scanner.Core.Metadata; using Microsoft.Extensions.Logging; @@ -229,9 +228,8 @@ public class PlexOtherVideoLibraryScanner : } foreach (Actor actor in existingMetadata.Actors - .Filter( - a => fullMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + fullMetadata.Actors.All(a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { existingMetadata.Actors.Remove(actor); diff --git a/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs b/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs index 9025a4ac3..d56936e84 100644 --- a/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs +++ b/ErsatzTV.Scanner/Core/Plex/PlexTelevisionLibraryScanner.cs @@ -340,9 +340,8 @@ public class PlexTelevisionLibraryScanner : } foreach (Actor actor in existingMetadata.Actors - .Filter( - a => fullMetadata.Actors.All( - a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) + .Filter(a => + fullMetadata.Actors.All(a2 => a2.Name != a.Name || a.Artwork == null && a2.Artwork != null)) .ToList()) { existingMetadata.Actors.Remove(actor); diff --git a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj index fe113e918..446c948c3 100644 --- a/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj +++ b/ErsatzTV.Scanner/ErsatzTV.Scanner.csproj @@ -13,27 +13,27 @@ - - - - + + + + - - - - - - - - - - - - - - + + + + + + + + + + + + + + @@ -42,4 +42,4 @@ - + \ No newline at end of file diff --git a/ErsatzTV.Scanner/Program.cs b/ErsatzTV.Scanner/Program.cs index 3af78e8d1..c374848bc 100644 --- a/ErsatzTV.Scanner/Program.cs +++ b/ErsatzTV.Scanner/Program.cs @@ -81,171 +81,169 @@ public class Program private static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) - .ConfigureServices( - (context, services) => + .ConfigureServices((context, services) => + { + string databaseProvider = context.Configuration.GetValue("provider", Provider.Sqlite.Name) ?? + string.Empty; + var sqliteConnectionString = $"Data Source={FileSystemLayout.DatabasePath};foreign keys=true;"; + string mySqlConnectionString = + context.Configuration.GetValue("MySql:ConnectionString") ?? string.Empty; + + services.AddDbContext( + options => + { + if (databaseProvider == Provider.Sqlite.Name) + { + options.UseSqlite( + sqliteConnectionString, + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); + }); + } + + if (databaseProvider == Provider.MySql.Name) + { + options.UseMySql( + mySqlConnectionString, + ServerVersion.AutoDetect(mySqlConnectionString), + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure.MySql"); + } + ); + } + }, + ServiceLifetime.Scoped, + ServiceLifetime.Singleton); + + services.AddDbContextFactory(options => { - string databaseProvider = context.Configuration.GetValue("provider", Provider.Sqlite.Name) ?? - string.Empty; - var sqliteConnectionString = $"Data Source={FileSystemLayout.DatabasePath};foreign keys=true;"; - string mySqlConnectionString = - context.Configuration.GetValue("MySql:ConnectionString") ?? string.Empty; - - services.AddDbContext( - options => - { - if (databaseProvider == Provider.Sqlite.Name) - { - options.UseSqlite( - sqliteConnectionString, - o => - { - o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); - }); - } - - if (databaseProvider == Provider.MySql.Name) - { - options.UseMySql( - mySqlConnectionString, - ServerVersion.AutoDetect(mySqlConnectionString), - o => - { - o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - o.MigrationsAssembly("ErsatzTV.Infrastructure.MySql"); - } - ); - } - }, - ServiceLifetime.Scoped, - ServiceLifetime.Singleton); - - services.AddDbContextFactory( - options => - { - if (databaseProvider == Provider.Sqlite.Name) - { - options.UseSqlite( - sqliteConnectionString, - o => - { - o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); - }); - } - - if (databaseProvider == Provider.MySql.Name) - { - options.UseMySql( - mySqlConnectionString, - ServerVersion.AutoDetect(mySqlConnectionString), - o => - { - o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); - o.MigrationsAssembly("ErsatzTV.Infrastructure.MySql"); - } - ); - } - }); - if (databaseProvider == Provider.Sqlite.Name) { - TvContext.LastInsertedRowId = "last_insert_rowid()"; - TvContext.CaseInsensitiveCollation = "NOCASE"; - - SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); - SqlMapper.AddTypeHandler(new GuidHandler()); - SqlMapper.AddTypeHandler(new TimeSpanHandler()); + options.UseSqlite( + sqliteConnectionString, + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure.Sqlite"); + }); } if (databaseProvider == Provider.MySql.Name) { - TvContext.LastInsertedRowId = "last_insert_id()"; - TvContext.CaseInsensitiveCollation = "utf8mb4_general_ci"; + options.UseMySql( + mySqlConnectionString, + ServerVersion.AutoDetect(mySqlConnectionString), + o => + { + o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery); + o.MigrationsAssembly("ErsatzTV.Infrastructure.MySql"); + } + ); } + }); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + if (databaseProvider == Provider.Sqlite.Name) + { + TvContext.LastInsertedRowId = "last_insert_rowid()"; + TvContext.CaseInsensitiveCollation = "NOCASE"; - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + SqlMapper.AddTypeHandler(new DateTimeOffsetHandler()); + SqlMapper.AddTypeHandler(new GuidHandler()); + SqlMapper.AddTypeHandler(new TimeSpanHandler()); + } - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + if (databaseProvider == Provider.MySql.Name) + { + TvContext.LastInsertedRowId = "last_insert_id()"; + TvContext.CaseInsensitiveCollation = "utf8mb4_general_ci"; + } - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); - services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - services.AddSingleton(); - // TODO: real bugsnag? - services.AddSingleton(_ => new BugsnagNoopClient()); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining()); - services.AddMemoryCache(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); - services.AddHostedService(); - }) + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + services.AddScoped(); + + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); + // TODO: real bugsnag? + services.AddSingleton(_ => new BugsnagNoopClient()); + + services.AddMediatR(config => config.RegisterServicesFromAssemblyContaining()); + services.AddMemoryCache(); + + services.AddHostedService(); + }) .UseSerilog(); private class BugsnagNoopClient : IClient diff --git a/ErsatzTV.Scanner/Worker.cs b/ErsatzTV.Scanner/Worker.cs index 405e4d5b7..6abd6619d 100644 --- a/ErsatzTV.Scanner/Worker.cs +++ b/ErsatzTV.Scanner/Worker.cs @@ -101,152 +101,144 @@ public class Worker : BackgroundService scanJellyfinCollectionsCommand.Arguments.Add(mediaSourceIdArgument); scanJellyfinCollectionsCommand.Options.Add(forceOption); - scanLocalCommand.SetAction( - async (parseResult, token) => + scanLocalCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - int libraryId = parseResult.GetValue(libraryIdArgument); + int libraryId = parseResult.GetValue(libraryIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new ScanLocalLibrary(libraryId, force); - await mediator.Send(scan, token); - } - }); + var scan = new ScanLocalLibrary(libraryId, force); + await mediator.Send(scan, token); + } + }); - scanPlexCommand.SetAction( - async (parseResult, token) => + scanPlexCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - bool deep = parseResult.GetValue(deepOption); - int libraryId = parseResult.GetValue(libraryIdArgument); + bool deep = parseResult.GetValue(deepOption); + int libraryId = parseResult.GetValue(libraryIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizePlexLibraryById(libraryId, force, deep); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizePlexLibraryById(libraryId, force, deep); + await mediator.Send(scan, token); + } + }); - scanPlexCollectionsCommand.SetAction( - async (parseResult, token) => + scanPlexCollectionsCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - int mediaSourceId = parseResult.GetValue(mediaSourceIdArgument); + int mediaSourceId = parseResult.GetValue(mediaSourceIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizePlexCollections(mediaSourceId, force); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizePlexCollections(mediaSourceId, force); + await mediator.Send(scan, token); + } + }); - scanPlexNetworksCommand.SetAction( - async (parseResult, token) => + scanPlexNetworksCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - int libraryId = parseResult.GetValue(libraryIdArgument); + int libraryId = parseResult.GetValue(libraryIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizePlexNetworks(libraryId, force); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizePlexNetworks(libraryId, force); + await mediator.Send(scan, token); + } + }); - scanEmbyCommand.SetAction( - async (parseResult, token) => + scanEmbyCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - bool deep = parseResult.GetValue(deepOption); - int libraryId = parseResult.GetValue(libraryIdArgument); + bool deep = parseResult.GetValue(deepOption); + int libraryId = parseResult.GetValue(libraryIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizeEmbyLibraryById(libraryId, force, deep); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizeEmbyLibraryById(libraryId, force, deep); + await mediator.Send(scan, token); + } + }); - scanEmbyCollectionsCommand.SetAction( - async (parseResult, token) => + scanEmbyCollectionsCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - int mediaSourceId = parseResult.GetValue(mediaSourceIdArgument); + int mediaSourceId = parseResult.GetValue(mediaSourceIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizeEmbyCollections(mediaSourceId, force); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizeEmbyCollections(mediaSourceId, force); + await mediator.Send(scan, token); + } + }); - scanJellyfinCommand.SetAction( - async (parseResult, token) => + scanJellyfinCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - bool deep = parseResult.GetValue(deepOption); - int libraryId = parseResult.GetValue(libraryIdArgument); + bool deep = parseResult.GetValue(deepOption); + int libraryId = parseResult.GetValue(libraryIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizeJellyfinLibraryById(libraryId, force, deep); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizeJellyfinLibraryById(libraryId, force, deep); + await mediator.Send(scan, token); + } + }); - scanJellyfinCollectionsCommand.SetAction( - async (parseResult, token) => + scanJellyfinCollectionsCommand.SetAction(async (parseResult, token) => + { + if (IsScanningEnabled()) { - if (IsScanningEnabled()) - { - bool force = parseResult.GetValue(forceOption); - SetProcessPriority(force); + bool force = parseResult.GetValue(forceOption); + SetProcessPriority(force); - int mediaSourceId = parseResult.GetValue(mediaSourceIdArgument); + int mediaSourceId = parseResult.GetValue(mediaSourceIdArgument); - using IServiceScope scope = _serviceScopeFactory.CreateScope(); - IMediator mediator = scope.ServiceProvider.GetRequiredService(); + using IServiceScope scope = _serviceScopeFactory.CreateScope(); + IMediator mediator = scope.ServiceProvider.GetRequiredService(); - var scan = new SynchronizeJellyfinCollections(mediaSourceId, force); - await mediator.Send(scan, token); - } - }); + var scan = new SynchronizeJellyfinCollections(mediaSourceId, force); + await mediator.Send(scan, token); + } + }); var rootCommand = new RootCommand(); rootCommand.Subcommands.Add(scanLocalCommand); diff --git a/ErsatzTV/Controllers/Api/LibrariesController.cs b/ErsatzTV/Controllers/Api/LibrariesController.cs index a7037ad24..c1d125b0f 100644 --- a/ErsatzTV/Controllers/Api/LibrariesController.cs +++ b/ErsatzTV/Controllers/Api/LibrariesController.cs @@ -8,10 +8,8 @@ namespace ErsatzTV.Controllers.Api; public class LibrariesController(IMediator mediator) { [HttpPost("/api/libraries/{id:int}/scan")] - public async Task ResetPlayout(int id) - { - return await mediator.Send(new QueueLibraryScanByLibraryId(id)) + public async Task ResetPlayout(int id) => + await mediator.Send(new QueueLibraryScanByLibraryId(id)) ? new OkResult() : new NotFoundResult(); - } } diff --git a/ErsatzTV/Controllers/ArtworkController.cs b/ErsatzTV/Controllers/ArtworkController.cs index 0e263eb10..954bbfdd7 100644 --- a/ErsatzTV/Controllers/ArtworkController.cs +++ b/ErsatzTV/Controllers/ArtworkController.cs @@ -20,9 +20,9 @@ namespace ErsatzTV.Controllers; [ApiExplorerSettings(IgnoreApi = true)] public class ArtworkController : ControllerBase { + private readonly IChannelLogoGenerator _channelLogoGenerator; private readonly IHttpClientFactory _httpClientFactory; private readonly IMediator _mediator; - private readonly IChannelLogoGenerator _channelLogoGenerator; public ArtworkController( IMediator mediator, @@ -37,22 +37,23 @@ public class ArtworkController : ControllerBase [HttpHead("/artwork/{id}")] [HttpGet("/artwork/{id}")] // This route redirect to the proper artwork from its Id - public async Task RedirectArtwork(int id, CancellationToken cancellationToken) { + public async Task RedirectArtwork(int id, CancellationToken cancellationToken) + { Either artwork = await _mediator.Send(new GetArtwork(id), cancellationToken); return artwork.Match( Left: _ => new NotFoundResult(), Right: r => r.ArtworkKind switch - { - ArtworkKind.Poster => new RedirectResult("/artwork/posters/" + r.Path), - ArtworkKind.Thumbnail => new RedirectResult("/artwork/thumbnails/" + r.Path), - ArtworkKind.Logo => new RedirectResult("/iptv/logos/" + r.Path), - ArtworkKind.FanArt => new RedirectResult("/artwork/fanart/" + r.Path), - ArtworkKind.Watermark => new RedirectResult("/artwork/watermarks/" + r.Path), - _ => new NotFoundResult() - } - ); + { + ArtworkKind.Poster => new RedirectResult("/artwork/posters/" + r.Path), + ArtworkKind.Thumbnail => new RedirectResult("/artwork/thumbnails/" + r.Path), + ArtworkKind.Logo => new RedirectResult("/iptv/logos/" + r.Path), + ArtworkKind.FanArt => new RedirectResult("/artwork/fanart/" + r.Path), + ArtworkKind.Watermark => new RedirectResult("/artwork/watermarks/" + r.Path), + _ => new NotFoundResult() + } + ); } [HttpHead("/iptv/artwork/posters/{fileName}")] @@ -63,17 +64,25 @@ public class ArtworkController : ControllerBase public async Task GetPoster(string fileName, CancellationToken cancellationToken) { Either cachedImagePath = - await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.Poster, string.Empty, 440), cancellationToken); + await _mediator.Send( + new GetCachedImagePath(fileName, ArtworkKind.Poster, string.Empty, 440), + cancellationToken); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); } [HttpGet("/artwork/watermarks/{fileName}")] - public async Task GetWatermark(string fileName, [FromQuery] string contentType, CancellationToken cancellationToken) + public async Task GetWatermark( + string fileName, + [FromQuery] + string contentType, + CancellationToken cancellationToken) { Either cachedImagePath = - await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.Watermark, contentType), cancellationToken); + await _mediator.Send( + new GetCachedImagePath(fileName, ArtworkKind.Watermark, contentType), + cancellationToken); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); @@ -155,7 +164,9 @@ public class ArtworkController : ControllerBase public async Task GetThumbnail(string fileName, CancellationToken cancellationToken) { Either cachedImagePath = - await _mediator.Send(new GetCachedImagePath(fileName, ArtworkKind.Thumbnail, string.Empty, 220), cancellationToken); + await _mediator.Send( + new GetCachedImagePath(fileName, ArtworkKind.Thumbnail, string.Empty, 220), + cancellationToken); return cachedImagePath.Match( Left: _ => new NotFoundResult(), Right: r => new PhysicalFileResult(r.FileName, r.MimeType)); @@ -263,8 +274,8 @@ public class ArtworkController : ControllerBase [HttpGet(ChannelLogoGenerator.GetRoute)] public IActionResult GenerateChannelLogo( - string text, // param name = ChannelLogoGenerator.GetRouteQueryParamName - CancellationToken cancellationToken) => + string text, // param name = ChannelLogoGenerator.GetRouteQueryParamName + CancellationToken cancellationToken) => _channelLogoGenerator .GenerateChannelLogo(text, 100, 200, cancellationToken).Match( Left: _ => new RedirectResult("/iptv/images/ersatztv-500.png"), diff --git a/ErsatzTV/Controllers/IptvController.cs b/ErsatzTV/Controllers/IptvController.cs index 81146343a..c8f5d3836 100644 --- a/ErsatzTV/Controllers/IptvController.cs +++ b/ErsatzTV/Controllers/IptvController.cs @@ -84,7 +84,9 @@ public class IptvController : ControllerBase string mode = null) { Option maybeChannel = await _mediator.Send(new GetChannelByNumber(channelNumber)); - if (maybeChannel.IsNone || await maybeChannel.Map(c => c.ActiveMode).IfNoneAsync(ChannelActiveMode.Inactive) is ChannelActiveMode.Inactive) + if (maybeChannel.IsNone || + await maybeChannel.Map(c => c.ActiveMode).IfNoneAsync(ChannelActiveMode.Inactive) is ChannelActiveMode + .Inactive) { return NotFound(); } @@ -119,37 +121,36 @@ public class IptvController : ControllerBase }; return await _mediator.Send(request) - .Map( - result => result.Match( - processModel => + .Map(result => result.Match( + processModel => + { + Command command = processModel.Process; + + _logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber); + _logger.LogDebug("ffmpeg arguments {FFmpegArguments}", command.Arguments); + var process = new FFmpegProcess { - Command command = processModel.Process; - - _logger.LogInformation("Starting ts stream for channel {ChannelNumber}", channelNumber); - _logger.LogDebug("ffmpeg arguments {FFmpegArguments}", command.Arguments); - var process = new FFmpegProcess + StartInfo = new ProcessStartInfo { - StartInfo = new ProcessStartInfo - { - FileName = command.TargetFilePath, - Arguments = command.Arguments, - RedirectStandardOutput = true, - RedirectStandardError = false, - UseShellExecute = false, - CreateNoWindow = true - } - }; - HttpContext.Response.RegisterForDispose(process); - - foreach ((string key, string value) in command.EnvironmentVariables) - { - process.StartInfo.Environment[key] = value; + FileName = command.TargetFilePath, + Arguments = command.Arguments, + RedirectStandardOutput = true, + RedirectStandardError = false, + UseShellExecute = false, + CreateNoWindow = true } + }; + HttpContext.Response.RegisterForDispose(process); - process.Start(); - return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t"); - }, - error => BadRequest(error.Value))); + foreach ((string key, string value) in command.EnvironmentVariables) + { + process.StartInfo.Environment[key] = value; + } + + process.Start(); + return new FileStreamResult(process.StandardOutput.BaseStream, "video/mp2t"); + }, + error => BadRequest(error.Value))); } [HttpHead("iptv/session/{channelNumber}/hls.m3u8")] @@ -174,7 +175,9 @@ public class IptvController : ControllerBase return NotFound(); } - _logger.LogWarning("Unable to locate session worker for channel {Channel}; will redirect to start session", channelNumber); + _logger.LogWarning( + "Unable to locate session worker for channel {Channel}; will redirect to start session", + channelNumber); return RedirectToAction(nameof(GetHttpLiveStreamingVideo), new { channelNumber }); } @@ -186,7 +189,9 @@ public class IptvController : ControllerBase string mode = "mixed") { Option maybeChannel = await _mediator.Send(new GetChannelByNumber(channelNumber)); - if (maybeChannel.IsNone || await maybeChannel.Map(c => c.ActiveMode).IfNoneAsync(ChannelActiveMode.Inactive) is ChannelActiveMode.Inactive) + if (maybeChannel.IsNone || + await maybeChannel.Map(c => c.ActiveMode).IfNoneAsync(ChannelActiveMode.Inactive) is ChannelActiveMode + .Inactive) { return NotFound(); } @@ -260,10 +265,9 @@ public class IptvController : ControllerBase Request.Host.ToString(), channelNumber, mode)) - .Map( - r => r.Match( - playlist => Content(playlist, "application/vnd.apple.mpegurl"), - error => BadRequest(error.Value))); + .Map(r => r.Match( + playlist => Content(playlist, "application/vnd.apple.mpegurl"), + error => BadRequest(error.Value))); } } @@ -312,9 +316,10 @@ public class IptvController : ControllerBase "Failed to return ffmpeg multi-variant playlist; falling back to generated playlist"); } - Option maybeResolutionAndBitrate = await _mediator.Send(new GetChannelResolutionAndBitrate(channelNumber)); + Option maybeResolutionAndBitrate = + await _mediator.Send(new GetChannelResolutionAndBitrate(channelNumber)); string resolution = string.Empty; - string bitrate = "10000000"; + var bitrate = "10000000"; foreach (ResolutionAndBitrateViewModel res in maybeResolutionAndBitrate) { resolution = $",RESOLUTION={res.Width}x{res.Height}"; diff --git a/ErsatzTV/ErsatzTV.csproj b/ErsatzTV/ErsatzTV.csproj index d31145157..54abd0e6c 100644 --- a/ErsatzTV/ErsatzTV.csproj +++ b/ErsatzTV/ErsatzTV.csproj @@ -16,92 +16,96 @@ - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + - - - - - - + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + - - Ersatztv.icns - Always - + + Ersatztv.icns + Always + - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\FONT-LICENSE" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.eot" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.otf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.svg" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.ttf" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.woff" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" /> - <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\FONT-LICENSE" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\css\open-iconic-bootstrap.min.css" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.eot" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.otf" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.svg" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.ttf" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\font\fonts\open-iconic.woff" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" /> + <_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" /> - + + + + + - + \ No newline at end of file diff --git a/ErsatzTV/Pages/Artist.razor b/ErsatzTV/Pages/Artist.razor index a594836eb..ec7b1f7f1 100644 --- a/ErsatzTV/Pages/Artist.razor +++ b/ErsatzTV/Pages/Artist.razor @@ -223,15 +223,14 @@ private async Task RefreshData() { - await Mediator.Send(new GetArtistById(ArtistId), _cts.Token).IfSomeAsync( - vm => - { - _artist = vm; - _sortedLanguages = _artist.Languages.OrderBy(ci => ci.EnglishName).ToList(); - _sortedGenres = _artist.Genres.OrderBy(g => g).ToList(); - _sortedStyles = _artist.Styles.OrderBy(s => s).ToList(); - _sortedMoods = _artist.Moods.OrderBy(m => m).ToList(); - }); + await Mediator.Send(new GetArtistById(ArtistId), _cts.Token).IfSomeAsync(vm => + { + _artist = vm; + _sortedLanguages = _artist.Languages.OrderBy(ci => ci.EnglishName).ToList(); + _sortedGenres = _artist.Genres.OrderBy(g => g).ToList(); + _sortedStyles = _artist.Styles.OrderBy(s => s).ToList(); + _sortedMoods = _artist.Moods.OrderBy(m => m).ToList(); + }); _musicVideos = await Mediator.Send(new GetMusicVideoCards(ArtistId, 1, 100), _cts.Token); } diff --git a/ErsatzTV/Pages/BlockEditor.razor b/ErsatzTV/Pages/BlockEditor.razor index 5fcc70c19..01e0b5077 100644 --- a/ErsatzTV/Pages/BlockEditor.razor +++ b/ErsatzTV/Pages/BlockEditor.razor @@ -482,16 +482,15 @@ private ReplaceBlockItems GenerateReplaceRequest() { - var items = _block.Items.Map( - item => new ReplaceBlockItem( - item.Index, - item.CollectionType, - item.Collection?.Id, - item.MultiCollection?.Id, - item.SmartCollection?.Id, - item.MediaItem?.MediaItemId, - item.PlaybackOrder, - item.IncludeInProgramGuide)).ToList(); + var items = _block.Items.Map(item => new ReplaceBlockItem( + item.Index, + item.CollectionType, + item.Collection?.Id, + item.MultiCollection?.Id, + item.SmartCollection?.Id, + item.MediaItem?.MediaItemId, + item.PlaybackOrder, + item.IncludeInProgramGuide)).ToList(); _block.Minutes = _durationHours * 60 + _durationMinutes; diff --git a/ErsatzTV/Pages/ChannelEditor.razor b/ErsatzTV/Pages/ChannelEditor.razor index fb86b0b10..cb7603378 100644 --- a/ErsatzTV/Pages/ChannelEditor.razor +++ b/ErsatzTV/Pages/ChannelEditor.razor @@ -19,241 +19,241 @@ @inject IMediator Mediator - - @(IsEdit ? "Save Channel" : "Add Channel") - -
- - @(IsEdit ? "Edit Channel" : "Add Channel") - - -
- Number -
- -
- -
- Name -
- -
- -
- Group -
- -
- -
- Categories -
- -
- -
- Active Mode -
- - Active - Hidden - Inactive - -
- -
- Progress Mode -
- - Always - On Demand - -
- -
- Streaming Mode -
- - MPEG-TS - MPEG-TS (Legacy) - HLS Direct - HLS Segmenter - HLS Segmenter V2 - -
- -
- FFmpeg Profile -
- - @foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) - { - @profile.Name - } - -
- -
- Stream Selector Mode -
- - Default - Custom - -
- @if (_model.StreamSelectorMode is ChannelStreamSelectorMode.Default) - { - -
- Preferred Audio Language -
- - (none) - @foreach (LanguageCodeViewModel culture in _availableCultures) - { - @culture.EnglishName - } - -
- -
- Preferred Audio Title -
- -
- -
- Preferred Subtitle Language -
- - (none) - @foreach (LanguageCodeViewModel culture in _availableCultures) - { - @culture.EnglishName - } - -
- -
- Subtitle Mode -
- - None - Forced - Default - Any - -
- } - else - { - -
- Stream Selector -
- - (none) - @foreach (string selector in _streamSelectors) - { - @selector - } - -
- } - -
- Music Video Credits Mode -
- - None - Generate Subtitles - -
- -
- Music Video Credits Template -
- - (none) - @foreach (string template in _musicVideoCreditsTemplates) - { - @template - } - -
- -
- Song Video Mode -
- - Default - With Progress - -
- -
- Logo -
-
- -
- External Logo URL -
- -
- -
- Logo Preview -
- @if (!string.IsNullOrWhiteSpace(_model.Logo?.Path) || !string.IsNullOrWhiteSpace(_model.ExternalLogoUrl)) + + @(IsEdit ? "Save Channel" : "Add Channel") + +
+ + @(IsEdit ? "Edit Channel" : "Add Channel") + + +
+ Number +
+ +
+ +
+ Name +
+ +
+ +
+ Group +
+ +
+ +
+ Categories +
+ +
+ +
+ Active Mode +
+ + Active + Hidden + Inactive + +
+ +
+ Progress Mode +
+ + Always + On Demand + +
+ +
+ Streaming Mode +
+ + MPEG-TS + MPEG-TS (Legacy) + HLS Direct + HLS Segmenter + HLS Segmenter V2 + +
+ +
+ FFmpeg Profile +
+ + @foreach (FFmpegProfileViewModel profile in _ffmpegProfiles) { - + @profile.Name } -
+ + + +
+ Stream Selector Mode +
+ + Default + Custom + +
+ @if (_model.StreamSelectorMode is ChannelStreamSelectorMode.Default) + {
- Watermark + Preferred Audio Language
- - (none) - @foreach (WatermarkViewModel watermark in _watermarks) + (none) + @foreach (LanguageCodeViewModel culture in _availableCultures) { - @watermark.Name + @culture.EnglishName }
- Fallback Filler + Preferred Audio Title
- - (none) - @foreach (FillerPresetViewModel fillerPreset in _fillerPresets) + +
+ +
+ Preferred Subtitle Language +
+ + (none) + @foreach (LanguageCodeViewModel culture in _availableCultures) { - @fillerPreset.Name + @culture.EnglishName }
-
-
+ +
+ Subtitle Mode +
+ + None + Forced + Default + Any + +
+ } + else + { + +
+ Stream Selector +
+ + (none) + @foreach (string selector in _streamSelectors) + { + @selector + } + +
+ } + +
+ Music Video Credits Mode +
+ + None + Generate Subtitles + +
+ +
+ Music Video Credits Template +
+ + (none) + @foreach (string template in _musicVideoCreditsTemplates) + { + @template + } + +
+ +
+ Song Video Mode +
+ + Default + With Progress + +
+ +
+ Logo +
+
+ +
+ External Logo URL +
+ +
+ +
+ Logo Preview +
+ @if (!string.IsNullOrWhiteSpace(_model.Logo?.Path) || !string.IsNullOrWhiteSpace(_model.ExternalLogoUrl)) + { + + } +
+ +
+ Watermark +
+ + (none) + @foreach (WatermarkViewModel watermark in _watermarks) + { + @watermark.Name + } + +
+ +
+ Fallback Filler +
+ + (none) + @foreach (FillerPresetViewModel fillerPreset in _fillerPresets) + { + @fillerPreset.Name + } + +
+
+
@code { diff --git a/ErsatzTV/Pages/Channels.razor b/ErsatzTV/Pages/Channels.razor index bab8df6e7..9a6add595 100644 --- a/ErsatzTV/Pages/Channels.razor +++ b/ErsatzTV/Pages/Channels.razor @@ -49,7 +49,7 @@ } else { - + } @context.Name @@ -230,11 +230,10 @@ var processedChannels = new List(); foreach (ChannelViewModel channel in sorted) { - Option maybeCultureInfo = allCultures.Find( - ci => string.Equals( - ci.ThreeLetterISOLanguageName, - channel.PreferredAudioLanguageCode, - StringComparison.OrdinalIgnoreCase)); + Option maybeCultureInfo = allCultures.Find(ci => string.Equals( + ci.ThreeLetterISOLanguageName, + channel.PreferredAudioLanguageCode, + StringComparison.OrdinalIgnoreCase)); maybeCultureInfo.Match( cultureInfo => processedChannels.Add(channel with { PreferredAudioLanguageCode = cultureInfo.EnglishName }), @@ -257,4 +256,5 @@ StreamingMode.TransportStreamHybrid => "MPEG-TS", _ => "MPEG-TS (Legacy)" }; -} + +} \ No newline at end of file diff --git a/ErsatzTV/Pages/CollectionEditor.razor b/ErsatzTV/Pages/CollectionEditor.razor index f39436600..ff9e70ce7 100644 --- a/ErsatzTV/Pages/CollectionEditor.razor +++ b/ErsatzTV/Pages/CollectionEditor.razor @@ -48,12 +48,11 @@ if (IsEdit) { Option maybeCollection = await Mediator.Send(new GetCollectionById(Id), _cts.Token); - maybeCollection.IfSome( - collection => - { - _model.Id = collection.Id; - _model.Name = collection.Name; - }); + maybeCollection.IfSome(collection => + { + _model.Id = collection.Id; + _model.Name = collection.Name; + }); } else { diff --git a/ErsatzTV/Pages/DecoEditor.razor b/ErsatzTV/Pages/DecoEditor.razor index acbc65204..510d31303 100644 --- a/ErsatzTV/Pages/DecoEditor.razor +++ b/ErsatzTV/Pages/DecoEditor.razor @@ -13,203 +13,203 @@ @inject IMediator Mediator - - Edit Deco - - - - - - - Save Changes - - - - - - - Watermark - - - - - Inherit - Disable - Override - - - (none) - @foreach (WatermarkViewModel watermark in _watermarks) + + Edit Deco + + + + + + + Save Changes + + + + + + + Watermark + + + + + Inherit + Disable + Override + + + (none) + @foreach (WatermarkViewModel watermark in _watermarks) + { + @watermark.Name + } + + + + + + + + + + + Default Filler +   + + + + + After all blocks have been scheduled, a second pass will be made to fill unscheduled time using random items from this collection. + + + + + + + Inherit + Disable + Override + + + Collection + Television Show + Television Season + Artist + Multi Collection + Smart Collection + + @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.Collection) + { + + @foreach (MediaCollectionViewModel collection in _mediaCollections) { - @watermark.Name + @collection.Name } - - - - - - - - - - Default Filler -   - - - - - After all blocks have been scheduled, a second pass will be made to fill unscheduled time using random items from this collection. - - - - - - - Inherit - Disable - Override - - - Collection - Television Show - Television Season - Artist - Multi Collection - Smart Collection - - @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.Collection) - { - - @foreach (MediaCollectionViewModel collection in _mediaCollections) - { - @collection.Name - } - - } - @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.MultiCollection) - { - - @foreach (MultiCollectionViewModel collection in _multiCollections) - { - @collection.Name - } - - } - @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.SmartCollection) - { - - @foreach (SmartCollectionViewModel collection in _smartCollections) - { - @collection.Name - } - - } - @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.TelevisionShow) - { - - @foreach (NamedMediaItemViewModel show in _televisionShows) - { - @show.Name - } - - } - @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) - { - - @foreach (NamedMediaItemViewModel season in _televisionSeasons) - { - @season.Name - } - - } - @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.Artist) - { - - @foreach (NamedMediaItemViewModel artist in _artists) - { - @artist.Name - } - - } - - - - - - - - - - Dead Air Fallback -   - - - - - When no playout item is found for the current time, *one* item will be randomly selected from this collection and looped and trimmed to exactly fit until the start of the next playout item. - This replaces the "Channel is Offline" image that would otherwise display. - - - - - - - Inherit - Disable - Override + Label="Multi Collection" + @bind-value="_deco.DefaultFillerMultiCollection"> + @foreach (MultiCollectionViewModel collection in _multiCollections) + { + @collection.Name + } - - Collection - Television Show - Television Season - Artist - Multi Collection - Smart Collection + } + @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.SmartCollection) + { + + @foreach (SmartCollectionViewModel collection in _smartCollections) + { + @collection.Name + } - @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Collection) - { + } + @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.TelevisionShow) + { + + @foreach (NamedMediaItemViewModel show in _televisionShows) + { + @show.Name + } + + } + @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) + { + + @foreach (NamedMediaItemViewModel season in _televisionSeasons) + { + @season.Name + } + + } + @if (_deco.DefaultFillerCollectionType == ProgramScheduleItemCollectionType.Artist) + { + + @foreach (NamedMediaItemViewModel artist in _artists) + { + @artist.Name + } + + } + + + + + + + + + + Dead Air Fallback +   + + + + + When no playout item is found for the current time, *one* item will be randomly selected from this collection and looped and trimmed to exactly fit until the start of the next playout item. + This replaces the "Channel is Offline" image that would otherwise display. + + + + + + + Inherit + Disable + Override + + + Collection + Television Show + Television Season + Artist + Multi Collection + Smart Collection + + @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Collection) + { @foreach (MediaCollectionViewModel collection in _mediaCollections) { - @collection.Name + @collection.Name } - } - @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.MultiCollection) - { + } + @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.MultiCollection) + { @foreach (MultiCollectionViewModel collection in _multiCollections) { - @collection.Name + @collection.Name } - } - @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.SmartCollection) - { + } + @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.SmartCollection) + { @foreach (SmartCollectionViewModel collection in _smartCollections) { - @collection.Name + @collection.Name } - } - @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionShow) - { + } + @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionShow) + { @foreach (NamedMediaItemViewModel show in _televisionShows) { - @show.Name + @show.Name } - } - @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) - { + } + @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.TelevisionSeason) + { @foreach (NamedMediaItemViewModel season in _televisionSeasons) { - @season.Name + @season.Name } - } - @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Artist) - { + } + @if (_deco.DeadAirFallbackCollectionType == ProgramScheduleItemCollectionType.Artist) + { @foreach (NamedMediaItemViewModel artist in _artists) { - @artist.Name + @artist.Name } - } - - - + } + + + @code { diff --git a/ErsatzTV/Pages/FFmpegEditor.razor b/ErsatzTV/Pages/FFmpegEditor.razor index 087522472..c55ac2d5c 100644 --- a/ErsatzTV/Pages/FFmpegEditor.razor +++ b/ErsatzTV/Pages/FFmpegEditor.razor @@ -19,253 +19,253 @@ @inject PersistentComponentState ApplicationState - - @(IsEdit ? "Save Profile" : "Add Profile") - -
- - General - - -
- Name -
- -
- -
- Thread Count -
- -
- -
- Preferred Resolution -
- - @foreach (ResolutionViewModel resolution in _resolutions) - { - @resolution.Name - } - -
- -
- Scaling Behavior -
- - Scale and Pad - Stretch - Crop - -
- Video - - -
- Format -
- - h264 - hevc - mpeg-2 - -
- -
- Profile -
- - main - high - -
- -
- Preset -
- @{ - ICollection presets = AvailablePresets.ForAccelAndFormat(MapAccel(_model.HardwareAcceleration), MapVideoFormat(_model.VideoFormat)); + + @(IsEdit ? "Save Profile" : "Add Profile") + +
+ + General + + +
+ Name +
+ +
+ +
+ Thread Count +
+ +
+ +
+ Preferred Resolution +
+ + @foreach (ResolutionViewModel resolution in _resolutions) + { + @resolution.Name } - - @foreach (string preset in presets) + +
+ +
+ Scaling Behavior +
+ + Scale and Pad + Stretch + Crop + +
+ Video + + +
+ Format +
+ + h264 + hevc + mpeg-2 + +
+ +
+ Profile +
+ + main + high + +
+ +
+ Preset +
+ @{ + ICollection presets = AvailablePresets.ForAccelAndFormat(MapAccel(_model.HardwareAcceleration), MapVideoFormat(_model.VideoFormat)); + } + + @foreach (string preset in presets) + { + if (!string.IsNullOrWhiteSpace(preset)) { - if (!string.IsNullOrWhiteSpace(preset)) + @preset + } + } + +
+ +
+ Allow B-Frames +
+ +
+ +
+ Bit Depth +
+ + 8-bit + 10-bit + +
+ +
+ Bitrate +
+ +
+ +
+ Buffer Size +
+ +
+ +
+ Hardware Acceleration +
+ + @foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds) + { + @hwAccel + } + +
+ @if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi) + { + +
+ VAAPI Driver +
+ + @foreach (VaapiDriver driver in Enum.GetValues()) { - @preset + @driver } - } - -
- -
- Allow B-Frames -
- -
- -
- Bit Depth -
- - 8-bit - 10-bit - -
- -
- Bitrate -
- -
- -
- Buffer Size -
- -
- -
- Hardware Acceleration -
- - @foreach (HardwareAccelerationKind hwAccel in _hardwareAccelerationKinds) - { - @hwAccel - } - -
- @if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi) - { - -
- VAAPI Driver -
- - @foreach (VaapiDriver driver in Enum.GetValues()) - { - @driver - } - -
- -
- VAAPI Display -
- - @foreach (string display in _vaapiDisplays) - { - @display - } - -
- } - - @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) - { - -
- @(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device") -
- - @foreach (string device in _vaapiDevices) - { - @device - } - -
- } - } - - @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) - { - -
- QSV Extra Hardware Frames -
- +
- } - else - {
- Tonemap Algorithm + VAAPI Display
- - @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues()) + + @foreach (string display in _vaapiDisplays) { - @algorithm + @display }
} + + @if (_model.HardwareAcceleration is HardwareAccelerationKind.Vaapi or HardwareAccelerationKind.Qsv) + { + +
+ @(_model.HardwareAcceleration == HardwareAccelerationKind.Vaapi ? "VAAPI Device" : "QSV Device") +
+ + @foreach (string device in _vaapiDevices) + { + @device + } + +
+ } + } + + @if (_model.HardwareAcceleration == HardwareAccelerationKind.Qsv) + {
- Normalize Frame Rate + QSV Extra Hardware Frames
- +
+ } + else + {
- Auto Deinterlace Video + Tonemap Algorithm
- -
- Audio - - -
- Format -
- - aac - ac3 + + @foreach (FFmpegProfileTonemapAlgorithm algorithm in Enum.GetValues()) + { + @algorithm + }
- -
- Bitrate -
- -
- -
- Buffer Size -
- -
- -
- Channels -
- -
- -
- Sample Rate -
- -
- -
- Normalize Loudness -
- - Off - loudnorm - -
-
-
+ } + +
+ Normalize Frame Rate +
+ +
+ +
+ Auto Deinterlace Video +
+ +
+ Audio + + +
+ Format +
+ + aac + ac3 + +
+ +
+ Bitrate +
+ +
+ +
+ Buffer Size +
+ +
+ +
+ Channels +
+ +
+ +
+ Sample Rate +
+ +
+ +
+ Normalize Loudness +
+ + Off + loudnorm + +
+
+
@code { diff --git a/ErsatzTV/Pages/FillerPresetEditor.razor b/ErsatzTV/Pages/FillerPresetEditor.razor index 4904bce8d..f46dc08c6 100644 --- a/ErsatzTV/Pages/FillerPresetEditor.razor +++ b/ErsatzTV/Pages/FillerPresetEditor.razor @@ -38,7 +38,7 @@ Random Count - + 5 (:00, :05, :10, :15, :20, etc) 10 (:00, :10, :20, :30, :40, :50) @@ -179,31 +179,30 @@ if (IsEdit) { Option maybeFillerPreset = await Mediator.Send(new GetFillerPresetById(Id), _cts.Token); - maybeFillerPreset.IfSome( - fillerPreset => - { - _model.Id = fillerPreset.Id; - _model.Name = fillerPreset.Name; - _model.FillerKind = fillerPreset.FillerKind; - _model.FillerMode = fillerPreset.FillerMode; - _model.Duration = fillerPreset.Duration; - _model.Count = fillerPreset.Count; - _model.PadToNearestMinute = fillerPreset.PadToNearestMinute; - _model.AllowWatermarks = fillerPreset.AllowWatermarks; - _model.CollectionType = fillerPreset.CollectionType; - _model.Collection = fillerPreset.CollectionId.HasValue - ? _mediaCollections.Find(c => c.Id == fillerPreset.CollectionId.Value) - : null; - _model.MultiCollection = fillerPreset.MultiCollectionId.HasValue - ? _multiCollections.Find(c => c.Id == fillerPreset.MultiCollectionId.Value) - : null; - _model.SmartCollection = fillerPreset.SmartCollectionId.HasValue - ? _smartCollections.Find(c => c.Id == fillerPreset.SmartCollectionId.Value) - : null; - _model.MediaItem = fillerPreset.MediaItemId.HasValue - ? _televisionShows.Append(_televisionSeasons).Append(_artists).ToList().Find(vm => vm.MediaItemId == fillerPreset.MediaItemId.Value) - : null; - }); + maybeFillerPreset.IfSome(fillerPreset => + { + _model.Id = fillerPreset.Id; + _model.Name = fillerPreset.Name; + _model.FillerKind = fillerPreset.FillerKind; + _model.FillerMode = fillerPreset.FillerMode; + _model.Duration = fillerPreset.Duration; + _model.Count = fillerPreset.Count; + _model.PadToNearestMinute = fillerPreset.PadToNearestMinute; + _model.AllowWatermarks = fillerPreset.AllowWatermarks; + _model.CollectionType = fillerPreset.CollectionType; + _model.Collection = fillerPreset.CollectionId.HasValue + ? _mediaCollections.Find(c => c.Id == fillerPreset.CollectionId.Value) + : null; + _model.MultiCollection = fillerPreset.MultiCollectionId.HasValue + ? _multiCollections.Find(c => c.Id == fillerPreset.MultiCollectionId.Value) + : null; + _model.SmartCollection = fillerPreset.SmartCollectionId.HasValue + ? _smartCollections.Find(c => c.Id == fillerPreset.SmartCollectionId.Value) + : null; + _model.MediaItem = fillerPreset.MediaItemId.HasValue + ? _televisionShows.Append(_televisionSeasons).Append(_artists).ToList().Find(vm => vm.MediaItemId == fillerPreset.MediaItemId.Value) + : null; + }); } else { diff --git a/ErsatzTV/Pages/LocalLibraryEditor.razor b/ErsatzTV/Pages/LocalLibraryEditor.razor index bdb3b9070..d6cd6679e 100644 --- a/ErsatzTV/Pages/LocalLibraryEditor.razor +++ b/ErsatzTV/Pages/LocalLibraryEditor.razor @@ -129,13 +129,11 @@ { _model.HasChanges = false; _model.Paths = await Mediator.Send(new GetLocalLibraryPaths(Id), _cts.Token) - .Map( - list => list.Map( - vm => new LocalLibraryPathEditViewModel - { - Id = vm.Id, - Path = vm.Path - }).ToList()); + .Map(list => list.Map(vm => new LocalLibraryPathEditViewModel + { + Id = vm.Id, + Path = vm.Path + }).ToList()); } private async Task MoveLibraryPath(LocalLibraryPathEditViewModel libraryPath) diff --git a/ErsatzTV/Pages/MultiCollectionEditor.razor b/ErsatzTV/Pages/MultiCollectionEditor.razor index c84ad39ad..e1c75af6c 100644 --- a/ErsatzTV/Pages/MultiCollectionEditor.razor +++ b/ErsatzTV/Pages/MultiCollectionEditor.razor @@ -140,30 +140,27 @@ if (IsEdit) { Option maybeCollection = await Mediator.Send(new GetMultiCollectionById(Id), _cts.Token); - maybeCollection.IfSome( - collection => - { - _model.Id = collection.Id; - _model.Name = collection.Name; - _model.Items = collection.Items - .Map( - item => - new MultiCollectionItemEditViewModel - { - Collection = item.Collection, - ScheduleAsGroup = item.ScheduleAsGroup, - PlaybackOrder = item.PlaybackOrder - }) - .Append( - collection.SmartItems.Map( - item => - new MultiCollectionSmartItemEditViewModel - { - SmartCollection = item.SmartCollection, - ScheduleAsGroup = item.ScheduleAsGroup, - PlaybackOrder = item.PlaybackOrder - })).ToList(); - }); + maybeCollection.IfSome(collection => + { + _model.Id = collection.Id; + _model.Name = collection.Name; + _model.Items = collection.Items + .Map(item => + new MultiCollectionItemEditViewModel + { + Collection = item.Collection, + ScheduleAsGroup = item.ScheduleAsGroup, + PlaybackOrder = item.PlaybackOrder + }) + .Append( + collection.SmartItems.Map(item => + new MultiCollectionSmartItemEditViewModel + { + SmartCollection = item.SmartCollection, + ScheduleAsGroup = item.ScheduleAsGroup, + PlaybackOrder = item.PlaybackOrder + })).ToList(); + }); } else { @@ -198,32 +195,30 @@ } private List GetUpdateItems() => - _model.Items.Map( - i => - i switch - { - MultiCollectionSmartItemEditViewModel smartVm => - new UpdateMultiCollectionItem( - null, - smartVm.SmartCollection.Id, - smartVm.ScheduleAsGroup, - smartVm.PlaybackOrder), - _ => new UpdateMultiCollectionItem(i.Collection.Id, null, i.ScheduleAsGroup, i.PlaybackOrder) - }).ToList(); + _model.Items.Map(i => + i switch + { + MultiCollectionSmartItemEditViewModel smartVm => + new UpdateMultiCollectionItem( + null, + smartVm.SmartCollection.Id, + smartVm.ScheduleAsGroup, + smartVm.PlaybackOrder), + _ => new UpdateMultiCollectionItem(i.Collection.Id, null, i.ScheduleAsGroup, i.PlaybackOrder) + }).ToList(); private List GetCreateItems() => - _model.Items.Map( - i => - i switch - { - MultiCollectionSmartItemEditViewModel smartVm => - new CreateMultiCollectionItem( - null, - smartVm.SmartCollection.Id, - smartVm.ScheduleAsGroup, - smartVm.PlaybackOrder), - _ => new CreateMultiCollectionItem(i.Collection.Id, null, i.ScheduleAsGroup, i.PlaybackOrder) - }).ToList(); + _model.Items.Map(i => + i switch + { + MultiCollectionSmartItemEditViewModel smartVm => + new CreateMultiCollectionItem( + null, + smartVm.SmartCollection.Id, + smartVm.ScheduleAsGroup, + smartVm.PlaybackOrder), + _ => new CreateMultiCollectionItem(i.Collection.Id, null, i.ScheduleAsGroup, i.PlaybackOrder) + }).ToList(); private void RemoveCollection(MultiCollectionItemEditViewModel item) => _model.Items.Remove(item); diff --git a/ErsatzTV/Pages/PlaylistEditor.razor b/ErsatzTV/Pages/PlaylistEditor.razor index 6b30a0040..445b13732 100644 --- a/ErsatzTV/Pages/PlaylistEditor.razor +++ b/ErsatzTV/Pages/PlaylistEditor.razor @@ -488,17 +488,16 @@ private ReplacePlaylistItems GenerateReplaceRequest() { - var items = _playlist.Items.Map( - item => new ReplacePlaylistItem( - item.Index, - item.CollectionType, - item.Collection?.Id, - item.MultiCollection?.Id, - item.SmartCollection?.Id, - item.MediaItem?.MediaItemId, - item.PlaybackOrder, - item.PlayAll, - item.IncludeInProgramGuide)).ToList(); + var items = _playlist.Items.Map(item => new ReplacePlaylistItem( + item.Index, + item.CollectionType, + item.Collection?.Id, + item.MultiCollection?.Id, + item.SmartCollection?.Id, + item.MediaItem?.MediaItemId, + item.PlaybackOrder, + item.PlayAll, + item.IncludeInProgramGuide)).ToList(); return new ReplacePlaylistItems(Id, _playlist.Name, items); } diff --git a/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor b/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor index ca5f11aee..b5eff6065 100644 --- a/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor +++ b/ErsatzTV/Pages/PlayoutAlternateSchedulesEditor.razor @@ -385,14 +385,13 @@ private async Task SaveChanges() { - var items = _items.Map( - item => new ReplacePlayoutAlternateSchedule( - item.Id, - item.Index, - item.ProgramSchedule.Id, - item.DaysOfWeek, - item.DaysOfMonth, - item.MonthsOfYear)).ToList(); + var items = _items.Map(item => new ReplacePlayoutAlternateSchedule( + item.Id, + item.Index, + item.ProgramSchedule.Id, + item.DaysOfWeek, + item.DaysOfMonth, + item.MonthsOfYear)).ToList(); Seq errorMessages = await Mediator.Send(new ReplacePlayoutAlternateScheduleItems(Id, items), _cts.Token) .Map(e => e.LeftToSeq()); diff --git a/ErsatzTV/Pages/PlayoutTemplatesEditor.razor b/ErsatzTV/Pages/PlayoutTemplatesEditor.razor index 9bd4c15db..f02994f12 100644 --- a/ErsatzTV/Pages/PlayoutTemplatesEditor.razor +++ b/ErsatzTV/Pages/PlayoutTemplatesEditor.razor @@ -520,20 +520,19 @@ else return; } - var items = _items.Map( - item => new ReplacePlayoutTemplate( - item.Id, - item.Index, - item.Template.Id, - item.DecoTemplate?.Id, - item.DaysOfWeek, - item.DaysOfMonth, - item.MonthsOfYear, - item.LimitToDateRange, - item.StartMonth, - item.StartDay, - item.EndMonth, - item.EndDay)).ToList(); + var items = _items.Map(item => new ReplacePlayoutTemplate( + item.Id, + item.Index, + item.Template.Id, + item.DecoTemplate?.Id, + item.DaysOfWeek, + item.DaysOfMonth, + item.MonthsOfYear, + item.LimitToDateRange, + item.StartMonth, + item.StartDay, + item.EndMonth, + item.EndDay)).ToList(); Option maybeError = await Mediator.Send(new ReplacePlayoutTemplateItems(Id, items), _cts.Token); diff --git a/ErsatzTV/Pages/Playouts.razor b/ErsatzTV/Pages/Playouts.razor index 3ab9d30ac..0d6290d31 100644 --- a/ErsatzTV/Pages/Playouts.razor +++ b/ErsatzTV/Pages/Playouts.razor @@ -2,8 +2,8 @@ @using System.Globalization @using ErsatzTV.Application.Configuration @using ErsatzTV.Application.Playouts -@using ErsatzTV.Core.Scheduling @using ErsatzTV.Core.Notifications +@using ErsatzTV.Core.Scheduling @using MediatR.Courier @implements IDisposable @inject IDialogService Dialog @@ -243,10 +243,7 @@ } } - protected override void OnInitialized() - { - Courier.Subscribe(HandlePlayoutUpdated); - } + protected override void OnInitialized() => Courier.Subscribe(HandlePlayoutUpdated); public void Dispose() { @@ -340,10 +337,7 @@ await Mediator.Send(new ResetAllPlayouts(), _cts.Token); } - private async Task ResetPlayout(PlayoutNameViewModel playout) - { - await WorkerChannel.WriteAsync(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token); - } + private async Task ResetPlayout(PlayoutNameViewModel playout) => await WorkerChannel.WriteAsync(new BuildPlayout(playout.PlayoutId, PlayoutBuildMode.Reset), _cts.Token); private async Task ScheduleReset(PlayoutNameViewModel playout) { diff --git a/ErsatzTV/Pages/ScheduleEditor.razor b/ErsatzTV/Pages/ScheduleEditor.razor index df00eacd6..f4136ff70 100644 --- a/ErsatzTV/Pages/ScheduleEditor.razor +++ b/ErsatzTV/Pages/ScheduleEditor.razor @@ -8,7 +8,7 @@ @inject ISnackbar Snackbar @inject IMediator Mediator - + @(IsEdit ? "Save Schedule" : "Add Schedule") @@ -20,7 +20,7 @@
Name
- +
@@ -52,7 +52,7 @@
Random Start Point
- +
@@ -74,6 +74,7 @@ public int Id { get; set; } private readonly ProgramScheduleEditViewModel _model = new(); + private MudForm _form; private bool _success; public void Dispose() @@ -110,6 +111,7 @@ private async Task HandleSubmitAsync() { + await _form.Validate(); if (_success) { Either result = IsEdit diff --git a/ErsatzTV/Pages/ScheduleItemsEditor.razor b/ErsatzTV/Pages/ScheduleItemsEditor.razor index 7142e66c1..600003164 100644 --- a/ErsatzTV/Pages/ScheduleItemsEditor.razor +++ b/ErsatzTV/Pages/ScheduleItemsEditor.razor @@ -649,7 +649,7 @@ MultipleCount = item.MultipleCount, PlayoutDuration = item.PlayoutDuration, TailMode = item.TailMode, - DiscardToFillAttempts = item.DiscardToFillAttempts, + DiscardToFillAttempts = item.DiscardToFillAttempts }; foreach (ProgramScheduleItemEditViewModel i in _schedule.Items.Filter(si => si.Index >= newItem.Index)) @@ -681,37 +681,36 @@ private async Task SaveChanges() { - var items = _schedule.Items.Map( - item => new ReplaceProgramScheduleItem( - item.Index, - item.StartType, - item.StartTime, - item.FixedStartTimeBehavior, - item.PlayoutMode, - item.CollectionType, - item.Collection?.Id, - item.MultiCollection?.Id, - item.SmartCollection?.Id, - item.MediaItem?.MediaItemId, - item.Playlist?.Id, - item.PlaybackOrder, - item.FillWithGroupMode, - item.MultipleCount, - item.PlayoutDuration, - item.TailMode, - item.DiscardToFillAttempts, - item.CustomTitle, - item.GuideMode, - item.PreRollFiller?.Id, - item.MidRollFiller?.Id, - item.PostRollFiller?.Id, - item.TailFiller?.Id, - item.FallbackFiller?.Id, - item.Watermark?.Id, - item.PreferredAudioLanguageCode, - item.PreferredAudioTitle, - item.PreferredSubtitleLanguageCode, - item.SubtitleMode)).ToList(); + var items = _schedule.Items.Map(item => new ReplaceProgramScheduleItem( + item.Index, + item.StartType, + item.StartTime, + item.FixedStartTimeBehavior, + item.PlayoutMode, + item.CollectionType, + item.Collection?.Id, + item.MultiCollection?.Id, + item.SmartCollection?.Id, + item.MediaItem?.MediaItemId, + item.Playlist?.Id, + item.PlaybackOrder, + item.FillWithGroupMode, + item.MultipleCount, + item.PlayoutDuration, + item.TailMode, + item.DiscardToFillAttempts, + item.CustomTitle, + item.GuideMode, + item.PreRollFiller?.Id, + item.MidRollFiller?.Id, + item.PostRollFiller?.Id, + item.TailFiller?.Id, + item.FallbackFiller?.Id, + item.Watermark?.Id, + item.PreferredAudioLanguageCode, + item.PreferredAudioTitle, + item.PreferredSubtitleLanguageCode, + item.SubtitleMode)).ToList(); Seq errorMessages = await Mediator.Send(new ReplaceProgramScheduleItems(Id, items), _cts.Token).Map(e => e.LeftToSeq()); @@ -738,8 +737,6 @@ } } - private string SelectedRowClassFunc(ProgramScheduleItemEditViewModel element, int rowNumber) - { - return _selectedItem != null && _selectedItem == element ? "selected" : string.Empty; - } + private string SelectedRowClassFunc(ProgramScheduleItemEditViewModel element, int rowNumber) => _selectedItem != null && _selectedItem == element ? "selected" : string.Empty; + } \ No newline at end of file diff --git a/ErsatzTV/Pages/Settings/FFmpegSettings.razor b/ErsatzTV/Pages/Settings/FFmpegSettings.razor index e08c3643e..cfe322e7b 100644 --- a/ErsatzTV/Pages/Settings/FFmpegSettings.razor +++ b/ErsatzTV/Pages/Settings/FFmpegSettings.razor @@ -12,14 +12,14 @@ @inject ILogger Logger @inject IDialogService Dialog - + - Save Settings + Save Settings
FFmpeg - +
FFmpeg Path @@ -30,7 +30,7 @@
FFprobe Path
- +
@@ -58,19 +58,19 @@
Use Embedded Subtitles
- +
Extract Embedded (Text) Subtitles
- +
Save Troubleshooting Reports To Disk
- +
@@ -112,7 +112,7 @@
HLS Segmenter Initial Segment Count
- +
@@ -125,7 +125,7 @@ Custom Resolutions - + Add Custom Resolution @@ -152,32 +152,33 @@ @code { - private readonly CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); - private bool _success; - private List _ffmpegProfiles = []; - private FFmpegSettingsViewModel _ffmpegSettings = new(); - private List _availableCultures = []; - private List _watermarks = []; - private List _fillerPresets = []; - private List _customResolutions = []; + private MudForm _form; + private bool _success; + private List _ffmpegProfiles = []; + private FFmpegSettingsViewModel _ffmpegSettings = new(); + private List _availableCultures = []; + private List _watermarks = []; + private List _fillerPresets = []; + private List _customResolutions = []; - public void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - } + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); + } - protected override async Task OnParametersSetAsync() - { - await LoadFFmpegProfilesAsync(); + protected override async Task OnParametersSetAsync() + { + await LoadFFmpegProfilesAsync(); - _ffmpegSettings = await Mediator.Send(new GetFFmpegSettings(), _cts.Token); - _success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath); - _availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token); - _watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token); - _fillerPresets = await Mediator.Send(new GetAllFillerPresets(), _cts.Token) - .Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList()); + _ffmpegSettings = await Mediator.Send(new GetFFmpegSettings(), _cts.Token); + _success = File.Exists(_ffmpegSettings.FFmpegPath) && File.Exists(_ffmpegSettings.FFprobePath); + _availableCultures = await Mediator.Send(new GetAllLanguageCodes(), _cts.Token); + _watermarks = await Mediator.Send(new GetAllWatermarks(), _cts.Token); + _fillerPresets = await Mediator.Send(new GetAllFillerPresets(), _cts.Token) + .Map(list => list.Filter(fp => fp.FillerKind == FillerKind.Fallback).ToList()); await RefreshCustomResolutions(); } @@ -195,18 +196,22 @@ private async Task SaveFFmpegSettings() { - Either result = await Mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings), _cts.Token); - result.Match( - Left: error => - { - Snackbar.Add(error.Value, Severity.Error); - Logger.LogError("Unexpected error saving FFmpeg settings: {Error}", error.Value); - }, - Right: _ => - { - Snackbar.Add("Successfully saved FFmpeg settings", Severity.Success); - _success = false; - }); + await _form.Validate(); + if (_success) + { + Either result = await Mediator.Send(new UpdateFFmpegSettings(_ffmpegSettings), _cts.Token); + result.Match( + Left: error => + { + Snackbar.Add(error.Value, Severity.Error); + Logger.LogError("Unexpected error saving FFmpeg settings: {Error}", error.Value); + }, + Right: _ => + { + Snackbar.Add("Successfully saved FFmpeg settings", Severity.Success); + _success = false; + }); + } } private async Task RefreshCustomResolutions() => _customResolutions = await Mediator.Send(new GetAllResolutions(), _cts.Token) diff --git a/ErsatzTV/Pages/Settings/HDHRSettings.razor b/ErsatzTV/Pages/Settings/HDHRSettings.razor index db52cca98..62ed2b8c4 100644 --- a/ErsatzTV/Pages/Settings/HDHRSettings.razor +++ b/ErsatzTV/Pages/Settings/HDHRSettings.razor @@ -6,61 +6,69 @@ @inject ISnackbar Snackbar @inject ILogger Logger - + - Save Settings + Save Settings
HDHomeRun - +
UUID
- +
Tuner Count
- +
@code { - private readonly CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); - private bool _hdhrSuccess; - private int _tunerCount; - [UsedImplicitly] private Guid _uuid; + private MudForm _form; + private bool _hdhrSuccess; + private int _tunerCount; - public void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - } + [UsedImplicitly] + private Guid _uuid; - protected override async Task OnParametersSetAsync() - { - _tunerCount = await Mediator.Send(new GetHDHRTunerCount(), _cts.Token); + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); + } + + protected override async Task OnParametersSetAsync() + { + _tunerCount = await Mediator.Send(new GetHDHRTunerCount(), _cts.Token); _uuid = await Mediator.Send(new GetHDHRUUID(), _cts.Token); - _hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount)); + _hdhrSuccess = string.IsNullOrWhiteSpace(ValidateTunerCount(_tunerCount)); } private static string ValidateTunerCount(int tunerCount) => tunerCount <= 0 ? "Tuner count must be greater than zero" : null; private async Task SaveHDHRSettings() { - Either result = await Mediator.Send(new UpdateHDHRTunerCount(_tunerCount), _cts.Token); - result.Match( - Left: error => - { - Snackbar.Add(error.Value, Severity.Error); - Logger.LogError("Unexpected error saving HDHomeRun settings: {Error}", error.Value); - }, - Right: _ => Snackbar.Add("Successfully saved HDHomeRun settings", Severity.Success)); + await _form.Validate(); + if (_hdhrSuccess) + { + Either result = await Mediator.Send(new UpdateHDHRTunerCount(_tunerCount), _cts.Token); + result.Match( + Left: error => + { + Snackbar.Add(error.Value, Severity.Error); + Logger.LogError("Unexpected error saving HDHomeRun settings: {Error}", error.Value); + }, + Right: _ => Snackbar.Add("Successfully saved HDHomeRun settings", Severity.Success)); + } } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/Settings/LoggingSettings.razor b/ErsatzTV/Pages/Settings/LoggingSettings.razor index fee6b4056..de27cc8d4 100644 --- a/ErsatzTV/Pages/Settings/LoggingSettings.razor +++ b/ErsatzTV/Pages/Settings/LoggingSettings.razor @@ -13,7 +13,7 @@
Logging - +
Default Minimum Log Level @@ -74,21 +74,18 @@ @code { - private readonly CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); - private LoggingSettingsViewModel _loggingSettings = new(); + private LoggingSettingsViewModel _loggingSettings = new(); - public void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - } - - protected override async Task OnParametersSetAsync() - { - _loggingSettings = await Mediator.Send(new GetLoggingSettings(), _cts.Token); + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); } + protected override async Task OnParametersSetAsync() => _loggingSettings = await Mediator.Send(new GetLoggingSettings(), _cts.Token); + private async Task SaveLoggingSettings() { Either result = await Mediator.Send(new UpdateLoggingSettings(_loggingSettings), _cts.Token); @@ -100,4 +97,5 @@ }, Right: _ => Snackbar.Add("Successfully saved logging settings", Severity.Success)); } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/Settings/PlayoutSettings.razor b/ErsatzTV/Pages/Settings/PlayoutSettings.razor index 8e64feeda..70ae740f1 100644 --- a/ErsatzTV/Pages/Settings/PlayoutSettings.razor +++ b/ErsatzTV/Pages/Settings/PlayoutSettings.razor @@ -5,19 +5,19 @@ @inject ISnackbar Snackbar @inject ILogger Logger - + - Save Settings + Save Settings
Playout - +
Days To Build
- +
@@ -32,19 +32,20 @@ @code { - private readonly CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); - private bool _playoutSuccess; - private PlayoutSettingsViewModel _playoutSettings = new(); + private MudForm _form; + private bool _playoutSuccess; + private PlayoutSettingsViewModel _playoutSettings = new(); - public void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - } + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); + } - protected override async Task OnParametersSetAsync() - { + protected override async Task OnParametersSetAsync() + { _playoutSettings = await Mediator.Send(new GetPlayoutSettings(), _cts.Token); _playoutSuccess = _playoutSettings.DaysToBuild > 0; } @@ -53,13 +54,18 @@ private async Task SavePlayoutSettings() { - Either result = await Mediator.Send(new UpdatePlayoutSettings(_playoutSettings), _cts.Token); - result.Match( - Left: error => - { - Snackbar.Add(error.Value, Severity.Error); - Logger.LogError("Unexpected error saving playout settings: {Error}", error.Value); - }, - Right: _ => Snackbar.Add("Successfully saved playout settings", Severity.Success)); + await _form.Validate(); + if (_playoutSuccess) + { + Either result = await Mediator.Send(new UpdatePlayoutSettings(_playoutSettings), _cts.Token); + result.Match( + Left: error => + { + Snackbar.Add(error.Value, Severity.Error); + Logger.LogError("Unexpected error saving playout settings: {Error}", error.Value); + }, + Right: _ => Snackbar.Add("Successfully saved playout settings", Severity.Success)); + } } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/Settings/ScannerSettings.razor b/ErsatzTV/Pages/Settings/ScannerSettings.razor index dfadd254f..d59fef054 100644 --- a/ErsatzTV/Pages/Settings/ScannerSettings.razor +++ b/ErsatzTV/Pages/Settings/ScannerSettings.razor @@ -5,38 +5,39 @@ @inject ISnackbar Snackbar @inject ILogger Logger - + - Save Settings + Save Settings
Scanner - +
Library Refresh Interval
- +
@code { - private readonly CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); - private bool _scannerSuccess; - private int _libraryRefreshInterval; + private MudForm _form; + private bool _scannerSuccess; + private int _libraryRefreshInterval; - public void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - } + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); + } - protected override async Task OnParametersSetAsync() - { + protected override async Task OnParametersSetAsync() + { _libraryRefreshInterval = await Mediator.Send(new GetLibraryRefreshInterval(), _cts.Token); _scannerSuccess = _libraryRefreshInterval is >= 0 and < 1_000_000; } @@ -50,13 +51,18 @@ private async Task SaveScannerSettings() { - Either result = await Mediator.Send(new UpdateLibraryRefreshInterval(_libraryRefreshInterval), _cts.Token); - result.Match( - Left: error => - { - Snackbar.Add(error.Value, Severity.Error); - Logger.LogError("Unexpected error saving scanner settings: {Error}", error.Value); - }, - Right: _ => Snackbar.Add("Successfully saved scanner settings", Severity.Success)); + await _form.Validate(); + if (_scannerSuccess) + { + Either result = await Mediator.Send(new UpdateLibraryRefreshInterval(_libraryRefreshInterval), _cts.Token); + result.Match( + Left: error => + { + Snackbar.Add(error.Value, Severity.Error); + Logger.LogError("Unexpected error saving scanner settings: {Error}", error.Value); + }, + Right: _ => Snackbar.Add("Successfully saved scanner settings", Severity.Success)); + } } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/Settings/XMLTVSettings.razor b/ErsatzTV/Pages/Settings/XMLTVSettings.razor index 2f770ad7d..26946d814 100644 --- a/ErsatzTV/Pages/Settings/XMLTVSettings.razor +++ b/ErsatzTV/Pages/Settings/XMLTVSettings.razor @@ -12,12 +12,12 @@
XMLTV - +
Days To Build
- +
@@ -33,21 +33,18 @@ @code { - private readonly CancellationTokenSource _cts = new(); + private readonly CancellationTokenSource _cts = new(); - private XmltvSettingsViewModel _xmltvSettings = new(); + private XmltvSettingsViewModel _xmltvSettings = new(); - public void Dispose() - { - _cts.Cancel(); - _cts.Dispose(); - } - - protected override async Task OnParametersSetAsync() - { - _xmltvSettings = await Mediator.Send(new GetXmltvSettings(), _cts.Token); + public void Dispose() + { + _cts.Cancel(); + _cts.Dispose(); } + protected override async Task OnParametersSetAsync() => _xmltvSettings = await Mediator.Send(new GetXmltvSettings(), _cts.Token); + private static string ValidateXmltvDaysToBuild(int daysToBuild) => daysToBuild <= 0 ? "XMLTV days to build must be greater than zero" : null; private async Task SaveXmltvSettings() @@ -61,4 +58,5 @@ }, Right: _ => Snackbar.Add("Successfully saved xmltv settings", Severity.Success)); } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/TraktLists.razor b/ErsatzTV/Pages/TraktLists.razor index 879b92467..85b27bb5b 100644 --- a/ErsatzTV/Pages/TraktLists.razor +++ b/ErsatzTV/Pages/TraktLists.razor @@ -89,15 +89,14 @@ .Map(maybeRows => maybeRows.Match(ce => int.TryParse(ce.Value, out int rows) ? rows : 10, () => 10)); private void LockChanged(object sender, EventArgs e) => - InvokeAsync( - async () => + InvokeAsync(async () => + { + StateHasChanged(); + if (_traktListsTable != null && !Locker.IsTraktLocked()) { - StateHasChanged(); - if (_traktListsTable != null && !Locker.IsTraktLocked()) - { - await _traktListsTable.ReloadServerData(); - } - }); + await _traktListsTable.ReloadServerData(); + } + }); private async Task MatchListItems(TraktListViewModel traktList) { diff --git a/ErsatzTV/Pages/YamlPlayoutEditor.razor b/ErsatzTV/Pages/YamlPlayoutEditor.razor index 53f69da18..4d5dbe68a 100644 --- a/ErsatzTV/Pages/YamlPlayoutEditor.razor +++ b/ErsatzTV/Pages/YamlPlayoutEditor.razor @@ -119,4 +119,5 @@ await Mediator.Send(new UpdateYamlPlayout(_playout.PlayoutId, result.Data as string ?? _playout.TemplateFile), _cts.Token); } } + } \ No newline at end of file diff --git a/ErsatzTV/Pages/_Host.cshtml b/ErsatzTV/Pages/_Host.cshtml index eb226d328..29598c0d3 100644 --- a/ErsatzTV/Pages/_Host.cshtml +++ b/ErsatzTV/Pages/_Host.cshtml @@ -1,5 +1,6 @@ @page "/" @using System.Reflection +@using MudBlazor @namespace ErsatzTV.Pages @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @{ @@ -15,7 +16,7 @@ - + @@ -103,7 +104,7 @@
- +