Browse Source

set variables from yaml playout graphics_on instruction (#2284)

pull/2285/head
Jason Dove 12 months ago committed by GitHub
parent
commit
f4eadae8ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 2
      ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs
  3. 5
      ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs
  4. 2
      ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs
  5. 1
      ErsatzTV.Core/Domain/PlayoutItemGraphicsElement.cs
  6. 24
      ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs
  7. 2
      ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs
  8. 3
      ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs
  9. 5
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs
  10. 5
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs
  11. 5
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs
  12. 2
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOffHandler.cs
  13. 14
      ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOnHandler.cs
  14. 2
      ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOnInstruction.cs
  15. 10
      ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs
  16. 6277
      ErsatzTV.Infrastructure.MySql/Migrations/20250808215226_Add_PlayoutItemGraphicsElementVariables.Designer.cs
  17. 29
      ErsatzTV.Infrastructure.MySql/Migrations/20250808215226_Add_PlayoutItemGraphicsElementVariables.cs
  18. 3
      ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs
  19. 6112
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250808213026_Add_PlayoutItemGraphicsElementVariables.Designer.cs
  20. 28
      ErsatzTV.Infrastructure.Sqlite/Migrations/20250808213026_Add_PlayoutItemGraphicsElementVariables.cs
  21. 3
      ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs
  22. 2
      ErsatzTV.Infrastructure/Streaming/GraphicsEngine.cs
  23. 13
      ErsatzTV.Infrastructure/Streaming/TextElement.cs
  24. 3
      ErsatzTV/Resources/yaml-playout-import.schema.json
  25. 3
      ErsatzTV/Resources/yaml-playout.schema.json

1
CHANGELOG.md

@ -27,6 +27,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- Supports constant opacity and opacity expression - Supports constant opacity and opacity expression
- YAML playout: add `graphics_on` and `graphics_off` instructions to control graphics elements - YAML playout: add `graphics_on` and `graphics_off` instructions to control graphics elements
- `graphics_on` requires the name of a graphics element template, e.g. `text/cool_element.yml` - `graphics_on` requires the name of a graphics element template, e.g. `text/cool_element.yml`
- The `variables` property can be used to dynamically replace text from the template
- `graphics_off` will turn off a specific element, or all elements if none are specified - `graphics_off` will turn off a specific element, or all elements if none are specified
### Fix ### Fix

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

@ -161,8 +161,6 @@ public class BuildPlayoutHandler : IRequestHandler<BuildPlayout, Either<BaseErro
if (anyGraphicsElements) if (anyGraphicsElements)
{ {
Console.WriteLine("has graphics elements!");
// copy playout item ids back to graphics elements // copy playout item ids back to graphics elements
var allGraphicsElements = result.AddedItems.SelectMany(item => var allGraphicsElements = result.AddedItems.SelectMany(item =>
item.PlayoutItemGraphicsElements.Select(graphicsElement => item.PlayoutItemGraphicsElements.Select(graphicsElement =>

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

@ -89,7 +89,8 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
.ThenInclude(d => d.Watermark) .ThenInclude(d => d.Watermark)
// get graphics elements // get graphics elements
.Include(i => i.GraphicsElements) .Include(i => i.PlayoutItemGraphicsElements)
.ThenInclude(pige => pige.GraphicsElement)
// get playout templates (and deco templates/decos) // get playout templates (and deco templates/decos)
.Include(i => i.Playout) .Include(i => i.Playout)
@ -344,7 +345,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler<
effectiveNow, effectiveNow,
playoutItemWatermarks, playoutItemWatermarks,
maybeGlobalWatermark, maybeGlobalWatermark,
playoutItemWithPath.PlayoutItem.GraphicsElements, playoutItemWithPath.PlayoutItem.PlayoutItemGraphicsElements,
channel.FFmpegProfile.VaapiDisplay, channel.FFmpegProfile.VaapiDisplay,
channel.FFmpegProfile.VaapiDriver, channel.FFmpegProfile.VaapiDriver,
channel.FFmpegProfile.VaapiDevice, channel.FFmpegProfile.VaapiDevice,

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

@ -145,7 +145,7 @@ public class PrepareTroubleshootingPlaybackHandler(
now, now,
watermarks, watermarks,
Option<ChannelWatermark>.None, Option<ChannelWatermark>.None,
graphicsElements, graphicsElements.Map(ge => new PlayoutItemGraphicsElement { GraphicsElement = ge }).ToList(),
ffmpegProfile.VaapiDisplay, ffmpegProfile.VaapiDisplay,
ffmpegProfile.VaapiDriver, ffmpegProfile.VaapiDriver,
ffmpegProfile.VaapiDevice, ffmpegProfile.VaapiDevice,

1
ErsatzTV.Core/Domain/PlayoutItemGraphicsElement.cs

@ -6,4 +6,5 @@ public class PlayoutItemGraphicsElement
public PlayoutItem PlayoutItem { get; set; } public PlayoutItem PlayoutItem { get; set; }
public int? GraphicsElementId { get; set; } public int? GraphicsElementId { get; set; }
public GraphicsElement GraphicsElement { get; set; } public GraphicsElement GraphicsElement { get; set; }
public string Variables { get; set; }
} }

24
ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs

@ -14,6 +14,7 @@ using ErsatzTV.FFmpeg.Pipeline;
using ErsatzTV.FFmpeg.Preset; using ErsatzTV.FFmpeg.Preset;
using ErsatzTV.FFmpeg.State; using ErsatzTV.FFmpeg.State;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using MediaStream = ErsatzTV.Core.Domain.MediaStream; using MediaStream = ErsatzTV.Core.Domain.MediaStream;
namespace ErsatzTV.Core.FFmpeg; namespace ErsatzTV.Core.FFmpeg;
@ -65,7 +66,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
DateTimeOffset now, DateTimeOffset now,
List<ChannelWatermark> playoutItemWatermarks, List<ChannelWatermark> playoutItemWatermarks,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
List<GraphicsElement> graphicsElements, List<PlayoutItemGraphicsElement> graphicsElements,
string vaapiDisplay, string vaapiDisplay,
VaapiDriver vaapiDriver, VaapiDriver vaapiDriver,
string vaapiDevice, string vaapiDevice,
@ -326,7 +327,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
Option<WatermarkInputFile> watermarkInputFile = Option<WatermarkInputFile>.None; Option<WatermarkInputFile> watermarkInputFile = Option<WatermarkInputFile>.None;
Option<GraphicsEngineInput> graphicsEngineInput = Option<GraphicsEngineInput>.None; Option<GraphicsEngineInput> graphicsEngineInput = Option<GraphicsEngineInput>.None;
Option<GraphicsEngineContext> graphicsEngineContext = Option<GraphicsEngineContext>.None; Option<GraphicsEngineContext> graphicsEngineContext = Option<GraphicsEngineContext>.None;
List<GraphicsElementContext> graphicsElementContexts = new List<GraphicsElementContext>(); List<GraphicsElementContext> graphicsElementContexts = [];
// use graphics engine for all watermarks // use graphics engine for all watermarks
if (!disableWatermarks) if (!disableWatermarks)
@ -374,28 +375,35 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService
graphicsElementContexts.AddRange(watermarks.Values); graphicsElementContexts.AddRange(watermarks.Values);
} }
foreach (var graphicsElement in graphicsElements) foreach (var playoutItemGraphicsElement in graphicsElements)
{ {
switch (graphicsElement.Kind) switch (playoutItemGraphicsElement.GraphicsElement.Kind)
{ {
case GraphicsElementKind.Text: case GraphicsElementKind.Text:
var maybeElement = await TextGraphicsElement.FromFile(graphicsElement.Path); var maybeElement = await TextGraphicsElement.FromFile(playoutItemGraphicsElement.GraphicsElement.Path);
if (maybeElement.IsNone) if (maybeElement.IsNone)
{ {
_logger.LogWarning( _logger.LogWarning(
"Failed to load text graphics element from file {Path}; ignoring", "Failed to load text graphics element from file {Path}; ignoring",
graphicsElement.Path); playoutItemGraphicsElement.GraphicsElement.Path);
} }
foreach (var element in maybeElement) foreach (var element in maybeElement)
{ {
graphicsElementContexts.Add(new TextElementContext(element)); var variables = new Dictionary<string, string>();
if (!string.IsNullOrWhiteSpace(playoutItemGraphicsElement.Variables))
{
variables = JsonConvert.DeserializeObject<Dictionary<string, string>>(
playoutItemGraphicsElement.Variables);
}
graphicsElementContexts.Add(new TextElementContext(element, variables));
} }
break; break;
default: default:
_logger.LogInformation( _logger.LogInformation(
"Ignoring unsupported graphics element kind {Kind}", "Ignoring unsupported graphics element kind {Kind}",
nameof(graphicsElement.Kind)); nameof(playoutItemGraphicsElement.GraphicsElement.Kind));
break; break;
} }
} }

2
ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs

@ -28,7 +28,7 @@ public interface IFFmpegProcessService
DateTimeOffset now, DateTimeOffset now,
List<ChannelWatermark> playoutItemWatermarks, List<ChannelWatermark> playoutItemWatermarks,
Option<ChannelWatermark> globalWatermark, Option<ChannelWatermark> globalWatermark,
List<GraphicsElement> graphicsElements, List<PlayoutItemGraphicsElement> graphicsElements,
string vaapiDisplay, string vaapiDisplay,
VaapiDriver vaapiDriver, VaapiDriver vaapiDriver,
string vaapiDevice, string vaapiDevice,

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

@ -17,4 +17,5 @@ public abstract record GraphicsElementContext;
public record WatermarkElementContext(WatermarkOptions Options) : GraphicsElementContext; public record WatermarkElementContext(WatermarkOptions Options) : GraphicsElementContext;
public record TextElementContext(TextGraphicsElement TextElement) : GraphicsElementContext; public record TextElementContext(TextGraphicsElement TextElement, Dictionary<string, string> Variables)
: GraphicsElementContext;

5
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs

@ -79,13 +79,14 @@ public class YamlPlayoutAllHandler(EnumeratorCache enumeratorCache) : YamlPlayou
}); });
} }
foreach (int graphicsElementId in context.GetGraphicsElementIds()) foreach ((int graphicsElementId, string variablesJson) in context.GetGraphicsElements())
{ {
playoutItem.PlayoutItemGraphicsElements.Add( playoutItem.PlayoutItemGraphicsElements.Add(
new PlayoutItemGraphicsElement new PlayoutItemGraphicsElement
{ {
PlayoutItem = playoutItem, PlayoutItem = playoutItem,
GraphicsElementId = graphicsElementId GraphicsElementId = graphicsElementId,
Variables = variablesJson
}); });
} }

