From bcacfc85175693457cfa7a7066da36856b73a252 Mon Sep 17 00:00:00 2001 From: TwistedUmbrellaX Date: Mon, 4 Mar 2024 11:51:55 -0500 Subject: [PATCH 1/6] Season 0 only recognizes one folder --- .../Configuration/configPage.html | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html index c60bdc7..9ae2000 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html @@ -30,11 +30,14 @@
- Analyze show extras (specials). + If checked, season 0 (specials / extras) will be included in analysis. +
+ Note: Shows containing both a specials and extra folder will identify extras as season 0 + and ignore specials, regardless of this setting.
@@ -727,10 +730,10 @@ rhs = await getJson("Intros/Episode/" + selectEpisode2.value + "/Chromaprint"); if (lhs === undefined) { - timestampError.value += "Error: " + selectEpisode1.value + " fingerprints missing!\n"; + timestampError.value += "Error: " + selectEpisode1.value + " fingerprints failed!\n"; } if (rhs === undefined) { - timestampError.value += "Error: " + selectEpisode2.value + " fingerprints missing!"; + timestampError.value += "Error: " + selectEpisode2.value + " fingerprints failed!"; } if (timestampError.value == "") { timestampError.style.display = "none"; From d8ebcfe772aeb9a30499b233f80486a3606ee458 Mon Sep 17 00:00:00 2001 From: TwistedUmbrellaX Date: Mon, 4 Mar 2024 17:50:12 -0500 Subject: [PATCH 2/6] Add option to bypass Chromaprint --- .../Analyzers/ChapterAnalyzer.cs | 17 +++++++++++++---- .../Configuration/PluginConfiguration.cs | 15 ++++++++++----- .../Configuration/configPage.html | 15 +++++++++++++++ .../ScheduledTasks/BaseItemAnalyzerTask.cs | 7 +++++-- 4 files changed, 43 insertions(+), 11 deletions(-) diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs index 763d461..1474d74 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChapterAnalyzer.cs @@ -149,12 +149,21 @@ public class ChapterAnalyzer : IMediaFileAnalyzer 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); - if (i > 0) - { - break; - } + break; } return matchingChapter; diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs index 0d4407e..76e5896 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/PluginConfiguration.cs @@ -17,11 +17,6 @@ public class PluginConfiguration : BasePluginConfiguration // ===== Analysis settings ===== - /// - /// Gets or sets a value indicating whether the episode's fingerprint should be cached to the filesystem. - /// - public bool CacheFingerprints { get; set; } = true; - /// /// Gets or sets the max degree of parallelism used when analyzing episodes. /// @@ -37,6 +32,16 @@ public class PluginConfiguration : BasePluginConfiguration /// public bool AnalyzeSeasonZero { get; set; } = false; + /// + /// Gets or sets a value indicating whether the episode's fingerprint should be cached to the filesystem. + /// + public bool CacheFingerprints { get; set; } = true; + + /// + /// Gets or sets a value indicating whether analysis will use Chromaprint to determine fingerprints. + /// + public bool UseChromaprint { get; set; } = true; + // ===== EDL handling ===== /// diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html index 9ae2000..0d7b8b9 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html @@ -213,6 +213,20 @@ Process Configuration
+
+ + +
+ If checked, analysis will use Chromaprint to compare episode audio and identify intros. +
+ WARNING: Disabling this option may result in incomplete or innaccurate analysis! +
+
+
+
- Maximum degree of parallelism to use when analyzing episodes. + Maximum number of simultaneous async episode analysis operations.
From a90175dd6688ae1a10ecb7bf883f8cf0d6c50d9d Mon Sep 17 00:00:00 2001 From: TwistedUmbrellaX Date: Tue, 5 Mar 2024 09:12:57 -0500 Subject: [PATCH 4/6] Add a wrapper for media segments pending approval from Jellyfin --- .../Analyzers/ChromaprintAnalyzer.cs | 4 +-- .../Analyzers/SegmentAnalyzer.cs | 36 +++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/SegmentAnalyzer.cs diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChromaprintAnalyzer.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChromaprintAnalyzer.cs index 2a03321..c2eb513 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChromaprintAnalyzer.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/ChromaprintAnalyzer.cs @@ -275,10 +275,10 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer { var modifiedPoint = (uint)(originalPoint + i); - if (rhsIndex.TryGetValue(modifiedPoint, out var value)) + if (rhsIndex.TryGetValue(modifiedPoint, out var rhsModifiedPoint)) { var lhsFirst = (int)lhsIndex[originalPoint]; - var rhsFirst = (int)value; + var rhsFirst = (int)rhsModifiedPoint; indexShifts.Add(rhsFirst - lhsFirst); } } diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/SegmentAnalyzer.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/SegmentAnalyzer.cs new file mode 100644 index 0000000..da17352 --- /dev/null +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Analyzers/SegmentAnalyzer.cs @@ -0,0 +1,36 @@ +namespace ConfusedPolarBear.Plugin.IntroSkipper; + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Globalization; +using System.Linq; +using System.Threading; +using MediaBrowser.Model.Entities; +using Microsoft.Extensions.Logging; + +/// +/// Chapter name analyzer. +/// +public class SegmentAnalyzer : IMediaFileAnalyzer +{ + private ILogger _logger; + + /// + /// Initializes a new instance of the class. + /// + /// Logger. + public SegmentAnalyzer(ILogger logger) + { + _logger = logger; + } + + /// + public ReadOnlyCollection AnalyzeMediaFiles( + ReadOnlyCollection analysisQueue, + AnalysisMode mode, + CancellationToken cancellationToken) + { + return analysisQueue; + } +} From 1c04ce80c396933a8309aa5f8e83ef61a1f84e24 Mon Sep 17 00:00:00 2001 From: TwistedUmbrellaX Date: Tue, 5 Mar 2024 10:08:29 -0500 Subject: [PATCH 5/6] Reduce script overhead for injection --- .../Configuration/inject.js | 65 +++---------------- ...nfusedPolarBear.Plugin.IntroSkipper.csproj | 4 +- 2 files changed, 10 insertions(+), 59 deletions(-) diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js index 1154dcb..1e5ed00 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js @@ -1,51 +1,39 @@ let introSkipper = { skipSegments: {}, videoPlayer: {}, - // .bind() is used here to prevent illegal invocation errors originalFetch: window.fetch.bind(window), }; - introSkipper.d = function (msg) { - console.debug("[intro skipper]", msg); + console.debug("[intro skipper] ", msg); } - /** Setup event listeners */ introSkipper.setup = function () { document.addEventListener("viewshow", introSkipper.viewShow); window.fetch = introSkipper.fetchWrapper; introSkipper.d("Registered hooks"); } - /** Wrapper around fetch() that retrieves skip segments for the currently playing item. */ introSkipper.fetchWrapper = async function (...args) { // Based on JellyScrub's trickplay.js let [resource, options] = args; let response = await introSkipper.originalFetch(resource, options); - // Bail early if this isn't a playback info URL try { let path = new URL(resource).pathname; - if (!path.includes("/PlaybackInfo")) { - return response; - } - - introSkipper.d("retrieving skip segments from URL"); + if (!path.includes("/PlaybackInfo")) { return response; } + introSkipper.d("Retrieving skip segments from URL"); introSkipper.d(path); - let id = path.split("/")[2]; introSkipper.skipSegments = await introSkipper.secureFetch(`Episode/${id}/IntroSkipperSegments`); - - introSkipper.d("successfully retrieved skip segments"); + introSkipper.d("Successfully retrieved skip segments"); introSkipper.d(introSkipper.skipSegments); } catch (e) { - console.error("unable to get skip segments from", resource, e); + console.error("Unable to get skip segments from", resource, e); } - return response; } - /** * Event handler that runs whenever the current view changes. * Used to detect the start of video playback. @@ -53,21 +41,17 @@ introSkipper.fetchWrapper = async function (...args) { introSkipper.viewShow = function () { const location = window.location.hash; introSkipper.d("Location changed to " + location); - if (location !== "#!/video") { introSkipper.d("Ignoring location change"); return; } - introSkipper.d("Adding button CSS and element"); introSkipper.injectCss(); introSkipper.injectButton(); - introSkipper.d("Hooking video timeupdate"); introSkipper.videoPlayer = document.querySelector("video"); introSkipper.videoPlayer.addEventListener("timeupdate", introSkipper.videoPositionChanged); } - /** * Injects the CSS used by the skip intro button. * Calling this function is a no-op if the CSS has already been injected. @@ -77,9 +61,7 @@ introSkipper.injectCss = function () { introSkipper.d("CSS already added"); return; } - introSkipper.d("Adding CSS"); - let styleElement = document.createElement("style"); styleElement.id = "introSkipperCss"; styleElement.innerText = ` @@ -130,7 +112,6 @@ introSkipper.injectCss = function () { `; document.querySelector("head").appendChild(styleElement); } - /** * Inject the skip intro button into the video player. * Calling this function is a no-op if the CSS has already been injected. @@ -141,20 +122,16 @@ introSkipper.injectButton = async function () { if (preExistingButton) { preExistingButton.style.display = "none"; } - if (introSkipper.testElement(".btnSkipIntro.injected")) { introSkipper.d("Button already added"); return; } - introSkipper.d("Adding button"); - let config = await introSkipper.secureFetch("Intros/UserInterfaceConfiguration"); if (!config.SkipButtonVisible) { introSkipper.d("Not adding button: not visible"); return; } - // Construct the skip button div const button = document.createElement("div"); button.id = "skipIntro" @@ -168,25 +145,21 @@ introSkipper.injectButton = async function () { `; button.dataset["intro_text"] = config.SkipButtonIntroText; button.dataset["credits_text"] = config.SkipButtonEndCreditsText; - /* * Alternative workaround for #44. Jellyfin's video component registers a global click handler * (located at src/controllers/playback/video/index.js:1492) that pauses video playback unless * the clicked element has a parent with the class "videoOsdBottom" or "upNextContainer". */ button.classList.add("upNextContainer"); - // Append the button to the video OSD let controls = document.querySelector("div#videoOsdPage"); controls.appendChild(button); } - /** Tests if the OSD controls are visible. */ introSkipper.osdVisible = function () { const osd = document.querySelector("div.videoOsdBottom"); return osd ? !osd.classList.contains("hide") : false; } - /** Get the currently playing skippable segment. */ introSkipper.getCurrentSegment = function (position) { for (let key in introSkipper.skipSegments) { @@ -196,71 +169,49 @@ introSkipper.getCurrentSegment = function (position) { return segment; } } - return { "SegmentType": "None" }; } - /** Playback position changed, check if the skip button needs to be displayed. */ introSkipper.videoPositionChanged = function () { const skipButton = document.querySelector("#skipIntro"); if (!skipButton) { return; } - const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime); switch (segment["SegmentType"]) { case "None": skipButton.classList.add("hide"); return; - case "Introduction": skipButton.querySelector("#btnSkipSegmentText").textContent = skipButton.dataset["intro_text"]; break; - case "Credits": skipButton.querySelector("#btnSkipSegmentText").textContent = skipButton.dataset["credits_text"]; break; } - skipButton.classList.remove("hide"); } - /** Seeks to the end of the intro. */ introSkipper.doSkip = function (e) { introSkipper.d("Skipping intro"); introSkipper.d(introSkipper.skipSegments); - const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime); if (segment["SegmentType"] === "None") { console.warn("[intro skipper] doSkip() called without an active segment"); return; } - introSkipper.videoPlayer.currentTime = segment["IntroEnd"]; } - /** Tests if an element with the provided selector exists. */ introSkipper.testElement = function (selector) { return document.querySelector(selector); } - /** Make an authenticated fetch to the Jellyfin server and parse the response body as JSON. */ introSkipper.secureFetch = async function (url) { url = ApiClient.serverAddress() + "/" + url; - - const reqInit = { - headers: { - "Authorization": "MediaBrowser Token=" + ApiClient.accessToken() - } - }; - + const reqInit = { headers: { "Authorization": "MediaBrowser Token=" + ApiClient.accessToken() } }; const res = await fetch(url, reqInit); - - if (res.status !== 200) { - throw new Error(`Expected status 200 from ${url}, but got ${res.status}`); - } - + if (res.status !== 200) { throw new Error(`Expected status 200 from ${url}, but got ${res.status}`); } return await res.json(); } - -introSkipper.setup(); +introSkipper.setup(); \ No newline at end of file diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/ConfusedPolarBear.Plugin.IntroSkipper.csproj b/ConfusedPolarBear.Plugin.IntroSkipper/ConfusedPolarBear.Plugin.IntroSkipper.csproj index 1f3c386..254bf30 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.15.0 - 0.1.15.0 + 0.1.16.0 + 0.1.16.0 true true enable From 8ae99a9d825995d15ea5c9cba4ddef1ba76337e4 Mon Sep 17 00:00:00 2001 From: TwistedUmbrellaX Date: Tue, 5 Mar 2024 10:18:14 -0500 Subject: [PATCH 6/6] v0.1.16.0 --- manifest.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/manifest.json b/manifest.json index 9dde9b2..54a271e 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.0", + "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/intro-skipper-v0.1.16.zip", + "checksum": "8989cbe9c438d5a14fab3002e21c26ba", + "timestamp": "2024-03-04T10:10:35Z" + }, { "version": "0.1.15.0", "changelog": "- See the full changelog at [GitHub](https://github.com/jumoog/intro-skipper/blob/master/CHANGELOG.md)\n",