Resolve all remaining FIXMEs with credit detection

This commit is contained in:
ConfusedPolarBear 2022-12-05 22:35:01 -06:00
parent 4bb978639c
commit 1966357e29
4 changed files with 64 additions and 61 deletions

View File

@ -60,7 +60,7 @@ public class Entrypoint : IServerEntryPoint
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible // Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
_logger.LogInformation("Running startup enqueue"); _logger.LogInformation("Running startup enqueue");
var queueManager = new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager); var queueManager = new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager);
queueManager.EnqueueAllEpisodes(); queueManager.GetMediaItems();
} }
catch (Exception ex) catch (Exception ex)
{ {

View File

@ -3,6 +3,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.IO;
using System.Linq; using System.Linq;
using Jellyfin.Data.Enums; using Jellyfin.Data.Enums;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
@ -37,10 +38,10 @@ public class QueueManager
} }
/// <summary> /// <summary>
/// Iterates through all libraries on the server and queues all episodes for analysis. /// Gets all media items on the server.
/// </summary> /// </summary>
/// <returns>Queued media items.</returns> /// <returns>Queued media items.</returns>
public ReadOnlyDictionary<Guid, List<QueuedEpisode>> EnqueueAllEpisodes() public ReadOnlyDictionary<Guid, List<QueuedEpisode>> GetMediaItems()
{ {
// Assert that ffmpeg with chromaprint is installed // Assert that ffmpeg with chromaprint is installed
if (!FFmpegWrapper.CheckFFmpegVersion()) if (!FFmpegWrapper.CheckFFmpegVersion())
@ -220,4 +221,51 @@ public class QueueManager
Plugin.Instance!.TotalQueued++; Plugin.Instance!.TotalQueued++;
} }
/// <summary>
/// Verify that a collection of queued media items still exist in Jellyfin and in storage.
/// This is done to ensure that we don't analyze items that were deleted between the call to GetMediaItems() and popping them from the queue.
/// </summary>
/// <param name="candidates">Queued media items.</param>
/// <param name="mode">Analysis mode.</param>
/// <returns>Media items that have been verified to exist in Jellyfin and in storage.</returns>
public (ReadOnlyCollection<QueuedEpisode> VerifiedItems, bool AnyUnanalyzed)
VerifyQueue(ReadOnlyCollection<QueuedEpisode> candidates, AnalysisMode mode)
{
var unanalyzed = false;
var verified = new List<QueuedEpisode>();
var timestamps = mode == AnalysisMode.Introduction ?
Plugin.Instance!.Intros :
Plugin.Instance!.Credits;
foreach (var candidate in candidates)
{
try
{
var path = Plugin.Instance!.GetItemPath(candidate.EpisodeId);
if (File.Exists(path))
{
verified.Add(candidate);
}
if (!timestamps.ContainsKey(candidate.EpisodeId))
{
unanalyzed = true;
}
}
catch (Exception ex)
{
_logger.LogDebug(
"Skipping {Mode} analysis of {Name} ({Id}): {Exception}",
mode,
candidate.Name,
candidate.EpisodeId,
ex);
}
}
return (verified.AsReadOnly(), unanalyzed);
}
} }

View File

