using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using Paca.Core.Platform; using Xunit; namespace Paca.Tests.Platform; public class PlatformMediaToolsExtremeRedTests { // ========================================== // 1. ChapterSplitter // ========================================== [Fact] public void ChapterSplitter_GenerateFfmetadata_NullChapters_ReturnsValidHeaderWithoutCrashing() { var meta = ChapterSplitter.GenerateFfmetadata(null!); Assert.NotNull(meta); Assert.Contains(";FFMETADATA1", meta); } [Fact] public void ChapterSplitter_GenerateFfmetadata_ListContainingNullChapter_SkipsNullChapter() { var chapters = new List { null!, new(1, "Intro", 0.0, 30.0), null! }; var meta = ChapterSplitter.GenerateFfmetadata(chapters); Assert.NotNull(meta); Assert.Contains("title=Intro", meta); } [Fact] public void ChapterSplitter_GetChapterFileName_NullTitle_ReturnsSafeDefault() { var name = ChapterSplitter.GetChapterFileName(1, null!, "mp4"); Assert.NotNull(name); Assert.StartsWith("01_", name); Assert.EndsWith(".mp4", name); } // ========================================== // 2. LosslessSmartTrimmer // ========================================== [Theory] [InlineData(double.NaN)] [InlineData(double.NegativeInfinity)] [InlineData(double.PositiveInfinity)] public void LosslessSmartTrimmer_FindNearestKeyframe_InvalidTargetSec_ReturnsDefault(double target) { var keyframes = new[] { 0.0, 5.0, 10.0, 15.0 }; var nearest = LosslessSmartTrimmer.FindNearestKeyframe(keyframes, target); Assert.False(double.IsNaN(nearest)); Assert.False(double.IsInfinity(nearest)); } [Fact] public void LosslessSmartTrimmer_FindNearestKeyframe_KeyframesWithNaNOrNegatives_FiltersCorrectly() { var keyframes = new[] { double.NaN, -10.0, 5.0, 10.0 }; var nearest = LosslessSmartTrimmer.FindNearestKeyframe(keyframes, 6.0); Assert.Equal(5.0, nearest); } [Fact] public async Task LosslessSmartTrimmer_TrimLosslessAsync_NaNTimestamps_ThrowsArgumentException() { var trimmer = new LosslessSmartTrimmer(DesktopProcessRunner.Default); await Assert.ThrowsAsync(() => trimmer.TrimLosslessAsync("ffmpeg.exe", "in.mp4", "out.mp4", double.NaN, 10.0, CancellationToken.None)); } // ========================================== // 3. VideoWatermarkBurner // ========================================== [Fact] public void VideoWatermarkBurner_BuildDrawTextFilter_NullText_DoesNotThrow() { var filter = VideoWatermarkBurner.BuildDrawTextFilter(null!); Assert.NotNull(filter); Assert.Contains("drawtext=", filter); } [Fact] public void VideoWatermarkBurner_BuildDrawTextFilter_TextWithNewlines_EscapedOrStripped() { var filter = VideoWatermarkBurner.BuildDrawTextFilter("Line 1\r\nLine 2\nLine 3"); Assert.NotNull(filter); Assert.DoesNotContain("\r", filter); Assert.DoesNotContain("\n", filter); } [Theory] [InlineData(-0.1)] [InlineData(1.1)] [InlineData(double.NaN)] public void VideoWatermarkBurner_BuildOverlayFilter_InvalidOpacity_ThrowsArgumentOutOfRangeException(double opacity) { Assert.Throws(() => VideoWatermarkBurner.BuildOverlayFilter(WatermarkPosition.Center, opacity: opacity)); } }