add ignore list (#281)
* add block list * better block list system. todo: -change the name blocklist. -fixing small bugs. -maybe moving from Dictionary to List<BlockListItem>. * - moving from ConcurrentDictionary to List<BlackListItem>, for better xml file. - changing block to black. - small fixes. todo: - maybe changing the blacklist naming. * moving to ignorelist. moving the blacklisting to Manage Fingerprints. changing the object BlackListItem. todo: - moving to the naming "ignorelist", instead of "blacklist". - adding "save for series" button. - improving the ui of the blacklist section". - fixing some more bugs. - changing the "Manage Fingerprints" to "Manage Timestamps & Fingerprints". * adding the option to apply ignorelist changes into a series. moving to ignorelist naming. changing "Manage Fingerprints" to "Manage Timestamps & Fingerprints". improving the ui of the ignorelist editor * small fixes * fix some bugs. improving the ignore feature * fix some stuff * Refactor CSS styles for ignore list checkboxes * small fixes * small changes * small changes * big changes * small fixes * Refactor IgnoreListItem to use SeasonId instead of Id * Refactor IgnoreListItem to use SeasonId instead of Id. changes to the ExecuteAsync function and to its documentation
This commit is contained in:
parent
06da138a17
commit
d867ede882
@ -1,4 +1,3 @@
|
|||||||
using System.Collections.Generic;
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
|
@ -537,7 +537,7 @@
|
|||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details id="visualizer">
|
<details id="visualizer">
|
||||||
<summary>Manage Fingerprints</summary>
|
<summary>Manage Timestamps & Fingerprints</summary>
|
||||||
|
|
||||||
<br />
|
<br />
|
||||||
<label class="inputLabel" for="troubleshooterShow">Select TV Series to manage</label>
|
<label class="inputLabel" for="troubleshooterShow">Select TV Series to manage</label>
|
||||||
@ -546,6 +546,35 @@
|
|||||||
<select is="emby-select" id="troubleshooterSeason" class="emby-select-withcolor emby-select"></select>
|
<select is="emby-select" id="troubleshooterSeason" class="emby-select-withcolor emby-select"></select>
|
||||||
<br />
|
<br />
|
||||||
|
|
||||||
|
<div id="ignorelistSection" style="display: none;">
|
||||||
|
<h3 style="margin:0;">Ignore list editor</h3>
|
||||||
|
<p style="margin:0;">
|
||||||
|
Add or remove items from the ignore list. Items on the ignore list will not be analyzed.<br />
|
||||||
|
You can apply the changes for the entire series or just the selected season.
|
||||||
|
</p>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<div id="ignoreListCheckboxContainer">
|
||||||
|
<label for="ignorelistIntro" style="margin-right: 1.5em; display: inline-block;">
|
||||||
|
<span>Ignore intros</span>
|
||||||
|
<input type="checkbox" id="ignorelistIntro">
|
||||||
|
</label>
|
||||||
|
<label for="ignorelistCredits" style="margin-right: 1.5em; display: inline-block;">
|
||||||
|
<span>Ignore credits</span>
|
||||||
|
<input type="checkbox" id="ignorelistCredits">
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
|
<button id="saveIgnoreListSeries" class="raised button-submit block emby-button">
|
||||||
|
Apply to series
|
||||||
|
</button>
|
||||||
|
<button id="saveIgnoreListSeason" class="raised button-submit block emby-button" style="display: none;">
|
||||||
|
Apply to season
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<br />
|
||||||
|
|
||||||
<label class="inputLabel" for="troubleshooterEpisode1">Select the first episode</label>
|
<label class="inputLabel" for="troubleshooterEpisode1">Select the first episode</label>
|
||||||
<select is="emby-select" id="troubleshooterEpisode1" class="emby-select-withcolor emby-select"></select>
|
<select is="emby-select" id="troubleshooterEpisode1" class="emby-select-withcolor emby-select"></select>
|
||||||
<label class="inputLabel" for="troubleshooterEpisode1">Select the second episode</label>
|
<label class="inputLabel" for="troubleshooterEpisode1">Select the second episode</label>
|
||||||
@ -705,6 +734,8 @@
|
|||||||
// seasons grouped by show
|
// seasons grouped by show
|
||||||
var shows = {};
|
var shows = {};
|
||||||
|
|
||||||
|
var IgnoreListSeasonId;
|
||||||
|
|
||||||
// settings elements
|
// settings elements
|
||||||
var visualizer = document.querySelector("details#visualizer");
|
var visualizer = document.querySelector("details#visualizer");
|
||||||
var support = document.querySelector("details#support");
|
var support = document.querySelector("details#support");
|
||||||
@ -759,6 +790,11 @@
|
|||||||
]
|
]
|
||||||
|
|
||||||
// visualizer elements
|
// visualizer elements
|
||||||
|
var ignorelistSection = document.querySelector("div#ignorelistSection");
|
||||||
|
var ignorelistIntro = ignorelistSection.querySelector("input#ignorelistIntro");
|
||||||
|
var ignorelistCredits = ignorelistSection.querySelector("input#ignorelistCredits");
|
||||||
|
var saveIgnoreListSeasonButton = ignorelistSection.querySelector("button#saveIgnoreListSeason");
|
||||||
|
var saveIgnoreListSeriesButton = ignorelistSection.querySelector("button#saveIgnoreListSeries");
|
||||||
var canvas = document.querySelector("canvas#troubleshooter");
|
var canvas = document.querySelector("canvas#troubleshooter");
|
||||||
var selectShow = document.querySelector("select#troubleshooterShow");
|
var selectShow = document.querySelector("select#troubleshooterShow");
|
||||||
var selectSeason = document.querySelector("select#troubleshooterSeason");
|
var selectSeason = document.querySelector("select#troubleshooterSeason");
|
||||||
@ -965,6 +1001,15 @@
|
|||||||
clearSelect(selectEpisode1);
|
clearSelect(selectEpisode1);
|
||||||
clearSelect(selectEpisode2);
|
clearSelect(selectEpisode2);
|
||||||
|
|
||||||
|
// show the ignore list editor.
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
const IgnoreList = await getJson("Intros/IgnoreList/" + encodeURI(selectShow.value));
|
||||||
|
ignorelistIntro.checked = IgnoreList.IgnoreIntro;
|
||||||
|
ignorelistCredits.checked = IgnoreList.IgnoreCredits;
|
||||||
|
ignorelistSection.style.display = "unset";
|
||||||
|
saveIgnoreListSeasonButton.style.display = "none";
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
|
||||||
// add all seasons from this show to the season select
|
// add all seasons from this show to the season select
|
||||||
for (var season of shows[selectShow.value]) {
|
for (var season of shows[selectShow.value]) {
|
||||||
addItem(selectSeason, season, season);
|
addItem(selectSeason, season, season);
|
||||||
@ -975,20 +1020,30 @@
|
|||||||
|
|
||||||
// season changed, reload all episodes
|
// season changed, reload all episodes
|
||||||
async function seasonChanged() {
|
async function seasonChanged() {
|
||||||
const url = "Intros/Show/" + encodeURI(selectShow.value) + "/" + selectSeason.value;
|
const seasonData = encodeURI(selectShow.value) + "/" + encodeURI(selectSeason.value);
|
||||||
const episodes = await getJson(url);
|
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
// show the ignore list editor.
|
||||||
|
saveIgnoreListSeasonButton.style.display = "block";
|
||||||
|
const IgnoreList = await getJson("Intros/IgnoreList/" + seasonData);
|
||||||
|
ignorelistIntro.checked = IgnoreList.IgnoreIntro;
|
||||||
|
ignorelistCredits.checked = IgnoreList.IgnoreCredits;
|
||||||
|
IgnoreListSeasonId = IgnoreList.SeasonId;
|
||||||
|
|
||||||
|
// show the erase season button
|
||||||
|
eraseSeasonContainer.style.display = "unset";
|
||||||
|
|
||||||
clearSelect(selectEpisode1);
|
clearSelect(selectEpisode1);
|
||||||
clearSelect(selectEpisode2);
|
clearSelect(selectEpisode2);
|
||||||
eraseSeasonContainer.style.display = "unset";
|
|
||||||
|
|
||||||
let i = 1;
|
let i = 1;
|
||||||
|
const episodes = await getJson("Intros/Show/" + seasonData);
|
||||||
for (let episode of episodes) {
|
for (let episode of episodes) {
|
||||||
const strI = i.toLocaleString("en", { minimumIntegerDigits: 2, maximumFractionDigits: 0 });
|
const strI = i.toLocaleString("en", { minimumIntegerDigits: 2, maximumFractionDigits: 0 });
|
||||||
addItem(selectEpisode1, strI + ": " + episode.Name, episode.Id);
|
addItem(selectEpisode1, strI + ": " + episode.Name, episode.Id);
|
||||||
addItem(selectEpisode2, strI + ": " + episode.Name, episode.Id);
|
addItem(selectEpisode2, strI + ": " + episode.Name, episode.Id);
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
selectEpisode1.selectedIndex = 0;
|
selectEpisode1.selectedIndex = 0;
|
||||||
@ -1292,6 +1347,35 @@
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
saveIgnoreListSeasonButton.addEventListener("click", () => {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
|
var url ="Intros/IgnoreList/UpdateSeason";
|
||||||
|
const newRhs = {
|
||||||
|
IgnoreIntro: ignorelistIntro.checked,
|
||||||
|
IgnoreCredits: ignorelistCredits.checked,
|
||||||
|
SeasonId: IgnoreListSeasonId,
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchWithAuth(url, "POST", JSON.stringify(newRhs));
|
||||||
|
|
||||||
|
Dashboard.alert("Ignore list updated for " + selectSeason.value + " of " + selectShow.value);
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
});
|
||||||
|
saveIgnoreListSeriesButton.addEventListener("click", () => {
|
||||||
|
Dashboard.showLoadingMsg();
|
||||||
|
|
||||||
|
var url ="Intros/IgnoreList/UpdateSeries" + "/" + encodeURIComponent(selectShow.value);
|
||||||
|
const newRhs = {
|
||||||
|
IgnoreIntro: ignorelistIntro.checked,
|
||||||
|
IgnoreCredits: ignorelistCredits.checked,
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchWithAuth(url, "POST", JSON.stringify(newRhs));
|
||||||
|
|
||||||
|
Dashboard.alert("Ignore list updated for " + selectShow.value);
|
||||||
|
Dashboard.hideLoadingMsg();
|
||||||
|
});
|
||||||
btnUpdateTimestamps.addEventListener("click", () => {
|
btnUpdateTimestamps.addEventListener("click", () => {
|
||||||
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
|
const lhsId = selectEpisode1.options[selectEpisode1.selectedIndex].value;
|
||||||
const newLhs = {
|
const newLhs = {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
using System.Net.Mime;
|
using System.Net.Mime;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
using MediaBrowser.Common.Api;
|
using MediaBrowser.Common.Api;
|
||||||
@ -72,6 +73,48 @@ public class VisualizationController : ControllerBase
|
|||||||
return showSeasons;
|
return showSeasons;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the ignore list for the provided season.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">Show name.</param>
|
||||||
|
/// <param name="season">Season name.</param>
|
||||||
|
/// <returns>List of episode titles.</returns>
|
||||||
|
[HttpGet("IgnoreList/{Series}/{Season}")]
|
||||||
|
public ActionResult<IgnoreListItem> GetIgnoreListSeason([FromRoute] string series, [FromRoute] string season)
|
||||||
|
{
|
||||||
|
if (!LookupSeasonIdByName(series, season, out var seasonId))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Plugin.Instance!.IgnoreList.TryGetValue(seasonId, out _))
|
||||||
|
{
|
||||||
|
return new IgnoreListItem(seasonId);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IgnoreListItem(Plugin.Instance!.IgnoreList[seasonId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Returns the ignore list for the provided series.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">Show name.</param>
|
||||||
|
/// <returns>List of episode titles.</returns>
|
||||||
|
[HttpGet("IgnoreList/{Series}")]
|
||||||
|
public ActionResult<IgnoreListItem> GetIgnoreListSeries([FromRoute] string series)
|
||||||
|
{
|
||||||
|
if (!LookupSeasonIdsByName(series, out var seasonIds))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new IgnoreListItem(Guid.Empty)
|
||||||
|
{
|
||||||
|
IgnoreIntro = seasonIds.All(seasonId => Plugin.Instance!.IsIgnored(seasonId, AnalysisMode.Introduction)),
|
||||||
|
IgnoreCredits = seasonIds.All(seasonId => Plugin.Instance!.IsIgnored(seasonId, AnalysisMode.Credits))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Returns the names and unique identifiers of all episodes in the provided season.
|
/// Returns the names and unique identifiers of all episodes in the provided season.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -79,9 +122,7 @@ public class VisualizationController : ControllerBase
|
|||||||
/// <param name="season">Season name.</param>
|
/// <param name="season">Season name.</param>
|
||||||
/// <returns>List of episode titles.</returns>
|
/// <returns>List of episode titles.</returns>
|
||||||
[HttpGet("Show/{Series}/{Season}")]
|
[HttpGet("Show/{Series}/{Season}")]
|
||||||
public ActionResult<List<EpisodeVisualization>> GetSeasonEpisodes(
|
public ActionResult<List<EpisodeVisualization>> GetSeasonEpisodes([FromRoute] string series, [FromRoute] string season)
|
||||||
[FromRoute] string series,
|
|
||||||
[FromRoute] string season)
|
|
||||||
{
|
{
|
||||||
var visualEpisodes = new List<EpisodeVisualization>();
|
var visualEpisodes = new List<EpisodeVisualization>();
|
||||||
|
|
||||||
@ -157,6 +198,61 @@ public class VisualizationController : ControllerBase
|
|||||||
return NoContent();
|
return NoContent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the ignore list for the provided season.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ignoreListItem">New ignore list items.</param>
|
||||||
|
/// <param name="save">Save the ignore list.</param>
|
||||||
|
/// <returns>No content.</returns>
|
||||||
|
[HttpPost("IgnoreList/UpdateSeason")]
|
||||||
|
public ActionResult UpdateIgnoreListSeason([FromBody] IgnoreListItem ignoreListItem, bool save = true)
|
||||||
|
{
|
||||||
|
if (!Plugin.Instance!.QueuedMediaItems.ContainsKey(ignoreListItem.SeasonId))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ignoreListItem.IgnoreIntro || ignoreListItem.IgnoreCredits)
|
||||||
|
{
|
||||||
|
Plugin.Instance!.IgnoreList.AddOrUpdate(ignoreListItem.SeasonId, ignoreListItem, (_, _) => ignoreListItem);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Plugin.Instance!.IgnoreList.TryRemove(ignoreListItem.SeasonId, out _);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (save)
|
||||||
|
{
|
||||||
|
Plugin.Instance!.SaveIgnoreList();
|
||||||
|
}
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates the ignore list for the provided series.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">Series name.</param>
|
||||||
|
/// <param name="ignoreListItem">New ignore list items.</param>
|
||||||
|
/// <returns>No content.</returns>
|
||||||
|
[HttpPost("IgnoreList/UpdateSeries/{Series}")]
|
||||||
|
public ActionResult UpdateIgnoreListSeries([FromRoute] string series, [FromBody] IgnoreListItem ignoreListItem)
|
||||||
|
{
|
||||||
|
if (!LookupSeasonIdsByName(series, out var seasonIds))
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var seasonId in seasonIds)
|
||||||
|
{
|
||||||
|
UpdateIgnoreListSeason(new IgnoreListItem(ignoreListItem) { SeasonId = seasonId }, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Plugin.Instance!.SaveIgnoreList();
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the introduction timestamps for the provided episode.
|
/// Updates the introduction timestamps for the provided episode.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -212,4 +308,60 @@ public class VisualizationController : ControllerBase
|
|||||||
episodes = [];
|
episodes = [];
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lookup a named season of a series and return its season id.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">Series name.</param>
|
||||||
|
/// <param name="season">Season name.</param>
|
||||||
|
/// <param name="seasonId">Season id.</param>
|
||||||
|
/// <returns>Boolean indicating if the requested season was found.</returns>
|
||||||
|
private bool LookupSeasonIdByName(string series, string season, out Guid seasonId)
|
||||||
|
{
|
||||||
|
foreach (var queuedEpisodes in Plugin.Instance!.QueuedMediaItems)
|
||||||
|
{
|
||||||
|
var first = queuedEpisodes.Value[0];
|
||||||
|
var firstSeasonName = GetSeasonName(first);
|
||||||
|
|
||||||
|
// Assert that the queued episode series and season are equal to what was requested
|
||||||
|
if (
|
||||||
|
!string.Equals(first.SeriesName, series, StringComparison.OrdinalIgnoreCase) ||
|
||||||
|
!string.Equals(firstSeasonName, season, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
seasonId = queuedEpisodes.Key;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
seasonId = Guid.Empty;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Lookup a named series and return all the season ids.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="series">Series name.</param>
|
||||||
|
/// <param name="seasons">Seasons.</param>
|
||||||
|
/// <returns>Boolean indicating if the requested series was found.</returns>
|
||||||
|
private bool LookupSeasonIdsByName(string series, out List<Guid> seasons)
|
||||||
|
{
|
||||||
|
seasons = new List<Guid>();
|
||||||
|
|
||||||
|
foreach (var queuedEpisodes in Plugin.Instance!.QueuedMediaItems)
|
||||||
|
{
|
||||||
|
var first = queuedEpisodes.Value[0];
|
||||||
|
|
||||||
|
// Assert that the queued episode series is equal to what was requested
|
||||||
|
if (!string.Equals(first.SeriesName, series, StringComparison.OrdinalIgnoreCase))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
seasons.Add(queuedEpisodes.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return seasons.Count > 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
89
ConfusedPolarBear.Plugin.IntroSkipper/Data/IgnoreListItem.cs
Normal file
89
ConfusedPolarBear.Plugin.IntroSkipper/Data/IgnoreListItem.cs
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.Serialization;
|
||||||
|
|
||||||
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents an item to ignore.
|
||||||
|
/// </summary>
|
||||||
|
[DataContract(Namespace = "http://schemas.datacontract.org/2004/07/ConfusedPolarBear.Plugin.IntroSkipper")]
|
||||||
|
public class IgnoreListItem
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="IgnoreListItem"/> class.
|
||||||
|
/// </summary>
|
||||||
|
public IgnoreListItem()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="IgnoreListItem"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="seasonId">The season id.</param>
|
||||||
|
public IgnoreListItem(Guid seasonId)
|
||||||
|
{
|
||||||
|
SeasonId = seasonId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="IgnoreListItem"/> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The item to copy.</param>
|
||||||
|
public IgnoreListItem(IgnoreListItem item)
|
||||||
|
{
|
||||||
|
SeasonId = item.SeasonId;
|
||||||
|
IgnoreIntro = item.IgnoreIntro;
|
||||||
|
IgnoreCredits = item.IgnoreCredits;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the season id.
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public Guid SeasonId { get; set; } = Guid.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to ignore the intro.
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public bool IgnoreIntro { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to ignore the credits.
|
||||||
|
/// </summary>
|
||||||
|
[DataMember]
|
||||||
|
public bool IgnoreCredits { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Toggles the provided mode to the provided value.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">Analysis mode.</param>
|
||||||
|
/// <param name="value">Value to set.</param>
|
||||||
|
public void Toggle(AnalysisMode mode, bool value)
|
||||||
|
{
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case AnalysisMode.Introduction:
|
||||||
|
IgnoreIntro = value;
|
||||||
|
break;
|
||||||
|
case AnalysisMode.Credits:
|
||||||
|
IgnoreCredits = value;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the provided mode is ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mode">Analysis mode.</param>
|
||||||
|
/// <returns>True if ignored, false otherwise.</returns>
|
||||||
|
public bool IsIgnored(AnalysisMode mode)
|
||||||
|
{
|
||||||
|
return mode switch
|
||||||
|
{
|
||||||
|
AnalysisMode.Introduction => IgnoreIntro,
|
||||||
|
AnalysisMode.Credits => IgnoreCredits,
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ using System;
|
|||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
@ -30,6 +31,7 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
private readonly ILogger<Plugin> _logger;
|
private readonly ILogger<Plugin> _logger;
|
||||||
private readonly string _introPath;
|
private readonly string _introPath;
|
||||||
private readonly string _creditsPath;
|
private readonly string _creditsPath;
|
||||||
|
private string _ignorelistPath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
/// Initializes a new instance of the <see cref="Plugin"/> class.
|
||||||
@ -66,6 +68,7 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
FingerprintCachePath = Path.Join(introsDirectory, pluginCachePath);
|
FingerprintCachePath = Path.Join(introsDirectory, pluginCachePath);
|
||||||
_introPath = Path.Join(applicationPaths.DataPath, pluginDirName, "intros.xml");
|
_introPath = Path.Join(applicationPaths.DataPath, pluginDirName, "intros.xml");
|
||||||
_creditsPath = Path.Join(applicationPaths.DataPath, pluginDirName, "credits.xml");
|
_creditsPath = Path.Join(applicationPaths.DataPath, pluginDirName, "credits.xml");
|
||||||
|
_ignorelistPath = Path.Join(applicationPaths.DataPath, pluginDirName, "ignorelist.xml");
|
||||||
|
|
||||||
var cacheRoot = applicationPaths.CachePath;
|
var cacheRoot = applicationPaths.CachePath;
|
||||||
var oldIntrosDirectory = Path.Join(cacheRoot, pluginDirName);
|
var oldIntrosDirectory = Path.Join(cacheRoot, pluginDirName);
|
||||||
@ -129,6 +132,15 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
_logger.LogWarning("Unable to load introduction timestamps: {Exception}", ex);
|
_logger.LogWarning("Unable to load introduction timestamps: {Exception}", ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
LoadIgnoreList();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Unable to load ignore list: {Exception}", ex);
|
||||||
|
}
|
||||||
|
|
||||||
// Inject the skip intro button code into the web interface.
|
// Inject the skip intro button code into the web interface.
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -164,6 +176,11 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public ConcurrentDictionary<Guid, EpisodeState> EpisodeStates { get; } = new();
|
public ConcurrentDictionary<Guid, EpisodeState> EpisodeStates { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the ignore list.
|
||||||
|
/// </summary>
|
||||||
|
public ConcurrentDictionary<Guid, IgnoreListItem> IgnoreList { get; } = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the total number of episodes in the queue.
|
/// Gets or sets the total number of episodes in the queue.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -226,6 +243,53 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Save IgnoreList to disk.
|
||||||
|
/// </summary>
|
||||||
|
public void SaveIgnoreList()
|
||||||
|
{
|
||||||
|
var ignorelist = Instance!.IgnoreList.Values.ToList();
|
||||||
|
|
||||||
|
lock (_serializationLock)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
XmlSerializationHelper.SerializeToXml(ignorelist, _ignorelistPath);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError("SaveIgnoreList {Message}", e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Check if an item is ignored.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Item id.</param>
|
||||||
|
/// <param name="mode">Mode.</param>
|
||||||
|
/// <returns>True if ignored, false otherwise.</returns>
|
||||||
|
public bool IsIgnored(Guid id, AnalysisMode mode)
|
||||||
|
{
|
||||||
|
return Instance!.IgnoreList.TryGetValue(id, out var item) && item.IsIgnored(mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Load IgnoreList from disk.
|
||||||
|
/// </summary>
|
||||||
|
public void LoadIgnoreList()
|
||||||
|
{
|
||||||
|
if (File.Exists(_ignorelistPath))
|
||||||
|
{
|
||||||
|
var ignorelist = XmlSerializationHelper.DeserializeFromXml<IgnoreListItem>(_ignorelistPath);
|
||||||
|
|
||||||
|
foreach (var item in ignorelist)
|
||||||
|
{
|
||||||
|
Instance!.IgnoreList.TryAdd(item.SeasonId, item);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Restore previous analysis results from disk.
|
/// Restore previous analysis results from disk.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -234,7 +298,7 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
if (File.Exists(_introPath))
|
if (File.Exists(_introPath))
|
||||||
{
|
{
|
||||||
// Since dictionaries can't be easily serialized, analysis results are stored on disk as a list.
|
// Since dictionaries can't be easily serialized, analysis results are stored on disk as a list.
|
||||||
var introList = XmlSerializationHelper.DeserializeFromXml(_introPath);
|
var introList = XmlSerializationHelper.DeserializeFromXml<Segment>(_introPath);
|
||||||
|
|
||||||
foreach (var intro in introList)
|
foreach (var intro in introList)
|
||||||
{
|
{
|
||||||
@ -244,7 +308,7 @@ public partial class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
|
|
||||||
if (File.Exists(_creditsPath))
|
if (File.Exists(_creditsPath))
|
||||||
{
|
{
|
||||||
var creditList = XmlSerializationHelper.DeserializeFromXml(_creditsPath);
|
var creditList = XmlSerializationHelper.DeserializeFromXml<Segment>(_creditsPath);
|
||||||
|
|
||||||
foreach (var credit in creditList)
|
foreach (var credit in creditList)
|
||||||
{
|
{
|
||||||
|
@ -114,7 +114,7 @@ public class BaseItemAnalyzerTask
|
|||||||
// of the current media items were deleted from Jellyfin since the task was started.
|
// of the current media items were deleted from Jellyfin since the task was started.
|
||||||
var (episodes, requiredModes) = queueManager.VerifyQueue(
|
var (episodes, requiredModes) = queueManager.VerifyQueue(
|
||||||
season.Value.AsReadOnly(),
|
season.Value.AsReadOnly(),
|
||||||
_analysisModes);
|
_analysisModes.Where(m => !Plugin.Instance!.IsIgnored(season.Key, m)).ToList().AsReadOnly());
|
||||||
|
|
||||||
var episodeCount = episodes.Count;
|
var episodeCount = episodes.Count;
|
||||||
|
|
||||||
|
@ -58,7 +58,9 @@ public class CleanCacheTask : IScheduledTask
|
|||||||
public string Key => "CPBIntroSkipperCleanCache";
|
public string Key => "CPBIntroSkipperCleanCache";
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Cleans the Intro Skipper cache by removing files that are no longer associated with episodes in the library.
|
/// Cleans the cache of unused files.
|
||||||
|
/// Clears the Segment cache by removing files that are no longer associated with episodes in the library.
|
||||||
|
/// Clears the IgnoreList cache by removing items that are no longer associated with seasons in the library.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="progress">Task progress.</param>
|
/// <param name="progress">Task progress.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
@ -103,6 +105,30 @@ public class CleanCacheTask : IScheduledTask
|
|||||||
FFmpegWrapper.DeleteEpisodeCache(episodeId);
|
FFmpegWrapper.DeleteEpisodeCache(episodeId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Clean up ignore list by removing items that are no longer exist..
|
||||||
|
var removedItems = false;
|
||||||
|
foreach (var ignoredItem in Plugin.Instance.IgnoreList.Values.ToList())
|
||||||
|
{
|
||||||
|
if (!Plugin.Instance.QueuedMediaItems.ContainsKey(ignoredItem.SeasonId))
|
||||||
|
{
|
||||||
|
removedItems = true;
|
||||||
|
Plugin.Instance.IgnoreList.TryRemove(ignoredItem.SeasonId, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save ignore list if at least one item was removed.
|
||||||
|
if (removedItems)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
Plugin.Instance!.SaveIgnoreList();
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
_logger.LogError("Failed to save ignore list: {Error}", e.Message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Xml;
|
using System.Xml;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
@ -22,40 +23,17 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper
|
|||||||
|
|
||||||
public static void MigrateFromIntro(string filePath)
|
public static void MigrateFromIntro(string filePath)
|
||||||
{
|
{
|
||||||
var intros = new List<Intro>();
|
List<Intro> intros = DeserializeFromXml<Intro>(filePath);
|
||||||
var segments = new List<Segment>();
|
|
||||||
try
|
|
||||||
{
|
|
||||||
// Create a FileStream to read the XML file
|
|
||||||
using FileStream fileStream = new FileStream(filePath, FileMode.Open);
|
|
||||||
// Create an XmlDictionaryReader to read the XML
|
|
||||||
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fileStream, new XmlDictionaryReaderQuotas());
|
|
||||||
|
|
||||||
// Create a DataContractSerializer for type T
|
|
||||||
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Intro>));
|
|
||||||
|
|
||||||
// Deserialize the object from the XML
|
|
||||||
intros = serializer.ReadObject(reader) as List<Intro>;
|
|
||||||
|
|
||||||
// Close the reader
|
|
||||||
reader.Close();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Console.WriteLine($"Error deserializing XML: {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
ArgumentNullException.ThrowIfNull(intros);
|
ArgumentNullException.ThrowIfNull(intros);
|
||||||
intros.ForEach(delegate(Intro name)
|
|
||||||
{
|
var segments = intros.Select(name => new Segment(name)).ToList();
|
||||||
segments.Add(new Segment(name));
|
|
||||||
});
|
|
||||||
SerializeToXml(segments, filePath);
|
SerializeToXml(segments, filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<Segment> DeserializeFromXml(string filePath)
|
public static List<T> DeserializeFromXml<T>(string filePath)
|
||||||
{
|
{
|
||||||
var result = new List<Segment>();
|
var result = new List<T>();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Create a FileStream to read the XML file
|
// Create a FileStream to read the XML file
|
||||||
@ -63,11 +41,11 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper
|
|||||||
// Create an XmlDictionaryReader to read the XML
|
// Create an XmlDictionaryReader to read the XML
|
||||||
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fileStream, new XmlDictionaryReaderQuotas());
|
XmlDictionaryReader reader = XmlDictionaryReader.CreateTextReader(fileStream, new XmlDictionaryReaderQuotas());
|
||||||
|
|
||||||
// Create a DataContractSerializer for type T
|
// Create a DataContractSerializer for type List<T>
|
||||||
DataContractSerializer serializer = new DataContractSerializer(typeof(List<Segment>));
|
DataContractSerializer serializer = new DataContractSerializer(typeof(List<T>));
|
||||||
|
|
||||||
// Deserialize the object from the XML
|
// Deserialize the object from the XML
|
||||||
result = serializer.ReadObject(reader) as List<Segment>;
|
result = serializer.ReadObject(reader) as List<T>;
|
||||||
|
|
||||||
// Close the reader
|
// Close the reader
|
||||||
reader.Close();
|
reader.Close();
|
||||||
|
Loading…
x
Reference in New Issue
Block a user