Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 2m17s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Build & test (.NET) (push) Successful in 2m38s
Build ErsatzTV Image / Build & push image (amd64) (push) Successful in 5m41s
NCalcSync 5.11.0 -> 6.3.2 clears CVE-2026-55254 / GHSA-3w5p-95mh-gq75 (the factorial-DoS advisory on NCalc.Core/NCalcSync). NCalc 6 split its assemblies and renamed the custom-function API, so port OpacityExpressionHelper: FunctionArgs -> FunctionEventArgs, and args.Parameters[i].Evaluate() -> args.Parameters.Evaluate(i) (FunctionData.Count / Evaluate(index)). Add a regression test covering the migrated opacity wiring (the feature had no tests). NCalc 6 transitively requires Microsoft.Extensions.Logging.Abstractions >= 10.0.7, so bump the centrally-pinned Microsoft.Extensions.* family 10.0.2 -> 10.0.7 to avoid the NU1605 downgrade error (a .NET 10 servicing patch bump). SQLitePCLRaw: EF Core 9's Sqlite provider pulls the vulnerable bundle 2.1.10 (GHSA-2m69-gcr7-jv3q, outdated bundled SQLite). Directly pin SQLitePCLRaw.bundle_e_sqlite3 3.0.3 in Infrastructure.Sqlite to override the transitive version with the patched native (lib.e_sqlite3 3.50.3); core 3.0.3 satisfies Microsoft.Data.Sqlite's >= 2.1.10 requirement under EF Core 9. Verified: `dotnet list package --vulnerable --include-transitive` reports 0 vulnerable projects; restore + Release build clean; full test suite green under UTC. (2 pre-existing PlayoutModeSchedulerBase filler tests fail only under non-UTC local timezones, unrelated to these deps; they pass in CI.) Refs #8 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
102 lines
3.6 KiB
C#
102 lines
3.6 KiB
C#
using System.Globalization;
|
|
using NCalc;
|
|
using NCalc.Handlers;
|
|
|
|
namespace ErsatzTV.Infrastructure.Streaming.Graphics;
|
|
|
|
public static class OpacityExpressionHelper
|
|
{
|
|
public static void EvaluateFunction(string name, FunctionEventArgs args)
|
|
{
|
|
switch (name)
|
|
{
|
|
case "LinearFadePoints":
|
|
{
|
|
if (args.Parameters.Count != 5)
|
|
{
|
|
throw new ArgumentException("LinearFadePoints() requires 5 arguments.");
|
|
}
|
|
|
|
var time = Convert.ToDouble(args.Parameters.Evaluate(0), CultureInfo.CurrentCulture);
|
|
var start = Convert.ToDouble(args.Parameters.Evaluate(1), CultureInfo.CurrentCulture);
|
|
var peakStart = Convert.ToDouble(args.Parameters.Evaluate(2), CultureInfo.CurrentCulture);
|
|
var peakEnd = Convert.ToDouble(args.Parameters.Evaluate(3), CultureInfo.CurrentCulture);
|
|
var end = Convert.ToDouble(args.Parameters.Evaluate(4), CultureInfo.CurrentCulture);
|
|
|
|
args.Result = LinearFadePoints(time, start, peakStart, peakEnd, end);
|
|
break;
|
|
}
|
|
case "LinearFadeDuration":
|
|
{
|
|
if (args.Parameters.Count != 4)
|
|
{
|
|
throw new ArgumentException("LinearFadeDuration() requires 4 arguments.");
|
|
}
|
|
|
|
var time = Convert.ToDouble(args.Parameters.Evaluate(0), CultureInfo.CurrentCulture);
|
|
var start = Convert.ToDouble(args.Parameters.Evaluate(1), CultureInfo.CurrentCulture);
|
|
var fadeSeconds = Convert.ToDouble(args.Parameters.Evaluate(2), CultureInfo.CurrentCulture);
|
|
var peakSeconds = Convert.ToDouble(args.Parameters.Evaluate(3), CultureInfo.CurrentCulture);
|
|
|
|
args.Result = LinearFadeDuration(time, start, fadeSeconds, peakSeconds);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
private static double LinearFadePoints(double time, double start, double peakStart, double peakEnd, double end)
|
|
{
|
|
if (time < start || time >= end)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
// fade in
|
|
if (time < peakStart)
|
|
{
|
|
return (time - start) / (peakStart - start);
|
|
}
|
|
|
|
// solid
|
|
if (time < peakEnd)
|
|
{
|
|
return 1.0;
|
|
}
|
|
|
|
// fade out
|
|
return (end - time) / (end - peakEnd);
|
|
}
|
|
|
|
private static double LinearFadeDuration(double time, double start, double fadeSeconds, double peakSeconds)
|
|
{
|
|
// edge case with no fade
|
|
if (fadeSeconds <= 0)
|
|
{
|
|
double noFadeEnd = start + peakSeconds;
|
|
return time >= start && time < noFadeEnd ? 1.0 : 0.0;
|
|
}
|
|
|
|
double peakStart = start + fadeSeconds;
|
|
double peakEnd = peakStart + peakSeconds;
|
|
double end = peakEnd + fadeSeconds;
|
|
|
|
return LinearFadePoints(time, start, peakStart, peakEnd, end);
|
|
}
|
|
|
|
public static float GetOpacity(
|
|
Expression expression,
|
|
TimeSpan timeOfDay,
|
|
TimeSpan contentTime,
|
|
TimeSpan contentTotalTime,
|
|
TimeSpan channelTime)
|
|
{
|
|
expression.Parameters["content_seconds"] = contentTime.TotalSeconds;
|
|
expression.Parameters["content_total_seconds"] = contentTotalTime.TotalSeconds;
|
|
expression.Parameters["channel_seconds"] = channelTime.TotalSeconds;
|
|
expression.Parameters["time_of_day_seconds"] = timeOfDay.TotalSeconds;
|
|
|
|
object expressionResult = expression.Evaluate();
|
|
return Convert.ToSingle(expressionResult, CultureInfo.InvariantCulture);
|
|
}
|
|
}
|