using System; using System.Collections.Generic; using Paca.Core; using Xunit; namespace Paca.Tests.Core; public class AudioEnginesExtremeRedTests { [Fact] public void AudioPitchShiftEngine_InPlaceOverwrite_ThrowsArgumentException() { var ex = Assert.Throws(() => AudioPitchShiftEngine.BuildPitchShiftCommandLine("track.mp3", "track.mp3", 2.0)); Assert.Contains("identical", ex.Message, StringComparison.OrdinalIgnoreCase); } [Theory] [InlineData(null, "out.mp3")] [InlineData("", "out.mp3")] [InlineData(" ", "out.mp3")] [InlineData("in.mp3", null)] [InlineData("in.mp3", "")] [InlineData("in.mp3", " ")] public void AudioPitchShiftEngine_NullOrEmptyPaths_ThrowsArgumentException(string? inPath, string? outPath) { Assert.Throws(() => AudioPitchShiftEngine.BuildPitchShiftCommandLine(inPath!, outPath!, 2.0)); } [Theory] [InlineData(double.NaN)] [InlineData(double.PositiveInfinity)] [InlineData(double.NegativeInfinity)] public void AudioPitchShiftEngine_NaNOrInfinitySemitones_FallsBackToZeroShift(double invalidSemitones) { var filter = AudioPitchShiftEngine.BuildPitchShiftFilter(invalidSemitones, 44100); Assert.DoesNotContain("NaN", filter, StringComparison.OrdinalIgnoreCase); Assert.DoesNotContain("Infinity", filter, StringComparison.OrdinalIgnoreCase); Assert.Contains("asetrate=44100", filter); } [Theory] [InlineData(0)] [InlineData(-44100)] public void AudioPitchShiftEngine_InvalidSampleRate_ThrowsArgumentOutOfRangeException(int sampleRate) { Assert.Throws(() => AudioPitchShiftEngine.BuildPitchShiftFilter(2.0, sampleRate)); } [Fact] public void AudioDynamicRangeCompressor_InPlaceOverwrite_ThrowsArgumentException() { var ex = Assert.Throws(() => AudioDynamicRangeCompressor.BuildFfmpegArgs("podcast.wav", "podcast.wav", CompressorPreset.NightListening)); Assert.Contains("identical", ex.Message, StringComparison.OrdinalIgnoreCase); } [Theory] [InlineData(null, "out.mp3")] [InlineData("", "out.mp3")] [InlineData("in.mp3", null)] [InlineData("in.mp3", "")] public void AudioDynamicRangeCompressor_NullOrEmptyPaths_ThrowsArgumentException(string? inPath, string? outPath) { Assert.Throws(() => AudioDynamicRangeCompressor.BuildFfmpegArgs(inPath!, outPath!, CompressorPreset.PodcastSpeech)); } [Fact] public void AudioDynamicRangeCompressor_InvalidBitrate_DefaultsTo192k() { var args = AudioDynamicRangeCompressor.BuildFfmpegArgs("in.mp4", "out.mp4", CompressorPreset.NightListening, audioBitrateKbps: 0); Assert.Contains("-b:a 192k", args); } [Theory] [InlineData(double.NaN, 4.0, 10.0, 100.0, 4.0, -1.0)] [InlineData(-20.0, double.PositiveInfinity, 10.0, 100.0, 4.0, -1.0)] [InlineData(-20.0, 4.0, double.NaN, 100.0, 4.0, -1.0)] [InlineData(-20.0, -1.0, 10.0, 100.0, 4.0, -1.0)] // Invalid ratio <= 0 public void AudioDynamicRangeCompressor_InvalidCustomParameters_ThrowsArgumentOutOfRangeException( double th, double ratio, double attack, double release, double makeup, double limit) { Assert.Throws(() => AudioDynamicRangeCompressor.BuildCustomFilter(th, ratio, attack, release, makeup, limit)); } [Fact] public void AudioAlbumTrackSplitter_InPlaceOverwrite_ThrowsArgumentException() { var seg = new AlbumTrackSegment(1, 0.0, 60.0, "Track 01"); var ex = Assert.Throws(() => AudioAlbumTrackSplitter.BuildSplitCommand("album.flac", seg, "album.flac")); Assert.Contains("identical", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AudioAlbumTrackSplitter_NullTrack_ThrowsArgumentNullException() { Assert.Throws(() => AudioAlbumTrackSplitter.BuildSplitCommand("in.mp3", null!, "out.mp3")); } [Theory] [InlineData(double.NaN)] [InlineData(double.PositiveInfinity)] [InlineData(-10.0)] public void AudioAlbumTrackSplitter_ParseSilenceToTracks_InvalidDuration_ReturnsEmpty(double totalDuration) { var tracks = AudioAlbumTrackSplitter.ParseSilenceToTracks("silence_start: 10.0\nsilence_end: 12.0", totalDuration); Assert.Empty(tracks); } [Fact] public void AudioAlbumTrackSplitter_GenerateM3uPlaylist_CrlfInjectionSanitized() { var tracks = new List { new(1, 0.0, 180.0, "Song\r\n#EXT-X-MALICIOUS-TAG") }; var m3u = AudioAlbumTrackSplitter.GenerateM3uPlaylist("MyAlbum\r\n#FORGED-HEADER", tracks); var lines = m3u.Split(new[] { "\r\n", "\n" }, StringSplitOptions.None); foreach (var line in lines) { if (line.StartsWith("#EXTINF:")) { Assert.DoesNotContain("\r", line); Assert.DoesNotContain("\n", line); Assert.Contains("MyAlbum", line); } Assert.False(line.Equals("#EXT-X-MALICIOUS-TAG", StringComparison.OrdinalIgnoreCase)); Assert.False(line.Equals("#FORGED-HEADER", StringComparison.OrdinalIgnoreCase)); } } [Fact] public void AudioVocalStemExtractor_InPlaceOverwrite_ThrowsArgumentException() { var ex = Assert.Throws(() => AudioVocalStemExtractor.BuildStemCommandLine("audio.wav", "audio.wav", AudioStemMode.VocalRemovalKaraoke)); Assert.Contains("identical", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AudioChannelDownmixer_InPlaceOverwrite_ThrowsArgumentException() { var ex = Assert.Throws(() => AudioChannelDownmixer.BuildDownmixCommandLine("movie.mkv", "movie.mkv", DownmixMode.StandardItuR775)); Assert.Contains("identical", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AudioVolumeNormalizer_InPlaceOverwrite_ThrowsArgumentException() { var ex = Assert.Throws(() => AudioVolumeNormalizer.BuildNormalizeCommandLine("stream.aac", "stream.aac", AudioVolumeNormalizer.PodcastStreamingPreset)); Assert.Contains("identical", ex.Message, StringComparison.OrdinalIgnoreCase); } [Fact] public void AudioVolumeNormalizer_NullPreset_ThrowsArgumentNullException() { Assert.Throws(() => AudioVolumeNormalizer.BuildNormalizeCommandLine("in.aac", "out.aac", null!)); Assert.Throws(() => AudioVolumeNormalizer.BuildLoudnormFilterArgs(null!)); } }