From cc830abae4eb3eefb7e37d0007e43884b5b83b43 Mon Sep 17 00:00:00 2001 From: ConfusedPolarBear <33811686+ConfusedPolarBear@users.noreply.github.com> Date: Sun, 17 Jul 2022 01:54:05 -0500 Subject: [PATCH] Support adding and editing intro timestamps Closes #26 --- CHANGELOG.md | 1 + .../Configuration/configPage.html | 100 ++++++++++++++++-- .../Controllers/VisualizationController.cs | 20 +++- .../Plugin.cs | 17 +-- 4 files changed, 119 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e99211f..d90d60b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ * Support selecting which libraries are analyzed * Support customizing [introduction requirements](README.md#introduction-requirements) * Changing these settings will increase episode analysis times + * Support adding and editing intro timestamps * Report how CPU time is being spent while analyzing episodes * CPU time reports can be viewed under "Analysis Statistics (experimental)" in the plugin configuration page * Sped up fingerprint analysis (not including fingerprint generation time) by 40% diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html index fcc5007..3174f71 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/configPage.html @@ -84,7 +84,8 @@ MPlayer compatible EDL files alongside your episode files.
- If this value is changed after EDL files are generated, you must check the "Regenerate EDL files" checkbox below. + If this value is changed after EDL files are generated, you must check the + "Regenerate EDL files" checkbox below. @@ -95,7 +96,8 @@
- If checked, the plugin will overwrite all EDL files associated with your episodes with the currently discovered introduction timestamps and EDL action. + If checked, the plugin will overwrite all EDL files associated with + your episodes with the currently discovered introduction timestamps and EDL action.
@@ -276,6 +278,23 @@

