Files
ersatztv/ErsatzTV.Infrastructure/Streaming/Graphics/OpacityExpressionHelper.cs
T
timothyandClaude Opus 4.8 01647b6e50
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
fix(deps): clear NCalcSync and SQLitePCLRaw vulnerability advisories
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>
2026-06-27 02:03:41 +02:00

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);
}
}