Refactor ChapterAnalyzer: Unify intro and credits detection loop (#204)
This commit is contained in:
parent
bcd302045b
commit
b9b9e88765
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user