diff --git a/CHANGELOG.md b/CHANGELOG.md index 24a1af9e..85f4f06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,16 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Synchronize Plex "network" metadata for Plex show libraries - Shows will have new `network` search field - Episodes will have new `show_network` search field +- YAML playout: add `stop_before_end` setting to `pad_until` and `duration` instructions + - When `stop_before_end: false`, content can run over the desired time before executing the next instruction +- YAML playout: add `offline_tail` setting to `pad_until` instruction + - This can be used to stop primary content before the desired time (`stop_before_end: true` and `offline_tail: false`) + - You can then have a second `pad_until` with the same target time and different content +- YAML playout: make `tomorrow` an expression on `pad_until` instruction + - `true` and `false` still work as normal + - The current time (as a decimal) can also be used in the expression, e.g. `now > 23` + - `now = hours + minutes / 60.0 + seconds / 3600.0` + - So `10:30 AM` would be `10.5`, `10:45 PM` would be `22.75`, etc ### Changed - Allow `Other Video` libraries and `Image` libraries to use the same folders diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs index dda79095..8df67e6d 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutDurationHandler.cs @@ -28,6 +28,12 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP return false; } + if (duration.StopBeforeEnd == false && duration.OfflineTail) + { + logger.LogError("offline_tail must be false when stop_before_end is false"); + return false; + } + DateTimeOffset targetTime = context.CurrentTime.Add(timeSpan); Option maybeEnumerator = await GetContentEnumerator( @@ -49,6 +55,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP instruction.Content, duration.Fallback, targetTime, + duration.StopBeforeEnd, duration.DiscardAttempts, duration.Trim, duration.OfflineTail, @@ -68,6 +75,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP string contentKey, string fallbackContentKey, DateTimeOffset targetTime, + bool stopBeforeEnd, int discardAttempts, bool trim, bool offlineTail, @@ -97,7 +105,7 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP //DisableWatermarks = !allowWatermarks }; - if (remainingToFill - itemDuration >= TimeSpan.Zero) + if (remainingToFill - itemDuration >= TimeSpan.Zero || !stopBeforeEnd) { context.Playout.Items.Add(playoutItem); context.AdvanceGuideGroup(); @@ -195,6 +203,11 @@ public class YamlPlayoutDurationHandler(EnumeratorCache enumeratorCache) : YamlP } } + if (!stopBeforeEnd) + { + return context.CurrentTime; + } + return offlineTail ? targetTime : context.CurrentTime; } } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs index 7121ff33..d547db31 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadToNextHandler.cs @@ -57,6 +57,7 @@ public class YamlPlayoutPadToNextHandler(EnumeratorCache enumeratorCache) : Yaml padToNext.Content, padToNext.Fallback, targetTime, + false, padToNext.DiscardAttempts, padToNext.Trim, offlineTail: true, diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs index 2acc2306..f46f0ee0 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Handlers/YamlPlayoutPadUntilHandler.cs @@ -29,7 +29,17 @@ public class YamlPlayoutPadUntilHandler(EnumeratorCache enumeratorCache) : YamlP if (timeOnly > result) { - if (padUntil.Tomorrow) + var expression = new NCalc.Expression(padUntil.Tomorrow); + expression.EvaluateParameter += (name, e) => + { + e.Result = name switch + { + "now" => timeOnly.Hour + timeOnly.Minute / 60.0 + timeOnly.Second / 3600.0, + _ => e.Result + }; + }; + + if (expression.Evaluate() as bool? == true) { // this is wrong when offset changes dayOnly = dayOnly.AddDays(1); @@ -67,9 +77,10 @@ public class YamlPlayoutPadUntilHandler(EnumeratorCache enumeratorCache) : YamlP padUntil.Content, padUntil.Fallback, targetTime, + padUntil.StopBeforeEnd, padUntil.DiscardAttempts, padUntil.Trim, - offlineTail: true, + padUntil.OfflineTail, GetFillerKind(padUntil), padUntil.CustomTitle, enumerator, diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutDurationInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutDurationInstruction.cs index 2c982734..714015b2 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutDurationInstruction.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutDurationInstruction.cs @@ -15,4 +15,7 @@ public class YamlPlayoutDurationInstruction : YamlPlayoutInstruction [YamlMember(Alias = "discard_attempts", ApplyNamingConventions = false)] public int DiscardAttempts { get; set; } + + [YamlMember(Alias = "stop_before_end", ApplyNamingConventions = false)] + public bool StopBeforeEnd { get; set; } = true; } diff --git a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPadUntilInstruction.cs b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPadUntilInstruction.cs index 5f9f4255..a26059a9 100644 --- a/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPadUntilInstruction.cs +++ b/ErsatzTV.Core/Scheduling/YamlScheduling/Models/YamlPlayoutPadUntilInstruction.cs @@ -7,12 +7,18 @@ public class YamlPlayoutPadUntilInstruction : YamlPlayoutInstruction [YamlMember(Alias = "pad_until", ApplyNamingConventions = false)] public string PadUntil { get; set; } - public bool Tomorrow { get; set; } + public string Tomorrow { get; set; } public bool Trim { get; set; } + [YamlMember(Alias = "offline_tail", ApplyNamingConventions = false)] + public bool OfflineTail { get; set; } + public string Fallback { get; set; } [YamlMember(Alias = "discard_attempts", ApplyNamingConventions = false)] public int DiscardAttempts { get; set; } + + [YamlMember(Alias = "stop_before_end", ApplyNamingConventions = false)] + public bool StopBeforeEnd { get; set; } = true; }