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
+

-

- -
- - -
- - -
-
-

Fingerprint Visualizer

Interactively compare the audio fingerprints of two episodes.
@@ -526,11 +510,27 @@

- +
+
+
+ + +


+ + +
+ + @@ -602,6 +602,7 @@ var txtSuggested = document.querySelector("span#suggestedShifts"); var btnSeasonEraseTimestamps = document.querySelector("button#btnEraseSeasonTimestamps"); var timestampError = document.querySelector("textarea#timestampError"); + var timestampEditor = document.querySelector("#timestampEditor"); var btnUpdateTimestamps = document.querySelector("button#btnUpdateTimestamps"); var timeContainer = document.querySelector("span#timestampContainer"); @@ -613,8 +614,8 @@ async function autoSkipChanged() { if (autoSkip.checked) { - skipFirstEpisode.style.display = 'block'; - autoSkipNotificationText.style.display = 'block'; + skipFirstEpisode.style.display = 'unset'; + autoSkipNotificationText.style.display = 'unset'; } else { skipFirstEpisode.style.display = 'none'; autoSkipNotificationText.style.display = 'none'; @@ -633,8 +634,8 @@ showAdjustment.style.display = 'none'; hideAdjustment.style.display = 'none'; } else { - showAdjustment.style.display = 'block'; - hideAdjustment.style.display = 'block'; + showAdjustment.style.display = 'unset'; + hideAdjustment.style.display = 'unset'; } } @@ -716,7 +717,7 @@ clearSelect(selectEpisode1); clearSelect(selectEpisode2); - btnSeasonEraseTimestamps.style.display = "block"; + btnSeasonEraseTimestamps.style.display = "unset"; let i = 1; for (let episode of episodes) { @@ -742,20 +743,29 @@ Dashboard.showLoadingMsg(); timestampError.value = ""; + canvas.style.display = "none"; lhs = await getJson("Intros/Episode/" + selectEpisode1.value + "/Chromaprint"); rhs = await getJson("Intros/Episode/" + selectEpisode2.value + "/Chromaprint"); - if (lhs === undefined) { + if (lhs === null) { + timestampError.value += "Error: " + selectEpisode1.value + " fingerprints missing!\n"; + } + else if (lhs === undefined) { timestampError.value += "Error: " + selectEpisode1.value + " fingerprints failed!\n"; } + + if (rhs === null) { + timestampError.value += "Error: " + selectEpisode2.value + " fingerprints missing!\n"; + } if (rhs === undefined) { timestampError.value += "Error: " + selectEpisode2.value + " fingerprints failed!"; } + if (timestampError.value == "") { timestampError.style.display = "none"; } else { - timestampError.style.display = "block"; + timestampError.style.display = "unset"; } Dashboard.hideLoadingMsg(); @@ -785,7 +795,7 @@ } // Update the editor for the first and second episodes - document.querySelector("#timestampEditor").style.display = "unset"; + timestampEditor.style.display = "unset"; document.querySelector("#editLeftEpisodeTitle").textContent = leftEpisode.text; document.querySelector("#editLeftEpisodeStart").value = Math.round(leftEpisodeIntro.IntroStart); document.querySelector("#editLeftEpisodeEnd").value = Math.round(leftEpisodeIntro.IntroEnd); @@ -805,6 +815,9 @@ function clearSelect(select) { timestampError.value = ""; timestampError.style.display = "none"; + timestampEditor.style.display = "none"; + timeContainer.style.display = "none"; + canvas.style.display = "none"; let i, L = select.options.length - 1; for (i = L; i >= 0; i--) { select.remove(i); @@ -842,12 +855,16 @@ switch (e.key) { case "ArrowDown": - // if the control key is pressed, shift LHS by 10s. Otherwise, shift by 1. - offsetDelta = e.ctrlKey ? 10 / 0.128 : 1; + if (timestampError.value != "") { + // if the control key is pressed, shift LHS by 10s. Otherwise, shift by 1. + offsetDelta = e.ctrlKey ? 10 / 0.128 : 1; + } break; case "ArrowUp": - offsetDelta = e.ctrlKey ? -10 / 0.128 : -1; + if (timestampError.value != "") { + offsetDelta = e.ctrlKey ? -10 / 0.128 : -1; + } break; case "ArrowRight": diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/visualizer.js b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/visualizer.js index 75b2b5a..b4c7401 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/visualizer.js +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/visualizer.js @@ -169,6 +169,8 @@ function paintFingerprintDiff(canvas, fp1, fp2, offset) { return; } + canvas.style.display = "unset"; + let leftOffset = 0, rightOffset = 0; if (offset < 0) { leftOffset -= offset; diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ConfusedPolarBear.Plugin.IntroSkipper.csproj b/ConfusedPolarBear.Plugin.IntroSkipper/ConfusedPolarBear.Plugin.IntroSkipper.csproj index 254bf30..86ac372 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/ConfusedPolarBear.Plugin.IntroSkipper.csproj +++ b/ConfusedPolarBear.Plugin.IntroSkipper/ConfusedPolarBear.Plugin.IntroSkipper.csproj @@ -2,8 +2,8 @@ net6.0 ConfusedPolarBear.Plugin.IntroSkipper - 0.1.16.0 - 0.1.16.0 + 0.1.16.1 + 0.1.16.1 true true enable diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs index 95779e6..26a9f75 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/FFmpegWrapper.cs @@ -605,6 +605,25 @@ public static class FFmpegWrapper Encoding.UTF8).ConfigureAwait(false); } + /// + /// Remove a cached episode fingerprint from disk. + /// + /// Episode to remove from cache. + /// Analysis mode. + public static void DeleteEpisodeCache(string episodeId, AnalysisMode mode) + { + var cachePath = Path.Join( + Plugin.Instance!.FingerprintCachePath, + episodeId); + + if (mode == AnalysisMode.Credits) + { + cachePath += "-credits"; + } + + File.Delete(cachePath); + } + /// /// Determines the path an episode should be cached at. /// This function was created before the unified caching mechanism was introduced (in v0.1.7). diff --git a/manifest.json b/manifest.json index 6a3e2ff..c131344 100644 --- a/manifest.json +++ b/manifest.json @@ -8,6 +8,14 @@ "category": "General", "imageUrl": "https://raw.githubusercontent.com/jumoog/intro-skipper/master/images/logo.png", "versions": [ + { + "version": "0.1.16.1", + "changelog": "- See the full changelog at [GitHub](https://github.com/jumoog/intro-skipper/blob/master/CHANGELOG.md)\n", + "targetAbi": "10.8.4.0", + "sourceUrl": "https://github.com/jumoog/intro-skipper/releases/download/v0.1.16.1/intro-skipper-v0.1.16.1.zip", + "checksum": "d8e5370f974bd5624206f87b3fed05bb", + "timestamp": "2024-03-06T10:17:25Z" + }, { "version": "0.1.16.0", "changelog": "- See the full changelog at [GitHub](https://github.com/jumoog/intro-skipper/blob/master/CHANGELOG.md)\n",