Browse Source

fix editorconfig and run code cleanup (#2324)

* fix formatting rules

* reformat ersatztv

* reformat ersatztv.application

* reformat ersatztv.core

* refactor ersatztv.core.tests

* reformat ersatztv.ffmpeg

* reformat ersatztv.ffmpeg.tests

* reformat ersatztv.infrastructure

* cleanup infra mysql

* cleanup infra sqlite

* cleanup infra tests

* cleanup ersatztv.scanner

* cleanup ersatztv.scanner.tests

* sln cleanup

* update dependencies
pull/2326/head
Jason Dove 5 months ago committed by GitHub
parent
commit
5d081ceeff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 2
      .config/dotnet-tools.json
  2. 36
      .editorconfig
  3. 2
      ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs
  4. 16
      ErsatzTV.Application/Emby/Commands/CallEmbyShowScannerHandler.cs
  5. 2
      ErsatzTV.Application/Emby/Commands/UpdateEmbyLibraryPreferencesHandler.cs
  6. 2
      ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs
  7. 3
      ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs
  8. 3
      ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs
  9. 10
      ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs
  10. 2
      ErsatzTV.Application/Graphics/Mapper.cs
  11. 16
      ErsatzTV.Application/Jellyfin/Commands/CallJellyfinShowScannerHandler.cs
  12. 2
      ErsatzTV.Application/Jellyfin/Commands/UpdateJellyfinLibraryPreferencesHandler.cs
  13. 2
      ErsatzTV.Application/Libraries/Commands/ILocalLibraryRequest.cs
  14. 14
      ErsatzTV.Application/Libraries/Commands/QueueShowScanByLibraryIdHandler.cs
  15. 2
      ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs
  16. 6
      ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs
  17. 2
      ErsatzTV.Application/MediaCollections/Queries/GetPlaylistItemsHandler.cs
  18. 2
      ErsatzTV.Application/MediaItems/Queries/GetRemoteStreamById.cs
  19. 55
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  20. 1
      ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs
  21. 1
      ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs
  22. 3
      ErsatzTV.Application/Playouts/Mapper.cs
  23. 8
      ErsatzTV.Application/Playouts/Queries/CheckForOverlappingPlayoutItemsHandler.cs
  24. 16
      ErsatzTV.Application/Plex/Commands/CallPlexShowScannerHandler.cs
  25. 2
      ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferencesHandler.cs
  26. 2
      ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs
  27. 12
      ErsatzTV.Application/ProgramSchedules/Mapper.cs
  28. 8
      ErsatzTV.Application/Scheduling/BlockViewModel.cs
  29. 4
      ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplateGroupHandler.cs
  30. 4
      ErsatzTV.Application/Scheduling/Commands/CreateTemplateGroupHandler.cs
  31. 9
      ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs
  32. 7
      ErsatzTV.Application/Scheduling/Mapper.cs
  33. 2
      ErsatzTV.Application/Scheduling/Queries/GetBlockItemsHandler.cs
  34. 1
      ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateTreeHandler.cs
  35. 1
      ErsatzTV.Application/Scheduling/Queries/GetTemplateTreeHandler.cs
  36. 3
      ErsatzTV.Application/Search/Queries/QuerySearchIndexRemoteStreams.cs
  37. 2
      ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs
  38. 27
      ErsatzTV.Application/Streaming/HlsSessionWorker.cs
  39. 13
      ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs
  40. 1
      ErsatzTV.Application/Streaming/PtsTime.cs
  41. 5
      ErsatzTV.Application/Streaming/Queries/GetLastPtsTimeHandler.cs
  42. 7
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  43. 4
      ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs
  44. 2
      ErsatzTV.Application/Television/Queries/GetTelevisionShowByIdHandler.cs
  45. 27
      ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs
  46. 19
      ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs
  47. 10
      ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs
  48. 138
      ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs
  49. 115
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs
  50. 2
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/GetStartTimeAfterTests.cs
  51. 220
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs
  52. 24
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs
  53. 18
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs
  54. 20
      ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ResetPlayoutTests.cs
  55. 3
      ErsatzTV.Core.Tests/Scheduling/FillerExpressionTests.cs
  56. 6
      ErsatzTV.Core.Tests/Scheduling/MultiPartEpisodeGrouperTests.cs
  57. 37
      ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs
  58. 2
      ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs
  59. 31
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  60. 2
      ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs
  61. 5
      ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs
  62. 5
      ErsatzTV.Core/Graphics/ImageGraphicsElement.cs
  63. 7
      ErsatzTV.Core/Health/HealthCheckResult.cs
  64. 4
      ErsatzTV.Core/Interfaces/Repositories/ITemplateDataRepository.cs
  65. 4
      ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs
  66. 2
      ErsatzTV.Core/Interfaces/Scheduling/IPlayoutTimeShifter.cs
  67. 2
      ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs
  68. 4
      ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs
  69. 1
      ErsatzTV.Core/PathUtils.cs
  70. 12
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs
  71. 3
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutChangeDetection.cs
  72. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs
  73. 2
      ErsatzTV.Core/Scheduling/BlockScheduling/EffectiveBlock.cs
  74. 32
      ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs
  75. 5
      ErsatzTV.Core/Scheduling/FillerExpression.cs
  76. 10
      ErsatzTV.Core/Scheduling/PlayoutBuildResult.cs
  77. 25
      ErsatzTV.Core/Scheduling/PlayoutBuilder.cs
  78. 14
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs
  79. 5
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs
  80. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs
  81. 5
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs
  82. 3
      ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs
  83. 6
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs
  84. 2
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs
  85. 4
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOffHandler.cs
  86. 4
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOnHandler.cs
  87. 3
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutMidRollHandler.cs
  88. 6
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutWatermarkHandler.cs
  89. 42
      ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs
  90. 10
      ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs
  91. 15
      ErsatzTV.Core/Troubleshooting/TroubleshootingNotifier.cs
  92. 5
      ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs
  93. 8
      ErsatzTV.FFmpeg/Capabilities/FourCC.cs
  94. 7
      ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs
  95. 6
      ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs
  96. 6
      ErsatzTV.FFmpeg/Capabilities/VideoToolbox/VideoToolboxUtil.cs
  97. 19
      ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs
  98. 2
      ErsatzTV.FFmpeg/Environment/CudaVisibleDevicesVariable.cs
  99. 5
      ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs
  100. 2
      ErsatzTV.FFmpeg/Filter/ComplexFilter.cs
  101. Some files were not shown because too many files have changed in this diff Show More

2
.config/dotnet-tools.json

@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
"isRoot": true,
"tools": {
"jetbrains.resharper.globaltools": {
"version": "2025.1.4",
"version": "2025.2.0",
"commands": [
"jb"
],

36
.editorconfig

@ -1,28 +1,11 @@ @@ -1,28 +1,11 @@
[*]
charset=utf-8
end_of_line=lf
trim_trailing_whitespace=true
insert_final_newline=false
insert_final_newline=true
indent_style=space
indent_size=4
[*.json]
ij_json_array_wrapping = normal
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
ij_json_keep_trailing_comma = false
ij_json_object_wrapping = normal
ij_json_property_alignment = do_not_align
ij_json_space_after_colon = true
ij_json_space_after_comma = true
ij_json_space_before_colon = false
ij_json_space_before_comma = false
ij_json_spaces_within_braces = true
ij_json_spaces_within_brackets = true
ij_json_wrap_long_lines = false
# Microsoft .NET properties
csharp_new_line_before_members_in_object_initializers=false
csharp_preferred_modifier_order=public, private, protected, internal, new, abstract, virtual, sealed, override, static, readonly, extern, unsafe, volatile, async:suggestion
@ -58,6 +41,8 @@ resharper_braces_for_for=required @@ -58,6 +41,8 @@ resharper_braces_for_for=required
resharper_braces_for_foreach=required
resharper_braces_for_ifelse=required
resharper_braces_for_while=required
resharper_csharp_arguments_literal=positional
resharper_csharp_arguments_named=positional
resharper_csharp_insert_final_newline=true
resharper_csharp_max_attribute_length_for_same_line=0
resharper_csharp_place_accessorholder_attribute_on_same_line=never
@ -100,6 +85,21 @@ tab_width=4 @@ -100,6 +85,21 @@ tab_width=4
indent_style = space
indent_size = 2
[*.json]
ij_json_array_wrapping = normal
ij_json_keep_blank_lines_in_code = 0
ij_json_keep_indents_on_empty_lines = false
ij_json_keep_line_breaks = true
ij_json_keep_trailing_comma = false
ij_json_object_wrapping = normal
ij_json_property_alignment = do_not_align
ij_json_space_after_colon = true
ij_json_space_after_comma = true
ij_json_space_before_colon = false
ij_json_space_before_comma = false
ij_json_spaces_within_braces = true
ij_json_spaces_within_brackets = true
ij_json_wrap_long_lines = false
[*.cs]
# disable CA1848: Use the LoggerMessage delegates`

2
ErsatzTV.Application/Channels/Queries/GetChannelPlaylistHandler.cs

@ -27,7 +27,7 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha @@ -27,7 +27,7 @@ public class GetChannelPlaylistHandler : IRequestHandler<GetChannelPlaylist, Cha
var result = new List<Channel>();
foreach (Channel channel in channels)
{
if (channel.IsEnabled == false)
if (!channel.IsEnabled)
{
continue;
}

16
ErsatzTV.Application/Emby/Commands/CallEmbyShowScannerHandler.cs

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
@ -5,8 +7,6 @@ using ErsatzTV.Core.Interfaces.Repositories; @@ -5,8 +7,6 @@ using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
using System.Threading.Channels;
namespace ErsatzTV.Application.Emby;
@ -67,16 +67,12 @@ public class CallEmbyShowScannerHandler : CallLibraryScannerHandler<SynchronizeE @@ -67,16 +67,12 @@ public class CallEmbyShowScannerHandler : CallLibraryScannerHandler<SynchronizeE
protected override Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
SynchronizeEmbyShowById request)
{
return Task.FromResult(DateTimeOffset.MinValue);
}
SynchronizeEmbyShowById request) =>
Task.FromResult(DateTimeOffset.MinValue);
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
SynchronizeEmbyShowById request)
{
return true;
}
SynchronizeEmbyShowById request) =>
true;
}

2
ErsatzTV.Application/Emby/Commands/UpdateEmbyLibraryPreferencesHandler.cs

@ -23,7 +23,7 @@ public class @@ -23,7 +23,7 @@ public class
UpdateEmbyLibraryPreferences request,
CancellationToken cancellationToken)
{
var toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id).ToList();
var toDisable = request.Preferences.Filter(p => !p.ShouldSyncItems).Map(p => p.Id).ToList();
List<int> ids = await _mediaSourceRepository.DisableEmbyLibrarySync(toDisable);
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();

2
ErsatzTV.Application/FFmpegProfiles/Commands/UpdateFFmpegSettingsHandler.cs

@ -97,7 +97,7 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings, @@ -97,7 +97,7 @@ public class UpdateFFmpegSettingsHandler : IRequestHandler<UpdateFFmpegSettings,
request.Settings.UseEmbeddedSubtitles);
// do not extract when subtitles are not used
if (request.Settings.UseEmbeddedSubtitles == false)
if (!request.Settings.UseEmbeddedSubtitles)
{
request.Settings.ExtractEmbeddedSubtitles = false;
}

3
ErsatzTV.Application/Filler/Commands/CreateFillerPresetHandler.cs

@ -39,7 +39,8 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit @@ -39,7 +39,8 @@ public class CreateFillerPresetHandler : IRequestHandler<CreateFillerPreset, Eit
SmartCollectionId = request.SmartCollectionId,
PlaylistId = request.PlaylistId,
Expression = request.FillerKind is FillerKind.MidRoll ? request.Expression : null,
UseChaptersAsMediaItems = request.FillerKind is not FillerKind.Fallback && request.UseChaptersAsMediaItems
UseChaptersAsMediaItems =
request.FillerKind is not FillerKind.Fallback && request.UseChaptersAsMediaItems
};
await dbContext.FillerPresets.AddAsync(fillerPreset, cancellationToken);

3
ErsatzTV.Application/Filler/Commands/UpdateFillerPresetHandler.cs

@ -39,7 +39,8 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit @@ -39,7 +39,8 @@ public class UpdateFillerPresetHandler : IRequestHandler<UpdateFillerPreset, Eit
existing.SmartCollectionId = request.SmartCollectionId;
existing.PlaylistId = request.PlaylistId;
existing.Expression = request.FillerKind is FillerKind.MidRoll ? request.Expression : null;
existing.UseChaptersAsMediaItems = request.FillerKind is not FillerKind.Fallback && request.UseChaptersAsMediaItems;
existing.UseChaptersAsMediaItems =
request.FillerKind is not FillerKind.Fallback && request.UseChaptersAsMediaItems;
await dbContext.SaveChangesAsync();

10
ErsatzTV.Application/Graphics/Commands/RefreshGraphicsElementsHandler.cs