@ -9,10 +9,6 @@ using Microsoft.Extensions.Logging;
namespace ConfusedPolarBear.Plugin.IntroSkipper; namespace ConfusedPolarBear.Plugin.IntroSkipper;
#if !DEBUG
#error Fix all FIXMEs introduced during initial credit implementation before release
#endif
/// <summary> /// <summary>
/// Analyze all television episodes for credits. /// Analyze all television episodes for credits.
/// </summary> /// </summary>
@ -84,7 +80,7 @@ public class DetectCreditsTask : IScheduledTask
_loggerFactory.CreateLogger<QueueManager>(), _loggerFactory.CreateLogger<QueueManager>(),
_libraryManager); _libraryManager);
var queue = queueManager.EnqueueAllEpisodes(); var queue = queueManager.GetMediaItems();
if (queue.Count == 0) if (queue.Count == 0)
{ {
@ -101,9 +97,11 @@ public class DetectCreditsTask : IScheduledTask
// Analyze all episodes in the queue using the degrees of parallelism the user specified. // Analyze all episodes in the queue using the degrees of parallelism the user specified.
Parallel.ForEach(queue, options, (season) => Parallel.ForEach(queue, options, (season) =>
{ {
// TODO: FIXME: use VerifyEpisodes var (episodes, unanalyzed) = queueManager.VerifyQueue(
var episodes = season.Value.AsReadOnly(); season.Value.AsReadOnly(),
if (episodes.Count == 0) AnalysisMode.Credits);
if (episodes.Count == 0 || unanalyzed)
{ {
return; return;
} }

View File

@ -83,7 +83,7 @@ public class DetectIntroductionsTask : IScheduledTask
_loggerFactory.CreateLogger<QueueManager>(), _loggerFactory.CreateLogger<QueueManager>(),
_libraryManager); _libraryManager);
var queue = queueManager.EnqueueAllEpisodes(); var queue = queueManager.GetMediaItems();
if (queue.Count == 0) if (queue.Count == 0)
{ {
@ -100,13 +100,15 @@ public class DetectIntroductionsTask : IScheduledTask
MaxDegreeOfParallelism = Plugin.Instance!.Configuration.MaxParallelism MaxDegreeOfParallelism = Plugin.Instance!.Configuration.MaxParallelism
}; };
// TODO: if the queue is modified while the task is running, the task will fail.
// clone the queue before running the task to prevent this.
// Analyze all episodes in the queue using the degrees of parallelism the user specified. // Analyze all episodes in the queue using the degrees of parallelism the user specified.
Parallel.ForEach(queue, options, (season) => Parallel.ForEach(queue, options, (season) =>
{ {
var (episodes, unanalyzed) = VerifyEpisodes(season.Value.AsReadOnly()); // Since the first run of the task can run for multiple hours, ensure that none
// of the current media items were deleted from Jellyfin since the task was started.
var (episodes, unanalyzed) = queueManager.VerifyQueue(
season.Value.AsReadOnly(),
AnalysisMode.Introduction);
if (episodes.Count == 0) if (episodes.Count == 0)
{ {
return; return;
@ -178,51 +180,6 @@ public class DetectIntroductionsTask : IScheduledTask
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Verify that all episodes in a season exist in Jellyfin and as a file in storage.
/// TODO: FIXME: move to queue manager.
/// </summary>
/// <param name="candidates">QueuedEpisodes.</param>
/// <returns>Verified QueuedEpisodes and a flag indicating if any episode in this season has not been analyzed yet.</returns>
private (
ReadOnlyCollection<QueuedEpisode> VerifiedEpisodes,
bool AnyUnanalyzed)
VerifyEpisodes(ReadOnlyCollection<QueuedEpisode> candidates)
{
var unanalyzed = false;
var verified = new List<QueuedEpisode>();
foreach (var candidate in candidates)
{
try
{
// Verify that the episode exists in Jellyfin and in storage
var path = Plugin.Instance!.GetItemPath(candidate.EpisodeId);
if (File.Exists(path))
{
verified.Add(candidate);
}
// Flag this season for analysis if the current episode hasn't been analyzed yet
if (!Plugin.Instance.Intros.ContainsKey(candidate.EpisodeId))
{
unanalyzed = true;
}
}
catch (Exception ex)
{
_logger.LogDebug(
"Skipping analysis of {Name} ({Id}): {Exception}",
candidate.Name,
candidate.EpisodeId,
ex);
}
}
return (verified.AsReadOnly(), unanalyzed);
}
/// <summary> /// <summary>
/// Fingerprints all episodes in the provided season and stores the timestamps of all introductions. /// Fingerprints all episodes in the provided season and stores the timestamps of all introductions.
/// </summary> /// </summary>