Update MediaSegments directly (#350)
Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> Co-authored-by: Kilian von Pflugk <github@jumoog.io>
This commit is contained in:
parent
8f7c63172f
commit
1a731e3acc
@ -68,6 +68,20 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool WithChromaprint { get; set; } = true;
|
public bool WithChromaprint { get; set; } = true;
|
||||||
|
|
||||||
|
// ===== Media Segment handling =====
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to update Media Segments.
|
||||||
|
/// </summary>
|
||||||
|
public bool UpdateMediaSegments { get; set; } = true;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating whether to regenerate all EDL files during the next scan.
|
||||||
|
/// By default, EDL files are only written for a season if the season had at least one newly analyzed episode.
|
||||||
|
/// If this is set, all EDL files will be regenerated and overwrite any existing EDL file.
|
||||||
|
/// </summary>
|
||||||
|
public bool RegenerateMediaSegments { get; set; } = true;
|
||||||
|
|
||||||
// ===== EDL handling =====
|
// ===== EDL handling =====
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -153,6 +153,29 @@
|
|||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
<details id="mediasegment">
|
||||||
|
<summary>Jellyfin Mediasegment Generation</summary>
|
||||||
|
|
||||||
|
<br />
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="UpdateMediaSegments" type="checkbox" is="emby-checkbox" />
|
||||||
|
<span>Update Media Segments for Newly Added Files During Scan</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="fieldDescription">Enable this option to update media segments for newly added files during a scan. <strong>Warning:</strong> This should be disabled if you're using media segment providers other than Intro Skipper.</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="RegenerateMediaSegments" type="checkbox" is="emby-checkbox" />
|
||||||
|
<span>Regenerate All Media Segments on Next Scan</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="fieldDescription">When enabled, this option will <strong>overwrite all existing media segments</strong> for your episodes with currently detected introduction and credit timestamps during the next scan.</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
<details id="edl">
|
<details id="edl">
|
||||||
<summary>EDL File Generation</summary>
|
<summary>EDL File Generation</summary>
|
||||||
|
|
||||||
@ -671,7 +694,7 @@
|
|||||||
"AutoSkipCreditsNotificationText",
|
"AutoSkipCreditsNotificationText",
|
||||||
];
|
];
|
||||||
|
|
||||||
var booleanConfigurationFields = ["AutoDetectIntros", "AutoDetectCredits", "AnalyzeMovies", "AnalyzeSeasonZero", "SelectAllLibraries", "RegenerateEdlFiles", "CacheFingerprints", "AutoSkip", "AutoSkipCredits", "SkipFirstEpisode", "PersistSkipButton", "SkipButtonVisible"];
|
var booleanConfigurationFields = ["AutoDetectIntros", "AutoDetectCredits", "AnalyzeMovies", "AnalyzeSeasonZero", "SelectAllLibraries", "UpdateMediaSegments", "RegenerateMediaSegments", "RegenerateEdlFiles", "CacheFingerprints", "AutoSkip", "AutoSkipCredits", "SkipFirstEpisode", "PersistSkipButton", "SkipButtonVisible"];
|
||||||
|
|
||||||
// visualizer elements
|
// visualizer elements
|
||||||
var ignorelistSection = document.querySelector("div#ignorelistSection");
|
var ignorelistSection = document.querySelector("div#ignorelistSection");
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
|
using MediaBrowser.Controller;
|
||||||
|
using MediaBrowser.Model;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Manager
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of the <see cref="MediaSegmentUpdateManager" /> class.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaSegmentManager">MediaSegmentManager.</param>
|
||||||
|
/// <param name="logger">logger.</param>
|
||||||
|
/// <param name="segmentProvider">segmentProvider.</param>
|
||||||
|
public class MediaSegmentUpdateManager(IMediaSegmentManager mediaSegmentManager, ILogger<MediaSegmentUpdateManager> logger, IMediaSegmentProvider segmentProvider)
|
||||||
|
{
|
||||||
|
private readonly IMediaSegmentManager _mediaSegmentManager = mediaSegmentManager;
|
||||||
|
private readonly ILogger<MediaSegmentUpdateManager> _logger = logger;
|
||||||
|
private readonly IMediaSegmentProvider _segmentProvider = segmentProvider;
|
||||||
|
private readonly string _name = Plugin.Instance!.Name;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Updates all media items in a List.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episodes">Queued media items.</param>
|
||||||
|
/// <param name="cancellationToken">CancellationToken.</param>
|
||||||
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
|
public async Task UpdateMediaSegmentsAsync(IReadOnlyList<QueuedEpisode> episodes, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
foreach (var episode in episodes)
|
||||||
|
{
|
||||||
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var existingSegments = await _mediaSegmentManager.GetSegmentsAsync(episode.EpisodeId, null).ConfigureAwait(false);
|
||||||
|
var deleteTasks = existingSegments.Select(s => _mediaSegmentManager.DeleteSegmentAsync(s.Id));
|
||||||
|
await Task.WhenAll(deleteTasks).ConfigureAwait(false);
|
||||||
|
|
||||||
|
var newSegments = await _segmentProvider.GetMediaSegments(new MediaSegmentGenerationRequest { ItemId = episode.EpisodeId }, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
|
if (newSegments.Count == 0)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("No segments found for episode {EpisodeId}", episode.EpisodeId);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var createTasks = newSegments.Select(s => _mediaSegmentManager.CreateSegmentAsync(s, _name));
|
||||||
|
await Task.WhenAll(createTasks).ConfigureAwait(false);
|
||||||
|
|
||||||
|
_logger.LogDebug("Updated {SegmentCount} segments for episode {EpisodeId}", newSegments.Count, episode.EpisodeId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "Error processing episode {EpisodeId}", episode.EpisodeId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -416,7 +416,7 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
List<string> oldRepos =
|
List<string> oldRepos =
|
||||||
[
|
[
|
||||||
"https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json",
|
"https://raw.githubusercontent.com/intro-skipper/intro-skipper/master/manifest.json",
|
||||||
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json"
|
"https://raw.githubusercontent.com/jumoog/intro-skipper/master/manifest.json"
|
||||||
];
|
];
|
||||||
// Access the current server configuration
|
// Access the current server configuration
|
||||||
var config = serverConfiguration.Configuration;
|
var config = serverConfiguration.Configuration;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Providers;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Providers;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||||
using MediaBrowser.Controller;
|
using MediaBrowser.Controller;
|
||||||
@ -18,6 +19,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper
|
|||||||
serviceCollection.AddHostedService<AutoSkipCredits>();
|
serviceCollection.AddHostedService<AutoSkipCredits>();
|
||||||
serviceCollection.AddHostedService<Entrypoint>();
|
serviceCollection.AddHostedService<Entrypoint>();
|
||||||
serviceCollection.AddSingleton<IMediaSegmentProvider, SegmentProvider>();
|
serviceCollection.AddSingleton<IMediaSegmentProvider, SegmentProvider>();
|
||||||
|
serviceCollection.AddSingleton<MediaSegmentUpdateManager>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,15 +15,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Providers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class SegmentProvider : IMediaSegmentProvider
|
public class SegmentProvider : IMediaSegmentProvider
|
||||||
{
|
{
|
||||||
private readonly long _remainingTicks;
|
private static long RemainingTicks => TimeSpan.FromSeconds(Plugin.Instance?.Configuration.RemainingSecondsOfIntro ?? 2).Ticks;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Initializes a new instance of the <see cref="SegmentProvider"/> class.
|
|
||||||
/// </summary>
|
|
||||||
public SegmentProvider()
|
|
||||||
{
|
|
||||||
_remainingTicks = TimeSpan.FromSeconds(Plugin.Instance?.Configuration.RemainingSecondsOfIntro ?? 2).Ticks;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public string Name => Plugin.Instance!.Name;
|
public string Name => Plugin.Instance!.Name;
|
||||||
@ -38,7 +30,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Providers
|
|||||||
segments.Add(new MediaSegmentDto
|
segments.Add(new MediaSegmentDto
|
||||||
{
|
{
|
||||||
StartTicks = TimeSpan.FromSeconds(introValue.Start).Ticks,
|
StartTicks = TimeSpan.FromSeconds(introValue.Start).Ticks,
|
||||||
EndTicks = TimeSpan.FromSeconds(introValue.End).Ticks - _remainingTicks,
|
EndTicks = TimeSpan.FromSeconds(introValue.End).Ticks - RemainingTicks,
|
||||||
ItemId = request.ItemId,
|
ItemId = request.ItemId,
|
||||||
Type = MediaSegmentType.Intro
|
Type = MediaSegmentType.Intro
|
||||||
});
|
});
|
||||||
@ -61,7 +53,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Providers
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
outroSegment.EndTicks = creditEndTicks - _remainingTicks;
|
outroSegment.EndTicks = creditEndTicks - RemainingTicks;
|
||||||
}
|
}
|
||||||
|
|
||||||
segments.Add(outroSegment);
|
segments.Add(outroSegment);
|
||||||
|
@ -18,12 +18,10 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
|||||||
public class BaseItemAnalyzerTask
|
public class BaseItemAnalyzerTask
|
||||||
{
|
{
|
||||||
private readonly IReadOnlyCollection<AnalysisMode> _analysisModes;
|
private readonly IReadOnlyCollection<AnalysisMode> _analysisModes;
|
||||||
|
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
|
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="BaseItemAnalyzerTask"/> class.
|
/// Initializes a new instance of the <see cref="BaseItemAnalyzerTask"/> class.
|
||||||
@ -32,16 +30,19 @@ public class BaseItemAnalyzerTask
|
|||||||
/// <param name="logger">Task logger.</param>
|
/// <param name="logger">Task logger.</param>
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="mediaSegmentUpdateManager">MediaSegmentUpdateManager.</param>
|
||||||
public BaseItemAnalyzerTask(
|
public BaseItemAnalyzerTask(
|
||||||
IReadOnlyCollection<AnalysisMode> modes,
|
IReadOnlyCollection<AnalysisMode> modes,
|
||||||
ILogger logger,
|
ILogger logger,
|
||||||
ILoggerFactory loggerFactory,
|
ILoggerFactory loggerFactory,
|
||||||
ILibraryManager libraryManager)
|
ILibraryManager libraryManager,
|
||||||
|
MediaSegmentUpdateManager mediaSegmentUpdateManager)
|
||||||
{
|
{
|
||||||
_analysisModes = modes;
|
_analysisModes = modes;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
|
_mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||||
|
|
||||||
if (Plugin.Instance!.Configuration.EdlAction != EdlAction.None)
|
if (Plugin.Instance!.Configuration.EdlAction != EdlAction.None)
|
||||||
{
|
{
|
||||||
@ -55,7 +56,8 @@ public class BaseItemAnalyzerTask
|
|||||||
/// <param name="progress">Progress.</param>
|
/// <param name="progress">Progress.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <param name="seasonsToAnalyze">Season Ids to analyze.</param>
|
/// <param name="seasonsToAnalyze">Season Ids to analyze.</param>
|
||||||
public void AnalyzeItems(
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
|
public async Task AnalyzeItems(
|
||||||
IProgress<double> progress,
|
IProgress<double> progress,
|
||||||
CancellationToken cancellationToken,
|
CancellationToken cancellationToken,
|
||||||
IReadOnlyCollection<Guid>? seasonsToAnalyze = null)
|
IReadOnlyCollection<Guid>? seasonsToAnalyze = null)
|
||||||
@ -95,12 +97,13 @@ public class BaseItemAnalyzerTask
|
|||||||
var totalProcessed = 0;
|
var totalProcessed = 0;
|
||||||
var options = new ParallelOptions
|
var options = new ParallelOptions
|
||||||
{
|
{
|
||||||
MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism
|
MaxDegreeOfParallelism = Plugin.Instance.Configuration.MaxParallelism,
|
||||||
|
CancellationToken = cancellationToken
|
||||||
};
|
};
|
||||||
|
|
||||||
Parallel.ForEach(queue, options, season =>
|
await Parallel.ForEachAsync(queue, options, async (season, ct) =>
|
||||||
{
|
{
|
||||||
var writeEdl = false;
|
var updateManagers = false;
|
||||||
|
|
||||||
// Since the first run of the task can run for multiple hours, ensure that none
|
// 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.
|
// of the current media items were deleted from Jellyfin since the task was started.
|
||||||
@ -132,17 +135,17 @@ public class BaseItemAnalyzerTask
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (ct.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (AnalysisMode mode in requiredModes)
|
foreach (AnalysisMode mode in requiredModes)
|
||||||
{
|
{
|
||||||
var analyzed = AnalyzeItems(episodes, mode, cancellationToken);
|
var analyzed = AnalyzeItems(episodes, mode, ct);
|
||||||
Interlocked.Add(ref totalProcessed, analyzed);
|
Interlocked.Add(ref totalProcessed, analyzed);
|
||||||
|
|
||||||
writeEdl = analyzed > 0 || Plugin.Instance.Configuration.RegenerateEdlFiles;
|
updateManagers = analyzed > 0 || updateManagers;
|
||||||
|
|
||||||
progress.Report(totalProcessed * 100 / totalQueued);
|
progress.Report(totalProcessed * 100 / totalQueued);
|
||||||
}
|
}
|
||||||
@ -156,15 +159,21 @@ public class BaseItemAnalyzerTask
|
|||||||
ex);
|
ex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (writeEdl && Plugin.Instance.Configuration.EdlAction != EdlAction.None)
|
if (Plugin.Instance.Configuration.RegenerateMediaSegments || (updateManagers && Plugin.Instance.Configuration.UpdateMediaSegments))
|
||||||
|
{
|
||||||
|
await _mediaSegmentUpdateManager.UpdateMediaSegmentsAsync(episodes, ct).ConfigureAwait(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Plugin.Instance.Configuration.RegenerateEdlFiles || (updateManagers && Plugin.Instance.Configuration.EdlAction != EdlAction.None))
|
||||||
{
|
{
|
||||||
EdlManager.UpdateEDLFiles(episodes);
|
EdlManager.UpdateEDLFiles(episodes);
|
||||||
}
|
}
|
||||||
});
|
}).ConfigureAwait(false);
|
||||||
|
|
||||||
if (Plugin.Instance.Configuration.RegenerateEdlFiles)
|
if (Plugin.Instance.Configuration.RegenerateMediaSegments || Plugin.Instance.Configuration.RegenerateEdlFiles)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Turning EDL file regeneration flag off");
|
_logger.LogInformation("Turning Mediasegment/EDL file regeneration flag off");
|
||||||
|
Plugin.Instance.Configuration.RegenerateMediaSegments = false;
|
||||||
Plugin.Instance.Configuration.RegenerateEdlFiles = false;
|
Plugin.Instance.Configuration.RegenerateEdlFiles = false;
|
||||||
Plugin.Instance.SaveConfiguration();
|
Plugin.Instance.SaveConfiguration();
|
||||||
}
|
}
|
||||||
@ -182,7 +191,7 @@ public class BaseItemAnalyzerTask
|
|||||||
AnalysisMode mode,
|
AnalysisMode mode,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var totalItems = items.Count;
|
var totalItems = items.Count(e => !e.State.IsAnalyzed(mode));
|
||||||
|
|
||||||
// Only analyze specials (season 0) if the user has opted in.
|
// Only analyze specials (season 0) if the user has opted in.
|
||||||
var first = items[0];
|
var first = items[0];
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
@ -14,29 +15,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
|||||||
/// Analyze all television episodes for credits.
|
/// Analyze all television episodes for credits.
|
||||||
/// TODO: analyze all media files.
|
/// TODO: analyze all media files.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DetectCreditsTask : IScheduledTask
|
/// <remarks>
|
||||||
|
/// Initializes a new instance of the <see cref="DetectCreditsTask"/> class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="logger">Logger.</param>
|
||||||
|
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||||
|
public class DetectCreditsTask(
|
||||||
|
ILogger<DetectCreditsTask> logger,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
MediaSegmentUpdateManager mediaSegmentUpdateManager) : IScheduledTask
|
||||||
{
|
{
|
||||||
private readonly ILogger<DetectCreditsTask> _logger;
|
private readonly ILogger<DetectCreditsTask> _logger = logger;
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory = loggerFactory;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||||
|
|
||||||
/// <summary>
|
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||||
/// Initializes a new instance of the <see cref="DetectCreditsTask"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
|
||||||
/// <param name="logger">Logger.</param>
|
|
||||||
public DetectCreditsTask(
|
|
||||||
ILogger<DetectCreditsTask> logger,
|
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
ILibraryManager libraryManager)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the task name.
|
/// Gets the task name.
|
||||||
@ -64,7 +62,7 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
/// <param name="progress">Task progress.</param>
|
/// <param name="progress">Task progress.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_libraryManager is null)
|
if (_libraryManager is null)
|
||||||
{
|
{
|
||||||
@ -75,10 +73,10 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
||||||
Entrypoint.CancelAutomaticTask(cancellationToken);
|
await Entrypoint.CancelAutomaticTaskAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (ScheduledTaskSemaphore.Acquire(cancellationToken))
|
using (await ScheduledTaskSemaphore.AcquireAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Scheduled Task is starting");
|
_logger.LogInformation("Scheduled Task is starting");
|
||||||
|
|
||||||
@ -88,11 +86,10 @@ public class DetectCreditsTask : IScheduledTask
|
|||||||
modes,
|
modes,
|
||||||
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
_loggerFactory.CreateLogger<DetectCreditsTask>(),
|
||||||
_loggerFactory,
|
_loggerFactory,
|
||||||
_libraryManager);
|
_libraryManager,
|
||||||
|
_mediaSegmentUpdateManager);
|
||||||
|
|
||||||
baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken);
|
await baseCreditAnalyzer.AnalyzeItems(progress, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
@ -13,29 +14,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Analyze all television episodes for introduction sequences.
|
/// Analyze all television episodes for introduction sequences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DetectIntrosCreditsTask : IScheduledTask
|
/// <remarks>
|
||||||
|
/// Initializes a new instance of the <see cref="DetectIntrosCreditsTask"/> class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="logger">Logger.</param>
|
||||||
|
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||||
|
public class DetectIntrosCreditsTask(
|
||||||
|
ILogger<DetectIntrosCreditsTask> logger,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
MediaSegmentUpdateManager mediaSegmentUpdateManager) : IScheduledTask
|
||||||
{
|
{
|
||||||
private readonly ILogger<DetectIntrosCreditsTask> _logger;
|
private readonly ILogger<DetectIntrosCreditsTask> _logger = logger;
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory = loggerFactory;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||||
|
|
||||||
/// <summary>
|
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||||
/// Initializes a new instance of the <see cref="DetectIntrosCreditsTask"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
|
||||||
/// <param name="logger">Logger.</param>
|
|
||||||
public DetectIntrosCreditsTask(
|
|
||||||
ILogger<DetectIntrosCreditsTask> logger,
|
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
ILibraryManager libraryManager)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the task name.
|
/// Gets the task name.
|
||||||
@ -63,7 +61,7 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
|||||||
/// <param name="progress">Task progress.</param>
|
/// <param name="progress">Task progress.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_libraryManager is null)
|
if (_libraryManager is null)
|
||||||
{
|
{
|
||||||
@ -74,10 +72,10 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
|||||||
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
||||||
Entrypoint.CancelAutomaticTask(cancellationToken);
|
await Entrypoint.CancelAutomaticTaskAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (ScheduledTaskSemaphore.Acquire(cancellationToken))
|
using (await ScheduledTaskSemaphore.AcquireAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Scheduled Task is starting");
|
_logger.LogInformation("Scheduled Task is starting");
|
||||||
|
|
||||||
@ -87,11 +85,10 @@ public class DetectIntrosCreditsTask : IScheduledTask
|
|||||||
modes,
|
modes,
|
||||||
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
|
_loggerFactory.CreateLogger<DetectIntrosCreditsTask>(),
|
||||||
_loggerFactory,
|
_loggerFactory,
|
||||||
_libraryManager);
|
_libraryManager,
|
||||||
|
_mediaSegmentUpdateManager);
|
||||||
|
|
||||||
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
await baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
|
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Services;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Tasks;
|
using MediaBrowser.Model.Tasks;
|
||||||
@ -13,29 +14,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Analyze all television episodes for introduction sequences.
|
/// Analyze all television episodes for introduction sequences.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class DetectIntrosTask : IScheduledTask
|
/// <remarks>
|
||||||
|
/// Initializes a new instance of the <see cref="DetectIntrosTask"/> class.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
|
/// <param name="logger">Logger.</param>
|
||||||
|
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||||
|
public class DetectIntrosTask(
|
||||||
|
ILogger<DetectIntrosTask> logger,
|
||||||
|
ILoggerFactory loggerFactory,
|
||||||
|
ILibraryManager libraryManager,
|
||||||
|
MediaSegmentUpdateManager mediaSegmentUpdateManager) : IScheduledTask
|
||||||
{
|
{
|
||||||
private readonly ILogger<DetectIntrosTask> _logger;
|
private readonly ILogger<DetectIntrosTask> _logger = logger;
|
||||||
|
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory = loggerFactory;
|
||||||
|
|
||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager = libraryManager;
|
||||||
|
|
||||||
/// <summary>
|
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||||
/// Initializes a new instance of the <see cref="DetectIntrosTask"/> class.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
|
||||||
/// <param name="libraryManager">Library manager.</param>
|
|
||||||
/// <param name="logger">Logger.</param>
|
|
||||||
public DetectIntrosTask(
|
|
||||||
ILogger<DetectIntrosTask> logger,
|
|
||||||
ILoggerFactory loggerFactory,
|
|
||||||
ILibraryManager libraryManager)
|
|
||||||
{
|
|
||||||
_logger = logger;
|
|
||||||
_loggerFactory = loggerFactory;
|
|
||||||
_libraryManager = libraryManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the task name.
|
/// Gets the task name.
|
||||||
@ -63,7 +61,7 @@ public class DetectIntrosTask : IScheduledTask
|
|||||||
/// <param name="progress">Task progress.</param>
|
/// <param name="progress">Task progress.</param>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
/// <returns>Task.</returns>
|
/// <returns>Task.</returns>
|
||||||
public Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
public async Task ExecuteAsync(IProgress<double> progress, CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_libraryManager is null)
|
if (_libraryManager is null)
|
||||||
{
|
{
|
||||||
@ -74,10 +72,10 @@ public class DetectIntrosTask : IScheduledTask
|
|||||||
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
if (Entrypoint.AutomaticTaskState == TaskState.Running || Entrypoint.AutomaticTaskState == TaskState.Cancelling)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
_logger.LogInformation("Automatic Task is {0} and will be canceled.", Entrypoint.AutomaticTaskState);
|
||||||
Entrypoint.CancelAutomaticTask(cancellationToken);
|
await Entrypoint.CancelAutomaticTaskAsync(cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
using (ScheduledTaskSemaphore.Acquire(cancellationToken))
|
using (await ScheduledTaskSemaphore.AcquireAsync(cancellationToken).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Scheduled Task is starting");
|
_logger.LogInformation("Scheduled Task is starting");
|
||||||
|
|
||||||
@ -87,11 +85,10 @@ public class DetectIntrosTask : IScheduledTask
|
|||||||
modes,
|
modes,
|
||||||
_loggerFactory.CreateLogger<DetectIntrosTask>(),
|
_loggerFactory.CreateLogger<DetectIntrosTask>(),
|
||||||
_loggerFactory,
|
_loggerFactory,
|
||||||
_libraryManager);
|
_libraryManager,
|
||||||
|
_mediaSegmentUpdateManager);
|
||||||
|
|
||||||
baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken);
|
await baseIntroAnalyzer.AnalyzeItems(progress, cancellationToken).ConfigureAwait(false);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||||
|
|
||||||
@ -11,9 +12,9 @@ internal sealed class ScheduledTaskSemaphore : IDisposable
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IDisposable Acquire(CancellationToken cancellationToken)
|
public static async Task<IDisposable> AcquireAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_semaphore.Wait(cancellationToken);
|
await _semaphore.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||||
return new ScheduledTaskSemaphore();
|
return new ScheduledTaskSemaphore();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,6 +6,7 @@ using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
|||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Data;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
using ConfusedPolarBear.Plugin.IntroSkipper.Manager;
|
||||||
using ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
using ConfusedPolarBear.Plugin.IntroSkipper.ScheduledTasks;
|
||||||
|
using MediaBrowser.Controller.Entities.Movies;
|
||||||
using MediaBrowser.Controller.Entities.TV;
|
using MediaBrowser.Controller.Entities.TV;
|
||||||
using MediaBrowser.Controller.Library;
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Entities;
|
using MediaBrowser.Model.Entities;
|
||||||
@ -25,9 +26,10 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
private readonly ILibraryManager _libraryManager;
|
private readonly ILibraryManager _libraryManager;
|
||||||
private readonly ILogger<Entrypoint> _logger;
|
private readonly ILogger<Entrypoint> _logger;
|
||||||
private readonly ILoggerFactory _loggerFactory;
|
private readonly ILoggerFactory _loggerFactory;
|
||||||
|
private readonly MediaSegmentUpdateManager _mediaSegmentUpdateManager;
|
||||||
private readonly HashSet<Guid> _seasonsToAnalyze = [];
|
private readonly HashSet<Guid> _seasonsToAnalyze = [];
|
||||||
private readonly Timer _queueTimer;
|
private readonly Timer _queueTimer;
|
||||||
private static readonly ManualResetEventSlim _autoTaskCompletEvent = new(false);
|
private static readonly SemaphoreSlim _analysisSemaphore = new(1, 1);
|
||||||
private PluginConfiguration _config;
|
private PluginConfiguration _config;
|
||||||
private bool _analyzeAgain;
|
private bool _analyzeAgain;
|
||||||
private static CancellationTokenSource? _cancellationTokenSource;
|
private static CancellationTokenSource? _cancellationTokenSource;
|
||||||
@ -39,16 +41,19 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
/// <param name="taskManager">Task manager.</param>
|
/// <param name="taskManager">Task manager.</param>
|
||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
/// <param name="loggerFactory">Logger factory.</param>
|
/// <param name="loggerFactory">Logger factory.</param>
|
||||||
|
/// <param name="mediaSegmentUpdateManager">MediaSegment Update Manager.</param>
|
||||||
public Entrypoint(
|
public Entrypoint(
|
||||||
ILibraryManager libraryManager,
|
ILibraryManager libraryManager,
|
||||||
ITaskManager taskManager,
|
ITaskManager taskManager,
|
||||||
ILogger<Entrypoint> logger,
|
ILogger<Entrypoint> logger,
|
||||||
ILoggerFactory loggerFactory)
|
ILoggerFactory loggerFactory,
|
||||||
|
MediaSegmentUpdateManager mediaSegmentUpdateManager)
|
||||||
{
|
{
|
||||||
_libraryManager = libraryManager;
|
_libraryManager = libraryManager;
|
||||||
_taskManager = taskManager;
|
_taskManager = taskManager;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_loggerFactory = loggerFactory;
|
_loggerFactory = loggerFactory;
|
||||||
|
_mediaSegmentUpdateManager = mediaSegmentUpdateManager;
|
||||||
|
|
||||||
_config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
_config = Plugin.Instance?.Configuration ?? new PluginConfiguration();
|
||||||
_queueTimer = new Timer(
|
_queueTimer = new Timer(
|
||||||
@ -61,42 +66,26 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets State of the automatic task.
|
/// Gets State of the automatic task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static TaskState AutomaticTaskState
|
public static TaskState AutomaticTaskState => _cancellationTokenSource switch
|
||||||
{
|
{
|
||||||
get
|
null => TaskState.Idle,
|
||||||
{
|
{ IsCancellationRequested: true } => TaskState.Cancelling,
|
||||||
if (_cancellationTokenSource is not null)
|
_ => TaskState.Running
|
||||||
{
|
};
|
||||||
return _cancellationTokenSource.IsCancellationRequested
|
|
||||||
? TaskState.Cancelling
|
|
||||||
: TaskState.Running;
|
|
||||||
}
|
|
||||||
|
|
||||||
return TaskState.Idle;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task StartAsync(CancellationToken cancellationToken)
|
public Task StartAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_libraryManager.ItemAdded += OnItemAdded;
|
_libraryManager.ItemAdded += OnItemChanged;
|
||||||
_libraryManager.ItemUpdated += OnItemModified;
|
_libraryManager.ItemUpdated += OnItemChanged;
|
||||||
_taskManager.TaskCompleted += OnLibraryRefresh;
|
_taskManager.TaskCompleted += OnLibraryRefresh;
|
||||||
Plugin.Instance!.ConfigurationChanged += OnSettingsChanged;
|
Plugin.Instance!.ConfigurationChanged += OnSettingsChanged;
|
||||||
|
|
||||||
FFmpegWrapper.Logger = _logger;
|
FFmpegWrapper.Logger = _logger;
|
||||||
|
|
||||||
try
|
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
|
||||||
{
|
_logger.LogInformation("Running startup enqueue");
|
||||||
// Enqueue all episodes at startup to ensure any FFmpeg errors appear as early as possible
|
new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager).GetMediaItems();
|
||||||
_logger.LogInformation("Running startup enqueue");
|
|
||||||
var queueManager = new QueueManager(_loggerFactory.CreateLogger<QueueManager>(), _libraryManager);
|
|
||||||
queueManager?.GetMediaItems();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
_logger.LogError("Unable to run startup enqueue: {Exception}", ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
@ -104,75 +93,33 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task StopAsync(CancellationToken cancellationToken)
|
public Task StopAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
_libraryManager.ItemAdded -= OnItemAdded;
|
_libraryManager.ItemAdded -= OnItemChanged;
|
||||||
_libraryManager.ItemUpdated -= OnItemModified;
|
_libraryManager.ItemUpdated -= OnItemChanged;
|
||||||
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
_taskManager.TaskCompleted -= OnLibraryRefresh;
|
||||||
|
Plugin.Instance!.ConfigurationChanged -= OnSettingsChanged;
|
||||||
|
|
||||||
// Stop the timer
|
|
||||||
_queueTimer.Change(Timeout.Infinite, 0);
|
_queueTimer.Change(Timeout.Infinite, 0);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Disclose source for inspiration
|
|
||||||
// Implementation based on the principles of jellyfin-plugin-media-analyzer:
|
|
||||||
// https://github.com/endrl/jellyfin-plugin-media-analyzer
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Library item was added.
|
/// Library item was added.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="sender">The sending entity.</param>
|
/// <param name="sender">The sending entity.</param>
|
||||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
||||||
private void OnItemAdded(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
private void OnItemChanged(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
||||||
{
|
{
|
||||||
// Don't do anything if auto detection is disabled
|
if ((_config.AutoDetectIntros || _config.AutoDetectCredits) &&
|
||||||
if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
|
itemChangeEventArgs.Item is { LocationType: not LocationType.Virtual } item)
|
||||||
{
|
{
|
||||||
return;
|
Guid? id = item is Episode episode ? episode.SeasonId : (item is Movie movie ? movie.Id : null);
|
||||||
|
|
||||||
|
if (id.HasValue)
|
||||||
|
{
|
||||||
|
_seasonsToAnalyze.Add(id.Value);
|
||||||
|
StartTimer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't do anything if it's not a supported media type
|
|
||||||
if (itemChangeEventArgs.Item is not Episode episode)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemChangeEventArgs.Item.LocationType == LocationType.Virtual)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_seasonsToAnalyze.Add(episode.SeasonId);
|
|
||||||
|
|
||||||
StartTimer();
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Library item was modified.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="sender">The sending entity.</param>
|
|
||||||
/// <param name="itemChangeEventArgs">The <see cref="ItemChangeEventArgs"/>.</param>
|
|
||||||
private void OnItemModified(object? sender, ItemChangeEventArgs itemChangeEventArgs)
|
|
||||||
{
|
|
||||||
// Don't do anything if auto detection is disabled
|
|
||||||
if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Don't do anything if it's not a supported media type
|
|
||||||
if (itemChangeEventArgs.Item is not Episode episode)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (itemChangeEventArgs.Item.LocationType == LocationType.Virtual)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_seasonsToAnalyze.Add(episode.SeasonId);
|
|
||||||
|
|
||||||
StartTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -182,31 +129,12 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
/// <param name="eventArgs">The <see cref="TaskCompletionEventArgs"/>.</param>
|
/// <param name="eventArgs">The <see cref="TaskCompletionEventArgs"/>.</param>
|
||||||
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
|
private void OnLibraryRefresh(object? sender, TaskCompletionEventArgs eventArgs)
|
||||||
{
|
{
|
||||||
// Don't do anything if auto detection is disabled
|
if ((_config.AutoDetectIntros || _config.AutoDetectCredits) &&
|
||||||
if (!_config.AutoDetectIntros && !_config.AutoDetectCredits)
|
eventArgs.Result is { Key: "RefreshLibrary", Status: TaskCompletionStatus.Completed } &&
|
||||||
|
AutomaticTaskState != TaskState.Running)
|
||||||
{
|
{
|
||||||
return;
|
StartTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = eventArgs.Result;
|
|
||||||
|
|
||||||
if (result.Key != "RefreshLibrary")
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result.Status != TaskCompletionStatus.Completed)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unless user initiated, this is likely an overlap
|
|
||||||
if (AutomaticTaskState == TaskState.Running)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
StartTimer();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSettingsChanged(object? sender, BasePluginConfiguration e) => _config = (PluginConfiguration)e;
|
private void OnSettingsChanged(object? sender, BasePluginConfiguration e) => _config = (PluginConfiguration)e;
|
||||||
@ -223,90 +151,80 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
else if (AutomaticTaskState == TaskState.Idle)
|
else if (AutomaticTaskState == TaskState.Idle)
|
||||||
{
|
{
|
||||||
_logger.LogDebug("Media Library changed, analyzis will start soon!");
|
_logger.LogDebug("Media Library changed, analyzis will start soon!");
|
||||||
_queueTimer.Change(TimeSpan.FromMilliseconds(20000), Timeout.InfiniteTimeSpan);
|
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void OnTimerCallback(object? state) =>
|
||||||
/// Wait for timer callback to be completed.
|
_ = RunAnalysisAsync();
|
||||||
/// </summary>
|
|
||||||
private void OnTimerCallback(object? state)
|
private async Task RunAnalysisAsync()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
PerformAnalysis();
|
await PerformAnalysisAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error in PerformAnalysis");
|
_logger.LogError(ex, "Error in RunAnalysisAsync");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up
|
|
||||||
_cancellationTokenSource = null;
|
_cancellationTokenSource = null;
|
||||||
_autoTaskCompletEvent.Set();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private async Task PerformAnalysisAsync()
|
||||||
/// Wait for timer to be completed.
|
|
||||||
/// </summary>
|
|
||||||
private void PerformAnalysis()
|
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Initiate automatic analysis task.");
|
await _analysisSemaphore.WaitAsync().ConfigureAwait(false);
|
||||||
_autoTaskCompletEvent.Reset();
|
try
|
||||||
|
|
||||||
using (_cancellationTokenSource = new CancellationTokenSource())
|
|
||||||
using (ScheduledTaskSemaphore.Acquire(_cancellationTokenSource.Token))
|
|
||||||
{
|
{
|
||||||
var seasonIds = new HashSet<Guid>(_seasonsToAnalyze);
|
using (_cancellationTokenSource = new CancellationTokenSource())
|
||||||
_seasonsToAnalyze.Clear();
|
using (await ScheduledTaskSemaphore.AcquireAsync(_cancellationTokenSource.Token).ConfigureAwait(false))
|
||||||
|
|
||||||
_analyzeAgain = false;
|
|
||||||
var progress = new Progress<double>();
|
|
||||||
var modes = new List<AnalysisMode>();
|
|
||||||
var tasklogger = _loggerFactory.CreateLogger("DefaultLogger");
|
|
||||||
|
|
||||||
if (_config.AutoDetectIntros)
|
|
||||||
{
|
{
|
||||||
modes.Add(AnalysisMode.Introduction);
|
_logger.LogInformation("Initiating automatic analysis task");
|
||||||
tasklogger = _loggerFactory.CreateLogger<DetectIntrosTask>();
|
var seasonIds = new HashSet<Guid>(_seasonsToAnalyze);
|
||||||
}
|
_seasonsToAnalyze.Clear();
|
||||||
|
_analyzeAgain = false;
|
||||||
if (_config.AutoDetectCredits)
|
|
||||||
{
|
var modes = new List<AnalysisMode>();
|
||||||
modes.Add(AnalysisMode.Credits);
|
|
||||||
tasklogger = modes.Count == 2
|
if (_config.AutoDetectIntros)
|
||||||
? _loggerFactory.CreateLogger<DetectIntrosCreditsTask>()
|
{
|
||||||
: _loggerFactory.CreateLogger<DetectCreditsTask>();
|
modes.Add(AnalysisMode.Introduction);
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseCreditAnalyzer = new BaseItemAnalyzerTask(
|
if (_config.AutoDetectCredits)
|
||||||
modes,
|
{
|
||||||
tasklogger,
|
modes.Add(AnalysisMode.Credits);
|
||||||
_loggerFactory,
|
}
|
||||||
_libraryManager);
|
|
||||||
|
var analyzer = new BaseItemAnalyzerTask(modes, _loggerFactory.CreateLogger<Entrypoint>(), _loggerFactory, _libraryManager, _mediaSegmentUpdateManager);
|
||||||
baseCreditAnalyzer.AnalyzeItems(progress, _cancellationTokenSource.Token, seasonIds);
|
await analyzer.AnalyzeItems(new Progress<double>(), _cancellationTokenSource.Token, seasonIds).ConfigureAwait(false);
|
||||||
|
|
||||||
// New item detected, start timer again
|
if (_analyzeAgain && !_cancellationTokenSource.IsCancellationRequested)
|
||||||
if (_analyzeAgain && !_cancellationTokenSource.IsCancellationRequested)
|
{
|
||||||
{
|
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
|
||||||
_logger.LogInformation("Analyzing ended, but we need to analyze again!");
|
_queueTimer.Change(TimeSpan.FromSeconds(60), Timeout.InfiniteTimeSpan);
|
||||||
StartTimer();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_analysisSemaphore.Release();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Method to cancel the automatic task.
|
/// Method to cancel the automatic task.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="cancellationToken">Cancellation token.</param>
|
/// <param name="cancellationToken">Cancellation token.</param>
|
||||||
public static void CancelAutomaticTask(CancellationToken cancellationToken)
|
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
|
||||||
|
public static async Task CancelAutomaticTaskAsync(CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
if (_cancellationTokenSource != null && !_cancellationTokenSource.IsCancellationRequested)
|
if (_cancellationTokenSource is { IsCancellationRequested: false })
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_cancellationTokenSource.Cancel();
|
await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (ObjectDisposedException)
|
catch (ObjectDisposedException)
|
||||||
{
|
{
|
||||||
@ -314,7 +232,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_autoTaskCompletEvent.Wait(TimeSpan.FromSeconds(60), cancellationToken); // Wait for the signal
|
await _analysisSemaphore.WaitAsync(TimeSpan.FromSeconds(60), cancellationToken).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
@ -322,7 +240,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper.Services
|
|||||||
{
|
{
|
||||||
_queueTimer.Dispose();
|
_queueTimer.Dispose();
|
||||||
_cancellationTokenSource?.Dispose();
|
_cancellationTokenSource?.Dispose();
|
||||||
_autoTaskCompletEvent.Dispose();
|
_analysisSemaphore.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user