Browse Source

fix deco selection logic (#2403)

pull/2404/head
Jason Dove 11 months ago committed by GitHub
parent
commit
a2fd23a131
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 5
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  3. 126
      ErsatzTV.Core.Tests/FFmpeg/DecoSelectorTests.cs
  4. 1
      ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorTests.cs
  5. 13
      ErsatzTV.Core/FFmpeg/DecoSelector.cs
  6. 6
      ErsatzTV.Core/FFmpeg/WatermarkSelector.cs
  7. 5
      ErsatzTV.Core/Interfaces/FFmpeg/DecoEntries.cs
  8. 8
      ErsatzTV.Core/Interfaces/FFmpeg/IDecoSelector.cs
  9. 2
      ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs
  10. 7
      ErsatzTV/Program.cs
  11. 1
      ErsatzTV/Startup.cs

1
CHANGELOG.md

@ -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 on primary content split by mid-roll filler
- Fix watermarks and graphics elements when `Scaling Behavior` is `Crop` - Fix watermarks and graphics elements when `Scaling Behavior` is `Crop`
- Fix hardware acceleration health check message on mobile - Fix hardware acceleration health check message on mobile
- Fix deco selection logic
### Changed ### Changed
- **BREAKING CHANGE**: change how `Scripted Schedule` system works - **BREAKING CHANGE**: change how `Scripted Schedule` system works

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

@ -37,6 +37,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
private readonly IMediaCollectionRepository _mediaCollectionRepository; private readonly IMediaCollectionRepository _mediaCollectionRepository;
private readonly IMusicVideoCreditsGenerator _musicVideoCreditsGenerator; private readonly IMusicVideoCreditsGenerator _musicVideoCreditsGenerator;
private readonly IWatermarkSelector _watermarkSelector; private readonly IWatermarkSelector _watermarkSelector;
private readonly IDecoSelector _decoSelector;
private readonly IPlexPathReplacementService _plexPathReplacementService; private readonly IPlexPathReplacementService _plexPathReplacementService;
private readonly ISongVideoGenerator _songVideoGenerator; private readonly ISongVideoGenerator _songVideoGenerator;
private readonly ITelevisionRepository _televisionRepository; private readonly ITelevisionRepository _televisionRepository;
@ -55,6 +56,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
ISongVideoGenerator songVideoGenerator, ISongVideoGenerator songVideoGenerator,
IMusicVideoCreditsGenerator musicVideoCreditsGenerator, IMusicVideoCreditsGenerator musicVideoCreditsGenerator,
IWatermarkSelector watermarkSelector, IWatermarkSelector watermarkSelector,
IDecoSelector decoSelector,
ILogger<GetPlayoutItemProcessByChannelNumberHandler> logger) ILogger<GetPlayoutItemProcessByChannelNumberHandler> logger)
: base(dbContextFactory) : base(dbContextFactory)
{ {
@ -70,6 +72,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
_songVideoGenerator = songVideoGenerator; _songVideoGenerator = songVideoGenerator;
_musicVideoCreditsGenerator = musicVideoCreditsGenerator; _musicVideoCreditsGenerator = musicVideoCreditsGenerator;
_watermarkSelector = watermarkSelector; _watermarkSelector = watermarkSelector;
_decoSelector = decoSelector;
_logger = logger; _logger = logger;
} }
@ -768,7 +771,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
private DeadAirFallbackResult GetDecoDeadAirFallback(Playout playout, DateTimeOffset now) 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 // first, check deco template / active deco
foreach (Deco templateDeco in decoEntries.TemplateDeco) foreach (Deco templateDeco in decoEntries.TemplateDeco)

126
ErsatzTV.Core.Tests/FFmpeg/DecoSelectorTests.cs

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

1
ErsatzTV.Core.Tests/FFmpeg/WatermarkSelectorTests.cs

