adaptive black frame search range (#132)

This commit is contained in:
rlauuzo 2024-05-01 11:13:13 +02:00 committed by TwistedUmbrellaX
parent 8053ed6c04
commit fd3cf0c075
2 changed files with 81 additions and 9 deletions

View File

@ -37,7 +37,7 @@ public class TestBlackFrames
var episode = queueFile("credits.mp4"); var episode = queueFile("credits.mp4");
episode.Duration = (int)new TimeSpan(0, 5, 30).TotalSeconds; episode.Duration = (int)new TimeSpan(0, 5, 30).TotalSeconds;
var result = analyzer.AnalyzeMediaFile(episode, AnalysisMode.Credits, 85); var result = analyzer.AnalyzeMediaFile(episode, 240, 30, 85);
Assert.NotNull(result); Assert.NotNull(result);
Assert.InRange(result.IntroStart, 300 - range, 300 + range); Assert.InRange(result.IntroStart, 300 - range, 300 + range);
} }

View File

@ -17,12 +17,23 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
private readonly ILogger<BlackFrameAnalyzer> _logger; private readonly ILogger<BlackFrameAnalyzer> _logger;
private int minimumCreditsDuration;
private int maximumCreditsDuration;
private int blackFrameMinimumPercentage;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="BlackFrameAnalyzer"/> class. /// Initializes a new instance of the <see cref="BlackFrameAnalyzer"/> class.
/// </summary> /// </summary>
/// <param name="logger">Logger.</param> /// <param name="logger">Logger.</param>
public BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) public BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger)
{ {
var config = Plugin.Instance?.Configuration ?? new Configuration.PluginConfiguration();
minimumCreditsDuration = config.MinimumCreditsDuration;
maximumCreditsDuration = 2 * config.MaximumCreditsDuration;
blackFrameMinimumPercentage = config.BlackFrameMinimumPercentage;
_logger = logger; _logger = logger;
} }
@ -39,6 +50,12 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
var creditTimes = new Dictionary<Guid, Intro>(); var creditTimes = new Dictionary<Guid, Intro>();
bool isFirstEpisode = true;
double searchStart = minimumCreditsDuration;
var searchDistance = 2 * minimumCreditsDuration;
foreach (var episode in analysisQueue) foreach (var episode in analysisQueue)
{ {
if (cancellationToken.IsCancellationRequested) if (cancellationToken.IsCancellationRequested)
@ -46,16 +63,54 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
break; break;
} }
// Pre-check to find reasonable starting point.
if (isFirstEpisode)
{
var scanTime = episode.Duration - searchStart;
var tr = new TimeRange(scanTime - 0.5, scanTime); // Short search range since accuracy isn't important here.
var frames = FFmpegWrapper.DetectBlackFrames(episode, tr, blackFrameMinimumPercentage);
while (frames.Length > 0) // While black frames are found increase searchStart
{
searchStart += searchDistance;
scanTime = episode.Duration - searchStart;
tr = new TimeRange(scanTime - 0.5, scanTime);
frames = FFmpegWrapper.DetectBlackFrames(episode, tr, blackFrameMinimumPercentage);
if (searchStart > maximumCreditsDuration)
{
searchStart = maximumCreditsDuration;
break;
}
}
if (searchStart == minimumCreditsDuration) // Skip if no black frames were found
{
continue;
}
isFirstEpisode = false;
}
var intro = AnalyzeMediaFile( var intro = AnalyzeMediaFile(
episode, episode,
mode, searchStart,
Plugin.Instance!.Configuration.BlackFrameMinimumPercentage); searchDistance,
blackFrameMinimumPercentage);
if (intro is null) if (intro is null)
{ {
// If no credits were found, reset the first-episode search logic for the next episode in the sequence.
searchStart = minimumCreditsDuration;
isFirstEpisode = true;
continue; continue;
} }
searchStart = episode.Duration - intro.IntroStart + (0.5 * searchDistance);
creditTimes[episode.EpisodeId] = intro; creditTimes[episode.EpisodeId] = intro;
} }
@ -71,16 +126,17 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
/// Analyzes an individual media file. Only public because of unit tests. /// Analyzes an individual media file. Only public because of unit tests.
/// </summary> /// </summary>
/// <param name="episode">Media file to analyze.</param> /// <param name="episode">Media file to analyze.</param>
/// <param name="mode">Analysis mode.</param> /// <param name="searchStart">Search Start Piont.</param>
/// <param name="searchDistance">Search Distance.</param>
/// <param name="minimum">Percentage of the frame that must be black.</param> /// <param name="minimum">Percentage of the frame that must be black.</param>
/// <returns>Credits timestamp.</returns> /// <returns>Credits timestamp.</returns>
public Intro? AnalyzeMediaFile(QueuedEpisode episode, AnalysisMode mode, int minimum) public Intro? AnalyzeMediaFile(QueuedEpisode episode, double searchStart, int searchDistance, int minimum)
{ {
var config = Plugin.Instance?.Configuration ?? new Configuration.PluginConfiguration();
// Start by analyzing the last N minutes of the file. // Start by analyzing the last N minutes of the file.
var start = TimeSpan.FromSeconds(config.MaximumCreditsDuration); var upperLimit = searchStart;
var end = TimeSpan.FromSeconds(config.MinimumCreditsDuration); var lowerLimit = searchStart - searchDistance;
var start = TimeSpan.FromSeconds(upperLimit);
var end = TimeSpan.FromSeconds(lowerLimit);
var firstFrameTime = 0.0; var firstFrameTime = 0.0;
// Continue bisecting the end of the file until the range that contains the first black // Continue bisecting the end of the file until the range that contains the first black
@ -112,12 +168,28 @@ public class BlackFrameAnalyzer : IMediaFileAnalyzer
{ {
// Since no black frames were found, slide the range closer to the end // Since no black frames were found, slide the range closer to the end
start = midpoint; start = midpoint;
if (midpoint - TimeSpan.FromSeconds(lowerLimit) < _maximumError)
{
lowerLimit = Math.Max(lowerLimit - (0.5 * searchDistance), minimumCreditsDuration);
// Reset end for a new search with the increased duration
end = TimeSpan.FromSeconds(lowerLimit);
}
} }
else else
{ {
// Some black frames were found, slide the range closer to the start // Some black frames were found, slide the range closer to the start
end = midpoint; end = midpoint;
firstFrameTime = frames[0].Time + scanTime; firstFrameTime = frames[0].Time + scanTime;
if (TimeSpan.FromSeconds(upperLimit) - midpoint < _maximumError)
{
upperLimit = Math.Min(upperLimit + (0.5 * searchDistance), maximumCreditsDuration);
// Reset start for a new search with the increased duration
start = TimeSpan.FromSeconds(upperLimit);
}
} }
} }