Merge branch 'custom-analysis'

This commit is contained in:
ConfusedPolarBear 2022-07-08 00:57:12 -05:00
commit 990dd28ed2
5 changed files with 153 additions and 27 deletions

View File

@ -14,6 +14,8 @@ public class PluginConfiguration : BasePluginConfiguration
{
}
// ===== Analysis settings =====
/// <summary>
/// Gets or sets a value indicating whether the episode's fingerprint should be cached to the filesystem.
/// </summary>
@ -29,6 +31,25 @@ public class PluginConfiguration : BasePluginConfiguration
/// </summary>
public string SelectedLibraries { get; set; } = string.Empty;
// ===== Custom analysis settings =====
/// <summary>
/// Gets or sets the percentage of each episode's audio track to analyze.
/// </summary>
public int AnalysisPercent { get; set; } = 25;
/// <summary>
/// Gets or sets the upper limit (in minutes) on the length of each episode's audio track that will be analyzed.
/// </summary>
public int AnalysisLengthLimit { get; set; } = 10;
/// <summary>
/// Gets or sets the minimum length of similar audio that will be considered an introduction.
/// </summary>
public int MinimumIntroDuration { get; set; } = 15;
// ===== Playback settings =====
/// <summary>
/// Gets or sets a value indicating whether introductions should be automatically skipped.
/// </summary>

View File

@ -48,6 +48,59 @@
blank, all libraries on the server containing television episodes will be analyzed.
</div>
</div>
<details>
<summary>Modify introduction requirements</summary>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxParallelism">
Percent of audio to analyze
</label>
<input id="AnalysisPercent" type="number" is="emby-input" min="1" max="90" />
<div class="fieldDescription">
Analysis will be limited to this percentage of each episode's audio. For example, a
value of 25 (the default) will limit analysis to the first quarter of each episode.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MaxParallelism">
Maximum runtime of audio to analyze (in minutes)
</label>
<input id="AnalysisLengthLimit" type="number" is="emby-input" min="1" />
<div class="fieldDescription">
Analysis will be limited to this amount of each episode's audio. For example, a
value of 10 (the default) will limit analysis to the first 10 minutes of each
episode.
</div>
</div>
<div class="inputContainer">
<label class="inputLabel inputLabelUnfocused" for="MinimumDuration">
Minimum introduction duration (in seconds)
</label>
<input id="MinimumDuration" type="number" is="emby-input" min="1" />
<div class="fieldDescription">
Similar sounding audio which is shorter than this duration will not be considered an
introduction.
</div>
</div>
<p>
The amount of each episode's audio that will be analyzed is determined using both
the percentage of audio and maximum runtime of audio to analyze. The minimum of
(episode duration * percent, maximum runtime) is the amount of audio that will
be analyzed.
</p>
<p>
If the audio percentage or maximum runtime settings are modified, the cached
fingerprints and introduction timestamps for each season you want to analyze with the
modified settings <strong>will have to be deleted.</strong>
Increasing either of these settings will cause episode analysis to take much longer.
</p>
</details>
</fieldset>
<fieldset class="verticalSection-extrabottompadding">
@ -267,8 +320,7 @@
let value = stats[f];
// If this statistic is a measure of CPU time, divide by 1,000 to turn milliseconds into seconds.
if (name.includes("time"))
{
if (name.includes("time")) {
value = Math.round(value / 1000);
}
@ -563,6 +615,10 @@
document.querySelector('#MaxParallelism').value = config.MaxParallelism;
document.querySelector('#SelectedLibraries').value = config.SelectedLibraries;
document.querySelector('#AnalysisPercent').value = config.AnalysisPercent;
document.querySelector('#AnalysisLengthLimit').value = config.AnalysisLengthLimit;
document.querySelector('#MinimumDuration').value = config.MinimumIntroDuration;
document.querySelector('#CacheFingerprints').checked = config.CacheFingerprints;
document.querySelector('#ShowPromptAdjustment').value = config.ShowPromptAdjustment;
document.querySelector('#HidePromptAdjustment').value = config.HidePromptAdjustment;
@ -579,6 +635,10 @@
config.MaxParallelism = document.querySelector('#MaxParallelism').value;
config.SelectedLibraries = document.querySelector('#SelectedLibraries').value;
config.AnalysisPercent = document.querySelector('#AnalysisPercent').value;
config.AnalysisLengthLimit = document.querySelector('#AnalysisLengthLimit').value;
config.MinimumIntroDuration = document.querySelector('#MinimumDuration').value;
config.CacheFingerprints = document.querySelector('#CacheFingerprints').checked;
config.ShowPromptAdjustment = document.querySelector("#ShowPromptAdjustment").value;
config.HidePromptAdjustment = document.querySelector("#HidePromptAdjustment").value;

View File

@ -18,6 +18,9 @@ public class QueueManager
private ILibraryManager _libraryManager;
private ILogger<QueueManager> _logger;
private double analysisPercent;
private IList<string> selectedLibraries;
/// <summary>
/// Initializes a new instance of the <see cref="QueueManager"/> class.
/// </summary>
@ -27,6 +30,8 @@ public class QueueManager
{
_logger = logger;
_libraryManager = libraryManager;
selectedLibraries = new List<string>();
}
/// <summary>
@ -44,19 +49,7 @@ public class QueueManager
Plugin.Instance!.AnalysisQueue.Clear();
Plugin.Instance!.TotalQueued = 0;
// Get the list of library names which have been selected for analysis, ignoring whitespace and empty entries.
var selected = Plugin.Instance!.Configuration.SelectedLibraries
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
if (selected.Count > 0)
{
_logger.LogInformation("Limiting analysis to the following libraries: {Selected}", selected);
}
else
{
_logger.LogDebug("Not limiting analysis by library name");
}
LoadAnalysisSettings();
// For all selected TV show libraries, enqueue all contained items.
foreach (var folder in _libraryManager.GetVirtualFolders())
@ -67,7 +60,7 @@ public class QueueManager
}
// If libraries have been selected for analysis, ensure this library was selected.
if (selected.Count > 0 && !selected.Contains(folder.Name))
if (selectedLibraries.Count > 0 && !selectedLibraries.Contains(folder.Name))
{
_logger.LogDebug("Not analyzing library \"{Name}\"", folder.Name);
continue;
@ -89,6 +82,43 @@ public class QueueManager
}
}
/// <summary>
/// Loads the list of libraries which have been selected for analysis and the minimum intro duration.
/// Settings which have been modified from the defaults are logged.
/// </summary>
private void LoadAnalysisSettings()
{
var config = Plugin.Instance!.Configuration;
// Store the analysis percent
analysisPercent = Convert.ToDouble(config.AnalysisPercent) / 100;
// Get the list of library names which have been selected for analysis, ignoring whitespace and empty entries.
selectedLibraries = config.SelectedLibraries
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
.ToList();
// If any libraries have been selected for analysis, log their names.
if (selectedLibraries.Count > 0)
{
_logger.LogInformation("Limiting analysis to the following libraries: {Selected}", selectedLibraries);
}
else
{
_logger.LogDebug("Not limiting analysis by library name");
}
// If analysis settings have been changed from the default, log the modified settings.
if (config.AnalysisLengthLimit != 10 || config.AnalysisPercent != 25 || config.MinimumIntroDuration != 15)
{
_logger.LogInformation(
"Analysis settings have been changed to: {Percent}%/{Minutes}m and a minimum of {Minimum}s",
config.AnalysisPercent,
config.AnalysisLengthLimit,
config.MinimumIntroDuration);
}
}
private void QueueLibraryContents(string rawId)
{
_logger.LogDebug("Constructing anonymous internal query");
@ -147,14 +177,25 @@ public class QueueManager
return;
}
// Only fingerprint up to 25% of the episode and at most 10 minutes.
var queue = Plugin.Instance.AnalysisQueue;
// Allocate a new list for each new season
if (!queue.ContainsKey(episode.SeasonId))
{
Plugin.Instance.AnalysisQueue[episode.SeasonId] = new List<QueuedEpisode>();
}
var config = Plugin.Instance!.Configuration;
// Limit analysis to the first X% of the episode and at most Y minutes.
// X and Y default to 25% and 10 minutes.
var duration = TimeSpan.FromTicks(episode.RunTimeTicks ?? 0).TotalSeconds;
if (duration >= 5 * 60)
{
duration /= 4;
duration *= analysisPercent;
}
duration = Math.Min(duration, 10 * 60);
duration = Math.Min(duration, 60 * config.AnalysisLengthLimit);
// Allocate a new list for each new season
Plugin.Instance!.AnalysisQueue.TryAdd(episode.SeasonId, new List<QueuedEpisode>());