5
ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs

@ -104,13 +104,14 @@ public class YamlPlayoutCountHandler(EnumeratorCache enumeratorCache) : YamlPlay
}); });
} }
foreach (int graphicsElementId in context.GetGraphicsElementIds()) foreach ((int graphicsElementId, string variablesJson) in context.GetGraphicsElements())
{ {
playoutItem.PlayoutItemGraphicsElements.Add( playoutItem.PlayoutItemGraphicsElements.Add(
new PlayoutItemGraphicsElement new PlayoutItemGraphicsElement
{ {
PlayoutItem = playoutItem, PlayoutItem = playoutItem,
GraphicsElementId = graphicsElementId GraphicsElementId = graphicsElementId,
Variables = variablesJson
}); });
} }

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

@ -138,13 +138,14 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP
}); });
} }
foreach (int graphicsElementId in context.GetGraphicsElementIds()) foreach ((int graphicsElementId, string variablesJson) in context.GetGraphicsElements())
{ {
playoutItem.PlayoutItemGraphicsElements.Add( playoutItem.PlayoutItemGraphicsElements.Add(
new PlayoutItemGraphicsElement new PlayoutItemGraphicsElement
{ {
PlayoutItem = playoutItem, PlayoutItem = playoutItem,
GraphicsElementId = graphicsElementId GraphicsElementId = graphicsElementId,
Variables = variablesJson
}); });
} }

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

