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
This commit is contained in:
Jason Dove
2025-08-16 09:44:48 -05:00
committed by GitHub
parent 6d32dac51b
commit 5d081ceeff
331 changed files with 4908 additions and 4271 deletions

View File

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

View File

@@ -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
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,7 +85,22 @@ 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`
dotnet_diagnostic.ca1848.severity = none
dotnet_diagnostic.ca1848.severity = none

View File

@@ -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;
}

View File

@@ -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;
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
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;
}

View File

@@ -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();

View File

@@ -30,4 +30,4 @@
<ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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();

View File

@@ -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(
.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(
.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(
.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);
@@ -86,4 +86,4 @@ public class RefreshGraphicsElementsHandler(
await dbContext.SaveChangesAsync(cancellationToken);
}
}
}

View File

@@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Graphics;
public record GraphicsElementViewModel(int Id, string Name);
public record GraphicsElementViewModel(int Id, string Name);

View File

@@ -6,13 +6,13 @@ 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}"),
GraphicsElementKind.Image => new GraphicsElementViewModel(graphicsElement.Id, $"image/{fileName}"),
GraphicsElementKind.Subtitle => new GraphicsElementViewModel(graphicsElement.Id, $"subtitle/{fileName}"),
GraphicsElementKind.Subtitle => new GraphicsElementViewModel(graphicsElement.Id, $"subtitle/{fileName}"),
_ => new GraphicsElementViewModel(graphicsElement.Id, graphicsElement.Path)
};
}
}
}

View File

@@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Graphics;
public record GetAllGraphicsElements : IRequest<List<GraphicsElementViewModel>>;
public record GetAllGraphicsElements : IRequest<List<GraphicsElementViewModel>>;

View File

@@ -16,4 +16,4 @@ public class GetAllGraphicsElementsHandler(IDbContextFactory<TvContext> dbContex
.ToListAsync(cancellationToken)
.Map(list => list.Map(ProjectToViewModel).ToList());
}
}
}

View File

@@ -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;
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
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;
}

View File

@@ -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();

View File

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

View File

@@ -1,3 +1,3 @@
namespace ErsatzTV.Application.Libraries;
public record QueueShowScanByLibraryId(int LibraryId, int ShowId, string ShowTitle, bool DeepScan) : IRequest<bool>;
public record QueueShowScanByLibraryId(int LibraryId, int ShowId, string ShowTitle, bool DeepScan) : IRequest<bool>;

View File

@@ -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(
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;
@@ -87,4 +91,4 @@ public class QueueShowScanByLibraryIdHandler(
return false;
}
}
}

View File

@@ -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

View File

@@ -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 =

View File

@@ -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)
{

View File

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

View File

@@ -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
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
}
// 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
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
break;
}
int changeCount = 0;
var changeCount = 0;
if (result.ClearItems)
{
@@ -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
.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
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
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
.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
.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
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
.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
.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
.ThenInclude(psi => psi.FallbackFiller)
.ToListAsync();
var playoutHistory = await dbContext.PlayoutHistory
List<PlayoutHistory> playoutHistory = await dbContext.PlayoutHistory
.AsNoTracking()
.Where(h => h.PlayoutId == playoutId)
.ToListAsync();

View File

@@ -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);
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -1,3 +1,4 @@
using ErsatzTV.Core.Domain;
using ErsatzTV.Infrastructure.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
@@ -18,22 +19,21 @@ public class CheckForOverlappingPlayoutItemsHandler(
.AnyAsync(
a => dbContext.PlayoutItems
.Where(b => b.PlayoutId == a.PlayoutId)
.Any(
b =>
a.Id < b.Id &&
a.Start < b.Finish &&
a.Finish > b.Start),
.Any(b =>
a.Id < b.Id &&
a.Start < b.Finish &&
a.Finish > b.Start),
cancellationToken);
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.",

View File

@@ -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;
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
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;
}

View File

@@ -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();

View File

@@ -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(

View File

@@ -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
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
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
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,

View File

@@ -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);

View File

@@ -43,4 +43,4 @@ public class CreateDecoGroupHandler(IDbContextFactory<TvContext> dbContextFactor
return (result1, result2).Apply((_, _) => createDecoGroup.Name);
}
}
}

View File

@@ -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(

View File

@@ -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(

View File

@@ -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)
break;
}
}
existing.DefaultFillerTrimToFit = request.DefaultFillerTrimToFit;
// dead air fallback

View File

@@ -9,9 +9,10 @@ internal static class Mapper
internal static TreeViewModel ProjectToViewModel(List<DecoTemplateGroup> decoTemplateGroups) =>
new(
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.Id,
dtg.Name,
dtg.DecoTemplates.OrderBy(dt => dt.Name).Map(dt => new TreeItemViewModel(dt.Id, dt.Name)).ToList()))
.ToList());
internal static TreeViewModel ProjectToViewModel(List<DecoGroup> decoGroups) =>
new(
@@ -30,9 +31,11 @@ internal static class Mapper
internal static BlockTreeViewModel ProjectToViewModel(List<BlockGroup> blockGroups) =>
new(
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.Id,
bg.Name,
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);

View File

@@ -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)
{

View File

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

View File

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

View File

@@ -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>;

View File

@@ -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;

View File

@@ -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
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
private HlsSessionState _state;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private DateTimeOffset _channelStart;
public HlsSessionWorker(
IServiceScopeFactory serviceScopeFactory,
@@ -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
{
_channelNumber = channelNumber;
foreach (var timeout in idleTimeout)
foreach (TimeSpan timeout in idleTimeout)
{
lock (_sync)
{
@@ -181,14 +184,14 @@ public class HlsSessionWorker : IHlsSessionWorker
Touch();
_transcodedUntil = DateTimeOffset.Now;
PlaylistStart = _transcodedUntil;
_channelStart = _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
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
{
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
{
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
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
}
finally
{
foreach (var pipe in maybePipe)
foreach (Pipe pipe in maybePipe)
{
await pipe.Writer.CompleteAsync();
}

View File

@@ -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
private HlsSessionState _state;
private Timer _timer;
private DateTimeOffset _transcodedUntil;
private DateTimeOffset _channelStart;
public HlsSessionWorkerV2(
IServiceScopeFactory serviceScopeFactory,
@@ -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
{
_channelNumber = channelNumber;
foreach (var timeout in idleTimeout)
foreach (TimeSpan timeout in idleTimeout)
{
lock (_sync)
{
@@ -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),

View File

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

View File

@@ -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 =
{

View File

@@ -290,18 +290,19 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
disableWatermarks = false;
playoutItemWatermarks.Clear();
playoutItemWatermarks.Add(new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
WidthPercent = 100,
HorizontalMarginPercent = 0,
VerticalMarginPercent = 0,
Opacity = 100,
Location = WatermarkLocation.TopLeft,
ImageSource = ChannelWatermarkImageSource.Resource,
Image = image
});
playoutItemWatermarks.Add(
new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
WidthPercent = 100,
HorizontalMarginPercent = 0,
VerticalMarginPercent = 0,
Opacity = 100,
Location = WatermarkLocation.TopLeft,
ImageSource = ChannelWatermarkImageSource.Resource,
Image = image
});
}
}
@@ -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,

View File

@@ -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
{
foreach (string path in GetRelativeOutputPath(mediaItemId, subtitle))
{
return _localFileSystem.FileExists(path) == false;
return !_localFileSystem.FileExists(path);
}
return false;

View File

@@ -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(

View File

@@ -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,8 +100,8 @@ public class PrepareTroubleshootingPlaybackHandler(
List<ChannelWatermark> watermarks = [];
if (request.WatermarkIds.Count > 0)
{
var channelWatermarks = await dbContext.ChannelWatermarks
.Where(w => request.WatermarkIds.Contains(w.Id))
List<ChannelWatermark> channelWatermarks = await dbContext.ChannelWatermarks
.Where(w => request.WatermarkIds.Contains(w.Id))
.ToListAsync();
watermarks.AddRange(channelWatermarks);
@@ -126,18 +130,19 @@ public class PrepareTroubleshootingPlaybackHandler(
string image = is43 ? "song_progress_overlay_43.png" : "song_progress_overlay.png";
watermarks.Clear();
watermarks.Add(new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
WidthPercent = 100,
HorizontalMarginPercent = 0,
VerticalMarginPercent = 0,
Opacity = 100,
Location = WatermarkLocation.TopLeft,
ImageSource = ChannelWatermarkImageSource.Resource,
Image = image
});
watermarks.Add(
new ChannelWatermark
{
Mode = ChannelWatermarkMode.Permanent,
Size = WatermarkSize.Scaled,
WidthPercent = 100,
HorizontalMarginPercent = 0,
VerticalMarginPercent = 0,
Opacity = 100,
Location = WatermarkLocation.TopLeft,
ImageSource = ChannelWatermarkImageSource.Resource,
Image = image
});
}
}
@@ -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(
}
}
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(
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(
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(
.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)

View File

@@ -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(
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(
}
finally
{
foreach (var pipe in maybePipe)
foreach (Pipe pipe in maybePipe)
{
await pipe.Writer.CompleteAsync();
}

View File

@@ -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();
}

View File

@@ -35,4 +35,4 @@
<ProjectReference Include="..\ErsatzTV.Infrastructure\ErsatzTV.Infrastructure.csproj" />
</ItemGroup>
</Project>
</Project>

View File

@@ -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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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();

View File

@@ -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
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
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
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
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
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
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
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
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
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
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
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);
@@ -728,4 +809,4 @@ public class ContinuePlayoutTests : PlayoutBuilderTestBase
playout.Anchor.DurationFinish.ShouldBeNull();
playout.Anchor.NextStartOffset.ShouldBe(start + TimeSpan.FromHours(6));
}
}
}

View File

@@ -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;
@@ -30,4 +30,4 @@ public class GetStartTimeAfterTests
result.ShouldBe(DateTimeOffset.Parse("2025-11-02T02:00:00-06:00"));
}
}
}

View File

@@ -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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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
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);
@@ -1692,4 +1824,4 @@ public class NewPlayoutTests : PlayoutBuilderTestBase
playout.Anchor.NextStartOffset.ShouldBe(start + TimeSpan.FromHours(48));
}
}
}

View File

@@ -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
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
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,10 +201,17 @@ 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);
}
protected record TestData(PlayoutBuilder Builder, Playout Playout, PlayoutReferenceData ReferenceData);
}
}

View File

@@ -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
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);
@@ -124,4 +138,4 @@ public class RefreshPlayoutTests : PlayoutBuilderTestBase
playout.Anchor.NextStartOffset.ShouldBe(HoursAfterMidnight(48));
}
}
}

View File

@@ -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
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);
@@ -53,4 +65,4 @@ public class ResetPlayoutTests : PlayoutBuilderTestBase
firstSeedValue.ShouldNotBe(secondSeedValue);
}
}
}

View File

@@ -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);

View File

@@ -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
[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)
{

View File

@@ -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
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
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
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
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
.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
.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
.ThenInclude(psi => psi.FallbackFiller)
.ToListAsync();
var playoutHistory = await dbContext.PlayoutHistory
List<PlayoutHistory> playoutHistory = await dbContext.PlayoutHistory
.AsNoTracking()
.Where(h => h.PlayoutId == playoutId)
.ToListAsync();

View File

@@ -7,4 +7,4 @@ public class GraphicsElement
public GraphicsElementKind Kind { get; set; }
public List<PlayoutItem> PlayoutItems { get; set; }
public List<PlayoutItemGraphicsElement> PlayoutItemGraphicsElements { get; set; }
}
}

View File

@@ -5,4 +5,4 @@ public enum GraphicsElementKind
Image = 0,
Text = 1,
Subtitle = 2
}
}

View File

@@ -44,4 +44,4 @@
<ProjectReference Include="..\ErsatzTV.FFmpeg\ErsatzTV.FFmpeg.csproj" />
</ItemGroup>
</Project>
</Project>

View File

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

View File

@@ -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
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
}
// 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
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
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
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
}
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
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
}
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
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
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
pipelineAction?.Invoke(pipeline);
var command = GetCommand(
Command command = GetCommand(
ffmpegPath,
videoInputFile,
audioInputFile,

View File

@@ -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;

View File

@@ -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",

View File

@@ -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; }
@@ -54,4 +59,4 @@ public class ImageGraphicsElement
return Option<ImageGraphicsElement>.None;
}
}
}
}

View File

@@ -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);

View File

@@ -3,4 +3,4 @@ using ErsatzTV.Core.Interfaces.Streaming;
namespace ErsatzTV.Core.Interfaces.FFmpeg;
public record PlayoutItemResult(Command Process, Option<GraphicsEngineContext> GraphicsEngineContext);
public record PlayoutItemResult(Command Process, Option<GraphicsEngineContext> GraphicsEngineContext);

View File

@@ -14,4 +14,4 @@ public interface IPlexTelevisionRepository : IMediaServerTelevisionRepository<Pl
public record PlexShowAddTagResult(Option<int> Existing, Option<int> Added);
public record PlexShowTitleKeyResult(string Title, string Key);
public record PlexShowTitleKeyResult(string Title, string Key);

View File

@@ -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);

View File

@@ -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,

View File

@@ -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);
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -5,4 +5,4 @@ namespace ErsatzTV.Core.Interfaces.Streaming;
public interface IGraphicsEngine
{
Task Run(GraphicsEngineContext context, PipeWriter pipeWriter, CancellationToken cancellationToken);
}
}

View File

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

View File

@@ -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(
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(
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

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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)
{

View File

@@ -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
}
}
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
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);
}

View File

@@ -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))
{

View File

@@ -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);
}

View File

@@ -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
{
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
{
// 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
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
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
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,

View File

@@ -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
// 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
};
// 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
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
if (remainingToFill - itemDuration >= TimeSpan.Zero)
{
var playoutItem = new PlayoutItem
{
PlayoutId = playoutBuilderState.PlayoutId,

View File

@@ -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
PlayoutItemWatermarks = []
};
foreach (var programScheduleItemWatermark in scheduleItem.ProgramScheduleItemWatermarks ?? [])
foreach (ProgramScheduleItemWatermark programScheduleItemWatermark in scheduleItem
.ProgramScheduleItemWatermarks ?? [])
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark

View File

@@ -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

View File

@@ -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
};
}
}
break;
}
}
@@ -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

View File

@@ -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

View File

@@ -10,4 +10,4 @@ public record PlayoutReferenceData(
List<PlayoutTemplate> PlayoutTemplates,
ProgramSchedule ProgramSchedule,
List<ProgramScheduleAlternate> ProgramScheduleAlternates,
List<PlayoutHistory> PlayoutHistory);
List<PlayoutHistory> PlayoutHistory);

View File

@@ -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)
{
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);

View File

@@ -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;

View File

@@ -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
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)
{
@@ -61,4 +61,4 @@ public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsEl
return Option<GraphicsElement>.None;
}
}
}

View File

@@ -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
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)
{
@@ -71,4 +71,4 @@ public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsEle
return Option<GraphicsElement>.None;
}
}
}

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