add option to edit intros/credits (#223)
new API Endpoint: ´Episode/{Id}/Timestamps´ HTTP POST to update Timestamps. HTTP GET to receive the unmodified Timestamps. If Intro/Outro not exists the API returns 0
This commit is contained in:
parent
252e30cde0
commit
0d4bb295cc
@ -525,32 +525,54 @@
|
|||||||
<summary>Manage Fingerprints</summary>
|
<summary>Manage Fingerprints</summary>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<h3 style="margin:0">Select episodes to manage</h3>
|
<label class="inputLabel" for="troubleshooterShow">Select TV Series to manage</label>
|
||||||
<br />
|
<select is="emby-select" id="troubleshooterShow" class="emby-select-withcolor emby-select"></select>
|
||||||
<select id="troubleshooterShow"></select>
|
<label class="inputLabel" for="troubleshooterSeason">Select Season to manage</label>
|
||||||
<select id="troubleshooterSeason"></select>
|
<select is="emby-select" id="troubleshooterSeason" class="emby-select-withcolor emby-select"></select>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<select id="troubleshooterEpisode1"></select>
|
<label class="inputLabel" for="troubleshooterEpisode1">Select the first episode</label>
|
||||||
<select id="troubleshooterEpisode2"></select>
|
<select is="emby-select" id="troubleshooterEpisode1" class="emby-select-withcolor emby-select"></select>
|
||||||
<br />
|
<label class="inputLabel" for="troubleshooterEpisode1">Select the seconds epsisode</label>
|
||||||
|
<select is="emby-select" id="troubleshooterEpisode2" class="emby-select-withcolor emby-select"></select>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
<div id="timestampEditor" style="display:none">
|
<div id="timestampEditor" style="display:none">
|
||||||
<h3 style="margin:0">Introduction timestamp editor</h3>
|
<h3 style="margin:0">Introduction timestamp editor</h3>
|
||||||
<p style="margin:0">All times are in seconds.</p>
|
<p style="margin:0">All times are displayed in the format (HH:MM:SS)</p>
|
||||||
|
|
||||||
<p id="editLeftEpisodeTitle" style="margin-bottom:0"></p>
|
|
||||||
<input style="width:4em" type="number" min="0" id="editLeftEpisodeStart"> to
|
|
||||||
<input style="width:4em;margin-bottom:10px" type="number" min="0" id="editLeftEpisodeEnd">
|
|
||||||
|
|
||||||
<p id="editRightEpisodeTitle" style="margin-top:0;margin-bottom:0"></p>
|
|
||||||
<input style="width:4em" type="number" min="0" id="editRightEpisodeStart"> to
|
|
||||||
<input style="width:4em;margin-bottom:10px" type="number" min="0" id="editRightEpisodeEnd">
|
|
||||||
<br />
|
<br />
|
||||||
|
<table class="detailTable">
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="detailTableHeaderCell">Episode</th>
|
||||||
|
<th scope="col" class="detailTableHeaderCell">Section</th>
|
||||||
|
<th scope="col" class="detailTableHeaderCell">Start Time</th>
|
||||||
|
<th scope="col" class="detailTableHeaderCell">End Time</th>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="2" id="editLeftEpisodeTitle"></td>
|
||||||
|
<td>Intro</td>
|
||||||
|
<td><input type="time" step="1" id="editLeftIntroEpisodeStart"></td>
|
||||||
|
<td><input type="time" step="1" id="editLeftIntroEpisodeEnd"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Credits</td>
|
||||||
|
<td><input type="time" step="1" id="editLeftCreditEpisodeStart"></td>
|
||||||
|
<td><input type="time" step="1" id="editLeftCreditEpisodeEnd"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td rowspan="2" id="editRightEpisodeTitle"></td>
|
||||||
|
<td>Intro</td>
|
||||||
|
<td><input type="time" step="1" id="editRightIntroEpisodeStart"></td>
|
||||||
|
<td><input type="time" step="1" id="editRightIntroEpisodeEnd"></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Credits</td>
|
||||||
|
<td><input type="time" step="1" id="editRightCreditEpisodeStart"></td>
|
||||||
|
<td><input type="time" step="1" id="editRightCreditEpisodeEnd"></td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
<br />
|
<br />
|
||||||
|
<button is="emby-select" id="btnUpdateTimestamps" class="raised button-submit block emby-button" type="button">
|
||||||
<button id="btnUpdateTimestamps" type="button">
|
|
||||||
Update timestamps
|
Update timestamps
|
||||||
</button>
|
</button>
|
||||||
<br />
|
<br />
|
||||||
@ -570,8 +592,10 @@
|
|||||||
</p>
|
</p>
|
||||||
<table>
|
<table>
|
||||||
<thead>
|
<thead>
|
||||||
|
<tr>
|
||||||
<td style="min-width: 100px; font-weight: bold">Key</td>
|
<td style="min-width: 100px; font-weight: bold">Key</td>
|
||||||
<td style="font-weight: bold">Function</td>
|
<td style="font-weight: bold">Function</td>
|
||||||
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
@ -902,27 +926,24 @@
|
|||||||
// Get the title and ID of the left and right episodes
|
// Get the title and ID of the left and right episodes
|
||||||
const leftEpisode = selectEpisode1.options[selectEpisode1.selectedIndex];
|
const leftEpisode = selectEpisode1.options[selectEpisode1.selectedIndex];
|
||||||
const rightEpisode = selectEpisode2.options[selectEpisode2.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
|
// Try to get the timestamps of each intro, falling back a default value of zero if no intro was found
|
||||||
let leftEpisodeIntro = await getJson("Episode/" + leftEpisode.value + "/IntroTimestamps/v1");
|
const leftEpisodeJson = await getJson("Episode/" + leftEpisode.value + "/Timestamps");
|
||||||
if (leftEpisodeIntro === null) {
|
const rightEpisodeJson = await getJson("Episode/" + rightEpisode.value + "/Timestamps");
|
||||||
leftEpisodeIntro = { IntroStart: 0, IntroEnd: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
let rightEpisodeIntro = await getJson("Episode/" + rightEpisode.value + "/IntroTimestamps/v1");
|
|
||||||
if (rightEpisodeIntro === null) {
|
|
||||||
rightEpisodeIntro = { IntroStart: 0, IntroEnd: 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update the editor for the first and second episodes
|
// Update the editor for the first and second episodes
|
||||||
timestampEditor.style.display = "unset";
|
timestampEditor.style.display = "unset";
|
||||||
document.querySelector("#editLeftEpisodeTitle").textContent = leftEpisode.text;
|
document.querySelector("#editLeftEpisodeTitle").textContent = leftEpisode.text;
|
||||||
document.querySelector("#editLeftEpisodeStart").value = Math.round(leftEpisodeIntro.IntroStart);
|
document.querySelector("#editLeftIntroEpisodeStart").value = setTime(Math.round(leftEpisodeJson.Introduction.IntroStart));
|
||||||
document.querySelector("#editLeftEpisodeEnd").value = Math.round(leftEpisodeIntro.IntroEnd);
|
document.querySelector("#editLeftIntroEpisodeEnd").value = setTime(Math.round(leftEpisodeJson.Introduction.IntroEnd));
|
||||||
|
document.querySelector("#editLeftCreditEpisodeStart").value = setTime(Math.round(leftEpisodeJson.Credits.IntroStart));
|
||||||
|
document.querySelector("#editLeftCreditEpisodeEnd").value = setTime(Math.round(leftEpisodeJson.Credits.IntroEnd));
|
||||||
|
|
||||||
document.querySelector("#editRightEpisodeTitle").textContent = rightEpisode.text;
|
document.querySelector("#editRightEpisodeTitle").textContent = rightEpisode.text;
|
||||||
document.querySelector("#editRightEpisodeStart").value = Math.round(rightEpisodeIntro.IntroStart);
|
document.querySelector("#editRightIntroEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Introduction.IntroStart));
|
||||||
document.querySelector("#editRightEpisodeEnd").value = Math.round(rightEpisodeIntro.IntroEnd);
|
document.querySelector("#editRightIntroEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Introduction.IntroEnd));
|
||||||
|
document.querySelector("#editRightCreditEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Credits.IntroStart));
|
||||||
|
document.querySelector("#editRightCreditEpisodeEnd").value = setTime(Math.round(rightEpisodeJson.Credits.IntroEnd));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// adds an item to a dropdown
|
// adds an item to a dropdown
|
||||||
@ -1048,7 +1069,7 @@
|
|||||||
|
|
||||||
// converts seconds to a readable timestamp (i.e. 127 becomes "02:07").
|
// converts seconds to a readable timestamp (i.e. 127 becomes "02:07").
|
||||||
function secondsToString(seconds) {
|
function secondsToString(seconds) {
|
||||||
return new Date(seconds * 1000).toISOString().substr(14, 5);
|
return new Date(seconds * 1000).toISOString().slice(14, 19);
|
||||||
}
|
}
|
||||||
|
|
||||||
// erase all intro/credits timestamps
|
// erase all intro/credits timestamps
|
||||||
@ -1151,19 +1172,30 @@
|
|||||||
});
|
});
|
||||||
btnUpdateTimestamps.addEventListener("click", () => {
|
btnUpdateTimestamps.addEventListener("click", () => {
|
||||||
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
|
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
|
||||||
const newLhsIntro = {
|
const newLhs = {
|
||||||
IntroStart: document.querySelector("#editLeftEpisodeStart").value,
|
Introduction: {
|
||||||
IntroEnd: document.querySelector("#editLeftEpisodeEnd").value,
|
IntroStart: getTimeInSeconds(document.getElementById('editLeftIntroEpisodeStart').value),
|
||||||
|
IntroEnd: getTimeInSeconds(document.getElementById('editLeftIntroEpisodeEnd').value)
|
||||||
|
},
|
||||||
|
Credits: {
|
||||||
|
IntroStart: getTimeInSeconds(document.getElementById('editLeftCreditEpisodeStart').value),
|
||||||
|
IntroEnd: getTimeInSeconds(document.getElementById('editLeftCreditEpisodeEnd').value)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const rhsId = selectEpisode2.options[selectEpisode2.selectedIndex].value;
|
const rhsId = selectEpisode2.options[selectEpisode2.selectedIndex].value;
|
||||||
const newRhsIntro = {
|
const newRhs = {
|
||||||
IntroStart: document.querySelector("#editRightEpisodeStart").value,
|
Introduction: {
|
||||||
IntroEnd: document.querySelector("#editRightEpisodeEnd").value,
|
IntroStart: getTimeInSeconds(document.getElementById('editRightIntroEpisodeStart').value),
|
||||||
|
IntroEnd: getTimeInSeconds(document.getElementById('editRightIntroEpisodeEnd').value)
|
||||||
|
},
|
||||||
|
Credits: {
|
||||||
|
IntroStart: getTimeInSeconds(document.getElementById('editRightCreditEpisodeStart').value),
|
||||||
|
IntroEnd: getTimeInSeconds(document.getElementById('editRightCreditEpisodeEnd').value)
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
fetchWithAuth("Episode/" + lhsId + "/Timestamps", "POST", JSON.stringify(newLhs));
|
||||||
fetchWithAuth("Intros/Episode/" + lhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newLhsIntro));
|
fetchWithAuth("Episode/" + rhsId + "/Timestamps", "POST", JSON.stringify(newRhs));
|
||||||
fetchWithAuth("Intros/Episode/" + rhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newRhsIntro));
|
|
||||||
|
|
||||||
Dashboard.alert("New introduction timestamps saved");
|
Dashboard.alert("New introduction timestamps saved");
|
||||||
});
|
});
|
||||||
@ -1207,6 +1239,27 @@
|
|||||||
timeContainer.style.left = "25px";
|
timeContainer.style.left = "25px";
|
||||||
timeContainer.style.top = (-1 * rect.height + y).toString() + "px";
|
timeContainer.style.top = (-1 * rect.height + y).toString() + "px";
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function setTime(seconds) {
|
||||||
|
// Calculate hours, minutes, and remaining seconds
|
||||||
|
let hours = Math.floor(seconds / 3600);
|
||||||
|
let minutes = Math.floor((seconds % 3600) / 60);
|
||||||
|
let remainingSeconds = seconds % 60;
|
||||||
|
|
||||||
|
// Format as HH:MM:SS
|
||||||
|
let formattedTime =
|
||||||
|
String(hours).padStart(2, '0') + ':' +
|
||||||
|
String(minutes).padStart(2, '0') + ':' +
|
||||||
|
String(remainingSeconds).padStart(2, '0');
|
||||||
|
|
||||||
|
// Set the value of the time input
|
||||||
|
return formattedTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTimeInSeconds(time) {
|
||||||
|
let [hours, minutes, seconds] = time.split(':').map(Number);
|
||||||
|
return (hours * 3600) + (minutes * 60) + seconds;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
@ -47,6 +48,58 @@ public class SkipIntroController : ControllerBase
|
|||||||
return intro;
|
return intro;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the timestamps for the provided episode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Episode ID to update timestamps for.</param>
|
||||||
|
/// <param name="timestamps">New timestamps Introduction/Credits start and end times.</param>
|
||||||
|
/// <response code="204">New timestamps saved.</response>
|
||||||
|
/// <returns>No content.</returns>
|
||||||
|
[Authorize(Policy = Policies.RequiresElevation)]
|
||||||
|
[HttpPost("Episode/{Id}/Timestamps")]
|
||||||
|
public ActionResult UpdateTimestamps([FromRoute] Guid id, [FromBody] TimeStamps timestamps)
|
||||||
|
{
|
||||||
|
if (timestamps?.Introduction.IntroEnd > 0.0)
|
||||||
|
{
|
||||||
|
var tr = new TimeRange(timestamps.Introduction.IntroStart, timestamps.Introduction.IntroEnd);
|
||||||
|
Plugin.Instance!.Intros[id] = new Intro(id, tr);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timestamps?.Credits.IntroEnd > 0.0)
|
||||||
|
{
|
||||||
|
var cr = new TimeRange(timestamps.Credits.IntroStart, timestamps.Credits.IntroEnd);
|
||||||
|
Plugin.Instance!.Credits[id] = new Intro(id, cr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.Instance!.SaveTimestamps();
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the timestamps for the provided episode.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Episode ID.</param>
|
||||||
|
/// <response code="204">Sucess.</response>
|
||||||
|
/// <returns>Episode Timestamps.</returns>
|
||||||
|
[HttpGet("Episode/{Id}/Timestamps")]
|
||||||
|
[ActionName("UpdateTimestamps")]
|
||||||
|
public ActionResult<TimeStamps> GetTimestamps([FromRoute] Guid id)
|
||||||
|
{
|
||||||
|
var times = new TimeStamps();
|
||||||
|
if (Plugin.Instance!.Intros.TryGetValue(id, out var introValue))
|
||||||
|
{
|
||||||
|
times.Introduction = introValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Plugin.Instance!.Credits.TryGetValue(id, out var creditValue))
|
||||||
|
{
|
||||||
|
times.Credits = creditValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return times;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a dictionary of all skippable segments.
|
/// Gets a dictionary of all skippable segments.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@ -149,18 +150,22 @@ public class VisualizationController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the timestamps for the provided episode.
|
/// Updates the introduction timestamps for the provided episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="id">Episode ID to update timestamps for.</param>
|
/// <param name="id">Episode ID to update timestamps for.</param>
|
||||||
/// <param name="timestamps">New introduction start and end times.</param>
|
/// <param name="timestamps">New introduction start and end times.</param>
|
||||||
/// <response code="204">New introduction timestamps saved.</response>
|
/// <response code="204">New introduction timestamps saved.</response>
|
||||||
/// <returns>No content.</returns>
|
/// <returns>No content.</returns>
|
||||||
[HttpPost("Episode/{Id}/UpdateIntroTimestamps")]
|
[HttpPost("Episode/{Id}/UpdateIntroTimestamps")]
|
||||||
public ActionResult UpdateTimestamps([FromRoute] Guid id, [FromBody] Intro timestamps)
|
[Obsolete("deprecated use Episode/{Id}/Timestamps")]
|
||||||
|
public ActionResult UpdateIntroTimestamps([FromRoute] Guid id, [FromBody] Intro timestamps)
|
||||||
|
{
|
||||||
|
if (timestamps.IntroEnd > 0.0)
|
||||||
{
|
{
|
||||||
var tr = new TimeRange(timestamps.IntroStart, timestamps.IntroEnd);
|
var tr = new TimeRange(timestamps.IntroStart, timestamps.IntroEnd);
|
||||||
Plugin.Instance!.Intros[id] = new Intro(id, tr);
|
Plugin.Instance!.Intros[id] = new Intro(id, tr);
|
||||||
Plugin.Instance!.SaveTimestamps();
|
Plugin.Instance.SaveTimestamps();
|
||||||
|
}
|
||||||
|
|
||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
19
ConfusedPolarBear.Plugin.IntroSkipper/Data/TimeStamps.cs
Normal file
19
ConfusedPolarBear.Plugin.IntroSkipper/Data/TimeStamps.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Data
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Result of fingerprinting and analyzing two episodes in a season.
|
||||||
|
/// All times are measured in seconds relative to the beginning of the media file.
|
||||||
|
/// </summary>
|
||||||
|
public class TimeStamps
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Introduction.
|
||||||
|
/// </summary>
|
||||||
|
public Intro Introduction { get; set; } = new Intro();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets Credits.
|
||||||
|
/// </summary>
|
||||||
|
public Intro Credits { get; set; } = new Intro();
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user