@ -32,7 +32,7 @@ public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsEl
{ {
foreach (var ge in await GetGraphicsElementByPath(graphicsOff.GraphicsOff)) foreach (var ge in await GetGraphicsElementByPath(graphicsOff.GraphicsOff))
{ {
context.RemoveGraphicsElementId(ge.Id); context.RemoveGraphicsElement(ge.Id);
} }
} }

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

@ -2,11 +2,17 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Interfaces.Repositories; using ErsatzTV.Core.Interfaces.Repositories;
using ErsatzTV.Core.Scheduling.YamlScheduling.Models; using ErsatzTV.Core.Scheduling.YamlScheduling.Models;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers;
public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsElementRepository) : IYamlPlayoutHandler public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsElementRepository) : IYamlPlayoutHandler
{ {
private static readonly JsonSerializerSettings JsonSettings = new()
{
NullValueHandling = NullValueHandling.Ignore
};
private readonly Dictionary<string, Option<GraphicsElement>> _graphicsElementCache = new(); private readonly Dictionary<string, Option<GraphicsElement>> _graphicsElementCache = new();
public bool Reset => false; public bool Reset => false;
@ -31,7 +37,13 @@ public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsEle
foreach (var ge in await GetGraphicsElementByPath(graphicsOn.GraphicsOn)) foreach (var ge in await GetGraphicsElementByPath(graphicsOn.GraphicsOn))
{ {
context.SetGraphicsElementId(ge.Id); string variables = null;
if (graphicsOn.Variables.Count > 0)
{
variables = JsonConvert.SerializeObject(graphicsOn.Variables, JsonSettings);
}
context.SetGraphicsElement(ge.Id, variables);
} }
return true; return true;

2
ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOnInstruction.cs

@ -6,4 +6,6 @@ public class YamlPlayoutGraphicsOnInstruction : YamlPlayoutInstruction
{ {
[YamlMember(Alias = "graphics_on", ApplyNamingConventions = false)] [YamlMember(Alias = "graphics_on", ApplyNamingConventions = false)]
public string GraphicsOn { get; set; } public string GraphicsOn { get; set; }
public Dictionary<string, string> Variables { get; set; } = [];
} }

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

@ -16,7 +16,7 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio
private readonly System.Collections.Generic.HashSet<int> _visitedInstructions = []; private readonly System.Collections.Generic.HashSet<int> _visitedInstructions = [];
private readonly Stack<FillerKind> _fillerKind = new(); private readonly Stack<FillerKind> _fillerKind = new();
private readonly System.Collections.Generic.HashSet<int> _channelWatermarkIds = []; private readonly System.Collections.Generic.HashSet<int> _channelWatermarkIds = [];
private readonly System.Collections.Generic.HashSet<int> _graphicsElementIds = []; private readonly Dictionary<int, string> _graphicsElements = [];
private int _guideGroup = guideGroup; private int _guideGroup = guideGroup;
private bool _guideGroupLocked; private bool _guideGroupLocked;
private int _instructionIndex; private int _instructionIndex;
@ -93,10 +93,10 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio
public void ClearChannelWatermarkIds() => _channelWatermarkIds.Clear(); public void ClearChannelWatermarkIds() => _channelWatermarkIds.Clear();
public List<int> GetChannelWatermarkIds() => _channelWatermarkIds.ToList(); public List<int> GetChannelWatermarkIds() => _channelWatermarkIds.ToList();
public void SetGraphicsElementId(int id) => _graphicsElementIds.Add(id); public void SetGraphicsElement(int id, string variablesJson) => _graphicsElements.Add(id, variablesJson);
public void RemoveGraphicsElementId(int id) => _graphicsElementIds.Remove(id); public void RemoveGraphicsElement(int id) => _graphicsElements.Remove(id);
public void ClearGraphicsElements() => _graphicsElementIds.Clear(); public void ClearGraphicsElements() => _graphicsElements.Clear();
public List<int> GetGraphicsElementIds() => _graphicsElementIds.ToList(); public IReadOnlyDictionary<int, string> GetGraphicsElements() => _graphicsElements;
public void SetPreRollSequence(string sequence) => _preRollSequence = sequence; public void SetPreRollSequence(string sequence) => _preRollSequence = sequence;
public void ClearPreRollSequence() => _preRollSequence = Option<string>.None; public void ClearPreRollSequence() => _preRollSequence = Option<string>.None;

6277
ErsatzTV.Infrastructure.MySql/Migrations/20250808215226_Add_PlayoutItemGraphicsElementVariables.Designer.cs generated

File diff suppressed because it is too large Load Diff

29
ErsatzTV.Infrastructure.MySql/Migrations/20250808215226_Add_PlayoutItemGraphicsElementVariables.cs

@ -0,0 +1,29 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.MySql.Migrations
{
/// <inheritdoc />
public partial class Add_PlayoutItemGraphicsElementVariables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Variables",
table: "PlayoutItemGraphicsElement",
type: "longtext",
nullable: true)
.Annotation("MySql:CharSet", "utf8mb4");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Variables",
table: "PlayoutItemGraphicsElement");
}
}
}

