diff --git a/CHANGELOG.md b/CHANGELOG.md index 87f20e3e0..3282ee735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Supported in playback troubleshooting - Displays multi-line text in a specified font, color, location, z-index - Supports constant opacity and opacity expression +- 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_off` will turn off a specific element, or all elements if none are specified ### Fix - Fix database operations that were slowing down playout builds diff --git a/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs b/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs index 39104536f..553ea55eb 100644 --- a/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs +++ b/ErsatzTV.Application/Playouts/Commands/BuildPlayoutHandler.cs @@ -136,7 +136,8 @@ public class BuildPlayoutHandler : IRequestHandler i.PlayoutItemWatermarks is not null && i.PlayoutItemWatermarks.Count > 0); - if (anyWatermarks) + bool anyGraphicsElements = result.AddedItems.Any(i => i.PlayoutItemGraphicsElements is not null && i.PlayoutItemGraphicsElements.Count > 0); + if (anyWatermarks || anyGraphicsElements) { bulkConfig.SetOutputIdentity = true; } @@ -157,6 +158,23 @@ public class BuildPlayoutHandler : IRequestHandler + item.PlayoutItemGraphicsElements.Select(graphicsElement => + { + graphicsElement.PlayoutItemId = item.Id; + graphicsElement.PlayoutItem = null; + return graphicsElement; + }) + ).ToList(); + + await dbContext.BulkInsertAsync(allGraphicsElements, cancellationToken: cancellationToken); + } } if (result.HistoryToRemove.Count > 0) diff --git a/ErsatzTV.Core/Interfaces/Repositories/IGraphicsElementRepository.cs b/ErsatzTV.Core/Interfaces/Repositories/IGraphicsElementRepository.cs new file mode 100644 index 000000000..fa3078ae7 --- /dev/null +++ b/ErsatzTV.Core/Interfaces/Repositories/IGraphicsElementRepository.cs @@ -0,0 +1,8 @@ +using ErsatzTV.Core.Domain; + +namespace ErsatzTV.Core.Interfaces.Repositories; + +public interface IGraphicsElementRepository +{ + Task> GetGraphicsElementByPath(string path); +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs index 8a9727ebe..2bcdd5e07 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutAllHandler.cs @@ -65,7 +65,8 @@ public class YamlPlayoutAllHandler(EnumeratorCache enumeratorCache) : YamlPlayou //BlockKey = JsonConvert.SerializeObject(effectiveBlock.BlockKey), //CollectionKey = JsonConvert.SerializeObject(collectionKey, JsonSettings), //CollectionEtag = collectionEtags[collectionKey] - PlayoutItemWatermarks = [] + PlayoutItemWatermarks = [], + PlayoutItemGraphicsElements = [] }; foreach (int watermarkId in context.GetChannelWatermarkIds()) @@ -78,6 +79,16 @@ public class YamlPlayoutAllHandler(EnumeratorCache enumeratorCache) : YamlPlayou }); } + foreach (int graphicsElementId in context.GetGraphicsElementIds()) + { + playoutItem.PlayoutItemGraphicsElements.Add( + new PlayoutItemGraphicsElement + { + PlayoutItem = playoutItem, + GraphicsElementId = graphicsElementId + }); + } + await AddItemAndMidRoll(context, playoutItem, mediaItem, executeSequence); context.AdvanceGuideGroup(); diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs index 6b19a713d..8e1295f1d 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutCountHandler.cs @@ -90,7 +90,8 @@ public class YamlPlayoutCountHandler(EnumeratorCache enumeratorCache) : YamlPlay //BlockKey = JsonConvert.SerializeObject(effectiveBlock.BlockKey), //CollectionKey = JsonConvert.SerializeObject(collectionKey, JsonSettings), //CollectionEtag = collectionEtags[collectionKey], - PlayoutItemWatermarks = [] + PlayoutItemWatermarks = [], + PlayoutItemGraphicsElements = [] }; foreach (int watermarkId in context.GetChannelWatermarkIds()) @@ -103,6 +104,16 @@ public class YamlPlayoutCountHandler(EnumeratorCache enumeratorCache) : YamlPlay }); } + foreach (int graphicsElementId in context.GetGraphicsElementIds()) + { + playoutItem.PlayoutItemGraphicsElements.Add( + new PlayoutItemGraphicsElement + { + PlayoutItem = playoutItem, + GraphicsElementId = graphicsElementId + }); + } + await AddItemAndMidRoll(context, playoutItem, mediaItem, executeSequence); context.AdvanceGuideGroup(); diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs index a5d45dfaa..172d9398f 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs @@ -124,7 +124,8 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP FillerKind = fillerKind, CustomTitle = string.IsNullOrWhiteSpace(customTitle) ? null : customTitle, DisableWatermarks = disableWatermarks, - PlayoutItemWatermarks = [] + PlayoutItemWatermarks = [], + PlayoutItemGraphicsElements = [] }; foreach (int watermarkId in context.GetChannelWatermarkIds()) @@ -137,6 +138,16 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP }); } + foreach (int graphicsElementId in context.GetGraphicsElementIds()) + { + playoutItem.PlayoutItemGraphicsElements.Add( + new PlayoutItemGraphicsElement + { + PlayoutItem = playoutItem, + GraphicsElementId = graphicsElementId + }); + } + if (remainingToFill - itemDuration >= TimeSpan.Zero || !stopBeforeEnd) { context.AddedItems.Add(playoutItem); diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOffHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOffHandler.cs new file mode 100644 index 000000000..542122dfd --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOffHandler.cs @@ -0,0 +1,64 @@ +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using ErsatzTV.Core.Scheduling.YamlScheduling.Models; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; + +public class YamlPlayoutGraphicsOffHandler(IGraphicsElementRepository graphicsElementRepository) : IYamlPlayoutHandler +{ + private readonly Dictionary> _graphicsElementCache = new(); + + public bool Reset => false; + + public async Task Handle( + YamlPlayoutContext context, + YamlPlayoutInstruction instruction, + PlayoutBuildMode mode, + Func executeSequence, + ILogger logger, + CancellationToken cancellationToken) + { + if (instruction is not YamlPlayoutGraphicsOffInstruction graphicsOff) + { + return false; + } + + if (string.IsNullOrWhiteSpace(graphicsOff.GraphicsOff)) + { + context.ClearGraphicsElements(); + } + else + { + foreach (var ge in await GetGraphicsElementByPath(graphicsOff.GraphicsOff)) + { + context.RemoveGraphicsElementId(ge.Id); + } + } + + return true; + } + + private async Task> GetGraphicsElementByPath(string path) + { + if (_graphicsElementCache.TryGetValue(path, out var cachedGraphicsElement)) + { + foreach (GraphicsElement graphicsElement in cachedGraphicsElement) + { + return graphicsElement; + } + } + else + { + Option maybeGraphicsElement = + await graphicsElementRepository.GetGraphicsElementByPath(path); + _graphicsElementCache.Add(path, maybeGraphicsElement); + foreach (GraphicsElement graphicsElement in maybeGraphicsElement) + { + return graphicsElement; + } + } + + return Option.None; + } +} \ No newline at end of file diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOnHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOnHandler.cs new file mode 100644 index 000000000..7f3105261 --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutGraphicsOnHandler.cs @@ -0,0 +1,62 @@ +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using ErsatzTV.Core.Scheduling.YamlScheduling.Models; +using Microsoft.Extensions.Logging; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Handlers; + +public class YamlPlayoutGraphicsOnHandler(IGraphicsElementRepository graphicsElementRepository) : IYamlPlayoutHandler +{ + private readonly Dictionary> _graphicsElementCache = new(); + + public bool Reset => false; + + public async Task Handle( + YamlPlayoutContext context, + YamlPlayoutInstruction instruction, + PlayoutBuildMode mode, + Func executeSequence, + ILogger logger, + CancellationToken cancellationToken) + { + if (instruction is not YamlPlayoutGraphicsOnInstruction graphicsOn) + { + return false; + } + + if (string.IsNullOrWhiteSpace(graphicsOn.GraphicsOn)) + { + return false; + } + + foreach (var ge in await GetGraphicsElementByPath(graphicsOn.GraphicsOn)) + { + context.SetGraphicsElementId(ge.Id); + } + + return true; + } + + private async Task> GetGraphicsElementByPath(string path) + { + if (_graphicsElementCache.TryGetValue(path, out var cachedGraphicsElement)) + { + foreach (GraphicsElement graphicsElement in cachedGraphicsElement) + { + return graphicsElement; + } + } + else + { + Option maybeGraphicsElement = + await graphicsElementRepository.GetGraphicsElementByPath(path); + _graphicsElementCache.Add(path, maybeGraphicsElement); + foreach (GraphicsElement graphicsElement in maybeGraphicsElement) + { + return graphicsElement; + } + } + + return Option.None; + } +} \ No newline at end of file diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutMidRollHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutMidRollHandler.cs index 18506620d..3e1c48fa1 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutMidRollHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutMidRollHandler.cs @@ -15,7 +15,7 @@ public class YamlPlayoutMidRollHandler : IYamlPlayoutHandler ILogger logger, CancellationToken cancellationToken) { - if (instruction is not YamlMidRollInstruction midRoll) + if (instruction is not YamlPlayoutMidRollInstruction midRoll) { return Task.FromResult(false); } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs index 9226d5aae..2d51ff3be 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPostRollHandler.cs @@ -15,7 +15,7 @@ public class YamlPlayoutPostRollHandler : IYamlPlayoutHandler ILogger logger, CancellationToken cancellationToken) { - if (instruction is not YamlPostRollInstruction postRoll) + if (instruction is not YamlPlayoutPostRollInstruction postRoll) { return Task.FromResult(false); } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPreRollHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPreRollHandler.cs index 6d8850001..1111bf640 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPreRollHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPreRollHandler.cs @@ -15,7 +15,7 @@ public class YamlPlayoutPreRollHandler : IYamlPlayoutHandler ILogger logger, CancellationToken cancellationToken) { - if (instruction is not YamlPreRollInstruction preRoll) + if (instruction is not YamlPlayoutPreRollInstruction preRoll) { return Task.FromResult(false); } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOffInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOffInstruction.cs new file mode 100644 index 000000000..06ebb989a --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOffInstruction.cs @@ -0,0 +1,9 @@ +using YamlDotNet.Serialization; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; + +public class YamlPlayoutGraphicsOffInstruction : YamlPlayoutInstruction +{ + [YamlMember(Alias = "graphics_off", ApplyNamingConventions = false)] + public string GraphicsOff { get; set; } +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOnInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOnInstruction.cs new file mode 100644 index 000000000..6a0a76240 --- /dev/null +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutGraphicsOnInstruction.cs @@ -0,0 +1,9 @@ +using YamlDotNet.Serialization; + +namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; + +public class YamlPlayoutGraphicsOnInstruction : YamlPlayoutInstruction +{ + [YamlMember(Alias = "graphics_on", ApplyNamingConventions = false)] + public string GraphicsOn { get; set; } +} diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlMidRollInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutMidRollInstruction.cs similarity index 81% rename from ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlMidRollInstruction.cs rename to ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutMidRollInstruction.cs index 1efb96782..3fe1c5039 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlMidRollInstruction.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutMidRollInstruction.cs @@ -2,7 +2,7 @@ using YamlDotNet.Serialization; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; -public class YamlMidRollInstruction : YamlPlayoutInstruction +public class YamlPlayoutMidRollInstruction : YamlPlayoutInstruction { [YamlMember(Alias = "mid_roll", ApplyNamingConventions = false)] public bool MidRoll { get; set; } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPostRollInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPostRollInstruction.cs similarity index 78% rename from ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPostRollInstruction.cs rename to ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPostRollInstruction.cs index 9f98f7775..c0d021ae7 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPostRollInstruction.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPostRollInstruction.cs @@ -2,7 +2,7 @@ using YamlDotNet.Serialization; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; -public class YamlPostRollInstruction : YamlPlayoutInstruction +public class YamlPlayoutPostRollInstruction : YamlPlayoutInstruction { [YamlMember(Alias = "post_roll", ApplyNamingConventions = false)] public bool PostRoll { get; set; } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPreRollInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPreRollInstruction.cs similarity index 78% rename from ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPreRollInstruction.cs rename to ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPreRollInstruction.cs index babdfc916..ba69f24dc 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPreRollInstruction.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPreRollInstruction.cs @@ -2,7 +2,7 @@ using YamlDotNet.Serialization; namespace ErsatzTV.Core.Scheduling.YamlScheduling.Models; -public class YamlPreRollInstruction : YamlPlayoutInstruction +public class YamlPlayoutPreRollInstruction : YamlPlayoutInstruction { [YamlMember(Alias = "pre_roll", ApplyNamingConventions = false)] public bool PreRoll { get; set; } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs index 6d03f2951..f04dc7923 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutBuilder.cs @@ -19,6 +19,7 @@ public class YamlPlayoutBuilder( IConfigElementRepository configElementRepository, IMediaCollectionRepository mediaCollectionRepository, IChannelRepository channelRepository, + IGraphicsElementRepository graphicsElementRepository, IYamlScheduleValidator yamlScheduleValidator, ILogger logger) : IYamlPlayoutBuilder @@ -395,9 +396,11 @@ public class YamlPlayoutBuilder( YamlPlayoutEpgGroupInstruction => new YamlPlayoutEpgGroupHandler(), YamlPlayoutWatermarkInstruction => new YamlPlayoutWatermarkHandler(channelRepository), YamlPlayoutShuffleSequenceInstruction => new YamlPlayoutShuffleSequenceHandler(), - YamlPreRollInstruction => new YamlPlayoutPreRollHandler(), - YamlPostRollInstruction => new YamlPlayoutPostRollHandler(), - YamlMidRollInstruction => new YamlPlayoutMidRollHandler(), + YamlPlayoutPreRollInstruction => new YamlPlayoutPreRollHandler(), + YamlPlayoutPostRollInstruction => new YamlPlayoutPostRollHandler(), + YamlPlayoutMidRollInstruction => new YamlPlayoutMidRollHandler(), + YamlPlayoutGraphicsOnInstruction => new YamlPlayoutGraphicsOnHandler(graphicsElementRepository), + YamlPlayoutGraphicsOffInstruction => new YamlPlayoutGraphicsOffHandler(graphicsElementRepository), YamlPlayoutSkipItemsInstruction => new YamlPlayoutSkipItemsHandler(enumeratorCache), YamlPlayoutSkipToItemInstruction => new YamlPlayoutSkipToItemHandler(enumeratorCache), @@ -457,12 +460,14 @@ public class YamlPlayoutBuilder( { "count", typeof(YamlPlayoutCountInstruction) }, { "duration", typeof(YamlPlayoutDurationInstruction) }, { "epg_group", typeof(YamlPlayoutEpgGroupInstruction) }, + { "graphics_on", typeof(YamlPlayoutGraphicsOnInstruction) }, + { "graphics_off", typeof(YamlPlayoutGraphicsOffInstruction) }, { "watermark", typeof(YamlPlayoutWatermarkInstruction) }, { "pad_to_next", typeof(YamlPlayoutPadToNextInstruction) }, { "pad_until", typeof(YamlPlayoutPadUntilInstruction) }, - { "pre_roll", typeof(YamlPreRollInstruction) }, - { "post_roll", typeof(YamlPostRollInstruction) }, - { "mid_roll", typeof(YamlMidRollInstruction) }, + { "pre_roll", typeof(YamlPlayoutPreRollInstruction) }, + { "post_roll", typeof(YamlPlayoutPostRollInstruction) }, + { "mid_roll", typeof(YamlPlayoutMidRollInstruction) }, { "repeat", typeof(YamlPlayoutRepeatInstruction) }, { "rewind", typeof(YamlPlayoutRewindInstruction) }, { "sequence", typeof(YamlPlayoutSequenceInstruction) }, diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs index d57fbb0b7..b9c982c3f 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/YamlPlayoutContext.cs @@ -16,6 +16,7 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio private readonly System.Collections.Generic.HashSet _visitedInstructions = []; private readonly Stack _fillerKind = new(); private readonly System.Collections.Generic.HashSet _channelWatermarkIds = []; + private readonly System.Collections.Generic.HashSet _graphicsElementIds = []; private int _guideGroup = guideGroup; private bool _guideGroupLocked; private int _instructionIndex; @@ -87,66 +88,30 @@ public class YamlPlayoutContext(Playout playout, YamlPlayoutDefinition definitio public void UnlockGuideGroup() => _guideGroupLocked = false; - public void SetChannelWatermarkId(int id) - { - _channelWatermarkIds.Add(id); - } - - public void RemoveChannelWatermarkId(int id) - { - _channelWatermarkIds.Remove(id); - } - - public void ClearChannelWatermarkIds() - { - _channelWatermarkIds.Clear(); - } - + public void SetChannelWatermarkId(int id) => _channelWatermarkIds.Add(id); + public void RemoveChannelWatermarkId(int id) => _channelWatermarkIds.Remove(id); + public void ClearChannelWatermarkIds() => _channelWatermarkIds.Clear(); public List GetChannelWatermarkIds() => _channelWatermarkIds.ToList(); - public void SetPreRollSequence(string sequence) - { - _preRollSequence = sequence; - } - - public void ClearPreRollSequence() - { - _preRollSequence = Option.None; - } + public void SetGraphicsElementId(int id) => _graphicsElementIds.Add(id); + public void RemoveGraphicsElementId(int id) => _graphicsElementIds.Remove(id); + public void ClearGraphicsElements() => _graphicsElementIds.Clear(); + public List GetGraphicsElementIds() => _graphicsElementIds.ToList(); + public void SetPreRollSequence(string sequence) => _preRollSequence = sequence; + public void ClearPreRollSequence() => _preRollSequence = Option.None; public Option GetPreRollSequence() => _preRollSequence; - public void SetPostRollSequence(string sequence) - { - _postRollSequence = sequence; - } - - public void ClearPostRollSequence() - { - _postRollSequence = Option.None; - } - + public void SetPostRollSequence(string sequence) => _postRollSequence = sequence; + public void ClearPostRollSequence() => _postRollSequence = Option.None; public Option GetPostRollSequence() => _postRollSequence; - public void SetMidRollSequence(MidRollSequence sequence) - { - _midRollSequence = sequence; - } - - public void ClearMidRollSequence() - { - _midRollSequence = Option.None; - } - + public void SetMidRollSequence(MidRollSequence sequence) => _midRollSequence = sequence; + public void ClearMidRollSequence() => _midRollSequence = Option.None; public Option GetMidRollSequence() => _midRollSequence; - public void PushFillerKind(FillerKind fillerKind) - { - _fillerKind.Push(fillerKind); - } - + public void PushFillerKind(FillerKind fillerKind) => _fillerKind.Push(fillerKind); public void PopFillerKind() => _fillerKind.Pop(); - public Option GetFillerKind() => _fillerKind.TryPeek(out FillerKind fillerKind) ? fillerKind : Option.None; diff --git a/ErsatzTV.Infrastructure/Data/Repositories/GraphicsElementRepository.cs b/ErsatzTV.Infrastructure/Data/Repositories/GraphicsElementRepository.cs new file mode 100644 index 000000000..af218ee2b --- /dev/null +++ b/ErsatzTV.Infrastructure/Data/Repositories/GraphicsElementRepository.cs @@ -0,0 +1,24 @@ +using ErsatzTV.Core; +using ErsatzTV.Core.Domain; +using ErsatzTV.Core.Interfaces.Repositories; +using ErsatzTV.Infrastructure.Extensions; +using Microsoft.EntityFrameworkCore; + +namespace ErsatzTV.Infrastructure.Data.Repositories; + +public class GraphicsElementRepository(IDbContextFactory dbContextFactory) : IGraphicsElementRepository +{ + public async Task> GetGraphicsElementByPath(string path) + { + await using TvContext dbContext = await dbContextFactory.CreateDbContextAsync(); + + if (!path.StartsWith(FileSystemLayout.GraphicsElementsTemplatesFolder, StringComparison.Ordinal)) + { + path = Path.Combine(FileSystemLayout.GraphicsElementsTemplatesFolder, path); + } + + return await dbContext.GraphicsElements + .AsNoTracking() + .SelectOneAsync(ge => ge.Path, ge => ge.Path == path); + } +} diff --git a/ErsatzTV/Resources/yaml-playout-import.schema.json b/ErsatzTV/Resources/yaml-playout-import.schema.json index b790b1fe2..42b5933e4 100644 --- a/ErsatzTV/Resources/yaml-playout-import.schema.json +++ b/ErsatzTV/Resources/yaml-playout-import.schema.json @@ -39,6 +39,8 @@ { "$ref": "#/$defs/scheduling/padUntilInstruction" }, { "$ref": "#/$defs/scheduling/sequenceInstruction" }, { "$ref": "#/$defs/control/epgGroupInstruction" }, + { "$ref": "#/$defs/control/graphicsOnInstruction" }, + { "$ref": "#/$defs/control/graphicsOffInstruction" }, { "$ref": "#/$defs/control/preRollInstruction"}, { "$ref": "#/$defs/control/postRollInstruction"}, { "$ref": "#/$defs/control/midRollInstruction"}, @@ -276,6 +278,22 @@ "required": [ "epg_group" ], "additionalProperties": false }, + "graphicsOnInstruction": { + "type": "object", + "properties": { + "graphics_on": { "type": "string" } + }, + "required": [ "graphics_on" ], + "additionalProperties": false + }, + "graphicsOffInstruction": { + "type": "object", + "properties": { + "graphics_off": { "type": ["null", "string"] } + }, + "required": [ "graphics_off" ], + "additionalProperties": false + }, "preRollInstruction": { "type": "object", "properties": { diff --git a/ErsatzTV/Resources/yaml-playout.schema.json b/ErsatzTV/Resources/yaml-playout.schema.json index 21925e92a..91fda8f6c 100644 --- a/ErsatzTV/Resources/yaml-playout.schema.json +++ b/ErsatzTV/Resources/yaml-playout.schema.json @@ -46,6 +46,8 @@ { "$ref": "#/$defs/scheduling/padUntilInstruction" }, { "$ref": "#/$defs/scheduling/sequenceInstruction" }, { "$ref": "#/$defs/control/epgGroupInstruction" }, + { "$ref": "#/$defs/control/graphicsOnInstruction" }, + { "$ref": "#/$defs/control/graphicsOffInstruction" }, { "$ref": "#/$defs/control/preRollInstruction"}, { "$ref": "#/$defs/control/postRollInstruction"}, { "$ref": "#/$defs/control/midRollInstruction"}, @@ -88,6 +90,8 @@ { "$ref": "#/$defs/scheduling/padUntilInstruction" }, { "$ref": "#/$defs/scheduling/sequenceInstruction" }, { "$ref": "#/$defs/control/epgGroupInstruction" }, + { "$ref": "#/$defs/control/graphicsOnInstruction" }, + { "$ref": "#/$defs/control/graphicsOffInstruction" }, { "$ref": "#/$defs/control/preRollInstruction"}, { "$ref": "#/$defs/control/postRollInstruction"}, { "$ref": "#/$defs/control/midRollInstruction"}, @@ -325,6 +329,22 @@ "required": [ "epg_group" ], "additionalProperties": false }, + "graphicsOnInstruction": { + "type": "object", + "properties": { + "graphics_on": { "type": "string" } + }, + "required": [ "graphics_on" ], + "additionalProperties": false + }, + "graphicsOffInstruction": { + "type": "object", + "properties": { + "graphics_off": { "type": ["null", "string"] } + }, + "required": [ "graphics_off" ], + "additionalProperties": false + }, "preRollInstruction": { "type": "object", "properties": { diff --git a/ErsatzTV/Startup.cs b/ErsatzTV/Startup.cs index 400f5933c..814812c62 100644 --- a/ErsatzTV/Startup.cs +++ b/ErsatzTV/Startup.cs @@ -719,6 +719,7 @@ public class Startup MultiEpisodeShuffleCollectionEnumeratorFactory>(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); services.AddScoped(); services.AddScoped();