Browse Source

add scripted control instructions (#2341)

* add start_epg_group, stop_epg_group

* fix imports

* add graphics_on, graphics_off

* add skip_items

* add skip_to_item

* add watermark_on, watermark_off
pull/2344/head
Jason Dove 11 months ago committed by GitHub
parent
commit
605c57bef3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 8
      ErsatzTV.Core/Scheduling/Engine/ISchedulingEngine.cs
  2. 232
      ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs
  3. 98
      ErsatzTV.Core/Scheduling/ScriptedScheduling/Modules/PlayoutModule.cs
  4. 8
      ErsatzTV.Core/Scheduling/ScriptedScheduling/ScriptedPlayoutBuilder.cs

8
ErsatzTV.Core/Scheduling/Engine/ISchedulingEngine.cs

@ -46,6 +46,14 @@ public interface ISchedulingEngine @@ -46,6 +46,14 @@ public interface ISchedulingEngine
bool disableWatermarks);
// control instructions
void LockGuideGroup(bool advance);
void UnlockGuideGroup();
Task GraphicsOn(List<string> graphicsElements, Dictionary<string, string> variables);
Task GraphicsOff(List<string> graphicsElements);
Task WatermarkOn(List<string> watermarks);
Task WatermarkOff(List<string> watermarks);
void SkipItems(string content, int count);
void SkipToItem(string content, int season, int episode);
ISchedulingEngine WaitUntil(TimeOnly waitUntil, bool tomorrow, bool rewindOnReset);

232
ErsatzTV.Core/Scheduling/Engine/SchedulingEngine.cs