3
ErsatzTV.Infrastructure.MySql/Migrations/TvContextModelSnapshot.cs

@ -1921,6 +1921,9 @@ namespace ErsatzTV.Infrastructure.MySql.Migrations
b.Property<int>("GraphicsElementId") b.Property<int>("GraphicsElementId")
.HasColumnType("int"); .HasColumnType("int");
b.Property<string>("Variables")
.HasColumnType("longtext");
b.HasKey("PlayoutItemId", "GraphicsElementId"); b.HasKey("PlayoutItemId", "GraphicsElementId");
b.HasIndex("GraphicsElementId"); b.HasIndex("GraphicsElementId");

6112
ErsatzTV.Infrastructure.Sqlite/Migrations/20250808213026_Add_PlayoutItemGraphicsElementVariables.Designer.cs generated

File diff suppressed because it is too large Load Diff

28
ErsatzTV.Infrastructure.Sqlite/Migrations/20250808213026_Add_PlayoutItemGraphicsElementVariables.cs

@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace ErsatzTV.Infrastructure.Sqlite.Migrations
{
/// <inheritdoc />
public partial class Add_PlayoutItemGraphicsElementVariables : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Variables",
table: "PlayoutItemGraphicsElement",
type: "TEXT",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Variables",
table: "PlayoutItemGraphicsElement");
}
}
}

