Refactor ChapterAnalyzer: Unify intro and credits detection loop (#204)

This commit is contained in:
rlauuzo 2024-06-26 17:31:18 +02:00 committed by GitHub
parent bcd302045b
commit b9b9e88765
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -89,115 +89,39 @@ public class ChapterAnalyzer : IMediaFileAnalyzer
string expression, string expression,
AnalysisMode mode) AnalysisMode mode)
{ {
Intro? matchingChapter = null;
var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
var (minDuration, maxDuration) = mode switch
{
AnalysisMode.Introduction => (config.MinimumIntroDuration, config.MaximumIntroDuration),
_ => (config.MinimumCreditsDuration, config.MaximumCreditsDuration)
};
if (chapters.Count == 0) if (chapters.Count == 0)
{ {
return null; return null;
} }
if (mode == AnalysisMode.Credits) var config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
{ var count = chapters.Count;
// Since the ending credits chapter may be the last chapter in the file, append a virtual var reversed = mode != AnalysisMode.Introduction;
// chapter at the very end of the file. var (minDuration, maxDuration) = !reversed
chapters.Add(new() ? (config.MinimumIntroDuration, config.MaximumIntroDuration)
{ : (config.MinimumCreditsDuration, config.MaximumCreditsDuration);
StartPositionTicks = TimeSpan.FromSeconds(episode.Duration).Ticks
});
// Check all chapters in reverse order, skipping the virtual chapter
for (int i = chapters.Count - 2; i > 0; i--)
{
var current = chapters[i];
var previous = chapters[i - 1];
if (string.IsNullOrWhiteSpace(current.Name))
{
continue;
}
var currentRange = new TimeRange(
TimeSpan.FromTicks(current.StartPositionTicks).TotalSeconds,
TimeSpan.FromTicks(chapters[i + 1].StartPositionTicks).TotalSeconds);
var baseMessage = string.Format(
CultureInfo.InvariantCulture,
"{0}: Chapter \"{1}\" ({2} - {3})",
episode.Path,
current.Name,
currentRange.Start,
currentRange.End);
if (currentRange.Duration < minDuration || currentRange.Duration > maxDuration)
{
_logger.LogTrace("{Base}: ignoring (invalid duration)", baseMessage);
continue;
}
// Regex.IsMatch() is used here in order to allow the runtime to cache the compiled regex
// between function invocations.
var match = Regex.IsMatch(
current.Name,
expression,
RegexOptions.None,
TimeSpan.FromSeconds(1));
if (!match)
{
_logger.LogTrace("{Base}: ignoring (does not match regular expression)", baseMessage);
continue;
}
if (!string.IsNullOrWhiteSpace(previous.Name))
{
// Check for possibility of overlapping keywords
var overlap = Regex.IsMatch(
previous.Name,
expression,
RegexOptions.None,
TimeSpan.FromSeconds(1));
if (overlap)
{
continue;
}
}
matchingChapter = new(episode.EpisodeId, currentRange);
_logger.LogTrace("{Base}: okay", baseMessage);
break;
}
}
else
{
// Check all chapters // Check all chapters
for (int i = 0; i < chapters.Count - 1; i++) for (int i = reversed ? count - 1 : 0; reversed ? i >= 0 : i < count; i += reversed ? -1 : 1)
{ {
var current = chapters[i]; var chapter = chapters[i];
var next = chapters[i + 1]; var next = chapters.ElementAtOrDefault(i + 1) ??
new ChapterInfo { StartPositionTicks = TimeSpan.FromSeconds(episode.Duration).Ticks }; // Since the ending credits chapter may be the last chapter in the file, append a virtual chapter.
if (string.IsNullOrWhiteSpace(current.Name)) if (string.IsNullOrWhiteSpace(chapter.Name))
{ {
continue; continue;
} }
var currentRange = new TimeRange( var currentRange = new TimeRange(
TimeSpan.FromTicks(current.StartPositionTicks).TotalSeconds, TimeSpan.FromTicks(chapter.StartPositionTicks).TotalSeconds,
TimeSpan.FromTicks(next.StartPositionTicks).TotalSeconds); TimeSpan.FromTicks(next.StartPositionTicks).TotalSeconds);
var baseMessage = string.Format( var baseMessage = string.Format(
CultureInfo.InvariantCulture, CultureInfo.InvariantCulture,
"{0}: Chapter \"{1}\" ({2} - {3})", "{0}: Chapter \"{1}\" ({2} - {3})",
episode.Path, episode.Path,
current.Name, chapter.Name,
currentRange.Start, currentRange.Start,
currentRange.End); currentRange.End);
@ -210,7 +134,7 @@ public class ChapterAnalyzer : IMediaFileAnalyzer
// Regex.IsMatch() is used here in order to allow the runtime to cache the compiled regex // Regex.IsMatch() is used here in order to allow the runtime to cache the compiled regex
// between function invocations. // between function invocations.
var match = Regex.IsMatch( var match = Regex.IsMatch(
current.Name, chapter.Name,
expression, expression,
RegexOptions.None, RegexOptions.None,
TimeSpan.FromSeconds(1)); TimeSpan.FromSeconds(1));
@ -221,27 +145,28 @@ public class ChapterAnalyzer : IMediaFileAnalyzer
continue; continue;
} }
if (!string.IsNullOrWhiteSpace(next.Name)) // Check if the next (or previous for Credits) chapter also matches
var adjacentChapter = reversed ? chapters.ElementAtOrDefault(i - 1) : next;
if (adjacentChapter != null && !string.IsNullOrWhiteSpace(adjacentChapter.Name))
{ {
// Check for possibility of overlapping keywords // Check for possibility of overlapping keywords
var overlap = Regex.IsMatch( var overlap = Regex.IsMatch(
next.Name, adjacentChapter.Name,
expression, expression,
RegexOptions.None, RegexOptions.None,
TimeSpan.FromSeconds(1)); TimeSpan.FromSeconds(1));
if (overlap) if (overlap)
{ {
_logger.LogTrace("{Base}: ignoring (adjacent chapter also matches)", baseMessage);
continue; continue;
} }
} }
matchingChapter = new(episode.EpisodeId, currentRange);
_logger.LogTrace("{Base}: okay", baseMessage); _logger.LogTrace("{Base}: okay", baseMessage);
break; return new Intro(episode.EpisodeId, currentRange);
}
} }
return matchingChapter; return null;
} }
} }