@ -11,7 +11,11 @@ using Newtonsoft.Json; @@ -11,7 +11,11 @@ using Newtonsoft.Json;
namespace ErsatzTV.Core.Scheduling.Engine;
public class SchedulingEngine(IMediaCollectionRepository mediaCollectionRepository, ILogger<SchedulingEngine> logger)
public class SchedulingEngine(
IMediaCollectionRepository mediaCollectionRepository,
IGraphicsElementRepository graphicsElementRepository,
IChannelRepository channelRepository,
ILogger<SchedulingEngine> logger)
: ISchedulingEngine
{
private static readonly JsonSerializerSettings JsonSettings = new()
@ -19,6 +23,8 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito @@ -19,6 +23,8 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
NullValueHandling = NullValueHandling.Ignore
};
private readonly Dictionary<string, Option<GraphicsElement>> _graphicsElementCache = new();
private readonly Dictionary<string, Option<ChannelWatermark>> _watermarkCache = new();
private readonly Dictionary<string, EnumeratorDetails> _enumerators = new();
private readonly SchedulingEngineState _state = new(0);
private PlayoutReferenceData _referenceData;
@ -394,26 +400,26 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito @@ -394,26 +400,26 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
PlayoutItemGraphicsElements = []
};
// foreach (int watermarkId in context.GetChannelWatermarkIds())
// {
// playoutItem.PlayoutItemWatermarks.Add(
// new PlayoutItemWatermark
// {
// PlayoutItem = playoutItem,
// WatermarkId = watermarkId
// });
// }
//
// foreach ((int graphicsElementId, string variablesJson) in context.GetGraphicsElements())
// {
// playoutItem.PlayoutItemGraphicsElements.Add(
// new PlayoutItemGraphicsElement
// {
// PlayoutItem = playoutItem,
// GraphicsElementId = graphicsElementId,
// Variables = variablesJson
// });
// }
foreach (int watermarkId in _state.GetChannelWatermarkIds())
{
playoutItem.PlayoutItemWatermarks.Add(
new PlayoutItemWatermark
{
PlayoutItem = playoutItem,
WatermarkId = watermarkId
});
}
foreach ((int graphicsElementId, string variablesJson) in _state.GetGraphicsElements())
{
playoutItem.PlayoutItemGraphicsElements.Add(
new PlayoutItemGraphicsElement
{
PlayoutItem = playoutItem,
GraphicsElementId = graphicsElementId,
Variables = variablesJson
});
}
//await AddItemAndMidRoll(context, playoutItem, mediaItem, executeSequence);
@ -444,6 +450,133 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito @@ -444,6 +450,133 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
return result;
}
public void LockGuideGroup(bool advance)
{
_state.LockGuideGroup(advance);
}
public void UnlockGuideGroup()
{
_state.UnlockGuideGroup();
}
public async Task GraphicsOn(List<string> graphicsElements, Dictionary<string, string> variables)
{
string variablesJson = null;
if (variables.Count > 0)
{
variablesJson = JsonConvert.SerializeObject(variables, JsonSettings);
}
foreach (string element in graphicsElements.Where(e => !string.IsNullOrWhiteSpace(e)))
{
foreach (GraphicsElement ge in await GetGraphicsElementByPath(element))
{
_state.SetGraphicsElement(ge.Id, variablesJson);
}
}
}
public async Task GraphicsOff(List<string> graphicsElements)
{
if (graphicsElements.Count == 0)
{
_state.ClearGraphicsElements();
}
else
{
foreach (string element in graphicsElements.Where(e => !string.IsNullOrWhiteSpace(e)))
{
foreach (GraphicsElement ge in await GetGraphicsElementByPath(element))
{
_state.RemoveGraphicsElement(ge.Id);
}
}
}
}
public async Task WatermarkOn(List<string> watermarks)
{
foreach (string watermark in watermarks.Where(e => !string.IsNullOrWhiteSpace(e)))
{
foreach (ChannelWatermark wm in await GetChannelWatermarkByName(watermark))
{
_state.SetChannelWatermarkId(wm.Id);
}
}
}
public async Task WatermarkOff(List<string> watermarks)
{
if (watermarks.Count == 0)
{
_state.ClearChannelWatermarkIds();
}
else
{
foreach (string watermark in watermarks.Where(e => !string.IsNullOrWhiteSpace(e)))
{
foreach (ChannelWatermark wm in await GetChannelWatermarkByName(watermark))
{
_state.RemoveChannelWatermarkId(wm.Id);
}
}
}
}
public void SkipItems(string content, int count)
{
if (!_enumerators.TryGetValue(content, out EnumeratorDetails enumeratorDetails))
{
logger.LogWarning("Unable to skip items for invalid content {Key}", content);
return;
}
for (var i = 0; i < count; i++)
{
enumeratorDetails.Enumerator.MoveNext();
}
}
public void SkipToItem(string content, int season, int episode)
{
if (!_enumerators.TryGetValue(content, out EnumeratorDetails enumeratorDetails))
{
logger.LogWarning("Unable to skip items for invalid content {Key}", content);
return;
}
if (season < 0 || episode < 1)
{
logger.LogWarning("Unable to skip to invalid season/episode: {Season}/{Episode}", season, episode);
return;
}
var done = false;
for (var index = 0; index < enumeratorDetails.Enumerator.Count; index++)
{
if (done)
{
break;
}
foreach (MediaItem mediaItem in enumeratorDetails.Enumerator.Current)
{
if (mediaItem is Episode e)
{
if (e.Season?.SeasonNumber == season &&
e.EpisodeMetadata.HeadOrNone().Map(em => em.EpisodeNumber) == episode)
{
done = true;
break;
}
}
enumeratorDetails.Enumerator.MoveNext();
}
}
}
public ISchedulingEngine WaitUntil(TimeOnly waitUntil, bool tomorrow, bool rewindOnReset)
{
var currentTime = _state.CurrentTime;
@ -763,6 +896,51 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito @@ -763,6 +896,51 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
return FillerKind.None;
}
private async Task<Option<GraphicsElement>> GetGraphicsElementByPath(string path)
{
if (_graphicsElementCache.TryGetValue(path, out Option<GraphicsElement> cachedGraphicsElement))
{
foreach (GraphicsElement graphicsElement in cachedGraphicsElement)
{
return graphicsElement;
}
}
else
{
Option<GraphicsElement> maybeGraphicsElement =
await graphicsElementRepository.GetGraphicsElementByPath(path);
_graphicsElementCache.Add(path, maybeGraphicsElement);
foreach (GraphicsElement graphicsElement in maybeGraphicsElement)
{
return graphicsElement;
}
}
return Option<GraphicsElement>.None;
}
private async Task<Option<ChannelWatermark>> GetChannelWatermarkByName(string name)
{
if (_watermarkCache.TryGetValue(name, out Option<ChannelWatermark> cachedWatermark))
{
foreach (ChannelWatermark channelWatermark in cachedWatermark)
{
return channelWatermark;
}
}
else
{
Option<ChannelWatermark> maybeWatermark = await channelRepository.GetWatermarkByName(name);
_watermarkCache.Add(name, maybeWatermark);
foreach (ChannelWatermark channelWatermark in maybeWatermark)
{
return channelWatermark;
}
}
return Option<ChannelWatermark>.None;
}
public record SerializedState(
int? GuideGroup,
bool? GuideGroupLocked);
@ -771,6 +949,8 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito @@ -771,6 +949,8 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
{
private int _guideGroup = guideGroup;
private bool _guideGroupLocked;
private readonly Dictionary<int, string> _graphicsElements = [];
private readonly System.Collections.Generic.HashSet<int> _channelWatermarkIds = [];
// state
public int PlayoutId { get; set; }
@ -823,6 +1003,16 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito @@ -823,6 +1003,16 @@ public class SchedulingEngine(IMediaCollectionRepository mediaCollectionReposito
public void UnlockGuideGroup() => _guideGroupLocked = false;
public void SetGraphicsElement(int id, string variablesJson) => _graphicsElements.Add(id, variablesJson);
public void RemoveGraphicsElement(int id) => _graphicsElements.Remove(id);
public void ClearGraphicsElements() => _graphicsElements.Clear();
public Dictionary<int, string> GetGraphicsElements() => _graphicsElements;
public void SetChannelWatermarkId(int id) => _channelWatermarkIds.Add(id);
public void RemoveChannelWatermarkId(int id) => _channelWatermarkIds.Remove(id);
public void ClearChannelWatermarkIds() => _channelWatermarkIds.Clear();
public List<int> GetChannelWatermarkIds() => _channelWatermarkIds.ToList();
// result
public Option<DateTimeOffset> RemoveBefore { get; set; }
public bool ClearItems { get; set; }

98
ErsatzTV.Core/Scheduling/ScriptedScheduling/Modules/PlayoutModule.cs

@ -1,6 +1,7 @@ @@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using ErsatzTV.Core.Domain.Filler;
using ErsatzTV.Core.Scheduling.Engine;
using IronPython.Runtime;
namespace ErsatzTV.Core.Scheduling.ScriptedScheduling.Modules;
@ -41,6 +42,103 @@ public class PlayoutModule(ISchedulingEngine schedulingEngine) @@ -41,6 +42,103 @@ public class PlayoutModule(ISchedulingEngine schedulingEngine)
// control instructions
public void start_epg_group(bool advance = true)
{
schedulingEngine.LockGuideGroup(advance);
}
public void stop_epg_group()
{
schedulingEngine.UnlockGuideGroup();
}
public void graphics_on(string graphics, PythonDictionary variables = null)
{
var maybeVariables = new Dictionary<string, string>();
if (variables != null)
{
maybeVariables = variables.ToDictionary(v => v.Key.ToString(), v => v.Value.ToString());
}
schedulingEngine
.GraphicsOn([graphics], maybeVariables)
.GetAwaiter()
.GetResult();
}
public void graphics_on(PythonList graphics, PythonDictionary variables = null)
{
var maybeVariables = new Dictionary<string, string>();
if (variables != null)
{
maybeVariables = variables.ToDictionary(v => v.Key.ToString(), v => v.Value.ToString());
}
schedulingEngine
.GraphicsOn(
graphics.Select(g => g.ToString()).ToList(),
maybeVariables)
.GetAwaiter()
.GetResult();
}
public void graphics_off(string graphics = null)
{
if (string.IsNullOrWhiteSpace(graphics))
{
schedulingEngine.GraphicsOff([]).GetAwaiter().GetResult();
}
else
{
schedulingEngine.GraphicsOff([graphics]).GetAwaiter().GetResult();
}
}
public void graphics_off(PythonList graphics)
{
schedulingEngine.GraphicsOff(graphics.Select(g => g.ToString()).ToList()).GetAwaiter().GetResult();
}
public void watermark_on(string watermark)
{
schedulingEngine.WatermarkOn([watermark]).GetAwaiter().GetResult();
}
public void watermark_on(PythonList watermark)
{
schedulingEngine
.WatermarkOn(watermark.Select(g => g.ToString()).ToList())
.GetAwaiter()
.GetResult();
}
public void watermark_off(string watermark = null)
{
if (string.IsNullOrWhiteSpace(watermark))
{
schedulingEngine.WatermarkOff([]).GetAwaiter().GetResult();
}
else
{
schedulingEngine.WatermarkOff([watermark]).GetAwaiter().GetResult();
}
}
public void watermark_off(PythonList watermark)
{
schedulingEngine.WatermarkOff(watermark.Select(g => g.ToString()).ToList()).GetAwaiter().GetResult();
}
public void skip_items(string content, int count)
{
schedulingEngine.SkipItems(content, count);
}
public void skip_to_item(string content, int season, int episode)
{
schedulingEngine.SkipToItem(content, season, episode);
}
public void wait_until(string when, bool tomorrow = false, bool rewind_on_reset = false)
{
if (TimeOnly.TryParse(when, out TimeOnly waitUntil))

8
ErsatzTV.Core/Scheduling/ScriptedScheduling/ScriptedPlayoutBuilder.cs

@ -50,6 +50,14 @@ public class ScriptedPlayoutBuilder( @@ -50,6 +50,14 @@ public class ScriptedPlayoutBuilder(
var engine = Python.CreateEngine();
var scope = engine.CreateScope();
string scriptPath = Path.GetDirectoryName(playout.ScheduleFile);
if (!string.IsNullOrWhiteSpace(scriptPath) && localFileSystem.FolderExists(scriptPath))
{
ICollection<string> paths = engine.GetSearchPaths();
paths.Add(scriptPath);
engine.SetSearchPaths(paths);
}
var sys = engine.GetSysModule();
var modules = (PythonDictionary)sys.GetVariable("modules");

Loading…
Cancel
Save