3
ErsatzTV.Infrastructure.Sqlite/Migrations/TvContextModelSnapshot.cs

@ -1830,6 +1830,9 @@ namespace ErsatzTV.Infrastructure.Sqlite.Migrations
b.Property<int>("GraphicsElementId") b.Property<int>("GraphicsElementId")
.HasColumnType("INTEGER"); .HasColumnType("INTEGER");
b.Property<string>("Variables")
.HasColumnType("TEXT");
b.HasKey("PlayoutItemId", "GraphicsElementId"); b.HasKey("PlayoutItemId", "GraphicsElementId");
b.HasIndex("GraphicsElementId"); b.HasIndex("GraphicsElementId");

2
ErsatzTV.Infrastructure/Streaming/GraphicsEngine.cs

@ -28,7 +28,7 @@ public class GraphicsEngine(ILogger<GraphicsEngine> logger) : IGraphicsEngine
break; break;
case TextElementContext textElementContext: case TextElementContext textElementContext:
elements.Add(new TextElement(textElementContext.TextElement, logger)); elements.Add(new TextElement(textElementContext.TextElement, textElementContext.Variables, logger));
break; break;
} }
} }

13
ErsatzTV.Infrastructure/Streaming/TextElement.cs

@ -2,6 +2,7 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Graphics; using ErsatzTV.Core.Graphics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using NCalc; using NCalc;
using Scriban;
using SixLabors.Fonts; using SixLabors.Fonts;
using SixLabors.ImageSharp; using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Drawing.Processing; using SixLabors.ImageSharp.Drawing.Processing;
@ -11,7 +12,7 @@ using Image=SixLabors.ImageSharp.Image;
namespace ErsatzTV.Infrastructure.Streaming; namespace ErsatzTV.Infrastructure.Streaming;
public class TextElement(TextGraphicsElement textElement, ILogger logger) : IGraphicsElement, IDisposable public class TextElement(TextGraphicsElement textElement, Dictionary<string, string> variables, ILogger logger) : IGraphicsElement, IDisposable
{ {
private Option<Expression> _maybeOpacityExpression; private Option<Expression> _maybeOpacityExpression;
private float _opacity; private float _opacity;
@ -22,7 +23,7 @@ public class TextElement(TextGraphicsElement textElement, ILogger logger) : IGra
public bool IsFailed { get; set; } public bool IsFailed { get; set; }
public Task InitializeAsync(Resolution frameSize, int frameRate, CancellationToken cancellationToken) public async Task InitializeAsync(Resolution frameSize, int frameRate, CancellationToken cancellationToken)
{ {
try try
{ {
@ -39,6 +40,8 @@ public class TextElement(TextGraphicsElement textElement, ILogger logger) : IGra
ZIndex = textElement.ZIndex ?? 0; ZIndex = textElement.ZIndex ?? 0;
string textToRender = await Template.Parse(textElement.Text).RenderAsync(variables);
var font = GraphicsEngineFonts.GetFont(textElement.FontFamily, textElement.FontSize ?? 48, FontStyle.Regular); var font = GraphicsEngineFonts.GetFont(textElement.FontFamily, textElement.FontSize ?? 48, FontStyle.Regular);
var fontColor = Color.White; var fontColor = Color.White;
if (Color.TryParse(textElement.FontColor, out Color parsedColor) || if (Color.TryParse(textElement.FontColor, out Color parsedColor) ||
@ -58,11 +61,11 @@ public class TextElement(TextGraphicsElement textElement, ILogger logger) : IGra
// textOptions.HorizontalAlignment = parsedAlignment; // textOptions.HorizontalAlignment = parsedAlignment;
// } // }
FontRectangle textBounds = TextMeasurer.MeasureBounds(textElement.Text, textOptions); FontRectangle textBounds = TextMeasurer.MeasureBounds(textToRender, textOptions);
textOptions.Origin = new PointF(-textBounds.X, -textBounds.Y); textOptions.Origin = new PointF(-textBounds.X, -textBounds.Y);
_image = new Image<Rgba32>((int)Math.Ceiling(textBounds.Width), (int)Math.Ceiling(textBounds.Height)); _image = new Image<Rgba32>((int)Math.Ceiling(textBounds.Width), (int)Math.Ceiling(textBounds.Height));
_image.Mutate(ctx => ctx.DrawText(textOptions, textElement.Text, fontColor)); _image.Mutate(ctx => ctx.DrawText(textOptions, textToRender, fontColor));
int horizontalMargin = (int)Math.Round((textElement.HorizontalMarginPercent ?? 0) / 100.0 * frameSize.Width); int horizontalMargin = (int)Math.Round((textElement.HorizontalMarginPercent ?? 0) / 100.0 * frameSize.Width);
int verticalMargin = (int)Math.Round((textElement.VerticalMarginPercent ?? 0) / 100.0 * frameSize.Height); int verticalMargin = (int)Math.Round((textElement.VerticalMarginPercent ?? 0) / 100.0 * frameSize.Height);
@ -81,8 +84,6 @@ public class TextElement(TextGraphicsElement textElement, ILogger logger) : IGra
IsFailed = true; IsFailed = true;
logger.LogWarning(ex, "Failed to initialize text element; will disable for this content"); logger.LogWarning(ex, "Failed to initialize text element; will disable for this content");
} }
return Task.CompletedTask;
} }
public ValueTask<Option<PreparedElementImage>> PrepareImage( public ValueTask<Option<PreparedElementImage>> PrepareImage(

3
ErsatzTV/Resources/yaml-playout-import.schema.json

@ -281,7 +281,8 @@
"graphicsOnInstruction": { "graphicsOnInstruction": {
"type": "object", "type": "object",
"properties": { "properties": {
"graphics_on": { "type": "string" } "graphics_on": { "type": "string" },
"variables": { "type": "object" }
}, },
"required": [ "graphics_on" ], "required": [ "graphics_on" ],
"additionalProperties": false "additionalProperties": false

3
ErsatzTV/Resources/yaml-playout.schema.json

@ -332,7 +332,8 @@
"graphicsOnInstruction": { "graphicsOnInstruction": {
"type": "object", "type": "object",
"properties": { "properties": {
"graphics_on": { "type": "string" } "graphics_on": { "type": "string" },
"variables": { "type": "object" }
}, },
"required": [ "graphics_on" ], "required": [ "graphics_on" ],
"additionalProperties": false "additionalProperties": false

Loading…
Cancel
Save