add new scheduling engine, basic scripted schedule system (#2337)
* start to add content to scheduling engine * add first content instruction * add search content * allow scripted schedule creation * don't use scheduling engine in sequential playout builder, yet
This commit is contained in:
@@ -1,22 +1,33 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Scheduling.Engine;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling.ScriptedScheduling.Modules;
|
||||
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
|
||||
public class ContentModule
|
||||
[SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits")]
|
||||
public class ContentModule(ISchedulingEngine schedulingEngine)
|
||||
{
|
||||
private Dictionary<string, IMediaCollectionEnumerator> _contentEnumerators = [];
|
||||
|
||||
public bool add_collection(string key, string name, string order)
|
||||
public bool add_search(string key, string query, string order)
|
||||
{
|
||||
if (_contentEnumerators.ContainsKey(key))
|
||||
if (!Enum.TryParse(order, ignoreCase: true, out PlaybackOrder playbackOrder))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLine($"Adding collection '{name}' with key '{key}' and order '{order}'");
|
||||
_contentEnumerators.Clear();
|
||||
schedulingEngine.AddSearch(key, query, playbackOrder).GetAwaiter().GetResult();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool add_collection(string key, string collection, string order)
|
||||
{
|
||||
if (!Enum.TryParse(order, ignoreCase: true, out PlaybackOrder playbackOrder))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
schedulingEngine.AddCollection(key, collection, playbackOrder).GetAwaiter().GetResult();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ErsatzTV.Core.Domain.Filler;
|
||||
using ErsatzTV.Core.Scheduling.Engine;
|
||||
|
||||
namespace ErsatzTV.Core.Scheduling.ScriptedScheduling.Modules;
|
||||
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
|
||||
[SuppressMessage("Usage", "VSTHRD002:Avoid problematic synchronous waits")]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
public class PlayoutModule(ISchedulingEngine schedulingEngine)
|
||||
{
|
||||
// content instructions
|
||||
|
||||
public void add_count(string content, int count, string filler_kind = null, string custom_title = null, bool disable_watermarks = false)
|
||||
{
|
||||
Option<FillerKind> maybeFillerKind = Option<FillerKind>.None;
|
||||
if (Enum.TryParse(filler_kind, ignoreCase: true, out FillerKind fillerKind))
|
||||
{
|
||||
maybeFillerKind = fillerKind;
|
||||
}
|
||||
|
||||
schedulingEngine.AddCount(content, count, maybeFillerKind, custom_title, disable_watermarks);
|
||||
}
|
||||
|
||||
|
||||
// control instructions
|
||||
|
||||
public void wait_until(string when, bool tomorrow = false, bool rewind_on_reset = false)
|
||||
{
|
||||
if (TimeOnly.TryParse(when, out TimeOnly waitUntil))
|
||||
{
|
||||
schedulingEngine.WaitUntil(waitUntil, tomorrow, rewind_on_reset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using ErsatzTV.Core.Domain;
|
||||
using ErsatzTV.Core.Interfaces.Metadata;
|
||||
using ErsatzTV.Core.Interfaces.Repositories;
|
||||
using ErsatzTV.Core.Interfaces.Scheduling;
|
||||
using ErsatzTV.Core.Scheduling.Engine;
|
||||
using ErsatzTV.Core.Scheduling.ScriptedScheduling.Modules;
|
||||
using IronPython.Hosting;
|
||||
using IronPython.Runtime;
|
||||
@@ -9,8 +12,8 @@ using Microsoft.Extensions.Logging;
|
||||
namespace ErsatzTV.Core.Scheduling.ScriptedScheduling;
|
||||
|
||||
public class ScriptedPlayoutBuilder(
|
||||
//IConfigElementRepository configElementRepository,
|
||||
//IMediaCollectionRepository mediaCollectionRepository,
|
||||
IConfigElementRepository configElementRepository,
|
||||
ISchedulingEngine schedulingEngine,
|
||||
ILocalFileSystem localFileSystem,
|
||||
ILogger<ScriptedPlayoutBuilder> logger)
|
||||
: IScriptedPlayoutBuilder
|
||||
@@ -22,27 +25,27 @@ public class ScriptedPlayoutBuilder(
|
||||
PlayoutBuildMode mode,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await Task.Delay(10, cancellationToken);
|
||||
|
||||
var result = PlayoutBuildResult.Empty;
|
||||
|
||||
if (!localFileSystem.FileExists(playout.ScheduleFile))
|
||||
{
|
||||
logger.LogError("Cannot build scripted playout; schedule file {File} does not exist", playout.ScheduleFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
logger.LogInformation("Building scripted playout...");
|
||||
|
||||
//int daysToBuild = await GetDaysToBuild();
|
||||
//DateTimeOffset finish = start.AddDays(daysToBuild);
|
||||
|
||||
//var enumeratorCache = new EnumeratorCache(mediaCollectionRepository, logger);
|
||||
|
||||
// apply all history???
|
||||
|
||||
try
|
||||
{
|
||||
if (!localFileSystem.FileExists(playout.ScheduleFile))
|
||||
{
|
||||
logger.LogError("Cannot build scripted playout; schedule file {File} does not exist", playout.ScheduleFile);
|
||||
return result;
|
||||
}
|
||||
|
||||
logger.LogInformation("Building scripted playout...");
|
||||
|
||||
int daysToBuild = await GetDaysToBuild();
|
||||
DateTimeOffset finish = start.AddDays(daysToBuild);
|
||||
|
||||
schedulingEngine.WithPlayoutId(playout.Id);
|
||||
schedulingEngine.WithMode(mode);
|
||||
schedulingEngine.WithSeed(playout.Seed);
|
||||
schedulingEngine.BuildBetween(start, finish);
|
||||
schedulingEngine.WithReferenceData(referenceData);
|
||||
|
||||
var engine = Python.CreateEngine();
|
||||
var scope = engine.CreateScope();
|
||||
|
||||
@@ -55,10 +58,48 @@ public class ScriptedPlayoutBuilder(
|
||||
dynamic ersatztv = engine.Operations.Invoke(moduleType, "ersatztv");
|
||||
modules["ersatztv"] = ersatztv;
|
||||
|
||||
var contentModule = new ContentModule();
|
||||
var contentModule = new ContentModule(schedulingEngine);
|
||||
engine.Operations.SetMember(ersatztv, "content", contentModule);
|
||||
|
||||
var playoutModule = new PlayoutModule(schedulingEngine);
|
||||
engine.Operations.SetMember(ersatztv, "playout", playoutModule);
|
||||
|
||||
engine.ExecuteFile(playout.ScheduleFile, scope);
|
||||
|
||||
// define_content is required
|
||||
if (!scope.TryGetVariable("define_content", out PythonFunction defineContentFunc))
|
||||
{
|
||||
logger.LogError("Script must contain a 'define_content' function");
|
||||
return result;
|
||||
}
|
||||
|
||||
// reset_playout is NOT required
|
||||
scope.TryGetVariable("reset_playout", out PythonFunction resetPlayoutFunc);
|
||||
|
||||
// build_playout is required
|
||||
if (!scope.TryGetVariable("build_playout", out PythonFunction buildPlayoutFunc))
|
||||
{
|
||||
logger.LogError("Script must contain a 'build_playout' function");
|
||||
return result;
|
||||
}
|
||||
|
||||
schedulingEngine.RestoreOrReset(Optional(playout.Anchor));
|
||||
|
||||
// define content first
|
||||
engine.Operations.Invoke(defineContentFunc);
|
||||
|
||||
// reset if applicable
|
||||
if (mode is PlayoutBuildMode.Reset && resetPlayoutFunc != null)
|
||||
{
|
||||
engine.Operations.Invoke(resetPlayoutFunc, new PythonPlayoutContext(schedulingEngine.GetState()));
|
||||
}
|
||||
|
||||
// build playout
|
||||
engine.Operations.Invoke(buildPlayoutFunc, new PythonPlayoutContext(schedulingEngine.GetState()));
|
||||
|
||||
playout.Anchor = schedulingEngine.GetAnchor();
|
||||
|
||||
result = MergeResult(result, schedulingEngine.GetState());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -69,8 +110,28 @@ public class ScriptedPlayoutBuilder(
|
||||
return result;
|
||||
}
|
||||
|
||||
// private async Task<int> GetDaysToBuild() =>
|
||||
// await configElementRepository
|
||||
// .GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
||||
// .IfNoneAsync(2);
|
||||
private async Task<int> GetDaysToBuild() =>
|
||||
await configElementRepository
|
||||
.GetValue<int>(ConfigElementKey.PlayoutDaysToBuild)
|
||||
.IfNoneAsync(2);
|
||||
|
||||
private static PlayoutBuildResult MergeResult(PlayoutBuildResult result, ISchedulingEngineState state) =>
|
||||
result with
|
||||
{
|
||||
ClearItems = state.ClearItems,
|
||||
RemoveBefore = state.RemoveBefore,
|
||||
AddedItems = state.AddedItems,
|
||||
//ItemsToRemove = state.ItemsToRemove,
|
||||
AddedHistory = state.AddedHistory,
|
||||
HistoryToRemove = state.HistoryToRemove
|
||||
};
|
||||
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
[SuppressMessage("Naming", "CA1707:Identifiers should not contain underscores")]
|
||||
public class PythonPlayoutContext(ISchedulingEngineState state)
|
||||
{
|
||||
public DateTimeOffset current_time => state.CurrentTime;
|
||||
public DateTimeOffset finish => state.Finish;
|
||||
public bool is_done() => current_time >= finish;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user