From 8ee400f1f1b0e6e1a263787579f32285376e18d6 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Tue, 1 Nov 2022 00:53:56 -0500 Subject: [PATCH] Add blackframe API --- .../TestBlackFrames.cs | 50 +++++++++++ .../video/rainbow.mp4 | Bin 0 -> 15787 bytes .../Configuration/PluginConfiguration.cs | 5 ++ .../Data/BlackFrame.cs | 28 ++++++ .../FFmpegWrapper.cs | 80 ++++++++++++++++-- 5 files changed, 154 insertions(+), 9 deletions(-) create mode 100644 ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestBlackFrames.cs create mode 100644 ConfusedPolarBear.Plugin.IntroSkipper.Tests/video/rainbow.mp4 create mode 100644 ConfusedPolarBear.Plugin.IntroSkipper/Data/BlackFrame.cs diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestBlackFrames.cs b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestBlackFrames.cs new file mode 100644 index 0000000..794ecd1 --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestBlackFrames.cs @@ -0,0 +1,50 @@ +namespace ConfusedPolarBear.Plugin.IntroSkipper.Tests; + +using System; +using System.Collections.Generic; +using Xunit; + +public class TestBlackFrames +{ + [FactSkipFFmpegTests] + public void TestBlackFrameDetection() + { + var expected = new List(); + expected.AddRange(CreateFrameSequence(2, 3)); + expected.AddRange(CreateFrameSequence(5, 6)); + expected.AddRange(CreateFrameSequence(8, 9.96)); + + var actual = FFmpegWrapper.DetectBlackFrames( + queueFile("rainbow.mp4"), + new TimeRange(0, 10) + ); + + for (var i = 0; i < expected.Count; i++) + { + var (e, a) = (expected[i], actual[i]); + Assert.Equal(e.Percentage, a.Percentage); + Assert.True(Math.Abs(e.Time - a.Time) <= 0.005); + } + } + + private QueuedEpisode queueFile(string path) + { + return new() + { + EpisodeId = Guid.NewGuid(), + Path = "../../../video/" + path + }; + } + + private BlackFrame[] CreateFrameSequence(double start, double end) + { + var frames = new List(); + + for (var i = start; i < end; i += 0.04) + { + frames.Add(new(100, i)); + } + + return frames.ToArray(); + } +} diff --git a/ConfusedPolarBear.Plugin.IntroSkipper.Tests/video/rainbow.mp4 b/ConfusedPolarBear.Plugin.IntroSkipper.Tests/video/rainbow.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..8e01cce620da0f4d72f647169d5f42f3a9b67c08 GIT binary patch literal 15787 zcmeHOeQXp(6rbbxN<*KgT2xGX-MxMnHuh+$MT-LALsUHGcK3QqcW<}5)Aovy z)Cj~sBqkb5C`lU0wJz5b|ldj0xy`Fm*~I z@!rL{E1&&1SbwCm@B%mZ;LpFr{N<%w5hodz!U<(%oaL_)DmWfjmdaI?P*Lv#h2rHa z>YgYn8DMSfnO+7Uv`>f++Iwl-g@Dl3MDqdvoGET+Bq zB1G3f$1sU%MD1!$!XYe)0%s{sD5>=!YB9`)Wm=>l>YAJREnp;wEbh@Uk@uAYYf@k*$1$FyFO6+WAoS`hRPuCVva}DOPdi*m+T=*IZcd;pvh3YJWLLxV zwuB4tITX(bbZ@VlRal$?vCoA_0`|HP6c8W|0ECtT(eFZ>th;+0#FcTQxH=BvS`xxY z#Cx|Q^eydYMxbZrc!-{&)KT;lk3q~1>@H10G$!KRImjD!5mu!kG^Y-t#~z0eOCqdG z#4Br1_A(cta|EFS^o<1Mz3)Y}rWIDaB4HW=ka%?whoG^|432Z?4Pw?d0Bv)*oHfJhMH)_U#XM5XBb6~8DR$RmcRd!v$znJ&HHMR& zPj|xkbk|(nFk0c=g{HSgWXMyJ_cB5c=O7QFe`9H!0L5u?&6wt`xJd}j3_y5t6I8xH znw&npDT+xn=r=v3^>eF!JvGqin$xgAk%OYbB=ptzHhW6nOR%PNK%PM6c? z?gyPAwC1?et|%gFmJ5LpjglYXX}*?5&DZ0otSM9{TZXu48ibqMsym>m!!D5Bx_lXe zvuR^Uw0=g5rExBwU0{=t27JFw?2!$jI)~^F1t6y>71J%KfoazRONpj`Xt>2GpLq!g zb4KNWTI+$J?Zo&061?phiQWEdc$h-unAkQUr@c_hg-G(`A}`EGh!V=PiL(K~IyyJ$ zj~*aZ!o29l(3Ay4+usXR0@MIR7k3NLPM||TCxCuHXxewu7#?8JcCe97PGM4IDpJ|$ z5AsZ}XYxJLQ)n*mVBn3G{g-@Cntqx*A${b*81965OvHY$wHkZ2)1Fn7CVMAh zttOxklxq{ofd3Ji=v{#a3bn`tL;jquI61IrLFy$9E_N0+O?rTf=2lKB3$v@ciHQ!_Ged literal 0 HcmV?d00001 diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs index ce79fdd..f4789db 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs @@ -77,6 +77,11 @@ public class PluginConfiguration : BasePluginConfiguration /// public int MaximumEpisodeCreditsDuration { get; set; } = 4; + /// + /// Gets or sets the minimum percentage of a frame that must consist of black pixels before it is considered a black frame. + /// + public int BlackFrameMinimumPercentage { get; set; } = 85; + // ===== Playback settings ===== /// diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Data/BlackFrame.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Data/BlackFrame.cs new file mode 100644 index 0000000..df3a957 --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Data/BlackFrame.cs @@ -0,0 +1,28 @@ +namespace ConfusedPolarBear.Plugin.IntroSkipper; + +/// +/// A frame of video that partially (or entirely) consists of black pixels. +/// +public class BlackFrame +{ + /// + /// Initializes a new instance of the class. + /// + /// Percentage of the frame that is black. + /// Time this frame appears at. + public BlackFrame(int percent, double time) + { + Percentage = percent; + Time = time; + } + + /// + /// Gets or sets the percentage of the frame that is black. + /// + public int Percentage { get; set; } + + /// + /// Gets or sets the time (in seconds) this frame appeared at. + /// + public double Time { get; set; } +} diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs index 66fc68b..e8ba464 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs @@ -16,16 +16,17 @@ public static class FFmpegWrapper { private static readonly object InvertedIndexCacheLock = new(); - // 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): (?