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>
|
||||
|
||||
<br />
|
||||
<h3 style="margin:0">Select episodes to manage</h3>
|
||||
<br />
|
||||
<select id="troubleshooterShow"></select>
|
||||
<select id="troubleshooterSeason"></select>
|
||||
<label class="inputLabel" for="troubleshooterShow">Select TV Series to manage</label>
|
||||
<select is="emby-select" id="troubleshooterShow" class="emby-select-withcolor emby-select"></select>
|
||||
<label class="inputLabel" for="troubleshooterSeason">Select Season to manage</label>
|
||||
<select is="emby-select" id="troubleshooterSeason" class="emby-select-withcolor emby-select"></select>
|
||||
<br />
|
||||
|
||||
<select id="troubleshooterEpisode1"></select>
|
||||
<select id="troubleshooterEpisode2"></select>
|
||||
<br />
|
||||
<label class="inputLabel" for="troubleshooterEpisode1">Select the first episode</label>
|
||||
<select is="emby-select" id="troubleshooterEpisode1" class="emby-select-withcolor emby-select"></select>
|
||||
<label class="inputLabel" for="troubleshooterEpisode1">Select the seconds epsisode</label>
|
||||
<select is="emby-select" id="troubleshooterEpisode2" class="emby-select-withcolor emby-select"></select>
|
||||
<br />
|
||||
|
||||
<div id="timestampEditor" style="display:none">
|
||||
<h3 style="margin:0">Introduction timestamp editor</h3>
|
||||
<p style="margin:0">All times are in seconds.</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">
|
||||
<p style="margin:0">All times are displayed in the format (HH:MM:SS)</p>
|
||||
<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 />
|
||||
|
||||
<button id="btnUpdateTimestamps" type="button">
|
||||
<button is="emby-select" id="btnUpdateTimestamps" class="raised button-submit block emby-button" type="button">
|
||||
Update timestamps
|
||||
</button>
|
||||
<br />
|
||||
@ -570,8 +592,10 @@
|
||||
</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<td style="min-width: 100px; font-weight: bold">Key</td>
|
||||
<td style="font-weight: bold">Function</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
@ -902,27 +926,24 @@
|
||||
// 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 = await getJson("Episode/" + leftEpisode.value + "/IntroTimestamps/v1");
|
||||
if (leftEpisodeIntro === null) {
|
||||
leftEpisodeIntro = { IntroStart: 0, IntroEnd: 0 };
|
||||
}
|
||||
|
||||
let rightEpisodeIntro = await getJson("Episode/" + rightEpisode.value + "/IntroTimestamps/v1");
|
||||
if (rightEpisodeIntro === null) {
|
||||
rightEpisodeIntro = { IntroStart: 0, IntroEnd: 0 };
|
||||
}
|
||||
const leftEpisodeJson = await getJson("Episode/" + leftEpisode.value + "/Timestamps");
|
||||
const rightEpisodeJson = await getJson("Episode/" + rightEpisode.value + "/Timestamps");
|
||||
|
||||
// Update the editor for the first and second episodes
|
||||
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("#editLeftIntroEpisodeStart").value = setTime(Math.round(leftEpisodeJson.Introduction.IntroStart));
|
||||
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("#editRightEpisodeStart").value = Math.round(rightEpisodeIntro.IntroStart);
|
||||
document.querySelector("#editRightEpisodeEnd").value = Math.round(rightEpisodeIntro.IntroEnd);
|
||||
document.querySelector("#editRightIntroEpisodeStart").value = setTime(Math.round(rightEpisodeJson.Introduction.IntroStart));
|
||||
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
|
||||
@ -1048,7 +1069,7 @@
|
||||
|
||||
// converts seconds to a readable timestamp (i.e. 127 becomes "02:07").
|
||||
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
|
||||
@ -1151,19 +1172,30 @@
|
||||
});
|
||||
btnUpdateTimestamps.addEventListener("click", () => {
|
||||
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
|
||||
const newLhsIntro = {
|
||||
IntroStart: document.querySelector("#editLeftEpisodeStart").value,
|
||||
IntroEnd: document.querySelector("#editLeftEpisodeEnd").value,
|
||||
const newLhs = {
|
||||
Introduction: {
|
||||
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 newRhsIntro = {
|
||||
IntroStart: document.querySelector("#editRightEpisodeStart").value,
|
||||
IntroEnd: document.querySelector("#editRightEpisodeEnd").value,
|
||||
const newRhs = {
|
||||
Introduction: {
|
||||
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("Intros/Episode/" + lhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newLhsIntro));
|
||||
fetchWithAuth("Intros/Episode/" + rhsId + "/UpdateIntroTimestamps", "POST", JSON.stringify(newRhsIntro));
|
||||
fetchWithAuth("Episode/" + lhsId + "/Timestamps", "POST", JSON.stringify(newLhs));
|
||||
fetchWithAuth("Episode/" + rhsId + "/Timestamps", "POST", JSON.stringify(newRhs));
|
||||
|
||||
Dashboard.alert("New introduction timestamps saved");
|
||||
});
|
||||
@ -1207,6 +1239,27 @@
|
||||
timeContainer.style.left = "25px";
|
||||
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>
|
||||
</div>
|
||||
</body>
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Net.Mime;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using MediaBrowser.Controller.Entities.TV;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
@ -47,6 +48,58 @@ public class SkipIntroController : ControllerBase
|
||||
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>
|
||||
/// Gets a dictionary of all skippable segments.
|
||||
/// </summary>
|
||||
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Net.Mime;
|
||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
@ -149,18 +150,22 @@ public class VisualizationController : ControllerBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates the timestamps for the provided episode.
|
||||
/// Updates the introduction timestamps for the provided episode.
|
||||
/// </summary>
|
||||
/// <param name="id">Episode ID to update timestamps for.</param>
|
||||
/// <param name="timestamps">New introduction start and end times.</param>
|
||||
/// <response code="204">New introduction timestamps saved.</response>
|
||||
/// <returns>No content.</returns>
|
||||
[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);
|
||||
Plugin.Instance!.Intros[id] = new Intro(id, tr);
|
||||
Plugin.Instance!.SaveTimestamps();
|
||||
Plugin.Instance.SaveTimestamps();
|
||||
}
|
||||
|
||||
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