fix deco selection logic (#2403)
This commit is contained in:
@@ -35,6 +35,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
- Fix watermarks and graphics elements on primary content split by mid-roll filler
|
||||
- Fix watermarks and graphics elements when `Scaling Behavior` is `Crop`
|
||||
- Fix hardware acceleration health check message on mobile
|
||||
- Fix deco selection logic
|
||||
|
||||
### Changed
|
||||
- **BREAKING CHANGE**: change how `Scripted Schedule` system works
|
||||
|
||||
+4
-1
@@ -37,6 +37,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
private readonly IMediaCollectionRepository _mediaCollectionRepository;
|
||||
private readonly IMusicVideoCreditsGenerator _musicVideoCreditsGenerator;
|
||||
private readonly IWatermarkSelector _watermarkSelector;
|
||||
private readonly IDecoSelector _decoSelector;
|
||||
private readonly IPlexPathReplacementService _plexPathReplacementService;
|
||||
private readonly ISongVideoGenerator _songVideoGenerator;
|
||||
private readonly ITelevisionRepository _televisionRepository;
|
||||
@@ -55,6 +56,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
ISongVideoGenerator songVideoGenerator,
|
||||
IMusicVideoCreditsGenerator musicVideoCreditsGenerator,
|
||||
IWatermarkSelector watermarkSelector,
|
||||
IDecoSelector decoSelector,
|
||||
ILogger<GetPlayoutItemProcessByChannelNumberHandler> logger)
|
||||
: base(dbContextFactory)
|
||||
{
|
||||
@@ -70,6 +72,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
_songVideoGenerator = songVideoGenerator;
|
||||
_musicVideoCreditsGenerator = musicVideoCreditsGenerator;
|
||||
_watermarkSelector = watermarkSelector;
|
||||
_decoSelector = decoSelector;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -768,7 +771,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
|
||||
|
||||
private DeadAirFallbackResult GetDecoDeadAirFallback(Playout playout, DateTimeOffset now)
|
||||
{
|
||||
DecoEntries decoEntries = DecoSelector.GetDecoEntries(playout, now);
|
||||
DecoEntries decoEntries = _decoSelector.GetDecoEntries(playout, now);
|
||||
|
||||
// first, check deco template / active deco
|
||||
foreach (Deco templateDeco in decoEntries.TemplateDeco)
|
||||
|
||||
@@ -0,0 +1,126 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.Core.FFmpeg;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NUnit.Framework;
|
||||
using Serilog;
|
||||
using Shouldly;
|
||||
|
||||
namespace ErsatzTV.Core.Tests.FFmpeg;
|
||||
|
||||
[TestFixture]
|
||||
public class DecoSelectorTests
|
||||
{
|
||||
private static readonly DecoSelector DecoSelector;
|
||||
|
||||
static DecoSelectorTests()
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Debug()
|
||||
.WriteTo.Console()
|
||||
.CreateLogger();
|
||||
|
||||
var loggerFactory = new LoggerFactory().AddSerilog(Log.Logger);
|
||||
|
||||
DecoSelector = new DecoSelector(loggerFactory.CreateLogger<DecoSelector>());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDecoEntries_Should_Not_Select_Deco_Before_Start_Time()
|
||||
{
|
||||
var deco = new Deco { Id = 1, Name = "Test Deco" };
|
||||
|
||||
var decoTemplateItem = new DecoTemplateItem
|
||||
{
|
||||
Id = 1,
|
||||
DecoId = 1,
|
||||
Deco = deco,
|
||||
StartTime = new TimeSpan(10, 0, 0),
|
||||
EndTime = new TimeSpan(18, 0, 0)
|
||||
};
|
||||
|
||||
var decoTemplate = new DecoTemplate
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Test Deco Template",
|
||||
Items = new List<DecoTemplateItem> { decoTemplateItem }
|
||||
};
|
||||
|
||||
var playoutTemplate = new PlayoutTemplate
|
||||
{
|
||||
Id = 1,
|
||||
Template = new Template { Id = 1, Name = "Test Template" },
|
||||
DecoTemplate = decoTemplate,
|
||||
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
|
||||
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
|
||||
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
|
||||
};
|
||||
|
||||
var playout = new Playout
|
||||
{
|
||||
Id = 1,
|
||||
Templates = new List<PlayoutTemplate> { playoutTemplate }
|
||||
};
|
||||
|
||||
var now = new DateTimeOffset(2025, 9, 9, 9, 0, 0, TimeSpan.FromHours(-5));
|
||||
|
||||
var result = DecoSelector.GetDecoEntries(playout, now);
|
||||
|
||||
result.TemplateDeco.IsNone.ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void GetDecoEntries_Should_Select_Correct_Deco_From_Multiple()
|
||||
{
|
||||
var deco1 = new Deco { Id = 1, Name = "Test Deco 1" };
|
||||
var deco2 = new Deco { Id = 2, Name = "Test Deco 2" };
|
||||
|
||||
var decoTemplateItem1 = new DecoTemplateItem
|
||||
{
|
||||
Id = 1,
|
||||
DecoId = 1,
|
||||
Deco = deco1,
|
||||
StartTime = new TimeSpan(8, 0, 0),
|
||||
EndTime = new TimeSpan(10, 0, 0)
|
||||
};
|
||||
|
||||
var decoTemplateItem2 = new DecoTemplateItem
|
||||
{
|
||||
Id = 2,
|
||||
DecoId = 2,
|
||||
Deco = deco2,
|
||||
StartTime = new TimeSpan(10, 0, 0),
|
||||
EndTime = new TimeSpan(18, 0, 0)
|
||||
};
|
||||
|
||||
var decoTemplate = new DecoTemplate
|
||||
{
|
||||
Id = 1,
|
||||
Name = "Test Deco Template",
|
||||
Items = new List<DecoTemplateItem> { decoTemplateItem1, decoTemplateItem2 }
|
||||
};
|
||||
|
||||
var playoutTemplate = new PlayoutTemplate
|
||||
{
|
||||
Id = 1,
|
||||
Template = new Template { Id = 1, Name = "Test Template" },
|
||||
DecoTemplate = decoTemplate,
|
||||
DaysOfWeek = PlayoutTemplate.AllDaysOfWeek(),
|
||||
DaysOfMonth = PlayoutTemplate.AllDaysOfMonth(),
|
||||
MonthsOfYear = PlayoutTemplate.AllMonthsOfYear()
|
||||
};
|
||||
|
||||
var playout = new Playout
|
||||
{
|
||||
Id = 1,
|
||||
Templates = new List<PlayoutTemplate> { playoutTemplate }
|
||||
};
|
||||
|
||||
var now = new DateTimeOffset(2025, 9, 9, 9, 0, 0, TimeSpan.FromHours(-5));
|
||||
|
||||
var result = DecoSelector.GetDecoEntries(playout, now);
|
||||
|
||||
result.TemplateDeco.IsSome.ShouldBeTrue();
|
||||
result.TemplateDeco.IfSome(d => d.ShouldBe(deco1));
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ public class WatermarkSelectorTests
|
||||
|
||||
WatermarkSelector = new WatermarkSelector(
|
||||
Substitute.For<IImageCache>(),
|
||||
new DecoSelector(loggerFactory.CreateLogger<DecoSelector>()),
|
||||
loggerFactory.CreateLogger<WatermarkSelector>());
|
||||
|
||||
WatermarkNone = Option<ChannelWatermark>.None;
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
using ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
using ErsatzTV.Core.Scheduling;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Core.FFmpeg;
|
||||
|
||||
public static class DecoSelector
|
||||
public class DecoSelector(ILogger<DecoSelector> logger) : IDecoSelector
|
||||
{
|
||||
public static DecoEntries GetDecoEntries(Playout playout, DateTimeOffset now)
|
||||
public DecoEntries GetDecoEntries(Playout playout, DateTimeOffset now)
|
||||
{
|
||||
logger.LogDebug("Checking for deco at {Now}", now);
|
||||
|
||||
if (playout is null)
|
||||
{
|
||||
return new DecoEntries(Option<Deco>.None, Option<Deco>.None);
|
||||
@@ -23,9 +27,10 @@ public static class DecoSelector
|
||||
{
|
||||
Option<DecoTemplateItem> maybeItem = Optional(activeTemplate.DecoTemplate)
|
||||
.SelectMany(dt => dt.Items)
|
||||
.Find(i => i.StartTime <= now.TimeOfDay && i.EndTime == TimeSpan.Zero || i.EndTime > now.TimeOfDay);
|
||||
.Find(i => i.StartTime <= now.TimeOfDay && (i.EndTime == TimeSpan.Zero || i.EndTime > now.TimeOfDay));
|
||||
foreach (DecoTemplateItem item in maybeItem)
|
||||
{
|
||||
logger.LogDebug("Selecting deco between {Start} and {End}", item.StartTime, item.EndTime);
|
||||
maybeTemplateDeco = Optional(item.Deco);
|
||||
}
|
||||
}
|
||||
@@ -33,5 +38,3 @@ public static class DecoSelector
|
||||
return new DecoEntries(maybeTemplateDeco, maybePlayoutDeco);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed record DecoEntries(Option<Deco> TemplateDeco, Option<Deco> PlayoutDeco);
|
||||
|
||||
@@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace ErsatzTV.Core.FFmpeg;
|
||||
|
||||
public class WatermarkSelector(IImageCache imageCache, ILogger<WatermarkSelector> logger)
|
||||
public class WatermarkSelector(IImageCache imageCache, IDecoSelector decoSelector, ILogger<WatermarkSelector> logger)
|
||||
: IWatermarkSelector
|
||||
{
|
||||
public List<WatermarkOptions> SelectWatermarks(
|
||||
@@ -17,6 +17,8 @@ public class WatermarkSelector(IImageCache imageCache, ILogger<WatermarkSelector
|
||||
PlayoutItem playoutItem,
|
||||
DateTimeOffset now)
|
||||
{
|
||||
logger.LogDebug("Checking for watermark at {Now}", now);
|
||||
|
||||
var result = new List<WatermarkOptions>();
|
||||
|
||||
if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect)
|
||||
@@ -30,7 +32,7 @@ public class WatermarkSelector(IImageCache imageCache, ILogger<WatermarkSelector
|
||||
return result;
|
||||
}
|
||||
|
||||
DecoEntries decoEntries = DecoSelector.GetDecoEntries(playoutItem.Playout, now);
|
||||
DecoEntries decoEntries = decoSelector.GetDecoEntries(playoutItem.Playout, now);
|
||||
|
||||
// first, check deco template / active deco
|
||||
foreach (Deco templateDeco in decoEntries.TemplateDeco)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
using ErsatzTV.Core.Domain.Scheduling;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
|
||||
public sealed record DecoEntries(Option<Deco> TemplateDeco, Option<Deco> PlayoutDeco);
|
||||
@@ -0,0 +1,8 @@
|
||||
using ErsatzTV.Core.Domain;
|
||||
|
||||
namespace ErsatzTV.Core.Interfaces.FFmpeg;
|
||||
|
||||
public interface IDecoSelector
|
||||
{
|
||||
DecoEntries GetDecoEntries(Playout playout, DateTimeOffset now);
|
||||
}
|
||||
@@ -348,6 +348,7 @@ public class TranscodingTests
|
||||
|
||||
WatermarkSelector watermarkSelector = new WatermarkSelector(
|
||||
mockImageCache,
|
||||
new DecoSelector(LoggerFactory.CreateLogger<DecoSelector>()),
|
||||
LoggerFactory.CreateLogger<WatermarkSelector>());
|
||||
|
||||
List<WatermarkOptions> watermarks = [];
|
||||
@@ -653,6 +654,7 @@ public class TranscodingTests
|
||||
|
||||
WatermarkSelector watermarkSelector = new WatermarkSelector(
|
||||
mockImageCache,
|
||||
new DecoSelector(LoggerFactory.CreateLogger<DecoSelector>()),
|
||||
LoggerFactory.CreateLogger<WatermarkSelector>());
|
||||
|
||||
List<WatermarkOptions> watermarks = [];
|
||||
|
||||
+1
-6
@@ -82,12 +82,7 @@ public class Program
|
||||
// streaming
|
||||
.MinimumLevel.Override("ErsatzTV.Application.Streaming", LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override("ErsatzTV.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override(
|
||||
"ErsatzTV.Core.FFmpeg.FFmpegLibraryProcessService",
|
||||
LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override(
|
||||
"ErsatzTV.Core.FFmpeg.FFmpegStreamSelector",
|
||||
LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override("ErsatzTV.Core.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override("ErsatzTV.Controllers.IptvController", LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override("ErsatzTV.Controllers.InternalController", LoggingLevelSwitches.StreamingLevelSwitch)
|
||||
.MinimumLevel.Override(
|
||||
|
||||
@@ -776,6 +776,7 @@ public class Startup
|
||||
services.AddScoped<IGraphicsElementRepository, GraphicsElementRepository>();
|
||||
services.AddScoped<ITemplateDataRepository, TemplateDataRepository>();
|
||||
services.AddScoped<TemplateFunctions>();
|
||||
services.AddScoped<IDecoSelector, DecoSelector>();
|
||||
services.AddScoped<IWatermarkSelector, WatermarkSelector>();
|
||||
|
||||
services.AddScoped<IFFmpegProcessService, FFmpegLibraryProcessService>();
|
||||
|
||||
Reference in New Issue
Block a user