diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 833d7bf..c3b1fa5 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,7 +39,7 @@ jobs:
- name: Upload artifact
uses: actions/upload-artifact@v4.3.1
with:
- name: intro-skipper-${{ github.sha }}.dll
+ name: ConfusedPolarBear.Plugin.IntroSkipper-${{ env.GIT_HASH }}.dll
path: ConfusedPolarBear.Plugin.IntroSkipper/bin/Debug/net6.0/ConfusedPolarBear.Plugin.IntroSkipper.dll
if-no-files-found: error
diff --git a/.gitignore b/.gitignore
index 6908286..bc18978 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,4 +8,3 @@ docker/dist
# Visual Studio
.vs/
-UpgradeLog*.htm
diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs
index 1474d74..dd2b2cf 100644
--- a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs
+++ b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs
@@ -96,6 +96,11 @@ public class ChapterAnalyzer : IMediaFileAnalyzer
config.MaximumIntroDuration :
config.MaximumEpisodeCreditsDuration;
+ if (chapters.Count == 0)
+ {
+ return null;
+ }
+
if (mode == AnalysisMode.Credits)
{
// Since the ending credits chapter may be the last chapter in the file, append a virtual
@@ -104,66 +109,119 @@ public class ChapterAnalyzer : IMediaFileAnalyzer
{
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 next = chapters[i + 1];
+
+ if (string.IsNullOrWhiteSpace(current.Name))
+ {
+ continue;
+ }
+
+ var currentRange = new TimeRange(
+ TimeSpan.FromTicks(current.StartPositionTicks).TotalSeconds,
+ TimeSpan.FromTicks(next.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;
+ }
+
+ matchingChapter = new(episode.EpisodeId, currentRange);
+ _logger.LogTrace("{Base}: okay", baseMessage);
+ break;
+ }
}
-
- // Check all chapters
- for (int i = 0; i < chapters.Count - 1; i++)
+ else
{
- var current = chapters[i];
- var next = chapters[i + 1];
-
- if (string.IsNullOrWhiteSpace(current.Name))
+ // Check all chapters
+ for (int i = 0; i < chapters.Count - 1; i++)
{
- continue;
+ var current = chapters[i];
+ var next = chapters[i + 1];
+
+ if (string.IsNullOrWhiteSpace(current.Name))
+ {
+ continue;
+ }
+
+ var currentRange = new TimeRange(
+ TimeSpan.FromTicks(current.StartPositionTicks).TotalSeconds,
+ TimeSpan.FromTicks(next.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(next.Name))
+ {
+ // Check for possibility of overlapping keywords
+ var overlap = Regex.IsMatch(
+ next.Name,
+ expression,
+ RegexOptions.None,
+ TimeSpan.FromSeconds(1));
+
+ if (overlap)
+ {
+ continue;
+ }
+ }
+
+ matchingChapter = new(episode.EpisodeId, currentRange);
+ _logger.LogTrace("{Base}: okay", baseMessage);
+ break;
}
-
- var currentRange = new TimeRange(
- TimeSpan.FromTicks(current.StartPositionTicks).TotalSeconds,
- TimeSpan.FromTicks(next.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;
- }
-
- // Check for possibility of overlapping keywords
- var overlap = Regex.IsMatch(
- next.Name,
- expression,
- RegexOptions.None,
- TimeSpan.FromSeconds(1));
-
- if (overlap)
- {
- continue;
- }
-
- matchingChapter = new(episode.EpisodeId, currentRange);
- _logger.LogTrace("{Base}: okay", baseMessage);
- break;
}
return matchingChapter;
diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs
index 76e5896..06384eb 100644
--- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs
+++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs
@@ -103,7 +103,7 @@ public class PluginConfiguration : BasePluginConfiguration
/// Gets or sets the regular expression used to detect ending credit chapters.
///
public string ChapterAnalyzerEndCreditsPattern { get; set; } =
- @"(^|\s)(Credits?|Ending)(\s|$)";
+ @"(^|\s)(Credits?|ED|Ending)(\s|$)";
// ===== Playback settings =====
diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html
index c4bc9a1..67010ea 100644
--- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html
+++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html
@@ -454,33 +454,17 @@
to
+
-
-
-
Interactively compare the audio fingerprints of two episodes.
@@ -526,11 +510,27 @@
-
+
+
+
+
+
+