Build ErsatzTV Image / CI image pin matches docker/ci (pull_request) Successful in 9s
Build ErsatzTV Image / Docs update reminder (pull_request) Successful in 5s
Build ErsatzTV Image / decisions.md append-only (pull_request) Successful in 5s
Build ErsatzTV Image / Build & test (.NET) (pull_request) Successful in 7m6s
Build ErsatzTV Image / API docs in sync (OpenAPI + endpoint index) (pull_request) Successful in 7s
Build ErsatzTV Image / Functional E2E (curl contracts) (pull_request) Successful in 14m27s
Build ErsatzTV Image / EF migration integrity (SQLite + MySql) (pull_request) Successful in 19m10s
Build ErsatzTV Image / Build & push image (amd64) (pull_request) Has been skipped
Build ErsatzTV Image / Formatting (changed .cs conform to .editorconfig) (pull_request) Successful in 13m24s
Adds one Information-level structured log per HLS tune-in cold-start so the real driver breakdown can be measured on prod before optimizing the transcode pipeline (measure-before-optimize). Log-only; no transcode behavior change. - WaitForPlaylistSegments returns a PlaylistSegmentsResult: Phase A (process startup -> playlist exists) vs Phase B (segment fill), segments reached, deadline-expired. - StartFFmpegSessionHandler emits one summary: total = setup + startup + fill, plus cleanly-detectable feature flags (subtitle burn-in, hwaccel family). - ColdStartFeatures: pure, unit-tested args->features helper (14 cases). Watermark / HDR->SDR / image-subtitle burn-in are deliberately not flagged (all reduce to overlay= in the args, indistinguishable); the full ffmpeg arguments remain available at Debug. Refs #350 (instrumentation slice; optimization deferred pending real data). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
90 lines
3.0 KiB
C#
90 lines
3.0 KiB
C#
using ErsatzTV.Core.FFmpeg;
|
|
using NUnit.Framework;
|
|
using Shouldly;
|
|
|
|
namespace ErsatzTV.Core.Tests.FFmpeg;
|
|
|
|
[TestFixture]
|
|
public class ColdStartFeaturesTests
|
|
{
|
|
[Test]
|
|
public void Should_Detect_Subtitle_BurnIn_From_Subtitles_Filter()
|
|
{
|
|
const string Args =
|
|
"-i ep.mkv -filter_complex \"[0:0]subtitles=http://localhost:8409/media/subtitle/1.ass[v]\" " +
|
|
"-c:v h264_vaapi -f hls";
|
|
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(Args);
|
|
|
|
features.SubtitleBurnIn.ShouldBeTrue();
|
|
}
|
|
|
|
[Test]
|
|
public void Should_Not_Flag_Subtitle_BurnIn_For_Copy_Or_Overlay()
|
|
{
|
|
// subtitle stream copy (-c:s) and a watermark/image-subtitle overlay must NOT be read as burn-in
|
|
const string Args = "-i ep.mkv -filter_complex \"[v][wm]overlay=10:10:format=0[vwm]\" -c:s copy -c:v libx264 -f hls";
|
|
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(Args);
|
|
|
|
features.SubtitleBurnIn.ShouldBeFalse();
|
|
}
|
|
|
|
[TestCase("-c:v h264_vaapi -f hls", "vaapi")]
|
|
[TestCase("-c:v hevc_vaapi -f hls", "vaapi")]
|
|
[TestCase("-c:v h264_nvenc -f hls", "nvenc")]
|
|
[TestCase("-c:v h264_qsv -f hls", "qsv")]
|
|
[TestCase("-c:v h264_videotoolbox -f hls", "videotoolbox")]
|
|
[TestCase("-c:v h264_amf -f hls", "amf")]
|
|
public void Should_Detect_Hardware_Family_From_Encoder(string args, string expected)
|
|
{
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(args);
|
|
|
|
features.HardwareAcceleration.ShouldBe(expected);
|
|
}
|
|
|
|
[Test]
|
|
public void Should_Detect_Hardware_Family_From_Vaapi_Filter_Even_Without_Encoder_Token()
|
|
{
|
|
// a vaapi-accelerated pipeline surfaces the family via filters (e.g. scale_vaapi) too
|
|
const string Args = "-hwaccel vaapi -i ep.mkv -vf scale_vaapi=1920:1080 -f hls";
|
|
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(Args);
|
|
|
|
features.HardwareAcceleration.ShouldBe("vaapi");
|
|
}
|
|
|
|
[Test]
|
|
public void Should_Fall_Back_To_Hwaccel_Flag_When_No_Hardware_Encoder_Or_Filter()
|
|
{
|
|
// hardware decode, software encode: attribute the decode accel
|
|
const string Args = "-hwaccel cuda -i ep.mkv -c:v libx264 -f hls";
|
|
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(Args);
|
|
|
|
features.HardwareAcceleration.ShouldBe("cuda");
|
|
}
|
|
|
|
[Test]
|
|
public void Should_Report_Software_When_No_Acceleration()
|
|
{
|
|
const string Args = "-i ep.mkv -c:v libx264 -c:a aac -f hls";
|
|
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(Args);
|
|
|
|
features.SubtitleBurnIn.ShouldBeFalse();
|
|
features.HardwareAcceleration.ShouldBe("software");
|
|
}
|
|
|
|
[TestCase("")]
|
|
[TestCase(" ")]
|
|
[TestCase(null)]
|
|
public void Should_Report_Unknown_For_Empty_Arguments(string args)
|
|
{
|
|
ColdStartFeatures features = ColdStartFeatures.FromFFmpegArguments(args);
|
|
|
|
features.SubtitleBurnIn.ShouldBeFalse();
|
|
features.HardwareAcceleration.ShouldBe("unknown");
|
|
}
|
|
}
|