Expose algorithm internal settings
This commit is contained in:
parent
6dc3a5fa41
commit
cf99dde0e2
@ -62,17 +62,6 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int MinimumIntroDuration { get; set; } = 15;
|
public int MinimumIntroDuration { get; set; } = 15;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 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.
|
|
||||||
/// </summary>
|
|
||||||
public int SilenceDetectionMaximumNoise { get; set; } = -50;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Gets or sets the minimum duration of audio (in seconds) that is considered silent.
|
|
||||||
/// </summary>
|
|
||||||
public double SilenceDetectionMinimumDuration { get; set; } = 0.50;
|
|
||||||
|
|
||||||
// ===== Playback settings =====
|
// ===== Playback settings =====
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -93,5 +82,34 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the amount of intro to play (in seconds).
|
/// Gets or sets the amount of intro to play (in seconds).
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int SecondsOfIntroToPlay { get; set; } = 2;
|
public int SecondsOfIntroToPlay { get; set; } = 3;
|
||||||
|
|
||||||
|
// ===== Internal algorithm settings =====
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of bits (out of 32 total) that can be different between two Chromaprint points before they are considered dissimilar.
|
||||||
|
/// Defaults to 6 (81% similar).
|
||||||
|
/// </summary>
|
||||||
|
public int MaximumFingerprintPointDifferences { get; set; } = 6;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the maximum number of seconds that can pass between two similar fingerprint points before a new time range is started.
|
||||||
|
/// </summary>
|
||||||
|
public double MaximumTimeSkip { get; set; } = 3.5;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the amount to shift inverted indexes by.
|
||||||
|
/// </summary>
|
||||||
|
public int InvertedIndexShift { get; set; } = 2;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 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.
|
||||||
|
/// </summary>
|
||||||
|
public int SilenceDetectionMaximumNoise { get; set; } = -50;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the minimum duration of audio (in seconds) that is considered silent.
|
||||||
|
/// </summary>
|
||||||
|
public double SilenceDetectionMinimumDuration { get; set; } = 0.33;
|
||||||
}
|
}
|
||||||
|
@ -154,6 +154,32 @@
|
|||||||
Increasing either of these settings will cause episode analysis to take much longer.
|
Increasing either of these settings will cause episode analysis to take much longer.
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details id="silence">
|
||||||
|
<summary>Silence detection options</summary>
|
||||||
|
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="SilenceDetectionMaximumNoise">
|
||||||
|
Noise tolerance
|
||||||
|
</label>
|
||||||
|
<input id="SilenceDetectionMaximumNoise" type="number" is="emby-input" min="-90"
|
||||||
|
max="0" />
|
||||||
|
<div class="fieldDescription">
|
||||||
|
Noise tolerance in negative decibels.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="SilenceDetectionMinimumDuration">
|
||||||
|
Minimum silence duration
|
||||||
|
</label>
|
||||||
|
<input id="SilenceDetectionMinimumDuration" type="number" is="emby-input" min="0"
|
||||||
|
step="0.01" />
|
||||||
|
<div class="fieldDescription">
|
||||||
|
Minimum silence duration in seconds before adjusting introduction end time.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<fieldset class="verticalSection-extrabottompadding">
|
<fieldset class="verticalSection-extrabottompadding">
|
||||||
@ -190,6 +216,16 @@
|
|||||||
Seconds after the introduction starts to hide the skip prompt at.
|
Seconds after the introduction starts to hide the skip prompt at.
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="inputContainer">
|
||||||
|
<label class="inputLabel inputLabelUnfocused" for="SecondsOfIntroToPlay">
|
||||||
|
Seconds of intro to play
|
||||||
|
</label>
|
||||||
|
<input id="SecondsOfIntroToPlay" type="number" is="emby-input" min="0" />
|
||||||
|
<div class="fieldDescription">
|
||||||
|
Seconds of introduction that should be played. Defaults to 2.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
@ -201,12 +237,6 @@
|
|||||||
<button id="btnEraseTimestamps" is="emby-button" class="raised block emby-button">
|
<button id="btnEraseTimestamps" is="emby-button" class="raised block emby-button">
|
||||||
<span>Erase introduction timestamps</span>
|
<span>Erase introduction timestamps</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<p>
|
|
||||||
Erasing introduction timestamps is only necessary after upgrading the plugin if specifically
|
|
||||||
requested to do so in the plugin's changelog. After the timestamps are erased, run the
|
|
||||||
Analyze episodes scheduled task to re-analyze all media on the server.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
@ -337,14 +367,20 @@
|
|||||||
|
|
||||||
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
|
// all plugin configuration fields that can be get or set with .value (i.e. strings or numbers).
|
||||||
var configurationFields = [
|
var configurationFields = [
|
||||||
|
// analysis
|
||||||
"MaxParallelism",
|
"MaxParallelism",
|
||||||
"SelectedLibraries",
|
"SelectedLibraries",
|
||||||
"AnalysisPercent",
|
"AnalysisPercent",
|
||||||
"AnalysisLengthLimit",
|
"AnalysisLengthLimit",
|
||||||
"MinimumIntroDuration",
|
"MinimumIntroDuration",
|
||||||
"EdlAction",
|
"EdlAction",
|
||||||
|
// playback
|
||||||
"ShowPromptAdjustment",
|
"ShowPromptAdjustment",
|
||||||
"HidePromptAdjustment"
|
"HidePromptAdjustment",
|
||||||
|
"SecondsOfIntroToPlay",
|
||||||
|
// internals
|
||||||
|
"SilenceDetectionMaximumNoise",
|
||||||
|
"SilenceDetectionMinimumDuration",
|
||||||
]
|
]
|
||||||
|
|
||||||
// visualizer elements
|
// visualizer elements
|
||||||
|
@ -15,22 +15,6 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class AnalyzeEpisodesTask : IScheduledTask
|
public class AnalyzeEpisodesTask : IScheduledTask
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Maximum number of bits (out of 32 total) that can be different between segments before they are considered dissimilar.
|
|
||||||
/// 6 bits means the audio must be at least 81% similar (1 - 6 / 32).
|
|
||||||
/// </summary>
|
|
||||||
private const double MaximumDifferences = 6;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Maximum time (in seconds) permitted between timestamps before they are considered non-contiguous.
|
|
||||||
/// </summary>
|
|
||||||
private const double MaximumDistance = 3.5;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Amount to shift inverted index offsets by.
|
|
||||||
/// </summary>
|
|
||||||
private const int InvertedIndexShift = 2;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Seconds of audio in one fingerprint point. This value is defined by the Chromaprint library and should not be changed.
|
/// Seconds of audio in one fingerprint point. This value is defined by the Chromaprint library and should not be changed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -57,6 +41,14 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private static int minimumIntroDuration = 15;
|
private static int minimumIntroDuration = 15;
|
||||||
|
|
||||||
|
private static int maximumDifferences = 6;
|
||||||
|
|
||||||
|
private static int invertedIndexShift = 2;
|
||||||
|
|
||||||
|
private static double maximumTimeSkip = 3.5;
|
||||||
|
|
||||||
|
private static double silenceDetectionMinimumDuration = 0.33;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="AnalyzeEpisodesTask"/> class.
|
/// Initializes a new instance of the <see cref="AnalyzeEpisodesTask"/> class.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -124,6 +116,13 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
"No episodes to analyze. If you are limiting the list of libraries to analyze, check that all library names have been spelled correctly.");
|
"No episodes to analyze. If you are limiting the list of libraries to analyze, check that all library names have been spelled correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Load analysis settings from configuration
|
||||||
|
var config = Plugin.Instance?.Configuration ?? new Configuration.PluginConfiguration();
|
||||||
|
maximumDifferences = config.MaximumFingerprintPointDifferences;
|
||||||
|
invertedIndexShift = config.InvertedIndexShift;
|
||||||
|
maximumTimeSkip = config.MaximumTimeSkip;
|
||||||
|
silenceDetectionMinimumDuration = config.SilenceDetectionMinimumDuration;
|
||||||
|
|
||||||
// Log EDL settings
|
// Log EDL settings
|
||||||
EdlManager.LogConfiguration();
|
EdlManager.LogConfiguration();
|
||||||
|
|
||||||
@ -448,7 +447,7 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
{
|
{
|
||||||
var originalPoint = kvp.Key;
|
var originalPoint = kvp.Key;
|
||||||
|
|
||||||
for (var i = -1 * InvertedIndexShift; i <= InvertedIndexShift; i++)
|
for (var i = -1 * invertedIndexShift; i <= invertedIndexShift; i++)
|
||||||
{
|
{
|
||||||
var modifiedPoint = (uint)(originalPoint + i);
|
var modifiedPoint = (uint)(originalPoint + i);
|
||||||
|
|
||||||
@ -542,7 +541,7 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
var diff = lhs[lhsPosition] ^ rhs[rhsPosition];
|
var diff = lhs[lhsPosition] ^ rhs[rhsPosition];
|
||||||
|
|
||||||
// If the difference between the samples is small, flag both times as similar.
|
// If the difference between the samples is small, flag both times as similar.
|
||||||
if (CountBits(diff) > MaximumDifferences)
|
if (CountBits(diff) > maximumDifferences)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -559,25 +558,25 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
rhsTimes.Add(double.MaxValue);
|
rhsTimes.Add(double.MaxValue);
|
||||||
|
|
||||||
// Now that both fingerprints have been compared at this shift, see if there's a contiguous time range.
|
// Now that both fingerprints have been compared at this shift, see if there's a contiguous time range.
|
||||||
var lContiguous = TimeRangeHelpers.FindContiguous(lhsTimes.ToArray(), MaximumDistance);
|
var lContiguous = TimeRangeHelpers.FindContiguous(lhsTimes.ToArray(), maximumTimeSkip);
|
||||||
if (lContiguous is null || lContiguous.Duration < minimumIntroDuration)
|
if (lContiguous is null || lContiguous.Duration < minimumIntroDuration)
|
||||||
{
|
{
|
||||||
return (new TimeRange(), new TimeRange());
|
return (new TimeRange(), new TimeRange());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since LHS had a contiguous time range, RHS must have one also.
|
// Since LHS had a contiguous time range, RHS must have one also.
|
||||||
var rContiguous = TimeRangeHelpers.FindContiguous(rhsTimes.ToArray(), MaximumDistance)!;
|
var rContiguous = TimeRangeHelpers.FindContiguous(rhsTimes.ToArray(), maximumTimeSkip)!;
|
||||||
|
|
||||||
// Tweak the end timestamps just a bit to ensure as little content as possible is skipped over.
|
// Tweak the end timestamps just a bit to ensure as little content as possible is skipped over.
|
||||||
if (lContiguous.Duration >= 90)
|
if (lContiguous.Duration >= 90)
|
||||||
{
|
{
|
||||||
lContiguous.End -= 2 * MaximumDistance;
|
lContiguous.End -= 2 * maximumTimeSkip;
|
||||||
rContiguous.End -= 2 * MaximumDistance;
|
rContiguous.End -= 2 * maximumTimeSkip;
|
||||||
}
|
}
|
||||||
else if (lContiguous.Duration >= 30)
|
else if (lContiguous.Duration >= 30)
|
||||||
{
|
{
|
||||||
lContiguous.End -= MaximumDistance;
|
lContiguous.End -= maximumTimeSkip;
|
||||||
rContiguous.End -= MaximumDistance;
|
rContiguous.End -= maximumTimeSkip;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (lContiguous, rContiguous);
|
return (lContiguous, rContiguous);
|
||||||
@ -612,9 +611,8 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since we only want to adjust the end timestamp of the intro, create a new TimeRange
|
// Only adjust the end timestamp of the intro
|
||||||
// that covers the last few seconds.
|
var originalIntroEnd = new TimeRange(originalIntro.IntroEnd - 15, originalIntro.IntroEnd);
|
||||||
var originalIntroEnd = new TimeRange(originalIntro.IntroEnd - 10, originalIntro.IntroEnd);
|
|
||||||
|
|
||||||
_logger.LogTrace(
|
_logger.LogTrace(
|
||||||
"{Name} original intro: {Start} - {End}",
|
"{Name} original intro: {Start} - {End}",
|
||||||
@ -636,8 +634,10 @@ public class AnalyzeEpisodesTask : IScheduledTask
|
|||||||
|
|
||||||
// Ignore any silence that:
|
// Ignore any silence that:
|
||||||
// * doesn't intersect the ending of the intro, or
|
// * doesn't intersect the ending of the intro, or
|
||||||
// * is less than half a second long
|
// * is shorter than the user defined minimum duration
|
||||||
if (!originalIntroEnd.Intersects(currentRange) || currentRange.Duration < 0.5)
|
if (
|
||||||
|
!originalIntroEnd.Intersects(currentRange) ||
|
||||||
|
currentRange.Duration < silenceDetectionMinimumDuration)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user