diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs index a940067..c0da162 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestAudioFingerprinting.cs @@ -108,6 +108,29 @@ public class TestAudioFingerprinting Assert.Equal(22.912, rhs.IntroEnd); } + /// + /// Test that the silencedetect wrapper is working. + /// + [FactSkipFFmpegTests] + public void TestSilenceDetection() + { + var clip = queueEpisode("audio/big_buck_bunny_clip.mp3"); + + var expected = new TimeRange[] + { + new TimeRange(44.6310, 44.8072), + new TimeRange(53.5905, 53.8070), + new TimeRange(53.8458, 54.2024), + new TimeRange(54.2611, 54.5935), + new TimeRange(54.7098, 54.9293), + new TimeRange(54.9294, 55.2590), + }; + + var actual = FFmpegWrapper.DetectSilence(clip, 60); + + Assert.Equal(expected, actual); + } + private QueuedEpisode queueEpisode(string path) { return new QueuedEpisode() diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs index 286aa0c..50121f6 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestContiguous.cs @@ -71,4 +71,22 @@ public class TestTimeRanges Assert.Equal(expected, actual); } + + /// + /// Tests that TimeRange intersections are detected correctly. + /// Tests each time range against a range of 5 to 10 seconds. + /// + [Theory] + [InlineData(1, 4, false)] // too early + [InlineData(4, 6, true)] // intersects on the left + [InlineData(7, 8, true)] // in the middle + [InlineData(9, 12, true)] // intersects on the right + [InlineData(13, 15, false)] // too late + public void TestTimeRangeIntersection(int start, int end, bool expected) + { + var large = new TimeRange(5, 10); + var testRange = new TimeRange(start, end); + + Assert.Equal(expected, large.Intersects(testRange)); + } } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/AutoSkip.cs b/ConfusedPolarBear.Plugin.IntroSkipper/AutoSkip.cs index 3846bb9..1cac686 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/AutoSkip.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/AutoSkip.cs @@ -169,6 +169,8 @@ public class AutoSkip : IServerEntryPoint // Send the seek command _logger.LogDebug("Sending seek command to {Session}", deviceId); + var introEnd = (long)intro.IntroEnd - Plugin.Instance!.Configuration.AmountOfIntroToPlay; + _sessionManager.SendPlaystateCommand( session.Id, session.Id, @@ -176,7 +178,7 @@ public class AutoSkip : IServerEntryPoint { Command = PlaystateCommand.Seek, ControllingUserId = session.UserId.ToString("N"), - SeekPositionTicks = (long)intro.IntroEnd * TimeSpan.TicksPerSecond, + SeekPositionTicks = introEnd * TimeSpan.TicksPerSecond, }, CancellationToken.None); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs index 741c561..d98554c 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs @@ -62,6 +62,17 @@ public class PluginConfiguration : BasePluginConfiguration /// public int MinimumIntroDuration { get; set; } = 15; + /// + /// Gets or sets the maximum amount of noise (in dB) that is considered silent. + /// Lowering this number will increase the filter's sensitivity to noise. + /// + public int SilenceDetectionMaximumNoise { get; set; } = -50; + + /// + /// Gets or sets the minimum duration of audio (in seconds) that is considered silent. + /// + public double SilenceDetectionMinimumDuration { get; set; } = 0.50; + // ===== Playback settings ===== /// @@ -83,5 +94,5 @@ public class PluginConfiguration : BasePluginConfiguration /// Gets or sets the amount of intro to play (in seconds). /// TODO: rename. /// - public int AmountOfIntroToPlay { get; set; } = 5; + public int AmountOfIntroToPlay { get; set; } = 2; } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Data/TimeRange.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Data/TimeRange.cs index 7e7b51b..451df2f 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Data/TimeRange.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Data/TimeRange.cs @@ -69,6 +69,18 @@ public class TimeRange : IComparable return tr.Duration.CompareTo(Duration); } + + /// + /// Tests if this TimeRange object intersects the provided TimeRange. + /// + /// Second TimeRange object to test. + /// true if tr intersects the current TimeRange, false otherwise. + public bool Intersects(TimeRange tr) + { + return + (Start < tr.Start && tr.Start < End) || + (Start < tr.End && tr.End < End); + } } #pragma warning restore CA1036 diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs index bda56ef..7852b97 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Globalization; using System.IO; using System.Text; +using System.Text.RegularExpressions; using Microsoft.Extensions.Logging; namespace ConfusedPolarBear.Plugin.IntroSkipper; @@ -13,6 +14,16 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper; /// public static class FFmpegWrapper { + // FFmpeg logs lines similar to the following: + // [silencedetect @ 0x000000000000] silence_start: 12.34 + // [silencedetect @ 0x000000000000] silence_end: 56.123 | silence_duration: 43.783 + + /// + /// Used with FFmpeg's silencedetect filter to extract the start and end times of silence. + /// + private static readonly Regex SilenceDetectionExpression = new( + "silence_(?start|end): (?