@ -66,6 +66,7 @@ public class WatermarkSelectorTests
WatermarkSelector = new WatermarkSelector( WatermarkSelector = new WatermarkSelector(
Substitute.For<IImageCache>(), Substitute.For<IImageCache>(),
new DecoSelector(loggerFactory.CreateLogger<DecoSelector>()),
loggerFactory.CreateLogger<WatermarkSelector>()); loggerFactory.CreateLogger<WatermarkSelector>());
WatermarkNone = Option<ChannelWatermark>.None; WatermarkNone = Option<ChannelWatermark>.None;

13
ErsatzTV.Core/FFmpeg/DecoSelector.cs

@ -1,13 +1,17 @@
using ErsatzTV.Core.Domain; using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Domain.Scheduling; using ErsatzTV.Core.Domain.Scheduling;
using ErsatzTV.Core.Interfaces.FFmpeg;
using ErsatzTV.Core.Scheduling; using ErsatzTV.Core.Scheduling;
using Microsoft.Extensions.Logging;
namespace ErsatzTV.Core.FFmpeg; 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) if (playout is null)
{ {
return new DecoEntries(Option<Deco>.None, Option<Deco>.None); return new DecoEntries(Option<Deco>.None, Option<Deco>.None);
@ -23,9 +27,10 @@ public static class DecoSelector
{ {
Option<DecoTemplateItem> maybeItem = Optional(activeTemplate.DecoTemplate) Option<DecoTemplateItem> maybeItem = Optional(activeTemplate.DecoTemplate)
.SelectMany(dt => dt.Items) .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) foreach (DecoTemplateItem item in maybeItem)
{ {
logger.LogDebug("Selecting deco between {Start} and {End}", item.StartTime, item.EndTime);
maybeTemplateDeco = Optional(item.Deco); maybeTemplateDeco = Optional(item.Deco);
} }
} }
@ -33,5 +38,3 @@ public static class DecoSelector
return new DecoEntries(maybeTemplateDeco, maybePlayoutDeco); return new DecoEntries(maybeTemplateDeco, maybePlayoutDeco);
} }
} }
public sealed record DecoEntries(Option<Deco> TemplateDeco, Option<Deco> PlayoutDeco);

6
ErsatzTV.Core/FFmpeg/WatermarkSelector.cs

