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 @@
+
+
Introduction timestamp editor. All times are in seconds.
+
+ to
+
+
+
+ to
+
+
+
+
+
+
+
@@ -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);
+ }
}
///