+ +
+
+
@@ -324,6 +343,7 @@ var txtOffset = document.querySelector("input#offset"); var txtSuggested = document.querySelector("span#suggestedShifts"); var btnSeasonEraseTimestamps = document.querySelector("button#btnEraseSeasonTimestamps"); + var btnUpdateTimestamps = document.querySelector("button#btnUpdateTimestamps"); var timeContainer = document.querySelector("span#timestampContainer"); var windowHashInterval = 0; @@ -446,14 +466,50 @@ Dashboard.showLoadingMsg(); - lhs = await getJson("Intros/Fingerprint/" + selectEpisode1.value); - rhs = await getJson("Intros/Fingerprint/" + selectEpisode2.value); + lhs = await getJson("Intros/Episode/" + selectEpisode1.value + "/Chromaprint"); + rhs = await getJson("Intros/Episode/" + selectEpisode2.value + "/Chromaprint"); Dashboard.hideLoadingMsg(); refreshBounds(); renderTroubleshooter(); findExactMatches(); + updateTimestampEditor(); + } + + // updates the timestamp editor + async function updateTimestampEditor() { + // Get the title and ID of the left and right episodes + const leftEpisode = selectEpisode1.options[selectEpisode1.selectedIndex]; + const rightEpisode = selectEpisode2.options[selectEpisode2.selectedIndex]; + + // Try to get the timestamps of each intro, falling back a default value of zero if no intro was found + let leftEpisodeIntro = { + IntroStart: 0, + IntroEnd: 0, + }; + + let rightEpisodeIntro = { + IntroStart: 0, + IntroEnd: 0, + }; + + try { + leftEpisodeIntro = await getJson("Episode/" + leftEpisode.value + "/IntroTimestamps/v1"); + rightEpisodeIntro = await getJson("Episode/" + rightEpisode.value + "/IntroTimestamps/v1"); + } catch { + // Ignore the server provided intro and use the defaults from above + } + + // Update the editor for the first and second episodes + document.querySelector("#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); + + document.querySelector("#editRightEpisodeTitle").textContent = rightEpisode.text; + document.querySelector("#editRightEpisodeStart").value = Math.round(rightEpisodeIntro.IntroStart); + document.querySelector("#editRightEpisodeEnd").value = Math.round(rightEpisodeIntro.IntroEnd); } // adds an item to a dropdown @@ -472,20 +528,26 @@ // make an authenticated GET to the server and parse the response as JSON async function getJson(url, method = "GET") { + return await fetchWithAuth(url, method).then(r => { return r.json(); }); + } + + // make an authenticated fetch to the server + async function fetchWithAuth(url, method, body) { url = ApiClient.serverAddress() + "/" + url; const reqInit = { method: method, headers: { "Authorization": "MediaBrowser Token=" + ApiClient.accessToken() - } + }, + body: body, }; - return await - fetch(url, reqInit) - .then(r => { - return r.json(); - }); + if (method === "POST") { + reqInit.headers["Content-Type"] = "application/json"; + } + + return await fetch(url, reqInit); } // key pressed @@ -626,6 +688,24 @@ Dashboard.alert("Erased timestamps for " + season + " of " + show); }); + btnUpdateTimestamps.addEventListener("click", () => { + const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value; + const newLhsIntro = { + IntroStart: document.querySelector("#editLeftEpisodeStart").value, + IntroEnd: document.querySelector("#editLeftEpisodeEnd").value, + }; + + const rhsId = selectEpisode2.options[selectEpisode2.selectedIndex].value; + const newRhsIntro = { + IntroStart: document.querySelector("#editRightEpisodeStart").value, + IntroEnd: document.querySelector("#editRightEpisodeEnd").value, + }; + + fetchWithAuth("Intros/Episode/" + lhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newLhsIntro)); + fetchWithAuth("Intros/Episode/" + rhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newRhsIntro)); + + Dashboard.alert("New introduction timestamps saved"); + }); document.addEventListener("keydown", keyDown); windowHashInterval = setInterval(checkWindowHash, 2500); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs index 0caad79..6d4d042 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/VisualizationController.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Globalization; using System.Net.Mime; using Microsoft.AspNetCore.Authorization; @@ -102,7 +101,7 @@ public class VisualizationController : ControllerBase /// /// Episode id. /// Read only collection of fingerprint points. - [HttpGet("Fingerprint/{Id}")] + [HttpGet("Episode/{Id}/Chromaprint")] public ActionResult GetEpisodeFingerprint([FromRoute] Guid id) { var queue = Plugin.Instance!.AnalysisQueue; @@ -150,6 +149,23 @@ public class VisualizationController : ControllerBase return NoContent(); } + /// + /// Updates the timestamps for the provided episode. + /// + /// Episode ID to update timestamps for. + /// New introduction start and end times. + /// New introduction timestamps saved. + /// No content. + [HttpPost("Episode/{Id}/UpdateIntroTimestamps")] + public ActionResult UpdateTimestamps([FromRoute] Guid id, [FromBody] Intro timestamps) + { + var tr = new TimeRange(timestamps.IntroStart, timestamps.IntroEnd); + Plugin.Instance!.Intros[id] = new Intro(id, tr); + Plugin.Instance!.SaveTimestamps(); + + return NoContent(); + } + /// /// Returns the statistics for the most recent analysis. /// diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Plugin.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Plugin.cs index 6b417d7..f14aad0 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Plugin.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Plugin.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using ConfusedPolarBear.Plugin.IntroSkipper.Configuration; using MediaBrowser.Common.Configuration; @@ -17,6 +16,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper; /// public class Plugin : BasePlugin, IHasWebPages { + private readonly object _serializationLock = new object(); private IXmlSerializer _xmlSerializer; private ILibraryManager _libraryManager; private string _introPath; @@ -110,14 +110,17 @@ public class Plugin : BasePlugin, IHasWebPages /// public void SaveTimestamps() { - var introList = new List(); - - foreach (var intro in Plugin.Instance!.Intros) + lock (_serializationLock) { - introList.Add(intro.Value); - } + var introList = new List(); - _xmlSerializer.SerializeToFile(introList, _introPath); + foreach (var intro in Plugin.Instance!.Intros) + { + introList.Add(intro.Value); + } + + _xmlSerializer.SerializeToFile(introList, _introPath); + } } ///