204 lines
7 KiB
C#
204 lines
7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Globalization;
|
|
using System.Threading;
|
|
using Paca.Core.Detection;
|
|
using Paca.Core.Gateway;
|
|
using Paca.Core.Media;
|
|
using Xunit;
|
|
|
|
namespace Paca.Tests.Core;
|
|
|
|
public class AiMediaAndShaderEnginesExtremeRedTests
|
|
{
|
|
private const string SampleInput = "C:\\Videos\\source.mp4";
|
|
private const string SampleOutput = "C:\\Videos\\shorts.mp4";
|
|
|
|
// ==========================================
|
|
// 1. AiHighlightClipper
|
|
// ==========================================
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-100)]
|
|
[InlineData(double.NaN)]
|
|
public void AiHighlightClipper_DetectHighlights_ZeroOrNegativeDuration_ReturnsEmpty(double duration)
|
|
{
|
|
var highlights = AiHighlightClipper.DetectHighlights(duration);
|
|
Assert.NotNull(highlights);
|
|
Assert.Empty(highlights);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0)]
|
|
[InlineData(-10)]
|
|
[InlineData(double.NaN)]
|
|
public void AiHighlightClipper_DetectHighlights_ZeroOrNegativeWindowSeconds_DoesNotCrashOrLoopInfinitely(double windowSec)
|
|
{
|
|
var highlights = AiHighlightClipper.DetectHighlights(120.0, windowSeconds: windowSec);
|
|
Assert.NotNull(highlights);
|
|
Assert.NotEmpty(highlights);
|
|
}
|
|
|
|
[Fact]
|
|
public void AiHighlightClipper_BuildFfmpegShortsCommand_SameInputAndOutput_ThrowsInvalidOperationException()
|
|
{
|
|
var spec = new ShortsClipSpec();
|
|
Assert.Throws<InvalidOperationException>(() =>
|
|
AiHighlightClipper.BuildFfmpegShortsCommand(SampleInput, SampleInput, spec));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(null, SampleOutput)]
|
|
[InlineData(SampleInput, null)]
|
|
[InlineData("", SampleOutput)]
|
|
[InlineData(SampleInput, " ")]
|
|
public void AiHighlightClipper_BuildFfmpegShortsCommand_NullOrWhitespacePaths_ThrowsArgumentException(string? inPath, string? outPath)
|
|
{
|
|
var spec = new ShortsClipSpec();
|
|
Assert.Throws<ArgumentException>(() =>
|
|
AiHighlightClipper.BuildFfmpegShortsCommand(inPath!, outPath!, spec));
|
|
}
|
|
|
|
[Fact]
|
|
public void AiHighlightClipper_BuildFfmpegShortsCommand_NullSpec_ThrowsArgumentNullException()
|
|
{
|
|
Assert.Throws<ArgumentNullException>(() =>
|
|
AiHighlightClipper.BuildFfmpegShortsCommand(SampleInput, SampleOutput, null!));
|
|
}
|
|
|
|
[Fact]
|
|
public void AiHighlightClipper_BuildFfmpegShortsCommand_PathsWithQuotes_Stripped()
|
|
{
|
|
var spec = new ShortsClipSpec();
|
|
string maliciousIn = "C:\\Videos\\so\"urce.mp4";
|
|
string maliciousOut = "C:\\Videos\\sh\"orts.mp4";
|
|
|
|
var args = AiHighlightClipper.BuildFfmpegShortsCommand(maliciousIn, maliciousOut, spec);
|
|
Assert.NotNull(args);
|
|
Assert.DoesNotContain(args, a => a.Contains('"'));
|
|
}
|
|
|
|
// ==========================================
|
|
// 2. FftAudioWaveformEngine
|
|
// ==========================================
|
|
|
|
[Fact]
|
|
public void FftAudioWaveformEngine_BuildFfmpegEqualizerFilter_NullOrEmptyBands_ReturnsEmptyString()
|
|
{
|
|
Assert.Equal(string.Empty, FftAudioWaveformEngine.BuildFfmpegEqualizerFilter(null!));
|
|
Assert.Equal(string.Empty, FftAudioWaveformEngine.BuildFfmpegEqualizerFilter(new List<EqualizerBand>()));
|
|
}
|
|
|
|
[Fact]
|
|
public void FftAudioWaveformEngine_BuildFfmpegEqualizerFilter_ListContainingNullBands_SkipsSafely()
|
|
{
|
|
var bands = new List<EqualizerBand>
|
|
{
|
|
null!,
|
|
new() { FrequencyHz = 1000, GainDb = 3.5 },
|
|
null!
|
|
};
|
|
|
|
var filter = FftAudioWaveformEngine.BuildFfmpegEqualizerFilter(bands);
|
|
Assert.NotNull(filter);
|
|
Assert.Contains("equalizer=f=1000", filter);
|
|
}
|
|
|
|
[Fact]
|
|
public void FftAudioWaveformEngine_BuildFfmpegEqualizerFilter_InCommaDecimalCulture_UsesDotDecimalSeparator()
|
|
{
|
|
var originalCulture = Thread.CurrentThread.CurrentCulture;
|
|
try
|
|
{
|
|
Thread.CurrentThread.CurrentCulture = new CultureInfo("de-DE"); // German culture uses ',' for decimal
|
|
|
|
var bands = new List<EqualizerBand>
|
|
{
|
|
new() { FrequencyHz = 500, GainDb = 4.5 }
|
|
};
|
|
|
|
var filter = FftAudioWaveformEngine.BuildFfmpegEqualizerFilter(bands);
|
|
Assert.Contains("g=4.5", filter);
|
|
Assert.DoesNotContain("g=4,5", filter);
|
|
}
|
|
finally
|
|
{
|
|
Thread.CurrentThread.CurrentCulture = originalCulture;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// 3. NeuralShaderUpscaler
|
|
// ==========================================
|
|
|
|
[Fact]
|
|
public void NeuralShaderUpscaler_GenerateHlslShaderCode_NullConfig_DoesNotThrow()
|
|
{
|
|
var shader = NeuralShaderUpscaler.GenerateHlslShaderCode(null!);
|
|
Assert.NotNull(shader);
|
|
Assert.Contains("float4 PS_NeuralUpscale", shader);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0, 0)]
|
|
[InlineData(-1920, -1080)]
|
|
public void NeuralShaderUpscaler_GenerateHlslShaderCode_ZeroOrNegativeDimensions_FallsBackToDefaultResolution(int w, int h)
|
|
{
|
|
var config = new ShaderUpscaleConfig { TargetWidth = w, TargetHeight = h };
|
|
var shader = NeuralShaderUpscaler.GenerateHlslShaderCode(config);
|
|
Assert.NotNull(shader);
|
|
Assert.DoesNotContain("1.0 / 0", shader);
|
|
Assert.DoesNotContain("1.0 / -", shader);
|
|
}
|
|
|
|
[Fact]
|
|
public void NeuralShaderUpscaler_GenerateHlslShaderCode_InCommaDecimalCulture_UsesDotDecimalSeparator()
|
|
{
|
|
var originalCulture = Thread.CurrentThread.CurrentCulture;
|
|
try
|
|
{
|
|
Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-FR"); // French culture uses ',' for decimal
|
|
|
|
var config = new ShaderUpscaleConfig { Sharpness = 2.5 };
|
|
var shader = NeuralShaderUpscaler.GenerateHlslShaderCode(config);
|
|
|
|
Assert.Contains("2.50", shader);
|
|
Assert.DoesNotContain("2,50", shader);
|
|
}
|
|
finally
|
|
{
|
|
Thread.CurrentThread.CurrentCulture = originalCulture;
|
|
}
|
|
}
|
|
|
|
// ==========================================
|
|
// 4. PacaConnectCodeGenerator
|
|
// ==========================================
|
|
|
|
[Theory]
|
|
[InlineData(null)]
|
|
[InlineData("")]
|
|
[InlineData(" ")]
|
|
[InlineData("PACA-12345")] // 5 chars instead of 6
|
|
[InlineData("PACA-1234567")] // 7 chars
|
|
[InlineData("PACA-000000")] // '0' is disallowed
|
|
[InlineData("PACA-111111")] // '1' is disallowed
|
|
[InlineData("PACA-IIIIII")] // 'I' is disallowed
|
|
[InlineData("PACA-OOOOOO")] // 'O' is disallowed
|
|
[InlineData("OTHER-234567")] // wrong prefix
|
|
public void PacaConnectCodeGenerator_IsValid_InvalidCodes_ReturnsFalse(string? code)
|
|
{
|
|
Assert.False(PacaConnectCodeGenerator.IsValid(code));
|
|
}
|
|
|
|
[Fact]
|
|
public void PacaConnectCodeGenerator_GenerateCode_AlwaysProducesValidCode()
|
|
{
|
|
for (int i = 0; i < 50; i++)
|
|
{
|
|
var code = PacaConnectCodeGenerator.GenerateCode();
|
|
Assert.True(PacaConnectCodeGenerator.IsValid(code), $"Generated code '{code}' should be valid.");
|
|
}
|
|
}
|
|
}
|