@ -8,7 +8,7 @@ using Microsoft.Extensions.Logging;
namespace ErsatzTV.Core.FFmpeg; namespace ErsatzTV.Core.FFmpeg;
public class WatermarkSelector(IImageCache imageCache, ILogger<WatermarkSelector> logger) public class WatermarkSelector(IImageCache imageCache, IDecoSelector decoSelector, ILogger<WatermarkSelector> logger)
: IWatermarkSelector : IWatermarkSelector
{ {
public List<WatermarkOptions> SelectWatermarks( public List<WatermarkOptions> SelectWatermarks(
@ -17,6 +17,8 @@ public class WatermarkSelector(IImageCache imageCache, ILogger<WatermarkSelector
PlayoutItem playoutItem, PlayoutItem playoutItem,
DateTimeOffset now) DateTimeOffset now)
{ {
logger.LogDebug("Checking for watermark at {Now}", now);
var result = new List<WatermarkOptions>(); var result = new List<WatermarkOptions>();
if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect) if (channel.StreamingMode == StreamingMode.HttpLiveStreamingDirect)
@ -30,7 +32,7 @@ public class WatermarkSelector(IImageCache imageCache, ILogger<WatermarkSelector
return result; return result;
} }
DecoEntries decoEntries = DecoSelector.GetDecoEntries(playoutItem.Playout, now); DecoEntries decoEntries = decoSelector.GetDecoEntries(playoutItem.Playout, now);
// first, check deco template / active deco // first, check deco template / active deco
foreach (Deco templateDeco in decoEntries.TemplateDeco) foreach (Deco templateDeco in decoEntries.TemplateDeco)

5
ErsatzTV.Core/Interfaces/FFmpeg/DecoEntries.cs

@ -0,0 +1,5 @@
using ErsatzTV.Core.Domain.Scheduling;
namespace ErsatzTV.Core.Interfaces.FFmpeg;
public sealed record DecoEntries(Option<Deco> TemplateDeco, Option<Deco> PlayoutDeco);

8
ErsatzTV.Core/Interfaces/FFmpeg/IDecoSelector.cs

@ -0,0 +1,8 @@
using ErsatzTV.Core.Domain;
namespace ErsatzTV.Core.Interfaces.FFmpeg;
public interface IDecoSelector
{
DecoEntries GetDecoEntries(Playout playout, DateTimeOffset now);
}

2
ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs

@ -348,6 +348,7 @@ public class TranscodingTests
WatermarkSelector watermarkSelector = new WatermarkSelector( WatermarkSelector watermarkSelector = new WatermarkSelector(
mockImageCache, mockImageCache,
new DecoSelector(LoggerFactory.CreateLogger<DecoSelector>()),
LoggerFactory.CreateLogger<WatermarkSelector>()); LoggerFactory.CreateLogger<WatermarkSelector>());
List<WatermarkOptions> watermarks = []; List<WatermarkOptions> watermarks = [];
@ -653,6 +654,7 @@ public class TranscodingTests
WatermarkSelector watermarkSelector = new WatermarkSelector( WatermarkSelector watermarkSelector = new WatermarkSelector(
mockImageCache, mockImageCache,
new DecoSelector(LoggerFactory.CreateLogger<DecoSelector>()),
LoggerFactory.CreateLogger<WatermarkSelector>()); LoggerFactory.CreateLogger<WatermarkSelector>());
List<WatermarkOptions> watermarks = []; List<WatermarkOptions> watermarks = [];

7
ErsatzTV/Program.cs

@ -82,12 +82,7 @@ public class Program
// streaming // streaming
.MinimumLevel.Override("ErsatzTV.Application.Streaming", LoggingLevelSwitches.StreamingLevelSwitch) .MinimumLevel.Override("ErsatzTV.Application.Streaming", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override("ErsatzTV.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch) .MinimumLevel.Override("ErsatzTV.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override( .MinimumLevel.Override("ErsatzTV.Core.FFmpeg", LoggingLevelSwitches.StreamingLevelSwitch)
"ErsatzTV.Core.FFmpeg.FFmpegLibraryProcessService",
LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override(
"ErsatzTV.Core.FFmpeg.FFmpegStreamSelector",
LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override("ErsatzTV.Controllers.IptvController", LoggingLevelSwitches.StreamingLevelSwitch) .MinimumLevel.Override("ErsatzTV.Controllers.IptvController", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override("ErsatzTV.Controllers.InternalController", LoggingLevelSwitches.StreamingLevelSwitch) .MinimumLevel.Override("ErsatzTV.Controllers.InternalController", LoggingLevelSwitches.StreamingLevelSwitch)
.MinimumLevel.Override( .MinimumLevel.Override(

1
ErsatzTV/Startup.cs

@ -776,6 +776,7 @@ public class Startup
services.AddScoped<IGraphicsElementRepository, GraphicsElementRepository>(); services.AddScoped<IGraphicsElementRepository, GraphicsElementRepository>();
services.AddScoped<ITemplateDataRepository, TemplateDataRepository>(); services.AddScoped<ITemplateDataRepository, TemplateDataRepository>();
services.AddScoped<TemplateFunctions>(); services.AddScoped<TemplateFunctions>();
services.AddScoped<IDecoSelector, DecoSelector>();
services.AddScoped<IWatermarkSelector, WatermarkSelector>(); services.AddScoped<IWatermarkSelector, WatermarkSelector>();
services.AddScoped<IFFmpegProcessService, FFmpegLibraryProcessService>(); services.AddScoped<IFFmpegProcessService, FFmpegLibraryProcessService>();

Loading…
Cancel
Save