@ -18,10 +18,10 @@ public class RefreshGraphicsElementsHandler( @@ -18,10 +18,10 @@ public class RefreshGraphicsElementsHandler(
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
// cleanup existing elements
var allExisting = await dbContext.GraphicsElements
List<GraphicsElement> allExisting = await dbContext.GraphicsElements
.ToListAsync(cancellationToken);
foreach (var existing in allExisting.Where(e => !localFileSystem.FileExists(e.Path)))
foreach (GraphicsElement existing in allExisting.Where(e => !localFileSystem.FileExists(e.Path)))
{
logger.LogWarning(
"Removing graphics element that references non-existing file {File}",
@ -35,7 +35,7 @@ public class RefreshGraphicsElementsHandler( @@ -35,7 +35,7 @@ public class RefreshGraphicsElementsHandler(
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
foreach (var path in newTextPaths)
foreach (string path in newTextPaths)
{
logger.LogDebug("Adding new graphics element from file {File}", path);
@ -53,7 +53,7 @@ public class RefreshGraphicsElementsHandler( @@ -53,7 +53,7 @@ public class RefreshGraphicsElementsHandler(
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
foreach (var path in newImagePaths)
foreach (string path in newImagePaths)
{
logger.LogDebug("Adding new graphics element from file {File}", path);
@ -71,7 +71,7 @@ public class RefreshGraphicsElementsHandler( @@ -71,7 +71,7 @@ public class RefreshGraphicsElementsHandler(
.Where(f => allExisting.All(e => e.Path != f))
.ToList();
foreach (var path in newSubtitlePaths)
foreach (string path in newSubtitlePaths)
{
logger.LogDebug("Adding new graphics element from file {File}", path);

2
ErsatzTV.Application/Graphics/Mapper.cs

@ -6,7 +6,7 @@ public static class Mapper @@ -6,7 +6,7 @@ public static class Mapper
{
public static GraphicsElementViewModel ProjectToViewModel(GraphicsElement graphicsElement)
{
var fileName = Path.GetFileName(graphicsElement.Path);
string fileName = Path.GetFileName(graphicsElement.Path);
return graphicsElement.Kind switch
{
GraphicsElementKind.Text => new GraphicsElementViewModel(graphicsElement.Id, $"text/{fileName}"),

16
ErsatzTV.Application/Jellyfin/Commands/CallJellyfinShowScannerHandler.cs

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
@ -5,8 +7,6 @@ using ErsatzTV.Core.Interfaces.Repositories; @@ -5,8 +7,6 @@ using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
using System.Threading.Channels;
namespace ErsatzTV.Application.Jellyfin;
@ -67,16 +67,12 @@ public class CallJellyfinShowScannerHandler : CallLibraryScannerHandler<Synchron @@ -67,16 +67,12 @@ public class CallJellyfinShowScannerHandler : CallLibraryScannerHandler<Synchron
protected override Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
SynchronizeJellyfinShowById request)
{
return Task.FromResult(DateTimeOffset.MinValue);
}
SynchronizeJellyfinShowById request) =>
Task.FromResult(DateTimeOffset.MinValue);
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
SynchronizeJellyfinShowById request)
{
return true;
}
SynchronizeJellyfinShowById request) =>
true;
}

2
ErsatzTV.Application/Jellyfin/Commands/UpdateJellyfinLibraryPreferencesHandler.cs

@ -23,7 +23,7 @@ public class @@ -23,7 +23,7 @@ public class
UpdateJellyfinLibraryPreferences request,
CancellationToken cancellationToken)
{
var toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id).ToList();
var toDisable = request.Preferences.Filter(p => !p.ShouldSyncItems).Map(p => p.Id).ToList();
List<int> ids = await _mediaSourceRepository.DisableJellyfinLibrarySync(toDisable);
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();

2
ErsatzTV.Application/Libraries/Commands/ILocalLibraryRequest.cs

@ -2,5 +2,5 @@ @@ -2,5 +2,5 @@
public interface ILocalLibraryRequest
{
public string Name { get; }
string Name { get; }
}

14
ErsatzTV.Application/Libraries/Commands/QueueShowScanByLibraryIdHandler.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using ErsatzTV.Application.Emby;
using ErsatzTV.Application.Jellyfin;
using ErsatzTV.Application.Plex;
using ErsatzTV.Core;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Locking;
using ErsatzTV.Infrastructure.Data;
@ -48,25 +49,28 @@ public class QueueShowScanByLibraryIdHandler( @@ -48,25 +49,28 @@ public class QueueShowScanByLibraryIdHandler(
return false;
}
logger.LogDebug("Queued show scan for library id {Id}, show: {ShowTitle}, deepScan: {DeepScan}",
library.Id, request.ShowTitle, request.DeepScan);
logger.LogDebug(
"Queued show scan for library id {Id}, show: {ShowTitle}, deepScan: {DeepScan}",
library.Id,
request.ShowTitle,
request.DeepScan);
try
{
switch (library)
{
case PlexLibrary:
var plexResult = await mediator.Send(
Either<BaseError, string> plexResult = await mediator.Send(
new SynchronizePlexShowById(library.Id, request.ShowId, request.DeepScan),
cancellationToken);
return plexResult.IsRight;
case JellyfinLibrary:
var jellyfinResult = await mediator.Send(
Either<BaseError, string> jellyfinResult = await mediator.Send(
new SynchronizeJellyfinShowById(library.Id, request.ShowId, request.DeepScan),
cancellationToken);
return jellyfinResult.IsRight;
case EmbyLibrary:
var embyResult = await mediator.Send(
Either<BaseError, string> embyResult = await mediator.Send(
new SynchronizeEmbyShowById(library.Id, request.ShowId, request.DeepScan),
cancellationToken);
return embyResult.IsRight;

2
ErsatzTV.Application/Libraries/Commands/UpdateLocalLibraryHandler.cs

@ -55,7 +55,7 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase, @@ -55,7 +55,7 @@ public class UpdateLocalLibraryHandler : LocalLibraryHandlerBase,
var toRemoveIds = toRemove.Map(lp => lp.Id).ToHashSet();
int changeCount = 0;
var changeCount = 0;
// save item ids first; will need to remove from search index
List<int> itemsToRemove = await dbContext.MediaItems

6
ErsatzTV.Application/MediaCollections/Commands/PreviewPlaylistPlayoutHandler.cs

@ -55,7 +55,11 @@ public class PreviewPlaylistPlayoutHandler( @@ -55,7 +55,11 @@ public class PreviewPlaylistPlayoutHandler(
// TODO: make an explicit method to preview, this is ugly
playoutBuilder.TrimStart = false;
playoutBuilder.DebugPlaylist = playout.ProgramSchedule.Items[0].Playlist;
var result = await playoutBuilder.Build(playout, referenceData, PlayoutBuildMode.Reset, cancellationToken);
PlayoutBuildResult result = await playoutBuilder.Build(
playout,
referenceData,
PlayoutBuildMode.Reset,
cancellationToken);
var maxItems = 0;
Dictionary<PlaylistItem, List<MediaItem>> map =

2
ErsatzTV.Application/MediaCollections/Queries/GetPlaylistItemsHandler.cs

@ -57,7 +57,7 @@ public class GetPlaylistItemsHandler(IDbContextFactory<TvContext> dbContextFacto @@ -57,7 +57,7 @@ public class GetPlaylistItemsHandler(IDbContextFactory<TvContext> dbContextFacto
.ThenInclude(mm => mm.Artwork)
.ToListAsync(cancellationToken);
if (allItems.All(bi => bi.IncludeInProgramGuide == false))
if (allItems.All(bi => !bi.IncludeInProgramGuide))
{
foreach (PlaylistItem bi in allItems)
{

2
ErsatzTV.Application/MediaItems/Queries/GetRemoteStreamById.cs

@ -1,5 +1,3 @@ @@ -1,5 +1,3 @@
using ErsatzTV.Core;
namespace ErsatzTV.Application.MediaItems;
public record GetRemoteStreamById(int RemoteStreamId) : IRequest<Option<RemoteStreamViewModel>>;

55
ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs

@ -13,6 +13,7 @@ using ErsatzTV.Core.Scheduling; @@ -13,6 +13,7 @@ using ErsatzTV.Core.Scheduling;
using ErsatzTV.Infrastructure.Data;
using ErsatzTV.Infrastructure.Extensions;
using Microsoft.EntityFrameworkCore;
using Channel = ErsatzTV.Core.Domain.Channel;
namespace ErsatzTV.Application.Playouts;
@ -23,10 +24,10 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -23,10 +24,10 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
private readonly IClient _client;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IEntityLocker _entityLocker;
private readonly IPlayoutTimeShifter _playoutTimeShifter;
private readonly IExternalJsonPlayoutBuilder _externalJsonPlayoutBuilder;
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
private readonly IPlayoutBuilder _playoutBuilder;
private readonly IPlayoutTimeShifter _playoutTimeShifter;
private readonly ChannelWriter<IBackgroundServiceRequest> _workerChannel;
private readonly IYamlPlayoutBuilder _yamlPlayoutBuilder;
@ -78,9 +79,9 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -78,9 +79,9 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
}
// after dbcontext is closed
foreach (var playoutBuildResult in result.RightToSeq())
foreach (PlayoutBuildResult playoutBuildResult in result.RightToSeq())
{
foreach (var timeShiftTo in playoutBuildResult.TimeShiftTo)
foreach (DateTimeOffset timeShiftTo in playoutBuildResult.TimeShiftTo)
{
await _playoutTimeShifter.TimeShift(request.PlayoutId, timeShiftTo, false);
}
@ -100,20 +101,28 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -100,20 +101,28 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
Playout playout,
CancellationToken cancellationToken)
{
string channelName = "[unknown]";
var channelName = "[unknown]";
try
{
var referenceData = await GetReferenceData(dbContext, playout.Id, playout.ProgramSchedulePlayoutType);
var channelNumber = referenceData.Channel.Number;
PlayoutReferenceData referenceData = await GetReferenceData(
dbContext,
playout.Id,
playout.ProgramSchedulePlayoutType);
string channelNumber = referenceData.Channel.Number;
channelName = referenceData.Channel.Name;
var result = PlayoutBuildResult.Empty;
PlayoutBuildResult result = PlayoutBuildResult.Empty;
switch (playout.ProgramSchedulePlayoutType)
{
case ProgramSchedulePlayoutType.Block:
result = await _blockPlayoutBuilder.Build(playout, referenceData, request.Mode, cancellationToken);
result = await _blockPlayoutFillerBuilder.Build(playout, referenceData, result, request.Mode, cancellationToken);
result = await _blockPlayoutFillerBuilder.Build(
playout,
referenceData,
result,
request.Mode,
cancellationToken);
break;
case ProgramSchedulePlayoutType.Yaml:
result = await _yamlPlayoutBuilder.Build(playout, referenceData, request.Mode, cancellationToken);
@ -128,7 +137,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -128,7 +137,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
break;
}
int changeCount = 0;
var changeCount = 0;
if (result.ClearItems)
{
@ -137,7 +146,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -137,7 +146,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.ExecuteDeleteAsync(cancellationToken);
}
foreach (var removeBefore in result.RemoveBefore)
foreach (DateTimeOffset removeBefore in result.RemoveBefore)
{
changeCount += await dbContext.PlayoutItems
.Where(pi => pi.PlayoutId == playout.Id)
@ -145,7 +154,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -145,7 +154,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.ExecuteDeleteAsync(cancellationToken);
}
foreach (var removeAfter in result.RemoveAfter)
foreach (DateTimeOffset removeAfter in result.RemoveAfter)
{
changeCount += await dbContext.PlayoutItems
.Where(pi => pi.PlayoutId == playout.Id)
@ -163,8 +172,10 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -163,8 +172,10 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
if (result.AddedItems.Count > 0)
{
changeCount += 1;
bool anyWatermarks = result.AddedItems.Any(i => i.PlayoutItemWatermarks is not null && i.PlayoutItemWatermarks.Count > 0);
bool anyGraphicsElements = result.AddedItems.Any(i => i.PlayoutItemGraphicsElements is not null && i.PlayoutItemGraphicsElements.Count > 0);
bool anyWatermarks = result.AddedItems.Any(i =>
i.PlayoutItemWatermarks is not null && i.PlayoutItemWatermarks.Count > 0);
bool anyGraphicsElements = result.AddedItems.Any(i =>
i.PlayoutItemGraphicsElements is not null && i.PlayoutItemGraphicsElements.Count > 0);
if (anyWatermarks || anyGraphicsElements)
{
// need to use slow ef core to also insert watermarks and graphics elements properly
@ -254,11 +265,11 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -254,11 +265,11 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
TvContext dbContext,
BuildPlayout buildPlayout)
{
var maybePlayout = await dbContext.Playouts
Option<Playout> maybePlayout = await dbContext.Playouts
.Include(p => p.Anchor)
.SelectOneAsync(p => p.Id, p => p.Id == buildPlayout.PlayoutId);
foreach (var playout in maybePlayout)
foreach (Playout playout in maybePlayout)
{
switch (playout.ProgramSchedulePlayoutType)
{
@ -267,7 +278,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -267,7 +278,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.Collection(p => p.FillGroupIndices)
.LoadAsync();
foreach (var fillGroupIndex in playout.FillGroupIndices)
foreach (PlayoutScheduleItemFillGroupIndex fillGroupIndex in playout.FillGroupIndices)
{
await dbContext.Entry(fillGroupIndex)
.Reference(fgi => fgi.EnumeratorState)
@ -278,7 +289,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -278,7 +289,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.Collection(p => p.ProgramScheduleAnchors)
.LoadAsync();
foreach (var anchor in playout.ProgramScheduleAnchors)
foreach (PlayoutProgramScheduleAnchor anchor in playout.ProgramScheduleAnchors)
{
await dbContext.Entry(anchor)
.Reference(a => a.EnumeratorState)
@ -297,12 +308,12 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -297,12 +308,12 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
int playoutId,
ProgramSchedulePlayoutType playoutType)
{
var channel = await dbContext.Channels
Channel channel = await dbContext.Channels
.AsNoTracking()
.Where(c => c.Playouts.Any(p => p.Id == playoutId))
.FirstOrDefaultAsync();
var deco = Option<Deco>.None;
Option<Deco> deco = Option<Deco>.None;
List<PlayoutItem> existingItems = [];
List<PlayoutTemplate> playoutTemplates = [];
@ -332,7 +343,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -332,7 +343,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.ToListAsync();
}
var programSchedule = await dbContext.ProgramSchedules
ProgramSchedule programSchedule = await dbContext.ProgramSchedules
.AsNoTracking()
.Where(ps => ps.Playouts.Any(p => p.Id == playoutId))
.Include(ps => ps.Items)
@ -354,7 +365,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -354,7 +365,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.ThenInclude(psi => psi.FallbackFiller)
.FirstOrDefaultAsync();
var programScheduleAlternates = await dbContext.ProgramScheduleAlternates
List<ProgramScheduleAlternate> programScheduleAlternates = await dbContext.ProgramScheduleAlternates
.AsNoTracking()
.Where(pt => pt.PlayoutId == playoutId)
.Include(a => a.ProgramSchedule)
@ -384,7 +395,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro @@ -384,7 +395,7 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
.ThenInclude(psi => psi.FallbackFiller)
.ToListAsync();
var playoutHistory = await dbContext.PlayoutHistory
List<PlayoutHistory> playoutHistory = await dbContext.PlayoutHistory
.AsNoTracking()
.Where(h => h.PlayoutId == playoutId)
.ToListAsync();

1
ErsatzTV.Application/Playouts/Commands/CreateBlockPlayoutHandler.cs

@ -33,6 +33,7 @@ public class CreateBlockPlayoutHandler( @@ -33,6 +33,7 @@ public class CreateBlockPlayoutHandler(
{
await channel.WriteAsync(new TimeShiftOnDemandPlayout(playout.Id, DateTimeOffset.Now, false));
}
await channel.WriteAsync(new RefreshChannelList());
return new CreatePlayoutResponse(playout.Id);
}

1
ErsatzTV.Application/Playouts/Commands/CreateYamlPlayoutHandler.cs

@ -46,6 +46,7 @@ public class CreateYamlPlayoutHandler @@ -46,6 +46,7 @@ public class CreateYamlPlayoutHandler
{
await _channel.WriteAsync(new TimeShiftOnDemandPlayout(playout.Id, DateTimeOffset.Now, false));
}
await _channel.WriteAsync(new RefreshChannelList());
return new CreatePlayoutResponse(playout.Id);
}

3
ErsatzTV.Application/Playouts/Mapper.cs

@ -76,7 +76,8 @@ internal static class Mapper @@ -76,7 +76,8 @@ internal static class Mapper
case Image i:
return i.ImageMetadata.HeadOrNone().Map(im => im.Title ?? string.Empty).IfNone("[unknown image]");
case RemoteStream rs:
return rs.RemoteStreamMetadata.HeadOrNone().Map(im => im.Title ?? string.Empty).IfNone("[unknown remote stream]");
return rs.RemoteStreamMetadata.HeadOrNone().Map(im => im.Title ?? string.Empty)
.IfNone("[unknown remote stream]");
default:
return string.Empty;
}

8
ErsatzTV.Application/Playouts/Queries/CheckForOverlappingPlayoutItemsHandler.cs

@ -1,3 +1,4 @@ @@ -1,3 +1,4 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@ -18,8 +19,7 @@ public class CheckForOverlappingPlayoutItemsHandler( @@ -18,8 +19,7 @@ public class CheckForOverlappingPlayoutItemsHandler(
.AnyAsync(
a => dbContext.PlayoutItems
.Where(b => b.PlayoutId == a.PlayoutId)
.Any(
b =>
.Any(b =>
a.Id < b.Id &&
a.Start < b.Finish &&
a.Finish > b.Start),
@ -27,13 +27,13 @@ public class CheckForOverlappingPlayoutItemsHandler( @@ -27,13 +27,13 @@ public class CheckForOverlappingPlayoutItemsHandler(
if (hasConflict)
{
var maybeChannel = await dbContext.Channels
Option<Channel> maybeChannel = await dbContext.Channels
.AsNoTracking()
.Where(c => c.Playouts.Any(p => p.Id == request.PlayoutId))
.FirstOrDefaultAsync(cancellationToken)
.Map(Optional);
foreach (var channel in maybeChannel)
foreach (Channel channel in maybeChannel)
{
logger.LogWarning(
"Playout for channel {ChannelName} has overlapping playout items; this may be a bug.",

16
ErsatzTV.Application/Plex/Commands/CallPlexShowScannerHandler.cs

@ -1,3 +1,5 @@ @@ -1,3 +1,5 @@
using System.Globalization;
using System.Threading.Channels;
using ErsatzTV.Application.Libraries;
using ErsatzTV.Core;
using ErsatzTV.Core.Errors;
@ -5,8 +7,6 @@ using ErsatzTV.Core.Interfaces.Repositories; @@ -5,8 +7,6 @@ using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.FFmpeg.Runtime;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using System.Globalization;
using System.Threading.Channels;
namespace ErsatzTV.Application.Plex;
@ -67,16 +67,12 @@ public class CallPlexShowScannerHandler : CallLibraryScannerHandler<SynchronizeP @@ -67,16 +67,12 @@ public class CallPlexShowScannerHandler : CallLibraryScannerHandler<SynchronizeP
protected override Task<DateTimeOffset> GetLastScan(
TvContext dbContext,
SynchronizePlexShowById request)
{
return Task.FromResult(DateTimeOffset.MinValue);
}
SynchronizePlexShowById request) =>
Task.FromResult(DateTimeOffset.MinValue);
protected override bool ScanIsRequired(
DateTimeOffset lastScan,
int libraryRefreshInterval,
SynchronizePlexShowById request)
{
return true;
}
SynchronizePlexShowById request) =>
true;
}

2
ErsatzTV.Application/Plex/Commands/UpdatePlexLibraryPreferencesHandler.cs

@ -23,7 +23,7 @@ public class @@ -23,7 +23,7 @@ public class
UpdatePlexLibraryPreferences request,
CancellationToken cancellationToken)
{
var toDisable = request.Preferences.Filter(p => p.ShouldSyncItems == false).Map(p => p.Id).ToList();
var toDisable = request.Preferences.Filter(p => !p.ShouldSyncItems).Map(p => p.Id).ToList();
List<int> ids = await _mediaSourceRepository.DisablePlexLibrarySync(toDisable);
await _searchIndex.RemoveItems(ids);
_searchIndex.Commit();

2
ErsatzTV.Application/ProgramSchedules/Commands/ProgramScheduleItemCommandBase.cs

@ -302,7 +302,7 @@ public abstract class ProgramScheduleItemCommandBase @@ -302,7 +302,7 @@ public abstract class ProgramScheduleItemCommandBase
_ => throw new NotSupportedException($"Unsupported playout mode {item.PlayoutMode}")
};
foreach (var watermarkId in item.WatermarkIds)
foreach (int watermarkId in item.WatermarkIds)
{
result.ProgramScheduleItemWatermarks ??= [];
result.ProgramScheduleItemWatermarks.Add(

12
ErsatzTV.Application/ProgramSchedules/Mapper.cs

@ -66,7 +66,8 @@ internal static class Mapper @@ -66,7 +66,8 @@ internal static class Mapper
duration.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(duration.FallbackFiller)
: null,
duration.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
duration.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark))
.ToList(),
duration.PreferredAudioLanguageCode,
duration.PreferredAudioTitle,
duration.PreferredSubtitleLanguageCode,
@ -117,7 +118,8 @@ internal static class Mapper @@ -117,7 +118,8 @@ internal static class Mapper
flood.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(flood.FallbackFiller)
: null,
flood.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
flood.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark))
.ToList(),
flood.PreferredAudioLanguageCode,
flood.PreferredAudioTitle,
flood.PreferredSubtitleLanguageCode,
@ -170,7 +172,8 @@ internal static class Mapper @@ -170,7 +172,8 @@ internal static class Mapper
multiple.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(multiple.FallbackFiller)
: null,
multiple.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
multiple.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark))
.ToList(),
multiple.PreferredAudioLanguageCode,
multiple.PreferredAudioTitle,
multiple.PreferredSubtitleLanguageCode,
@ -221,7 +224,8 @@ internal static class Mapper @@ -221,7 +224,8 @@ internal static class Mapper
one.FallbackFiller != null
? Filler.Mapper.ProjectToViewModel(one.FallbackFiller)
: null,
one.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark)).ToList(),
one.ProgramScheduleItemWatermarks.Map(wm => Watermarks.Mapper.ProjectToViewModel(wm.Watermark))
.ToList(),
one.PreferredAudioLanguageCode,
one.PreferredAudioTitle,
one.PreferredSubtitleLanguageCode,

8
ErsatzTV.Application/Scheduling/BlockViewModel.cs

@ -2,4 +2,10 @@ using ErsatzTV.Core.Domain.Scheduling; @@ -2,4 +2,10 @@ using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Application.Scheduling;
public record BlockViewModel(int Id, int GroupId, string GroupName, string Name, int Minutes, BlockStopScheduling StopScheduling);
public record BlockViewModel(
int Id,
int GroupId,
string GroupName,
string Name,
int Minutes,
BlockStopScheduling StopScheduling);

4
ErsatzTV.Application/Scheduling/Commands/CreateDecoTemplateGroupHandler.cs

@ -26,7 +26,9 @@ public class CreateDecoTemplateGroupHandler(IDbContextFactory<TvContext> dbConte @@ -26,7 +26,9 @@ public class CreateDecoTemplateGroupHandler(IDbContextFactory<TvContext> dbConte
return Mapper.ProjectToViewModel(decoDecoTemplateGroup);
}
private static Task<Validation<BaseError, DecoTemplateGroup>> Validate(TvContext dbContext, CreateDecoTemplateGroup request) =>
private static Task<Validation<BaseError, DecoTemplateGroup>> Validate(
TvContext dbContext,
CreateDecoTemplateGroup request) =>
ValidateName(dbContext, request).MapT(name => new DecoTemplateGroup { Name = name, DecoTemplates = [] });
private static async Task<Validation<BaseError, string>> ValidateName(

4
ErsatzTV.Application/Scheduling/Commands/CreateTemplateGroupHandler.cs

@ -26,7 +26,9 @@ public class CreateTemplateGroupHandler(IDbContextFactory<TvContext> dbContextFa @@ -26,7 +26,9 @@ public class CreateTemplateGroupHandler(IDbContextFactory<TvContext> dbContextFa
return Mapper.ProjectToViewModel(templateGroup);
}
private static Task<Validation<BaseError, TemplateGroup>> Validate(TvContext dbContext, CreateTemplateGroup request) =>
private static Task<Validation<BaseError, TemplateGroup>> Validate(
TvContext dbContext,
CreateTemplateGroup request) =>
ValidateName(dbContext, request).MapT(name => new TemplateGroup { Name = name, Templates = [] });
private static async Task<Validation<BaseError, string>> ValidateName(

9
ErsatzTV.Application/Scheduling/Commands/UpdateDecoHandler.cs

@ -32,10 +32,12 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -32,10 +32,12 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
if (request.WatermarkMode is DecoMode.Override)
{
// this is different than schedule item/playout item because we have to merge watermark ids
var toAdd = request.WatermarkIds.Where(id => existing.DecoWatermarks.All(wm => wm.WatermarkId != id));
var toRemove = existing.DecoWatermarks.Where(wm => !request.WatermarkIds.Contains(wm.WatermarkId));
IEnumerable<int> toAdd =
request.WatermarkIds.Where(id => existing.DecoWatermarks.All(wm => wm.WatermarkId != id));
IEnumerable<DecoWatermark> toRemove =
existing.DecoWatermarks.Where(wm => !request.WatermarkIds.Contains(wm.WatermarkId));
existing.DecoWatermarks.RemoveAll(toRemove.Contains);
foreach (var watermarkId in toAdd)
foreach (int watermarkId in toAdd)
{
existing.DecoWatermarks.Add(
new DecoWatermark
@ -75,6 +77,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -75,6 +77,7 @@ public class UpdateDecoHandler(IDbContextFactory<TvContext> dbContextFactory)
break;
}
}
existing.DefaultFillerTrimToFit = request.DefaultFillerTrimToFit;
// dead air fallback

7
ErsatzTV.Application/Scheduling/Mapper.cs

@ -11,7 +11,8 @@ internal static class Mapper @@ -11,7 +11,8 @@ internal static class Mapper
decoTemplateGroups.OrderBy(dtg => dtg.Name).Map(dtg => new TreeGroupViewModel(
dtg.Id,
dtg.Name,
dtg.DecoTemplates.OrderBy(dt => dt.Name).Map(dt => new TreeItemViewModel(dt.Id, dt.Name)).ToList())).ToList());
dtg.DecoTemplates.OrderBy(dt => dt.Name).Map(dt => new TreeItemViewModel(dt.Id, dt.Name)).ToList()))
.ToList());
internal static TreeViewModel ProjectToViewModel(List<DecoGroup> decoGroups) =>
new(
@ -32,7 +33,9 @@ internal static class Mapper @@ -32,7 +33,9 @@ internal static class Mapper
blockGroups.OrderBy(bg => bg.Name).Map(bg => new BlockTreeBlockGroupViewModel(
bg.Id,
bg.Name,
bg.Blocks.OrderBy(b => b.Name).Map(b => new BlockTreeBlockViewModel(b.Id, b.Name, b.Minutes)).ToList())).ToList());
bg.Blocks.OrderBy(b => b.Name).Map(b => new BlockTreeBlockViewModel(b.Id, b.Name, b.Minutes))
.ToList()))
.ToList());
internal static BlockGroupViewModel ProjectToViewModel(BlockGroup blockGroup) =>
new(blockGroup.Id, blockGroup.Name);

2
ErsatzTV.Application/Scheduling/Queries/GetBlockItemsHandler.cs

@ -33,7 +33,7 @@ public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory) @@ -33,7 +33,7 @@ public class GetBlockItemsHandler(IDbContextFactory<TvContext> dbContextFactory)
.ThenInclude(am => am.Artwork)
.ToListAsync(cancellationToken);
if (allItems.All(bi => bi.IncludeInProgramGuide == false))
if (allItems.All(bi => !bi.IncludeInProgramGuide))
{
foreach (BlockItem bi in allItems)
{

1
ErsatzTV.Application/Scheduling/Queries/GetDecoTemplateTreeHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using ErsatzTV.Application.Tree;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

1
ErsatzTV.Application/Scheduling/Queries/GetTemplateTreeHandler.cs

@ -1,5 +1,4 @@ @@ -1,5 +1,4 @@
using ErsatzTV.Application.Tree;
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;

3
ErsatzTV.Application/Search/Queries/QuerySearchIndexRemoteStreams.cs

@ -2,4 +2,5 @@ @@ -2,4 +2,5 @@
namespace ErsatzTV.Application.Search;
public record QuerySearchIndexRemoteStreams(string Query, int PageNumber, int PageSize) : IRequest<RemoteStreamCardResultsViewModel>;
public record QuerySearchIndexRemoteStreams(string Query, int PageNumber, int PageSize)
: IRequest<RemoteStreamCardResultsViewModel>;

2
ErsatzTV.Application/Streaming/Commands/StartFFmpegSessionHandler.cs

@ -21,8 +21,8 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit @@ -21,8 +21,8 @@ public class StartFFmpegSessionHandler : IRequestHandler<StartFFmpegSession, Eit
{
private readonly IClient _client;
private readonly IConfigElementRepository _configElementRepository;
private readonly IGraphicsEngine _graphicsEngine;
private readonly IFFmpegSegmenterService _ffmpegSegmenterService;
private readonly IGraphicsEngine _graphicsEngine;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly IHostApplicationLifetime _hostApplicationLifetime;
private readonly ILocalFileSystem _localFileSystem;

27
ErsatzTV.Application/Streaming/HlsSessionWorker.cs

@ -24,9 +24,9 @@ namespace ErsatzTV.Application.Streaming; @@ -24,9 +24,9 @@ namespace ErsatzTV.Application.Streaming;
public class HlsSessionWorker : IHlsSessionWorker
{
private static int _workAheadCount;
private readonly IGraphicsEngine _graphicsEngine;
private readonly IClient _client;
private readonly IConfigElementRepository _configElementRepository;
private readonly IGraphicsEngine _graphicsEngine;
private readonly IHlsPlaylistFilter _hlsPlaylistFilter;
private readonly ILocalFileSystem _localFileSystem;
private readonly ILogger<HlsSessionWorker> _logger;
@ -36,6 +36,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -36,6 +36,7 @@ public class HlsSessionWorker : IHlsSessionWorker
private readonly Option<int> _targetFramerate;
private CancellationTokenSource _cancellationTokenSource;
private string _channelNumber;
private DateTimeOffset _channelStart;
private bool _disposedValue;
private bool _hasWrittenSegments;
private DateTimeOffset _lastAccess;
@ -44,7 +45,6 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -44,7 +45,6 @@ public class HlsSessionWorker : IHlsSessionWorker
private HlsSessionState _state;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private DateTimeOffset _channelStart;
public HlsSessionWorker(
IServiceScopeFactory serviceScopeFactory,
@ -152,7 +152,10 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -152,7 +152,10 @@ public class HlsSessionWorker : IHlsSessionWorker
GC.SuppressFinalize(this);
}
public async Task Run(string channelNumber, Option<TimeSpan> idleTimeout, CancellationToken incomingCancellationToken)
public async Task Run(
string channelNumber,
Option<TimeSpan> idleTimeout,
CancellationToken incomingCancellationToken)
{
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(incomingCancellationToken);
@ -160,7 +163,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -160,7 +163,7 @@ public class HlsSessionWorker : IHlsSessionWorker
{
_channelNumber = channelNumber;
foreach (var timeout in idleTimeout)
foreach (TimeSpan timeout in idleTimeout)
{
lock (_sync)
{
@ -183,12 +186,12 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -183,12 +186,12 @@ public class HlsSessionWorker : IHlsSessionWorker
PlaylistStart = _transcodedUntil;
_channelStart = _transcodedUntil;
var maybePlayoutId = await _mediator.Send(
Option<int> maybePlayoutId = await _mediator.Send(
new GetPlayoutIdByChannelNumber(_channelNumber),
cancellationToken);
// time shift on-demand playout if needed
foreach (var playoutId in maybePlayoutId)
foreach (int playoutId in maybePlayoutId)
{
await _mediator.Send(
new TimeShiftOnDemandPlayout(playoutId, _transcodedUntil, true),
@ -205,7 +208,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -205,7 +208,7 @@ public class HlsSessionWorker : IHlsSessionWorker
while (!cancellationToken.IsCancellationRequested)
{
foreach (var timeout in idleTimeout)
foreach (TimeSpan timeout in idleTimeout)
{
if (DateTimeOffset.Now - _lastAccess > timeout)
{
@ -461,7 +464,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -461,7 +464,7 @@ public class HlsSessionWorker : IHlsSessionWorker
{
await TrimAndDelete(cancellationToken);
var maybePipe = Option<Pipe>.None;
Option<Pipe> maybePipe = Option<Pipe>.None;
var stdErrBuffer = new StringBuilder();
Command process = processModel.Process;
@ -472,8 +475,8 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -472,8 +475,8 @@ public class HlsSessionWorker : IHlsSessionWorker
{
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var processWithPipe = process;
foreach (var graphicsEngineContext in processModel.GraphicsEngineContext)
Command processWithPipe = process;
foreach (GraphicsEngineContext graphicsEngineContext in processModel.GraphicsEngineContext)
{
var pipe = new Pipe();
maybePipe = pipe;
@ -505,7 +508,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -505,7 +508,7 @@ public class HlsSessionWorker : IHlsSessionWorker
await linkedCts.CancelAsync();
// detect the non-zero exit code and transcode the ffmpeg error message instead
string errorMessage = stdErrBuffer.ToString();
var errorMessage = stdErrBuffer.ToString();
if (string.IsNullOrWhiteSpace(errorMessage))
{
errorMessage = $"Unknown FFMPEG error; exit code {commandResult.ExitCode}";
@ -563,7 +566,7 @@ public class HlsSessionWorker : IHlsSessionWorker @@ -563,7 +566,7 @@ public class HlsSessionWorker : IHlsSessionWorker
}
finally
{
foreach (var pipe in maybePipe)
foreach (Pipe pipe in maybePipe)
{
await pipe.Writer.CompleteAsync();
}

13
ErsatzTV.Application/Streaming/HlsSessionWorkerV2.cs

@ -28,6 +28,7 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -28,6 +28,7 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
private readonly Option<int> _targetFramerate;
private CancellationTokenSource _cancellationTokenSource;
private string _channelNumber;
private DateTimeOffset _channelStart;
private bool _disposedValue;
private DateTimeOffset _lastAccess;
private Option<PlayoutItemProcessModel> _lastProcessModel;
@ -35,7 +36,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -35,7 +36,6 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
private HlsSessionState _state;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private DateTimeOffset _channelStart;
public HlsSessionWorkerV2(
IServiceScopeFactory serviceScopeFactory,
@ -99,7 +99,10 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -99,7 +99,10 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
GC.SuppressFinalize(this);
}
public async Task Run(string channelNumber, Option<TimeSpan> idleTimeout, CancellationToken incomingCancellationToken)
public async Task Run(
string channelNumber,
Option<TimeSpan> idleTimeout,
CancellationToken incomingCancellationToken)
{
_cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(incomingCancellationToken);
@ -107,7 +110,7 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -107,7 +110,7 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
{
_channelNumber = channelNumber;
foreach (var timeout in idleTimeout)
foreach (TimeSpan timeout in idleTimeout)
{
lock (_sync)
{
@ -130,12 +133,12 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker @@ -130,12 +133,12 @@ public class HlsSessionWorkerV2 : IHlsSessionWorker
PlaylistStart = _transcodedUntil;
_channelStart = _transcodedUntil;
var maybePlayoutId = await _mediator.Send(
Option<int> maybePlayoutId = await _mediator.Send(
new GetPlayoutIdByChannelNumber(_channelNumber),
cancellationToken);
// time shift on-demand playout if needed
foreach (var playoutId in maybePlayoutId)
foreach (int playoutId in maybePlayoutId)
{
await _mediator.Send(
new TimeShiftOnDemandPlayout(playoutId, _transcodedUntil, true),

1
ErsatzTV.Application/Streaming/PtsTime.cs

@ -14,6 +14,7 @@ public record PtsTime(long Value) @@ -14,6 +14,7 @@ public record PtsTime(long Value)
{
ptsTime += duration;
}
return new PtsTime(ptsTime);
}
}

5
ErsatzTV.Application/Streaming/Queries/GetLastPtsTimeHandler.cs

@ -60,7 +60,10 @@ public class GetLastPtsTimeHandler : IRequestHandler<GetLastPtsTime, Either<Base @@ -60,7 +60,10 @@ public class GetLastPtsTimeHandler : IRequestHandler<GetLastPtsTime, Either<Base
return BaseError.New($"Failed to determine last pts duration for channel {parameters.ChannelNumber}");
}
private async Task<Option<PtsTime>> GetPts(RequestParameters parameters, FileInfo segment, CancellationToken cancellationToken)
private async Task<Option<PtsTime>> GetPts(
RequestParameters parameters,
FileInfo segment,
CancellationToken cancellationToken)
{
string[] argumentList =
{

7
ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs

@ -290,7 +290,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -290,7 +290,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
disableWatermarks = false;
playoutItemWatermarks.Clear();
playoutItemWatermarks.Add(new ChannelWatermark
playoutItemWatermarks.Add(
new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
@ -355,7 +356,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< @@ -355,7 +356,9 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
channel.FFmpegProfile.VaapiDevice,
Optional(channel.FFmpegProfile.QsvExtraHardwareFrames),
hlsRealtime: request.HlsRealtime,
playoutItemWithPath.PlayoutItem.MediaItem is RemoteStream { IsLive: true } ? StreamInputKind.Live : StreamInputKind.Vod,
playoutItemWithPath.PlayoutItem.MediaItem is RemoteStream { IsLive: true }
? StreamInputKind.Live
: StreamInputKind.Vod,
playoutItemWithPath.PlayoutItem.FillerKind,
inPoint,
outPoint,

4
ErsatzTV.Application/Subtitles/Commands/ExtractEmbeddedSubtitlesHandler.cs

@ -280,7 +280,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu @@ -280,7 +280,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
.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) ||
.Filter(s => !s.IsExtracted || string.IsNullOrWhiteSpace(s.Path) ||
FileDoesntExist(mediaItem.Id, s));
// find cache paths for each subtitle
@ -347,7 +347,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu @@ -347,7 +347,7 @@ public class ExtractEmbeddedSubtitlesHandler : IRequestHandler<ExtractEmbeddedSu
{
foreach (string path in GetRelativeOutputPath(mediaItemId, subtitle))
{
return _localFileSystem.FileExists(path) == false;
return !_localFileSystem.FileExists(path);
}
return false;

2
ErsatzTV.Application/Television/Queries/GetTelevisionShowByIdHandler.cs

@ -9,8 +9,8 @@ namespace ErsatzTV.Application.Television; @@ -9,8 +9,8 @@ namespace ErsatzTV.Application.Television;
public class GetTelevisionShowByIdHandler : IRequestHandler<GetTelevisionShowById, Option<TelevisionShowViewModel>>
{
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly IDbContextFactory<TvContext> _dbContextFactory;
private readonly IMediaSourceRepository _mediaSourceRepository;
private readonly ISearchRepository _searchRepository;
public GetTelevisionShowByIdHandler(

27
ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs

@ -34,12 +34,16 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -34,12 +34,16 @@ public class PrepareTroubleshootingPlaybackHandler(
ILogger<PrepareTroubleshootingPlaybackHandler> logger)
: IRequestHandler<PrepareTroubleshootingPlayback, Either<BaseError, PlayoutItemResult>>
{
public async Task<Either<BaseError, PlayoutItemResult>> Handle(PrepareTroubleshootingPlayback request, CancellationToken cancellationToken)
public async Task<Either<BaseError, PlayoutItemResult>> Handle(
PrepareTroubleshootingPlayback request,
CancellationToken cancellationToken)
{
try
{
await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(cancellationToken);
Validation<BaseError, Tuple<MediaItem, string, string, FFmpegProfile>> validation = await Validate(dbContext, request);
Validation<BaseError, Tuple<MediaItem, string, string, FFmpegProfile>> validation = await Validate(
dbContext,
request);
return await validation.Match(
tuple => GetProcess(dbContext, request, tuple.Item1, tuple.Item2, tuple.Item3, tuple.Item4),
error => Task.FromResult<Either<BaseError, PlayoutItemResult>>(error.Join()));
@ -96,7 +100,7 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -96,7 +100,7 @@ public class PrepareTroubleshootingPlaybackHandler(
List<ChannelWatermark> watermarks = [];
if (request.WatermarkIds.Count > 0)
{
var channelWatermarks = await dbContext.ChannelWatermarks
List<ChannelWatermark> channelWatermarks = await dbContext.ChannelWatermarks
.Where(w => request.WatermarkIds.Contains(w.Id))
.ToListAsync();
@ -126,7 +130,8 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -126,7 +130,8 @@ public class PrepareTroubleshootingPlaybackHandler(
string image = is43 ? "song_progress_overlay_43.png" : "song_progress_overlay.png";
watermarks.Clear();
watermarks.Add(new ChannelWatermark
watermarks.Add(
new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
@ -156,7 +161,7 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -156,7 +161,7 @@ public class PrepareTroubleshootingPlaybackHandler(
TimeSpan outPoint = duration;
if (!hlsRealtime)
{
foreach (var seekSeconds in request.SeekSeconds)
foreach (int seekSeconds in request.SeekSeconds)
{
inPoint = TimeSpan.FromSeconds(seekSeconds);
if (inPoint > version.Duration)
@ -173,7 +178,7 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -173,7 +178,7 @@ public class PrepareTroubleshootingPlaybackHandler(
}
}
var graphicsElements = await dbContext.GraphicsElements
List<GraphicsElement> graphicsElements = await dbContext.GraphicsElements
.Where(ge => request.GraphicsElementIds.Contains(ge.Id))
.ToListAsync();
@ -216,7 +221,9 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -216,7 +221,9 @@ public class PrepareTroubleshootingPlaybackHandler(
return playoutItemResult;
}
private static async Task<List<Subtitle>> GetSelectedSubtitle(MediaItem mediaItem, PrepareTroubleshootingPlayback request)
private static async Task<List<Subtitle>> GetSelectedSubtitle(
MediaItem mediaItem,
PrepareTroubleshootingPlayback request)
{
if (request.SubtitleId is not null)
{
@ -268,9 +275,8 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -268,9 +275,8 @@ public class PrepareTroubleshootingPlaybackHandler(
private static async Task<Validation<BaseError, MediaItem>> MediaItemMustExist(
TvContext dbContext,
PrepareTroubleshootingPlayback request)
{
return await dbContext.MediaItems
PrepareTroubleshootingPlayback request) =>
await dbContext.MediaItems
.AsNoTracking()
.Include(mi => (mi as Episode).EpisodeMetadata)
.ThenInclude(em => em.Subtitles)
@ -325,7 +331,6 @@ public class PrepareTroubleshootingPlaybackHandler( @@ -325,7 +331,6 @@ public class PrepareTroubleshootingPlaybackHandler(
.Include(mi => (mi as RemoteStream).RemoteStreamMetadata)
.SelectOneAsync(mi => mi.Id, mi => mi.Id == request.MediaItemId)
.Map(o => o.ToValidation<BaseError>(new UnableToLocatePlayoutItem()));
}
private static Task<Validation<BaseError, string>> FFmpegPathMustExist(TvContext dbContext) =>
dbContext.ConfigElements.GetValue<string>(ConfigElementKey.FFmpegPath)

19
ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs

@ -70,8 +70,8 @@ public class StartTroubleshootingPlaybackHandler( @@ -70,8 +70,8 @@ public class StartTroubleshootingPlaybackHandler(
cancellationToken);
}
if (hwAccel is HardwareAccelerationKind.Vaapi || (hwAccel is HardwareAccelerationKind.Qsv &&
runtimeInfo.IsOSPlatform(OSPlatform.Linux)))
if (hwAccel is HardwareAccelerationKind.Vaapi || hwAccel is HardwareAccelerationKind.Qsv &&
runtimeInfo.IsOSPlatform(OSPlatform.Linux))
{
await File.WriteAllTextAsync(
Path.Combine(FileSystemLayout.TranscodeTroubleshootingFolder, "capabilities_vaapi.txt"),
@ -87,20 +87,23 @@ public class StartTroubleshootingPlaybackHandler( @@ -87,20 +87,23 @@ public class StartTroubleshootingPlaybackHandler(
cancellationToken);
}
logger.LogDebug("ffmpeg troubleshooting arguments {FFmpegArguments}", request.PlayoutItemResult.Process.Arguments);
logger.LogDebug(
"ffmpeg troubleshooting arguments {FFmpegArguments}",
request.PlayoutItemResult.Process.Arguments);
var maybePipe = Option<Pipe>.None;
Option<Pipe> maybePipe = Option<Pipe>.None;
try
{
using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
var processWithPipe = request.PlayoutItemResult.Process;
foreach (var graphicsEngineContext in request.PlayoutItemResult.GraphicsEngineContext)
Command processWithPipe = request.PlayoutItemResult.Process;
foreach (GraphicsEngineContext graphicsEngineContext in request.PlayoutItemResult.GraphicsEngineContext)
{
var pipe = new Pipe();
maybePipe = pipe;
processWithPipe = processWithPipe.WithStandardInputPipe(PipeSource.FromStream(pipe.Reader.AsStream()));
processWithPipe =
processWithPipe.WithStandardInputPipe(PipeSource.FromStream(pipe.Reader.AsStream()));
// fire and forget graphics engine task
_ = graphicsEngine.Run(
@ -136,7 +139,7 @@ public class StartTroubleshootingPlaybackHandler( @@ -136,7 +139,7 @@ public class StartTroubleshootingPlaybackHandler(
}
finally
{
foreach (var pipe in maybePipe)
foreach (Pipe pipe in maybePipe)
{
await pipe.Writer.CompleteAsync();
}

10
ErsatzTV.Application/Troubleshooting/Queries/GetTroubleshootingInfoHandler.cs

@ -135,23 +135,25 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI @@ -135,23 +135,25 @@ public class GetTroubleshootingInfoHandler : IRequestHandler<GetTroubleshootingI
if (_runtimeInfo.IsOSPlatform(OSPlatform.OSX))
{
var decoders = _hardwareCapabilitiesFactory.GetVideoToolboxDecoders();
List<string> decoders = _hardwareCapabilitiesFactory.GetVideoToolboxDecoders();
videoToolboxCapabilities.AppendLine("VideoToolbox Decoders: ");
videoToolboxCapabilities.AppendLine();
foreach (var decoder in decoders)
foreach (string decoder in decoders)
{
videoToolboxCapabilities.AppendLine(CultureInfo.InvariantCulture, $"\t{decoder}");
}
videoToolboxCapabilities.AppendLine();
videoToolboxCapabilities.AppendLine();
var encoders = _hardwareCapabilitiesFactory.GetVideoToolboxEncoders();
List<string> encoders = _hardwareCapabilitiesFactory.GetVideoToolboxEncoders();
videoToolboxCapabilities.AppendLine("VideoToolbox Encoders: ");
videoToolboxCapabilities.AppendLine();
foreach (var encoder in encoders)
foreach (string encoder in encoders)
{
videoToolboxCapabilities.AppendLine(CultureInfo.InvariantCulture, $"\t{encoder}");
}
videoToolboxCapabilities.AppendLine();
videoToolboxCapabilities.AppendLine();
}

138
ErsatzTV.Core.Tests/FFmpeg/CustomStreamSelectorTests.cs

@ -58,7 +58,11 @@ public class CustomStreamSelectorTests @@ -58,7 +58,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -83,7 +87,11 @@ public class CustomStreamSelectorTests @@ -83,7 +87,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -108,7 +116,11 @@ public class CustomStreamSelectorTests @@ -108,7 +116,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -139,7 +151,11 @@ public class CustomStreamSelectorTests @@ -139,7 +151,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -165,7 +181,11 @@ public class CustomStreamSelectorTests @@ -165,7 +181,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -192,7 +212,11 @@ public class CustomStreamSelectorTests @@ -192,7 +212,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -219,7 +243,11 @@ public class CustomStreamSelectorTests @@ -219,7 +243,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeFalse();
}
@ -241,7 +269,11 @@ public class CustomStreamSelectorTests @@ -241,7 +269,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -269,7 +301,11 @@ public class CustomStreamSelectorTests @@ -269,7 +301,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -303,7 +339,11 @@ public class CustomStreamSelectorTests @@ -303,7 +339,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -334,7 +374,11 @@ public class CustomStreamSelectorTests @@ -334,7 +374,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -367,7 +411,11 @@ public class CustomStreamSelectorTests @@ -367,7 +411,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -481,7 +529,11 @@ public class CustomStreamSelectorTests @@ -481,7 +529,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -509,7 +561,11 @@ public class CustomStreamSelectorTests @@ -509,7 +561,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -539,7 +595,11 @@ public class CustomStreamSelectorTests @@ -539,7 +595,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -569,7 +629,11 @@ public class CustomStreamSelectorTests @@ -569,7 +629,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -596,7 +660,11 @@ public class CustomStreamSelectorTests @@ -596,7 +660,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -623,7 +691,11 @@ public class CustomStreamSelectorTests @@ -623,7 +691,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -650,7 +722,11 @@ public class CustomStreamSelectorTests @@ -650,7 +722,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -677,7 +753,11 @@ public class CustomStreamSelectorTests @@ -677,7 +753,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -703,7 +783,11 @@ public class CustomStreamSelectorTests @@ -703,7 +783,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeTrue();
@ -730,7 +814,11 @@ public class CustomStreamSelectorTests @@ -730,7 +814,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.Subtitle.IsSome.ShouldBeTrue();
@ -758,7 +846,11 @@ public class CustomStreamSelectorTests @@ -758,7 +846,11 @@ public class CustomStreamSelectorTests
new FakeLocalFileSystem([new FakeFileEntry(TestFileName) { Contents = yaml }]),
new NullLogger<CustomStreamSelector>());
StreamSelectorResult result = await streamSelector.SelectStreams(_channel, DateTimeOffset.Now, _audioVersion, _subtitles);
StreamSelectorResult result = await streamSelector.SelectStreams(
_channel,
DateTimeOffset.Now,
_audioVersion,
_subtitles);
result.AudioStream.IsSome.ShouldBeFalse();
result.Subtitle.IsSome.ShouldBeFalse();

115
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ContinuePlayoutTests.cs

@ -28,7 +28,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -28,7 +28,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(1);
@ -73,7 +80,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -73,7 +80,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(1);
@ -119,7 +133,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -119,7 +133,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromDays(1);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(4);
result.AddedItems.Map(i => i.MediaItemId).ToList().ShouldBe([1, 2, 1, 2]);
@ -156,7 +177,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -156,7 +177,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start2 = HoursAfterMidnight(1);
DateTimeOffset finish2 = start2 + TimeSpan.FromDays(1);
result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Continue, start2, finish2, CancellationToken);
result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue,
start2,
finish2,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems[0].StartOffset.ShouldBe(finish);
@ -171,7 +199,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -171,7 +199,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start3 = HoursAfterMidnight(2);
DateTimeOffset finish3 = start3 + TimeSpan.FromDays(1);
result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Continue, start3, finish3, CancellationToken);
result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue,
start3,
finish3,
CancellationToken);
result.AddedItems.Count.ShouldBe(0);
@ -196,8 +231,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -196,8 +231,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
playout.ProgramScheduleAnchors.Count.ShouldBe(1);
@ -235,11 +276,19 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -235,11 +276,19 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
mediaItems.Add(TestMovie(i, TimeSpan.FromMinutes(55), DateTime.Today.AddHours(i)));
}
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) = TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle);
(PlayoutBuilder builder, Playout playout, PlayoutReferenceData referenceData) =
TestDataFloodForItems(mediaItems, PlaybackOrder.Shuffle);
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5);
DateTimeOffset finish = start + TimeSpan.FromDays(2);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(53);
playout.ProgramScheduleAnchors.Count.ShouldBe(2);
@ -301,7 +350,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -301,7 +350,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
playout.ProgramScheduleAnchors.Count.ShouldBe(2);
@ -346,7 +402,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -346,7 +402,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0).AddSeconds(5);
DateTimeOffset finish = start + TimeSpan.FromDays(2);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(53);
playout.ProgramScheduleAnchors.Count.ShouldBe(4);
@ -482,8 +545,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -482,8 +545,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(32);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(5);
@ -595,8 +664,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -595,8 +664,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(4);
@ -707,8 +782,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase @@ -707,8 +782,14 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
localFileSystem,
Logger);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Continue,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(5);

2
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/GetStartTimeAfterTests.cs

@ -1,7 +1,7 @@ @@ -1,7 +1,7 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Scheduling;
using Shouldly;
using NUnit.Framework;
using Shouldly;
namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling;

220
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/NewPlayoutTests.cs

@ -46,8 +46,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -46,8 +46,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(2);
@ -102,8 +108,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -102,8 +108,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(2);
@ -158,8 +170,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -158,8 +170,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(2);
@ -189,8 +207,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -189,8 +207,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().StartOffset.ShouldBe(start);
@ -219,8 +243,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -219,8 +243,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().StartOffset.ShouldBe(start);
@ -242,8 +272,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -242,8 +272,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().StartOffset.ShouldBe(start);
@ -266,8 +302,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -266,8 +302,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(1);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(2);
result.AddedItems[0].StartOffset.ShouldBe(midnight);
@ -291,8 +333,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -291,8 +333,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(4);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(4);
result.AddedItems[0].StartOffset.ShouldBe(start);
@ -321,8 +369,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -321,8 +369,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(1);
@ -367,8 +421,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -367,8 +421,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(1);
result.AddedItems.Head().MediaItemId.ShouldBe(1);
@ -482,8 +542,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -482,8 +542,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(5);
result.AddedItems[0].StartOffset.ShouldBe(start);
@ -582,8 +648,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -582,8 +648,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(30);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(28);
result.AddedItems[0].StartOffset.ShouldBe(start);
@ -733,8 +805,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -733,8 +805,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(7);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
@ -843,8 +921,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -843,8 +921,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(7);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
@ -952,8 +1036,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -952,8 +1036,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(24);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
@ -1062,8 +1152,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1062,8 +1152,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(7);
@ -1177,8 +1273,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1177,8 +1273,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
@ -1292,8 +1394,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1292,8 +1394,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(5);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(5);
@ -1416,8 +1524,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1416,8 +1524,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(12);
@ -1533,8 +1647,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1533,8 +1647,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(1);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(2);
@ -1616,8 +1736,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1616,8 +1736,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
@ -1654,8 +1780,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase @@ -1654,8 +1780,14 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromDays(2);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(8);
result.AddedItems[0].MediaItemId.ShouldBe(1);

24
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/PlayoutBuilderTestBase.cs

@ -16,9 +16,6 @@ namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling; @@ -16,9 +16,6 @@ namespace ErsatzTV.Core.Tests.Scheduling.ClassicScheduling;
public abstract class PlayoutBuilderTestBase
{
[SetUp]
public void SetUp() => CancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
protected readonly ILogger<PlayoutBuilder> Logger;
protected CancellationToken CancellationToken;
@ -35,6 +32,9 @@ public abstract class PlayoutBuilderTestBase @@ -35,6 +32,9 @@ public abstract class PlayoutBuilderTestBase
Logger = loggerFactory.CreateLogger<PlayoutBuilder>();
}
[SetUp]
public void SetUp() => CancellationToken = new CancellationTokenSource(TimeSpan.FromSeconds(10)).Token;
protected static DateTimeOffset HoursAfterMidnight(int hours)
{
DateTimeOffset now = DateTimeOffset.Now;
@ -88,7 +88,14 @@ public abstract class PlayoutBuilderTestBase @@ -88,7 +88,14 @@ public abstract class PlayoutBuilderTestBase
FillGroupIndices = []
};
var referenceData = new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
var referenceData = new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[]);
return new TestData(builder, playout, referenceData);
}
@ -194,7 +201,14 @@ public abstract class PlayoutBuilderTestBase @@ -194,7 +201,14 @@ public abstract class PlayoutBuilderTestBase
FillGroupIndices = []
};
var referenceData = new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
var referenceData = new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[]);
return new TestData(builder, playout, referenceData);
}

18
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/RefreshPlayoutTests.cs

@ -86,7 +86,14 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase @@ -86,7 +86,14 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
Playout = playout
});
var referenceData = new PlayoutReferenceData(playout.Channel, Option<Deco>.None, [], [], playout.ProgramSchedule, [], []);
var referenceData = new PlayoutReferenceData(
playout.Channel,
Option<Deco>.None,
[],
[],
playout.ProgramSchedule,
[],
[]);
IConfigElementRepository configRepo = Substitute.For<IConfigElementRepository>();
var televisionRepo = new FakeTelevisionRepository();
@ -106,7 +113,14 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase @@ -106,7 +113,14 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(24);
DateTimeOffset finish = start + TimeSpan.FromDays(1);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Refresh, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Refresh,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(4);
result.AddedItems[0].MediaItemId.ShouldBe(2);

20
ErsatzTV.Core.Tests/Scheduling/ClassicScheduling/ResetPlayoutTests.cs

@ -26,8 +26,14 @@ public class ResetPlayoutTests : PlayoutBuilderTestBase @@ -26,8 +26,14 @@ public class ResetPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start = HoursAfterMidnight(0);
DateTimeOffset finish = start + TimeSpan.FromHours(6);
PlayoutBuildResult result = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start, finish, CancellationToken);
PlayoutBuildResult result = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
CancellationToken);
result.AddedItems.Count.ShouldBe(6);
playout.Anchor.NextStartOffset.ShouldBe(finish);
@ -40,8 +46,14 @@ public class ResetPlayoutTests : PlayoutBuilderTestBase @@ -40,8 +46,14 @@ public class ResetPlayoutTests : PlayoutBuilderTestBase
DateTimeOffset start2 = HoursAfterMidnight(0);
DateTimeOffset finish2 = start2 + TimeSpan.FromHours(6);
PlayoutBuildResult result2 = await builder.Build(playout, referenceData, PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset, start2, finish2, CancellationToken);
PlayoutBuildResult result2 = await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start2,
finish2,
CancellationToken);
result2.AddedItems.Count.ShouldBe(6);
playout.Anchor.NextStartOffset.ShouldBe(finish);

3
ErsatzTV.Core.Tests/Scheduling/FillerExpressionTests.cs

@ -64,7 +64,8 @@ public class FillerExpressionTests @@ -64,7 +64,8 @@ public class FillerExpressionTests
var fillerPreset = new FillerPreset
{
FillerKind = FillerKind.MidRoll,
Expression = "(total_progress >= 0.2 and matched_points = 0) or (total_progress >= 0.6 and matched_points = 1)"
Expression =
"(total_progress >= 0.2 and matched_points = 0) or (total_progress >= 0.6 and matched_points = 1)"
};
List<MediaChapter> result = FillerExpression.FilterChapters(fillerPreset.Expression, chapters, playoutItem);

6
ErsatzTV.Core.Tests/Scheduling/MultiPartEpisodeGrouperTests.cs

@ -38,7 +38,6 @@ public class MultiPartEpisodeGrouperTests @@ -38,7 +38,6 @@ public class MultiPartEpisodeGrouperTests
[TestCase("Episode 1 Part One", "Episode 2 (II)", "Episode 3")]
[TestCase("Episode 1 (Part One)", "Episode 2 (II)", "Episode 3")]
[TestCase("Episode 1 (1)", "Episode 2 (Part 2)", "Episode 3")]
public void MixedNaming_Group(string one, string two, string three)
{
var mediaItems = new List<MediaItem>
@ -58,7 +57,10 @@ public class MultiPartEpisodeGrouperTests @@ -58,7 +57,10 @@ public class MultiPartEpisodeGrouperTests
[Test]
[TestCase("The Meddlers (Part One)", "The Meddlers (Part Two)", "The Meddlers (Part Three)")]
[TestCase("The Slaves of Jedikiah, Part 1", "The Slaves of Jedikiah, Part 2", "The Slaves of Jedikiah, Part 3")]
[TestCase("An Unearthly Child: An Unearthly Child (1)", "An Unearthly Child: The Cave of Skulls (2)", "An Unearthly Child: The Forest of Fear (3)")]
[TestCase(
"An Unearthly Child: An Unearthly Child (1)",
"An Unearthly Child: The Cave of Skulls (2)",
"An Unearthly Child: The Forest of Fear (3)")]
[TestCase("The Savages (1)", "The Savages (2)", "The Savages (3)")]
public void All_Grouped(string one, string two, string three)
{

37
ErsatzTV.Core.Tests/Scheduling/ScheduleIntegrationTests.cs

@ -130,9 +130,19 @@ public class ScheduleIntegrationTests @@ -130,9 +130,19 @@ public class ScheduleIntegrationTests
Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID);
Playout playout = maybePlayout.ValueUnsafe();
var referenceData = await GetReferenceData(context, PLAYOUT_ID, ProgramSchedulePlayoutType.Classic);
PlayoutReferenceData referenceData = await GetReferenceData(
context,
PLAYOUT_ID,
ProgramSchedulePlayoutType.Classic);
await builder.Build(playout, referenceData, PlayoutBuildResult.Empty, PlayoutBuildMode.Reset, start, finish, _cancellationToken);
await builder.Build(
playout,
referenceData,
PlayoutBuildResult.Empty,
PlayoutBuildMode.Reset,
start,
finish,
_cancellationToken);
// TODO: would need to apply changes from build result
await context.SaveChangesAsync(_cancellationToken);
@ -144,7 +154,10 @@ public class ScheduleIntegrationTests @@ -144,7 +154,10 @@ public class ScheduleIntegrationTests
Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID);
Playout playout = maybePlayout.ValueUnsafe();
var referenceData = await GetReferenceData(context, PLAYOUT_ID, ProgramSchedulePlayoutType.Classic);
PlayoutReferenceData referenceData = await GetReferenceData(
context,
PLAYOUT_ID,
ProgramSchedulePlayoutType.Classic);
await builder.Build(
playout,
@ -165,7 +178,10 @@ public class ScheduleIntegrationTests @@ -165,7 +178,10 @@ public class ScheduleIntegrationTests
Option<Playout> maybePlayout = await GetPlayout(context, PLAYOUT_ID);
Playout playout = maybePlayout.ValueUnsafe();
var referenceData = await GetReferenceData(context, PLAYOUT_ID, ProgramSchedulePlayoutType.Classic);
PlayoutReferenceData referenceData = await GetReferenceData(
context,
PLAYOUT_ID,
ProgramSchedulePlayoutType.Classic);
await builder.Build(
playout,
@ -310,7 +326,10 @@ public class ScheduleIntegrationTests @@ -310,7 +326,10 @@ public class ScheduleIntegrationTests
Option<Playout> maybePlayout = await GetPlayout(context, playoutId);
Playout playout = maybePlayout.ValueUnsafe();
var referenceData = await GetReferenceData(context, playoutId, ProgramSchedulePlayoutType.Classic);
PlayoutReferenceData referenceData = await GetReferenceData(
context,
playoutId,
ProgramSchedulePlayoutType.Classic);
await builder.Build(
playout,
@ -380,7 +399,7 @@ public class ScheduleIntegrationTests @@ -380,7 +399,7 @@ public class ScheduleIntegrationTests
int playoutId,
ProgramSchedulePlayoutType playoutType)
{
var channel = await dbContext.Channels
Channel channel = await dbContext.Channels
.AsNoTracking()
.Where(c => c.Playouts.Any(p => p.Id == playoutId))
.FirstOrDefaultAsync();
@ -408,7 +427,7 @@ public class ScheduleIntegrationTests @@ -408,7 +427,7 @@ public class ScheduleIntegrationTests
.ToListAsync();
}
var programSchedule = await dbContext.ProgramSchedules
ProgramSchedule programSchedule = await dbContext.ProgramSchedules
.AsNoTracking()
.Where(ps => ps.Playouts.Any(p => p.Id == playoutId))
.Include(ps => ps.Items)
@ -430,7 +449,7 @@ public class ScheduleIntegrationTests @@ -430,7 +449,7 @@ public class ScheduleIntegrationTests
.ThenInclude(psi => psi.FallbackFiller)
.FirstOrDefaultAsync();
var programScheduleAlternates = await dbContext.ProgramScheduleAlternates
List<ProgramScheduleAlternate> programScheduleAlternates = await dbContext.ProgramScheduleAlternates
.AsNoTracking()
.Where(pt => pt.PlayoutId == playoutId)
.Include(a => a.ProgramSchedule)
@ -460,7 +479,7 @@ public class ScheduleIntegrationTests @@ -460,7 +479,7 @@ public class ScheduleIntegrationTests
.ThenInclude(psi => psi.FallbackFiller)
.ToListAsync();
var playoutHistory = await dbContext.PlayoutHistory
List<PlayoutHistory> playoutHistory = await dbContext.PlayoutHistory
.AsNoTracking()
.Where(h => h.PlayoutId == playoutId)
.ToListAsync();

2
ErsatzTV.Core/FFmpeg/FFmpegComplexFilterBuilder.cs

@ -199,7 +199,7 @@ public class FFmpegComplexFilterBuilder @@ -199,7 +199,7 @@ public class FFmpegComplexFilterBuilder
}
});
if (scaleOrPad && _boxBlur == false)
if (scaleOrPad && !_boxBlur)
{
videoFilterQueue.Add("setsar=1");
}

31
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -216,7 +216,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -216,7 +216,10 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
videoPath != audioPath, // still image when paths are different
videoVersion.VideoScanKind == VideoScanKind.Progressive ? ScanKind.Progressive : ScanKind.Interlaced);
var videoInputFile = new VideoInputFile(videoPath, new List<VideoStream> { ffmpegVideoStream }, streamInputKind);
var videoInputFile = new VideoInputFile(
videoPath,
new List<VideoStream> { ffmpegVideoStream },
streamInputKind);
Option<AudioInputFile> audioInputFile = maybeAudioStream.Map(audioStream =>
{
@ -347,7 +350,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -347,7 +350,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
None,
None);
foreach (var watermark in options.Watermark)
foreach (ChannelWatermark watermark in options.Watermark)
{
// don't allow duplicates
watermarks.TryAdd(watermark.Id, new WatermarkElementContext(options));
@ -355,7 +358,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -355,7 +358,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
}
// load all playout item watermarks
foreach (var playoutItemWatermark in playoutItemWatermarks)
foreach (ChannelWatermark playoutItemWatermark in playoutItemWatermarks)
{
WatermarkOptions options = await _ffmpegProcessService.GetWatermarkOptions(
ffprobePath,
@ -366,7 +369,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -366,7 +369,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
None,
None);
foreach (var watermark in options.Watermark)
foreach (ChannelWatermark watermark in options.Watermark)
{
// don't allow duplicates
watermarks.TryAdd(watermark.Id, new WatermarkElementContext(options));
@ -448,13 +451,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -448,13 +451,13 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playbackSettings.VideoTrackTimeScale,
playbackSettings.Deinterlace);
foreach (var playoutItemGraphicsElement in graphicsElements)
foreach (PlayoutItemGraphicsElement playoutItemGraphicsElement in graphicsElements)
{
switch (playoutItemGraphicsElement.GraphicsElement.Kind)
{
case GraphicsElementKind.Text:
{
var maybeElement =
Option<TextGraphicsElement> maybeElement =
await TextGraphicsElement.FromFile(playoutItemGraphicsElement.GraphicsElement.Path);
if (maybeElement.IsNone)
{
@ -463,7 +466,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -463,7 +466,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playoutItemGraphicsElement.GraphicsElement.Path);
}
foreach (var element in maybeElement)
foreach (TextGraphicsElement element in maybeElement)
{
var variables = new Dictionary<string, string>();
if (!string.IsNullOrWhiteSpace(playoutItemGraphicsElement.Variables))
@ -479,7 +482,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -479,7 +482,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
}
case GraphicsElementKind.Image:
{
var maybeElement =
Option<ImageGraphicsElement> maybeElement =
await ImageGraphicsElement.FromFile(playoutItemGraphicsElement.GraphicsElement.Path);
if (maybeElement.IsNone)
{
@ -488,7 +491,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -488,7 +491,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playoutItemGraphicsElement.GraphicsElement.Path);
}
foreach (var element in maybeElement)
foreach (ImageGraphicsElement element in maybeElement)
{
graphicsElementContexts.Add(new ImageElementContext(element));
}
@ -497,7 +500,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -497,7 +500,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
}
case GraphicsElementKind.Subtitle:
{
var maybeElement =
Option<SubtitlesGraphicsElement> maybeElement =
await SubtitlesGraphicsElement.FromFile(playoutItemGraphicsElement.GraphicsElement.Path);
if (maybeElement.IsNone)
{
@ -506,7 +509,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -506,7 +509,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
playoutItemGraphicsElement.GraphicsElement.Path);
}
foreach (var element in maybeElement)
foreach (SubtitlesGraphicsElement element in maybeElement)
{
var variables = new Dictionary<string, string>();
if (!string.IsNullOrWhiteSpace(playoutItemGraphicsElement.Variables))
@ -540,8 +543,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -540,8 +543,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
new Resolution { Width = desiredState.ScaledSize.Width, Height = desiredState.ScaledSize.Height },
channel.FFmpegProfile.Resolution,
await playbackSettings.FrameRate.IfNoneAsync(24),
ChannelStartTime: channelStartTime,
ContentStartTime: start,
channelStartTime,
start,
await playbackSettings.StreamSeek.IfNoneAsync(TimeSpan.Zero),
finish - now);
}
@ -592,7 +595,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService @@ -592,7 +595,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
pipelineAction?.Invoke(pipeline);
var command = GetCommand(
Command command = GetCommand(
ffmpegPath,
videoInputFile,
audioInputFile,

2
ErsatzTV.Core/FFmpeg/FFmpegPlaybackSettingsCalculator.cs

@ -114,7 +114,7 @@ public static class FFmpegPlaybackSettingsCalculator @@ -114,7 +114,7 @@ public static class FFmpegPlaybackSettingsCalculator
result.VideoTrackTimeScale = 90000;
foreach (MediaStream stream in videoStream.Where(s => s.AttachedPic == false))
foreach (MediaStream stream in videoStream.Where(s => !s.AttachedPic))
{
result.VideoFormat = ffmpegProfile.VideoFormat;
result.VideoBitrate = ffmpegProfile.VideoBitrate;

5
ErsatzTV.Core/FFmpeg/FFmpegStreamSelector.cs

@ -153,10 +153,11 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector @@ -153,10 +153,11 @@ public class FFmpegStreamSelector : IFFmpegStreamSelector
candidateSubtitles = candidateSubtitles.Filter(s => s.SubtitleKind is not SubtitleKind.Embedded).ToList();
}
foreach (Subtitle subtitle in candidateSubtitles.Filter(s => s.SubtitleKind is SubtitleKind.Embedded && !s.IsImage)
foreach (Subtitle subtitle in candidateSubtitles
.Filter(s => s.SubtitleKind is SubtitleKind.Embedded && !s.IsImage)
.ToList())
{
if (subtitle.IsExtracted == false)
if (!subtitle.IsExtracted)
{
_logger.LogDebug(
"Ignoring embedded subtitle with index {Index} that has not been extracted",

5
ErsatzTV.Core/Graphics/ImageGraphicsElement.cs

@ -15,19 +15,24 @@ public class ImageGraphicsElement @@ -15,19 +15,24 @@ public class ImageGraphicsElement
public string OpacityExpression { get; set; }
public WatermarkLocation Location { get; set; }
[YamlMember(Alias = "horizontal_margin_percent", ApplyNamingConventions = false)]
public double? HorizontalMarginPercent { get; set; }
[YamlMember(Alias = "vertical_margin_percent", ApplyNamingConventions = false)]
public double? VerticalMarginPercent { get; set; }
[YamlMember(Alias = "location_x", ApplyNamingConventions = false)]
public double? LocationX { get; set; }
[YamlMember(Alias = "location_y", ApplyNamingConventions = false)]
public double? LocationY { get; set; }
[YamlMember(Alias = "z_index", ApplyNamingConventions = false)]
public int? ZIndex { get; set; }
public bool Scale { get; set; }
[YamlMember(Alias = "scale_width_percent", ApplyNamingConventions = false)]
public double? ScaleWidthPercent { get; set; }

7
ErsatzTV.Core/Health/HealthCheckResult.cs

@ -1,3 +1,8 @@ @@ -1,3 +1,8 @@
namespace ErsatzTV.Core.Health;
public record HealthCheckResult(string Title, HealthCheckStatus Status, string Message, string BriefMessage, Option<HealthCheckLink> Link);
public record HealthCheckResult(
string Title,
HealthCheckStatus Status,
string Message,
string BriefMessage,
Option<HealthCheckLink> Link);

4
ErsatzTV.Core/Interfaces/Repositories/ITemplateDataRepository.cs

@ -4,9 +4,9 @@ namespace ErsatzTV.Core.Interfaces.Repositories; @@ -4,9 +4,9 @@ namespace ErsatzTV.Core.Interfaces.Repositories;
public interface ITemplateDataRepository
{
public Task<Option<Dictionary<string, object>>> GetMediaItemTemplateData(MediaItem mediaItem);
Task<Option<Dictionary<string, object>>> GetMediaItemTemplateData(MediaItem mediaItem);
public Task<Option<Dictionary<string, object>>> GetEpgTemplateData(
Task<Option<Dictionary<string, object>>> GetEpgTemplateData(
string channelNumber,
DateTimeOffset time,
int count);

4
ErsatzTV.Core/Interfaces/Scheduling/IPlayoutBuilder.cs

@ -5,8 +5,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling; @@ -5,8 +5,8 @@ namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IPlayoutBuilder
{
public bool TrimStart { get; set; }
public Playlist DebugPlaylist { get; set; }
bool TrimStart { get; set; }
Playlist DebugPlaylist { get; set; }
Task<PlayoutBuildResult> Build(
Playout playout,

2
ErsatzTV.Core/Interfaces/Scheduling/IPlayoutTimeShifter.cs

@ -2,5 +2,5 @@ namespace ErsatzTV.Core.Interfaces.Scheduling; @@ -2,5 +2,5 @@ namespace ErsatzTV.Core.Interfaces.Scheduling;
public interface IPlayoutTimeShifter
{
public Task TimeShift(int playoutId, DateTimeOffset now, bool force);
Task TimeShift(int playoutId, DateTimeOffset now, bool force);
}

2
ErsatzTV.Core/Interfaces/Search/ISearchIndex.cs

@ -9,7 +9,7 @@ namespace ErsatzTV.Core.Interfaces.Search; @@ -9,7 +9,7 @@ namespace ErsatzTV.Core.Interfaces.Search;
public interface ISearchIndex : IDisposable
{
public int Version { get; }
int Version { get; }
Task<bool> IndexExists();
Task<bool> Initialize(ILocalFileSystem localFileSystem, IConfigElementRepository configElementRepository);
Task<Unit> Rebuild(ICachingSearchRepository searchRepository, IFallbackMetadataProvider fallbackMetadataProvider);

4
ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs

@ -28,7 +28,9 @@ public record TextElementDataContext(TextGraphicsElement TextElement, Dictionary @@ -28,7 +28,9 @@ public record TextElementDataContext(TextGraphicsElement TextElement, Dictionary
public record ImageElementContext(ImageGraphicsElement ImageElement) : GraphicsElementContext;
public record SubtitleElementDataContext(SubtitlesGraphicsElement SubtitlesElement, Dictionary<string, string> Variables)
public record SubtitleElementDataContext(
SubtitlesGraphicsElement SubtitlesElement,
Dictionary<string, string> Variables)
: GraphicsElementContext, ITemplateDataContext
{
public int EpgEntries => SubtitlesElement.EpgEntries;

1
ErsatzTV.Core/PathUtils.cs

@ -14,6 +14,7 @@ public static class PathUtils @@ -14,6 +14,7 @@ public static class PathUtils
{
builder.Append(b.ToString("x2", CultureInfo.InvariantCulture));
}
return builder.ToString();
}
}

12
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutBuilder.cs

@ -33,7 +33,7 @@ public class BlockPlayoutBuilder( @@ -33,7 +33,7 @@ public class BlockPlayoutBuilder(
PlayoutBuildMode mode,
CancellationToken cancellationToken)
{
var result = PlayoutBuildResult.Empty;
PlayoutBuildResult result = PlayoutBuildResult.Empty;
logger.LogDebug(
"Building block playout {PlayoutId} for channel {ChannelNumber} - {ChannelName}",
@ -72,7 +72,8 @@ public class BlockPlayoutBuilder( @@ -72,7 +72,8 @@ public class BlockPlayoutBuilder(
BlockPlayoutChangeDetection.GetPlayoutItemToBlockKeyMap(referenceData);
// remove items without a block key (shouldn't happen often, just upgrades)
foreach (var item in referenceData.ExistingItems.Where(i => i.FillerKind is not FillerKind.DecoDefault && !itemBlockKeys.ContainsKey(i)))
foreach (PlayoutItem item in referenceData.ExistingItems.Where(i =>
i.FillerKind is not FillerKind.DecoDefault && !itemBlockKeys.ContainsKey(i)))
{
result.ItemsToRemove.Add(item.Id);
}
@ -327,9 +328,12 @@ public class BlockPlayoutBuilder( @@ -327,9 +328,12 @@ public class BlockPlayoutBuilder(
return $"{showTitle}s{e.Season.SeasonNumber:00}{numbersString} - {titlesString}";
}
private static PlayoutBuildResult CleanUpHistory(PlayoutReferenceData referenceData, DateTimeOffset start, PlayoutBuildResult result)
private static PlayoutBuildResult CleanUpHistory(
PlayoutReferenceData referenceData,
DateTimeOffset start,
PlayoutBuildResult result)
{
var allItemsToDelete = referenceData.PlayoutHistory
IEnumerable<PlayoutHistory> allItemsToDelete = referenceData.PlayoutHistory
.Append(result.AddedHistory)
.GroupBy(h => (h.BlockId, h.Key))
.SelectMany(group => group

3
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutChangeDetection.cs

@ -46,7 +46,8 @@ internal static class BlockPlayoutChangeDetection @@ -46,7 +46,8 @@ internal static class BlockPlayoutChangeDetection
{
foreach (PlayoutItem playoutItem in playoutItems)
{
if (!itemBlockKeys.TryGetValue(playoutItem, out var blockKey) || effectiveBlock.Block.Id != blockKey.b)
if (!itemBlockKeys.TryGetValue(playoutItem, out BlockKey blockKey) ||
effectiveBlock.Block.Id != blockKey.b)
{
continue;
}

2
ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs

@ -37,7 +37,7 @@ public class BlockPlayoutFillerBuilder( @@ -37,7 +37,7 @@ public class BlockPlayoutFillerBuilder(
{
// remove all playout items with type filler
// except block items that are hidden from the guide (guide mode)
foreach (var item in filteredExistingItems)
foreach (PlayoutItem item in filteredExistingItems)
{
if (item.FillerKind is FillerKind.None or FillerKind.GuideMode)
{

2
ErsatzTV.Core/Scheduling/BlockScheduling/EffectiveBlock.cs

@ -63,7 +63,7 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St @@ -63,7 +63,7 @@ internal record EffectiveBlock(Block Block, BlockKey BlockKey, DateTimeOffset St
private static EffectiveBlock NormalizeGuideMode(EffectiveBlock effectiveBlock)
{
if (effectiveBlock.Block.Items is not null &&
effectiveBlock.Block.Items.All(bi => bi.IncludeInProgramGuide == false))
effectiveBlock.Block.Items.All(bi => !bi.IncludeInProgramGuide))
{
foreach (BlockItem blockItem in effectiveBlock.Block.Items)
{

32
ErsatzTV.Core/Scheduling/ChronologicalMediaCollectionEnumerator.cs

@ -6,9 +6,9 @@ namespace ErsatzTV.Core.Scheduling; @@ -6,9 +6,9 @@ namespace ErsatzTV.Core.Scheduling;
public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnumerator
{
private readonly Lazy<Dictionary<int, int>> _lazyMediaItemGroupSize;
private readonly Lazy<Option<TimeSpan>> _lazyMinimumDuration;
private readonly List<MediaItem> _sortedMediaItems;
private readonly Lazy<Dictionary<int, int>> _lazyMediaItemGroupSize;
public ChronologicalMediaCollectionEnumerator(
IEnumerable<MediaItem> mediaItems,
@ -36,6 +36,21 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu @@ -36,6 +36,21 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu
}
}
public void ResetState(CollectionEnumeratorState state) =>
// seed doesn't matter in chronological
State.Index = state.Index;
public CollectionEnumeratorState State { get; }
public Option<MediaItem> Current => _sortedMediaItems.Count != 0 ? _sortedMediaItems[State.Index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count;
public Option<TimeSpan> MinimumDuration => _lazyMinimumDuration.Value;
public int Count => _sortedMediaItems.Count;
private Dictionary<int, int> CalculateMediaItemGroupSizes()
{
var result = new Dictionary<int, int>();
@ -54,21 +69,6 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu @@ -54,21 +69,6 @@ public sealed class ChronologicalMediaCollectionEnumerator : IMediaCollectionEnu
return result;
}
public void ResetState(CollectionEnumeratorState state) =>
// seed doesn't matter in chronological
State.Index = state.Index;
public CollectionEnumeratorState State { get; }
public Option<MediaItem> Current => _sortedMediaItems.Count != 0 ? _sortedMediaItems[State.Index] : None;
public Option<bool> CurrentIncludeInProgramGuide { get; }
public void MoveNext() => State.Index = (State.Index + 1) % _sortedMediaItems.Count;
public Option<TimeSpan> MinimumDuration => _lazyMinimumDuration.Value;
public int Count => _sortedMediaItems.Count;
public int GroupSizeForMediaItem(MediaItem mediaItem) =>
_lazyMediaItemGroupSize.Value.GetValueOrDefault(mediaItem.Id, 1);
}

5
ErsatzTV.Core/Scheduling/FillerExpression.cs

@ -5,7 +5,10 @@ namespace ErsatzTV.Core.Scheduling; @@ -5,7 +5,10 @@ namespace ErsatzTV.Core.Scheduling;
public static class FillerExpression
{
public static List<MediaChapter> FilterChapters(string fillerExpression, List<MediaChapter> effectiveChapters, PlayoutItem playoutItem)
public static List<MediaChapter> FilterChapters(
string fillerExpression,
List<MediaChapter> effectiveChapters,
PlayoutItem playoutItem)
{
if (effectiveChapters.Count == 0 || string.IsNullOrWhiteSpace(fillerExpression))
{

10
ErsatzTV.Core/Scheduling/PlayoutBuildResult.cs

@ -14,5 +14,13 @@ public record PlayoutBuildResult( @@ -14,5 +14,13 @@ public record PlayoutBuildResult(
Option<DateTimeOffset> TimeShiftTo)
{
public static PlayoutBuildResult Empty =>
new(false, Option<DateTimeOffset>.None, Option<DateTimeOffset>.None, [], [], [], [], Option<DateTimeOffset>.None);
new(
false,
Option<DateTimeOffset>.None,
Option<DateTimeOffset>.None,
[],
[],
[],
[],
Option<DateTimeOffset>.None);
}

25
ErsatzTV.Core/Scheduling/PlayoutBuilder.cs

@ -65,7 +65,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -65,7 +65,7 @@ public class PlayoutBuilder : IPlayoutBuilder
PlayoutBuildMode mode,
CancellationToken cancellationToken)
{
var result = PlayoutBuildResult.Empty;
PlayoutBuildResult result = PlayoutBuildResult.Empty;
if (playout.ProgramSchedulePlayoutType is not ProgramSchedulePlayoutType.Classic)
{
@ -117,7 +117,13 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -117,7 +117,13 @@ public class PlayoutBuilder : IPlayoutBuilder
{
foreach (PlayoutParameters parameters in await Validate(playout, referenceData))
{
result = await Build(playout, referenceData, result, mode, parameters with { Start = start, Finish = finish }, cancellationToken);
result = await Build(
playout,
referenceData,
result,
mode,
parameters with { Start = start, Finish = finish },
cancellationToken);
}
return result;
@ -443,7 +449,7 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -443,7 +449,7 @@ public class PlayoutBuilder : IPlayoutBuilder
{
// check for future items that aren't grouped inside range
var futureItems = result.AddedItems.Filter(i => i.StartOffset > trimAfter).ToList();
var futureItemCount = futureItems.Count(futureItem =>
int futureItemCount = futureItems.Count(futureItem =>
result.AddedItems.All(i => i == futureItem || i.GuideGroup != futureItem.GuideGroup));
// it feels hacky to have to clean up a playlist like this,
@ -808,7 +814,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -808,7 +814,8 @@ public class PlayoutBuilder : IPlayoutBuilder
playoutBuilderState.CurrentTime);
// if we ended in a different alternate schedule, fix the anchor data
if (playoutBuilderState.CurrentTime > playoutFinish && activeScheduleAtAnchor.Id != activeSchedule.Id && activeScheduleAtAnchor.Items.Count > 0)
if (playoutBuilderState.CurrentTime > playoutFinish && activeScheduleAtAnchor.Id != activeSchedule.Id &&
activeScheduleAtAnchor.Items.Count > 0)
{
PlayoutBuilderState cleanState = playoutBuilderState with
{
@ -875,7 +882,8 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -875,7 +882,8 @@ public class PlayoutBuilder : IPlayoutBuilder
private async Task<Map<CollectionKey, List<MediaItem>>> GetCollectionMediaItems(PlayoutReferenceData referenceData)
{
IEnumerable<KeyValuePair<CollectionKey, Option<FillerPreset>>> collectionKeys = GetAllCollectionKeys(referenceData);
IEnumerable<KeyValuePair<CollectionKey, Option<FillerPreset>>> collectionKeys =
GetAllCollectionKeys(referenceData);
IEnumerable<Task<KeyValuePair<CollectionKey, List<MediaItem>>>> tasks = collectionKeys.Select(async key =>
{
@ -886,14 +894,13 @@ public class PlayoutBuilder : IPlayoutBuilder @@ -886,14 +894,13 @@ public class PlayoutBuilder : IPlayoutBuilder
return Map.createRange(await Task.WhenAll(tasks));
}
private static IEnumerable<KeyValuePair<CollectionKey, Option<FillerPreset>>> GetAllCollectionKeys(PlayoutReferenceData referenceData)
{
return referenceData.ProgramSchedule.Items
private static IEnumerable<KeyValuePair<CollectionKey, Option<FillerPreset>>> GetAllCollectionKeys(
PlayoutReferenceData referenceData) =>
referenceData.ProgramSchedule.Items
.Append(referenceData.ProgramScheduleAlternates.Bind(psa => psa.ProgramSchedule.Items))
.DistinctBy(item => item.Id)
.SelectMany(CollectionKeysForItem)
.DistinctBy(kvp => kvp.Key);
}
private async Task<List<MediaItem>> FetchMediaItemsForKeyAsync(
CollectionKey collectionKey,

14
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerBase.cs

@ -286,7 +286,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -286,7 +286,7 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
// missing pad-to-nearest-minute value is invalid; use no filler
FillerPreset invalidPadFiller = allFiller
.FirstOrDefault(f => f.FillerMode == FillerMode.Pad && f.PadToNearestMinute.HasValue == false);
.FirstOrDefault(f => f.FillerMode == FillerMode.Pad && !f.PadToNearestMinute.HasValue);
if (invalidPadFiller is not null)
{
Logger.LogError(
@ -340,7 +340,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -340,7 +340,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
// convert playlist filler
if (allFiller.Any(f => f.CollectionType is ProgramScheduleItemCollectionType.Playlist))
{
var toRemove = allFiller.Filter(f => f.CollectionType is ProgramScheduleItemCollectionType.Playlist).ToList();
var toRemove = allFiller.Filter(f => f.CollectionType is ProgramScheduleItemCollectionType.Playlist)
.ToList();
allFiller.RemoveAll(toRemove.Contains);
foreach (FillerPreset playlistFiller in toRemove)
@ -367,7 +368,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -367,7 +368,8 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
};
// if filler count is 2, we need to schedule 2 * (number of items in one full playlist iteration)
var fillerEnumerator = enumerators[CollectionKey.ForFillerPreset(playlistFiller)];
IMediaCollectionEnumerator fillerEnumerator =
enumerators[CollectionKey.ForFillerPreset(playlistFiller)];
if (fillerEnumerator is PlaylistEnumerator playlistEnumerator)
{
clone.Count *= playlistEnumerator.CountForFiller;
@ -428,7 +430,10 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -428,7 +430,10 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
foreach (FillerPreset filler in allFiller.Filter(f =>
f.FillerKind == FillerKind.MidRoll && f.FillerMode != FillerMode.Pad))
{
List<MediaChapter> filteredChapters = FillerExpression.FilterChapters(filler.Expression, effectiveChapters, playoutItem);
List<MediaChapter> filteredChapters = FillerExpression.FilterChapters(
filler.Expression,
effectiveChapters,
playoutItem);
if (filteredChapters.Count <= 1)
{
result.Add(playoutItem);
@ -797,7 +802,6 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe @@ -797,7 +802,6 @@ public abstract class PlayoutModeSchedulerBase<T> : IPlayoutModeScheduler<T> whe
if (remainingToFill - itemDuration >= TimeSpan.Zero)
{
var playoutItem = new PlayoutItem
{
PlayoutId = playoutBuilderState.PlayoutId,

5
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerDuration.cs

@ -55,7 +55,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -55,7 +55,7 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
if (itemStartTime >= nextState.DurationFinish.IfNone(SystemTime.MaxValueUtc) ||
// don't start if the first item will already be after the hard stop
(playoutItems.Count == 0 && itemStartTime >= hardStop))
playoutItems.Count == 0 && itemStartTime >= hardStop)
{
nextState = nextState with { CurrentTime = hardStop };
break;
@ -165,7 +165,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche @@ -165,7 +165,8 @@ public class PlayoutModeSchedulerDuration : PlayoutModeSchedulerBase<ProgramSche
PlayoutItemWatermarks = []
};
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
foreach (ProgramScheduleItemWatermark programScheduleItemWatermark in scheduleItem
.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerFlood.cs

@ -84,7 +84,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul @@ -84,7 +84,8 @@ public class PlayoutModeSchedulerFlood : PlayoutModeSchedulerBase<ProgramSchedul
PlayoutItemWatermarks = []
};
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
foreach (ProgramScheduleItemWatermark programScheduleItemWatermark in scheduleItem
.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark

5
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerMultiple.cs

@ -60,6 +60,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -60,6 +60,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
.Enumerator.Count
};
}
break;
case MultipleMode.MultiEpisodeGroupSize:
if (contentEnumerator is ChronologicalMediaCollectionEnumerator chronologicalEnumerator)
@ -72,6 +73,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -72,6 +73,7 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
};
}
}
break;
}
}
@ -108,7 +110,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche @@ -108,7 +110,8 @@ public class PlayoutModeSchedulerMultiple : PlayoutModeSchedulerBase<ProgramSche
PlayoutItemWatermarks = []
};
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
foreach (ProgramScheduleItemWatermark programScheduleItemWatermark in scheduleItem
.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark

3
ErsatzTV.Core/Scheduling/PlayoutModeSchedulerOne.cs

@ -55,7 +55,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI @@ -55,7 +55,8 @@ public class PlayoutModeSchedulerOne : PlayoutModeSchedulerBase<ProgramScheduleI
PlayoutItemWatermarks = []
};
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
foreach (ProgramScheduleItemWatermark programScheduleItemWatermark in scheduleItem
.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark

6
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutContentHandler.cs

@ -185,9 +185,9 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache) @@ -185,9 +185,9 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache)
}
else
{
foreach (var midRollSequence in maybeMidRollSequence)
foreach (YamlPlayoutContext.MidRollSequence midRollSequence in maybeMidRollSequence)
{
var filteredChapters = FillerExpression.FilterChapters(
List<MediaChapter> filteredChapters = FillerExpression.FilterChapters(
midRollSequence.Expression,
itemChapters,
playoutItem);
@ -201,7 +201,7 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache) @@ -201,7 +201,7 @@ public abstract class YamlPlayoutContentHandler(EnumeratorCache enumeratorCache)
{
for (var j = 0; j < filteredChapters.Count; j++)
{
var nextItem = playoutItem.ForChapter(filteredChapters[j]);
PlayoutItem nextItem = playoutItem.ForChapter(filteredChapters[j]);
nextItem.Start = context.CurrentTime.UtcDateTime;
nextItem.Finish = context.CurrentTime.UtcDateTime + (nextItem.OutPoint - nextItem.InPoint);

2
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs

@ -29,7 +29,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP @@ -29,7 +29,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP
return false;
}
if (duration.StopBeforeEnd == false && duration.OfflineTail)
if (!duration.StopBeforeEnd && duration.OfflineTail)
{
logger.LogError("offline_tail must be false when stop_before_end is false");
return false;

4
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOffHandler.cs

@ -30,7 +30,7 @@ public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsEl @@ -30,7 +30,7 @@ public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsEl
}
else
{
foreach (var ge in await GetGraphicsElementByPath(graphicsOff.GraphicsOff))
foreach (GraphicsElement ge in await GetGraphicsElementByPath(graphicsOff.GraphicsOff))
{
context.RemoveGraphicsElement(ge.Id);
}
@ -41,7 +41,7 @@ public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsEl @@ -41,7 +41,7 @@ public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsEl
private async Task<Option<GraphicsElement>> GetGraphicsElementByPath(string path)
{
if (_graphicsElementCache.TryGetValue(path, out var cachedGraphicsElement))
if (_graphicsElementCache.TryGetValue(path, out Option<GraphicsElement> cachedGraphicsElement))
{
foreach (GraphicsElement graphicsElement in cachedGraphicsElement)
{

4
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOnHandler.cs

@ -35,7 +35,7 @@ public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsEle @@ -35,7 +35,7 @@ public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsEle
return false;
}
foreach (var ge in await GetGraphicsElementByPath(graphicsOn.GraphicsOn))
foreach (GraphicsElement ge in await GetGraphicsElementByPath(graphicsOn.GraphicsOn))
{
string variables = null;
if (graphicsOn.Variables.Count > 0)
@ -51,7 +51,7 @@ public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsEle @@ -51,7 +51,7 @@ public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsEle
private async Task<Option<GraphicsElement>> GetGraphicsElementByPath(string path)
{
if (_graphicsElementCache.TryGetValue(path, out var cachedGraphicsElement))
if (_graphicsElementCache.TryGetValue(path, out Option<GraphicsElement> cachedGraphicsElement))
{
foreach (GraphicsElement graphicsElement in cachedGraphicsElement)
{

3
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutMidRollHandler.cs

@ -20,7 +20,8 @@ public class YamlPlayoutMidRollHandler : IYamlPlayoutHandler @@ -20,7 +20,8 @@ public class YamlPlayoutMidRollHandler : IYamlPlayoutHandler
return Task.FromResult(false);
}
if (midRoll.MidRoll && !string.IsNullOrWhiteSpace(midRoll.Sequence) && !string.IsNullOrWhiteSpace(midRoll.Expression))
if (midRoll.MidRoll && !string.IsNullOrWhiteSpace(midRoll.Sequence) &&
!string.IsNullOrWhiteSpace(midRoll.Expression))
{
context.SetMidRollSequence(new YamlPlayoutContext.MidRollSequence(midRoll.Sequence, midRoll.Expression));
}

6
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutWatermarkHandler.cs

@ -26,7 +26,7 @@ public class YamlPlayoutWatermarkHandler(IChannelRepository channelRepository) : @@ -26,7 +26,7 @@ public class YamlPlayoutWatermarkHandler(IChannelRepository channelRepository) :
if (watermark.Watermark && !string.IsNullOrWhiteSpace(watermark.Name))
{
foreach (var wm in await GetChannelWatermarkByName(watermark.Name))
foreach (ChannelWatermark wm in await GetChannelWatermarkByName(watermark.Name))
{
context.SetChannelWatermarkId(wm.Id);
}
@ -35,7 +35,7 @@ public class YamlPlayoutWatermarkHandler(IChannelRepository channelRepository) : @@ -35,7 +35,7 @@ public class YamlPlayoutWatermarkHandler(IChannelRepository channelRepository) :
{
if (!string.IsNullOrWhiteSpace(watermark.Name))
{
foreach (var wm in await GetChannelWatermarkByName(watermark.Name))
foreach (ChannelWatermark wm in await GetChannelWatermarkByName(watermark.Name))
{
context.RemoveChannelWatermarkId(wm.Id);
}
@ -51,7 +51,7 @@ public class YamlPlayoutWatermarkHandler(IChannelRepository channelRepository) : @@ -51,7 +51,7 @@ public class YamlPlayoutWatermarkHandler(IChannelRepository channelRepository) :
private async Task<Option<ChannelWatermark>> GetChannelWatermarkByName(string name)
{
if (_watermarkCache.TryGetValue(name, out var cachedWatermark))
if (_watermarkCache.TryGetValue(name, out Option<ChannelWatermark> cachedWatermark))
{
foreach (ChannelWatermark channelWatermark in cachedWatermark)
{

42
ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs

@ -30,7 +30,7 @@ public class YamlPlayoutBuilder( @@ -30,7 +30,7 @@ public class YamlPlayoutBuilder(
PlayoutBuildMode mode,
CancellationToken cancellationToken)
{
var result = PlayoutBuildResult.Empty;
PlayoutBuildResult result = PlayoutBuildResult.Empty;
if (!localFileSystem.FileExists(playout.TemplateFile))
{
@ -39,7 +39,7 @@ public class YamlPlayoutBuilder( @@ -39,7 +39,7 @@ public class YamlPlayoutBuilder(
}
Option<YamlPlayoutDefinition> maybePlayoutDefinition =
await LoadYamlDefinition(playout.TemplateFile, isImport: false, cancellationToken);
await LoadYamlDefinition(playout.TemplateFile, false, cancellationToken);
if (maybePlayoutDefinition.IsNone)
{
logger.LogWarning("YAML playout file {File} is invalid; aborting.", playout.TemplateFile);
@ -49,11 +49,11 @@ public class YamlPlayoutBuilder( @@ -49,11 +49,11 @@ public class YamlPlayoutBuilder(
// using ValueUnsafe to avoid nesting
YamlPlayoutDefinition playoutDefinition = maybePlayoutDefinition.ValueUnsafe();
foreach (var import in playoutDefinition.Import)
foreach (string import in playoutDefinition.Import)
{
try
{
var path = import;
string path = import;
if (!File.Exists(import))
{
path = Path.Combine(
@ -66,16 +66,23 @@ public class YamlPlayoutBuilder( @@ -66,16 +66,23 @@ public class YamlPlayoutBuilder(
}
}
var maybeImportedDefinition = await LoadYamlDefinition(path, isImport: true, cancellationToken);
foreach (var importedDefinition in maybeImportedDefinition)
Option<YamlPlayoutDefinition> maybeImportedDefinition =
await LoadYamlDefinition(path, true, cancellationToken);
foreach (YamlPlayoutDefinition importedDefinition in maybeImportedDefinition)
{
var contentToAdd = importedDefinition.Content
.Where(c => playoutDefinition.Content.All(c2 => !string.Equals(c2.Key, c.Key, StringComparison.OrdinalIgnoreCase)));
IEnumerable<YamlPlayoutContentItem> contentToAdd = importedDefinition.Content
.Where(c => playoutDefinition.Content.All(c2 => !string.Equals(
c2.Key,
c.Key,
StringComparison.OrdinalIgnoreCase)));
playoutDefinition.Content.AddRange(contentToAdd);
var sequencesToAdd = importedDefinition.Sequence
.Where(s => playoutDefinition.Sequence.All(s2 => !string.Equals(s2.Key, s.Key, StringComparison.OrdinalIgnoreCase)));
IEnumerable<YamlPlayoutSequenceItem> sequencesToAdd = importedDefinition.Sequence
.Where(s => playoutDefinition.Sequence.All(s2 => !string.Equals(
s2.Key,
s.Key,
StringComparison.OrdinalIgnoreCase)));
playoutDefinition.Sequence.AddRange(sequencesToAdd);
}
@ -236,7 +243,9 @@ public class YamlPlayoutBuilder( @@ -236,7 +243,9 @@ public class YamlPlayoutBuilder(
continue;
async Task ExecuteSequenceLocal(string sequence) => await ExecuteSequence(
async Task ExecuteSequenceLocal(string sequence)
{
await ExecuteSequence(
handlers,
enumeratorCache,
mode,
@ -244,6 +253,7 @@ public class YamlPlayoutBuilder( @@ -244,6 +253,7 @@ public class YamlPlayoutBuilder(
sequence,
cancellationToken);
}
}
if (!instruction.ChangesIndex)
{
@ -303,7 +313,13 @@ public class YamlPlayoutBuilder( @@ -303,7 +313,13 @@ public class YamlPlayoutBuilder(
foreach (IYamlPlayoutHandler handler in maybeHandler)
{
if (!await handler.Handle(context, instruction, mode, _ => Task.CompletedTask, logger, cancellationToken))
if (!await handler.Handle(
context,
instruction,
mode,
_ => Task.CompletedTask,
logger,
cancellationToken))
{
logger.LogInformation("YAML playout instruction handler failed");
}
@ -432,7 +448,7 @@ public class YamlPlayoutBuilder( @@ -432,7 +448,7 @@ public class YamlPlayoutBuilder(
try
{
string yaml = await File.ReadAllTextAsync(fileName, cancellationToken);
if (await yamlScheduleValidator.ValidateSchedule(yaml, isImport) == false)
if (!await yamlScheduleValidator.ValidateSchedule(yaml, isImport))
{
return Option<YamlPlayoutDefinition>.None;
}

10
ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs

@ -13,16 +13,17 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio @@ -13,16 +13,17 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio
NullValueHandling = NullValueHandling.Ignore
};
private readonly System.Collections.Generic.HashSet<int> _visitedInstructions = [];
private readonly Stack<FillerKind> _fillerKind = new();
private readonly System.Collections.Generic.HashSet<int> _channelWatermarkIds = [];
private readonly Stack<FillerKind> _fillerKind = new();
private readonly Dictionary<int, string> _graphicsElements = [];
private readonly System.Collections.Generic.HashSet<int> _visitedInstructions = [];
private int _guideGroup = guideGroup;
private bool _guideGroupLocked;
private int _instructionIndex;
private Option<string> _preRollSequence;
private Option<string> _postRollSequence;
private Option<MidRollSequence> _midRollSequence;
private Option<string> _postRollSequence;
private Option<string> _preRollSequence;
public Playout Playout { get; } = playout;
@ -112,6 +113,7 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio @@ -112,6 +113,7 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio
public void PushFillerKind(FillerKind fillerKind) => _fillerKind.Push(fillerKind);
public void PopFillerKind() => _fillerKind.Pop();
public Option<FillerKind> GetFillerKind() =>
_fillerKind.TryPeek(out FillerKind fillerKind) ? fillerKind : Option<FillerKind>.None;

15
ErsatzTV.Core/Troubleshooting/TroubleshootingNotifier.cs

@ -7,18 +7,9 @@ public class TroubleshootingNotifier : ITroubleshootingNotifier @@ -7,18 +7,9 @@ public class TroubleshootingNotifier : ITroubleshootingNotifier
{
private readonly ConcurrentDictionary<Guid, bool> _failedSessions = new();
public bool IsFailed(Guid sessionId)
{
return _failedSessions.TryGetValue(sessionId, out _);
}
public bool IsFailed(Guid sessionId) => _failedSessions.TryGetValue(sessionId, out _);
public void NotifyFailed(Guid sessionId)
{
_failedSessions[sessionId] = true;
}
public void NotifyFailed(Guid sessionId) => _failedSessions[sessionId] = true;
public void RemoveSession(Guid sessionId)
{
_failedSessions.TryRemove(sessionId, out _);
}
public void RemoveSession(Guid sessionId) => _failedSessions.TryRemove(sessionId, out _);
}

5
ErsatzTV.FFmpeg/Capabilities/FFmpegKnownEncoder.cs

@ -2,13 +2,12 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -2,13 +2,12 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public record FFmpegKnownEncoder
{
public static readonly FFmpegKnownEncoder H264VideoToolbox = new("h264_videotoolbox");
public static readonly FFmpegKnownEncoder HevcVideoToolbox = new("hevc_videotoolbox");
private FFmpegKnownEncoder(string Name) => this.Name = Name;
public string Name { get; }
public static readonly FFmpegKnownEncoder H264VideoToolbox = new("h264_videotoolbox");
public static readonly FFmpegKnownEncoder HevcVideoToolbox = new("hevc_videotoolbox");
// only list the encoders that we actually check for
public static IList<string> AllEncoders =>
[

8
ErsatzTV.FFmpeg/Capabilities/FourCC.cs

@ -2,14 +2,14 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -2,14 +2,14 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public static class FourCC
{
public const string H264 = "avc1";
public const string Hevc = "hvc1";
public const string Vp9 = "vp90";
public static readonly List<string> AllVideoToolbox =
[
H264,
Hevc,
Vp9
];
public const string H264 = "avc1";
public const string Hevc = "hvc1";
public const string Vp9 = "vp90";
}

7
ErsatzTV.FFmpeg/Capabilities/HardwareCapabilitiesFactory.cs

@ -265,7 +265,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -265,7 +265,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
{
var result = new List<string>();
foreach (var fourCC in FourCC.AllVideoToolbox)
foreach (string fourCC in FourCC.AllVideoToolbox)
{
if (VideoToolboxUtil.IsHardwareDecoderSupported(fourCC, _logger))
{
@ -276,10 +276,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory @@ -276,10 +276,7 @@ public class HardwareCapabilitiesFactory : IHardwareCapabilitiesFactory
return result;
}
public List<string> GetVideoToolboxEncoders()
{
return VideoToolboxUtil.GetAvailableEncoders(_logger);
}
public List<string> GetVideoToolboxEncoders() => VideoToolboxUtil.GetAvailableEncoders(_logger);
private async Task<IReadOnlySet<string>> GetFFmpegCapabilities(
string ffmpegPath,

6
ErsatzTV.FFmpeg/Capabilities/IHardwareCapabilities.cs

@ -4,18 +4,18 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -4,18 +4,18 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public interface IHardwareCapabilities
{
public FFmpegCapability CanDecode(
FFmpegCapability CanDecode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat,
bool isHdr);
public FFmpegCapability CanEncode(
FFmpegCapability CanEncode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat);
public Option<RateControlMode> GetRateControlMode(
Option<RateControlMode> GetRateControlMode(
string videoFormat,
Option<IPixelFormat> maybePixelFormat);
}

6
ErsatzTV.FFmpeg/Capabilities/VideoToolbox/VideoToolboxUtil.cs

@ -89,7 +89,7 @@ internal static partial class VideoToolboxUtil @@ -89,7 +89,7 @@ internal static partial class VideoToolboxUtil
}
long maxSize = length * 4 + 1;
byte[] buffer = new byte[maxSize];
var buffer = new byte[maxSize];
if (CFStringGetCString(cfString, buffer, maxSize, kCFStringEncodingUTF8))
{
int terminator = Array.IndexOf(buffer, (byte)0);
@ -107,7 +107,7 @@ internal static partial class VideoToolboxUtil @@ -107,7 +107,7 @@ internal static partial class VideoToolboxUtil
throw new ArgumentException("FourCC must be 4 characters long.", nameof(fourCC));
}
return ((uint)fourCC[0] << 24) | ((uint)fourCC[1] << 16) | ((uint)fourCC[2] << 8) | (uint)fourCC[3];
return ((uint)fourCC[0] << 24) | ((uint)fourCC[1] << 16) | ((uint)fourCC[2] << 8) | fourCC[3];
}
internal static List<string> GetAvailableEncoders(ILogger logger)
@ -138,7 +138,7 @@ internal static partial class VideoToolboxUtil @@ -138,7 +138,7 @@ internal static partial class VideoToolboxUtil
}
var count = (int)CFArrayGetCount(encoderList);
for (int i = 0; i < count; i++)
for (var i = 0; i < count; i++)
{
IntPtr encoderDict = CFArrayGetValueAtIndex(encoderList, i);
if (encoderDict == IntPtr.Zero)

19
ErsatzTV.FFmpeg/Capabilities/VideoToolboxHardwareCapabilities.cs

@ -8,8 +8,8 @@ namespace ErsatzTV.FFmpeg.Capabilities; @@ -8,8 +8,8 @@ namespace ErsatzTV.FFmpeg.Capabilities;
public class VideoToolboxHardwareCapabilities : IHardwareCapabilities
{
private static readonly ConcurrentDictionary<string, bool> Encoders = new ();
private static readonly ConcurrentDictionary<string, bool> Decoders = new ();
private static readonly ConcurrentDictionary<string, bool> Encoders = new();
private static readonly ConcurrentDictionary<string, bool> Decoders = new();
private readonly IFFmpegCapabilities _ffmpegCapabilities;
private readonly ILogger _logger;
@ -20,7 +20,11 @@ public class VideoToolboxHardwareCapabilities : IHardwareCapabilities @@ -20,7 +20,11 @@ public class VideoToolboxHardwareCapabilities : IHardwareCapabilities
_logger = logger;
}
public FFmpegCapability CanDecode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat, bool isHdr)
public FFmpegCapability CanDecode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat,
bool isHdr)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && Decoders.IsEmpty)
{
@ -50,15 +54,18 @@ public class VideoToolboxHardwareCapabilities : IHardwareCapabilities @@ -50,15 +54,18 @@ public class VideoToolboxHardwareCapabilities : IHardwareCapabilities
};
}
public FFmpegCapability CanEncode(string videoFormat, Option<string> videoProfile, Option<IPixelFormat> maybePixelFormat)
public FFmpegCapability CanEncode(
string videoFormat,
Option<string> videoProfile,
Option<IPixelFormat> maybePixelFormat)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) && Encoders.IsEmpty)
{
var encoderList = VideoToolboxUtil.GetAvailableEncoders(_logger);
List<string> encoderList = VideoToolboxUtil.GetAvailableEncoders(_logger);
_logger.LogDebug("VideoToolbox reports {Count} encoders", encoderList.Count);
// we only really care about h264 and hevc hardware encoders
foreach (var encoder in encoderList)
foreach (string encoder in encoderList)
{
if (encoder.Contains("HEVC (HW)", StringComparison.OrdinalIgnoreCase))
{

2
ErsatzTV.FFmpeg/Environment/CudaVisibleDevicesVariable.cs

@ -2,7 +2,7 @@ @@ -2,7 +2,7 @@
public class CudaVisibleDevicesVariable(string visibleDevices) : IPipelineStep
{
public EnvironmentVariable[] EnvironmentVariables => [ new("CUDA_VISIBLE_DEVICES", visibleDevices) ];
public EnvironmentVariable[] EnvironmentVariables => [new("CUDA_VISIBLE_DEVICES", visibleDevices)];
public string[] GlobalOptions => [];
public string[] InputOptions(InputFile inputFile) => [];
public string[] FilterOptions => [];

5
ErsatzTV.FFmpeg/Filter/ColorspaceFilter.cs

@ -7,8 +7,8 @@ public class ColorspaceFilter : BaseFilter @@ -7,8 +7,8 @@ public class ColorspaceFilter : BaseFilter
private readonly FrameState _currentState;
private readonly IPixelFormat _desiredPixelFormat;
private readonly bool _forceInputOverrides;
private readonly VideoStream _videoStream;
private readonly bool _isQsv;
private readonly VideoStream _videoStream;
public ColorspaceFilter(
FrameState currentState,
@ -106,7 +106,8 @@ public class ColorspaceFilter : BaseFilter @@ -106,7 +106,8 @@ public class ColorspaceFilter : BaseFilter
string colorspace = _desiredPixelFormat.BitDepth switch
{
_ when cp.IsUnknown && _isQsv => $"{hwdownload}setparams=range=tv:colorspace=bt709:color_trc=bt709:color_primaries=bt709",
_ when cp.IsUnknown && _isQsv =>
$"{hwdownload}setparams=range=tv:colorspace=bt709:color_trc=bt709:color_primaries=bt709",
_ when cp.IsUnknown => "setparams=range=tv:colorspace=bt709:color_trc=bt709:color_primaries=bt709",
10 when !cp.IsUnknown =>
$"{hwdownload}colorspace={inputOverrides}all=bt709:format=yuv420p10",

2
ErsatzTV.FFmpeg/Filter/ComplexFilter.cs

@ -6,8 +6,8 @@ namespace ErsatzTV.FFmpeg.Filter; @@ -6,8 +6,8 @@ namespace ErsatzTV.FFmpeg.Filter;
public class ComplexFilter : IPipelineStep
{
private readonly Option<AudioInputFile> _maybeAudioInputFile;
private readonly Option<SubtitleInputFile> _maybeSubtitleInputFile;
private readonly Option<GraphicsEngineInput> _maybeGraphicsEngineInput;
private readonly Option<SubtitleInputFile> _maybeSubtitleInputFile;
private readonly Option<VideoInputFile> _maybeVideoInputFile;
private readonly Option<WatermarkInputFile> _maybeWatermarkInputFile;
private readonly PipelineContext _pipelineContext;

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save