View File

@ -15,11 +15,6 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
/// </summary>
public class FingerprinterTask : IScheduledTask
{
/// <summary>
/// Minimum time (in seconds) for a contiguous time range to be considered an introduction.
/// </summary>
private const int MinimumIntroDuration = 15;
/// <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).
@ -73,6 +68,11 @@ public class FingerprinterTask : IScheduledTask
/// </summary>
private AnalysisStatistics analysisStatistics = new AnalysisStatistics();
/// <summary>
/// Minimum duration of similar audio that will be considered an introduction.
/// </summary>
private static int minimumIntroDuration = 15;
/// <summary>
/// Initializes a new instance of the <see cref="FingerprinterTask"/> class.
/// </summary>
@ -151,6 +151,8 @@ public class FingerprinterTask : IScheduledTask
analysisStatistics = new AnalysisStatistics();
analysisStatistics.TotalQueuedEpisodes = Plugin.Instance!.TotalQueued;
minimumIntroDuration = Plugin.Instance!.Configuration.MinimumIntroDuration;
Parallel.ForEach(queue, options, (season) =>
{
var workerStart = DateTime.Now;
@ -635,7 +637,7 @@ public class FingerprinterTask : IScheduledTask
// 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);
if (lContiguous is null || lContiguous.Duration < MinimumIntroDuration)
if (lContiguous is null || lContiguous.Duration < minimumIntroDuration)
{
return (new TimeRange(), new TimeRange());
}
@ -701,7 +703,7 @@ public class FingerprinterTask : IScheduledTask
var id = episode.EpisodeId;
var duration = GetIntroDuration(id);
if (duration < MinimumIntroDuration)
if (duration < minimumIntroDuration)
{
continue;
}

View File

@ -23,7 +23,9 @@ Plugin versions v0.1.0 and older require `fpcalc` to be installed.
Show introductions will only be detected if they are:
* Located within the first 25% of an episode, or the first 10 minutes, whichever is smaller
* At least 20 seconds long
* At least 15 seconds long
Both of these requirements can be customized as needed.
## Step 1: Optional: use the modified web interface
While this plugin is fully compatible with an unmodified version of Jellyfin 10.8.0, using a modified web interface allows you to click a button to skip intros. If you skip this step and do not use the modified web interface, you will have to enable the "Automatically skip intros" option in the plugin settings.