Merge branch 'edl'
This commit is contained in:
commit
0d69c3a9d5
@ -1,6 +1,6 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
## v0.1.6.0
|
## v0.1.6.0 (unreleased)
|
||||||
* Write EDL files with intro timestamps
|
* Write EDL files with intro timestamps
|
||||||
* Restore per season status updates
|
* Restore per season status updates
|
||||||
|
|
||||||
|
44
ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestEdl.cs
Normal file
44
ConfusedPolarBear.Plugin.IntroSkipper.Tests/TestEdl.cs
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
using System;
|
||||||
|
using Xunit;
|
||||||
|
|
||||||
|
namespace ConfusedPolarBear.Plugin.IntroSkipper.Tests;
|
||||||
|
|
||||||
|
public class TestEdl
|
||||||
|
{
|
||||||
|
// Test data is from https://kodi.wiki/view/Edit_decision_list#MPlayer_EDL
|
||||||
|
[Theory]
|
||||||
|
[InlineData(5.3, 7.1, EdlAction.Cut, "5.3 7.1 0")]
|
||||||
|
[InlineData(15, 16.7, EdlAction.Mute, "15 16.7 1")]
|
||||||
|
[InlineData(420, 822, EdlAction.CommercialBreak, "420 822 3")]
|
||||||
|
[InlineData(1, 255.3, EdlAction.SceneMarker, "1 255.3 2")]
|
||||||
|
[InlineData(1.123456789, 5.654647987, EdlAction.CommercialBreak, "1.12 5.65 3")]
|
||||||
|
public void TestEdlSerialization(double start, double end, EdlAction action, string expected)
|
||||||
|
{
|
||||||
|
var intro = MakeIntro(start, end);
|
||||||
|
var actual = intro.ToEdl(action);
|
||||||
|
|
||||||
|
Assert.Equal(expected, actual);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void TestEdlInvalidSerialization()
|
||||||
|
{
|
||||||
|
Assert.Throws<ArgumentException>(() => {
|
||||||
|
var intro = MakeIntro(0, 5);
|
||||||
|
intro.ToEdl(EdlAction.None);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Theory]
|
||||||
|
[InlineData("Death Note - S01E12 - Love.mkv", "Death Note - S01E12 - Love.edl")]
|
||||||
|
[InlineData("/full/path/to/file.rm", "/full/path/to/file.edl")]
|
||||||
|
public void TestEdlPath(string mediaPath, string edlPath)
|
||||||
|
{
|
||||||
|
Assert.Equal(edlPath, EdlManager.GetEdlPath(mediaPath));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Intro MakeIntro(double start, double end)
|
||||||
|
{
|
||||||
|
return new Intro(Guid.Empty, new TimeRange(start, end));
|
||||||
|
}
|
||||||
|
}
|
@ -31,6 +31,20 @@ public class PluginConfiguration : BasePluginConfiguration
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string SelectedLibraries { get; set; } = string.Empty;
|
public string SelectedLibraries { get; set; } = string.Empty;
|
||||||
|
|
||||||
|
// ===== EDL handling =====
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets a value indicating the action to write to created EDL files.
|
||||||
|
/// </summary>
|
||||||
|
public EdlAction EdlAction { get; set; } = EdlAction.None;
|
||||||
|
|
||||||
|
/// <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 RegenerateEdlFiles { get; set; } = false;
|
||||||
|
|
||||||
// ===== Custom analysis settings =====
|
// ===== Custom analysis settings =====
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -49,6 +49,58 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>EDL file generation</summary>
|
||||||
|
|
||||||
|
<div class="selectContainer">
|
||||||
|
<label class="selectLabel" for="Options">EDL Action</label>
|
||||||
|
<select is="emby-select" id="EdlAction" class="emby-select-withcolor emby-select">
|
||||||
|
<option value="None">
|
||||||
|
None (do not create or modify EDL files)
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="CommercialBreak">
|
||||||
|
Commercial Break (recommended, skips past the intro once)
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="Cut">
|
||||||
|
Cut (player will remove the intro from the video)
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="Intro">
|
||||||
|
Intro (show a skip button, *experimental*)
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="Mute">
|
||||||
|
Mute (audio will be muted)
|
||||||
|
</option>
|
||||||
|
|
||||||
|
<option value="SceneMarker">
|
||||||
|
Scene Marker (create a chapter marker)
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div class="fieldDescription">
|
||||||
|
If set to a value other than None, specifies which action to write to
|
||||||
|
<a href="https://kodi.wiki/view/Edit_decision_list">MPlayer compatible EDL files</a>
|
||||||
|
alongside your episode files. <br />
|
||||||
|
|
||||||
|
If this value is changed after EDL files are generated, you must check the "Regenerate EDL files" checkbox below.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="checkboxContainer checkboxContainer-withDescription">
|
||||||
|
<label class="emby-checkbox-label">
|
||||||
|
<input id="RegenerateEdl" type="checkbox" is="emby-checkbox" />
|
||||||
|
<span>Regenerate EDL files during next scan</span>
|
||||||
|
</label>
|
||||||
|
|
||||||
|
<div class="fieldDescription">
|
||||||
|
If checked, the plugin will <strong>overwrite all EDL files</strong> associated with your episodes with the currently discovered introduction timestamps and EDL action.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</details>
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Modify introduction requirements</summary>
|
<summary>Modify introduction requirements</summary>
|
||||||
|
|
||||||
@ -619,6 +671,9 @@
|
|||||||
document.querySelector('#AnalysisLengthLimit').value = config.AnalysisLengthLimit;
|
document.querySelector('#AnalysisLengthLimit').value = config.AnalysisLengthLimit;
|
||||||
document.querySelector('#MinimumDuration').value = config.MinimumIntroDuration;
|
document.querySelector('#MinimumDuration').value = config.MinimumIntroDuration;
|
||||||
|
|
||||||
|
document.querySelector('#EdlAction').value = config.EdlAction;
|
||||||
|
document.querySelector('#RegenerateEdl').checked = config.RegenerateEdlFiles;
|
||||||
|
|
||||||
document.querySelector('#CacheFingerprints').checked = config.CacheFingerprints;
|
document.querySelector('#CacheFingerprints').checked = config.CacheFingerprints;
|
||||||
document.querySelector('#ShowPromptAdjustment').value = config.ShowPromptAdjustment;
|
document.querySelector('#ShowPromptAdjustment').value = config.ShowPromptAdjustment;
|
||||||
document.querySelector('#HidePromptAdjustment').value = config.HidePromptAdjustment;
|
document.querySelector('#HidePromptAdjustment').value = config.HidePromptAdjustment;
|
||||||
@ -639,11 +694,15 @@
|
|||||||
config.AnalysisLengthLimit = document.querySelector('#AnalysisLengthLimit').value;
|
config.AnalysisLengthLimit = document.querySelector('#AnalysisLengthLimit').value;
|
||||||
config.MinimumIntroDuration = document.querySelector('#MinimumDuration').value;
|
config.MinimumIntroDuration = document.querySelector('#MinimumDuration').value;
|
||||||
|
|
||||||
|
config.EdlAction = document.querySelector('#EdlAction').value;
|
||||||
|
config.RegenerateEdlFiles = document.querySelector('#RegenerateEdl').checked;
|
||||||
|
|
||||||
config.CacheFingerprints = document.querySelector('#CacheFingerprints').checked;
|
config.CacheFingerprints = document.querySelector('#CacheFingerprints').checked;
|
||||||
config.ShowPromptAdjustment = document.querySelector("#ShowPromptAdjustment").value;
|
config.ShowPromptAdjustment = document.querySelector("#ShowPromptAdjustment").value;
|
||||||
config.HidePromptAdjustment = document.querySelector("#HidePromptAdjustment").value;
|
config.HidePromptAdjustment = document.querySelector("#HidePromptAdjustment").value;
|
||||||
|
|
||||||
ApiClient.updatePluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b", config).then(function (result) {
|
ApiClient.updatePluginConfiguration("c83d86bb-a1e0-4c35-a113-e2101cf4ee6b", config)
|
||||||
|
.then(function (result) {
|
||||||
Dashboard.processPluginConfigurationUpdateResult(result);
|
Dashboard.processPluginConfigurationUpdateResult(result);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
37
ConfusedPolarBear.Plugin.IntroSkipper/Data/EdlAction.cs
Normal file
37
ConfusedPolarBear.Plugin.IntroSkipper/Data/EdlAction.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Taken from https://kodi.wiki/view/Edit_decision_list#MPlayer_EDL.
|
||||||
|
/// </summary>
|
||||||
|
public enum EdlAction
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Do not create EDL files.
|
||||||
|
/// </summary>
|
||||||
|
None = -1,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Completely remove the intro from playback as if it was never in the original video.
|
||||||
|
/// </summary>
|
||||||
|
Cut,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Mute audio, continue playback.
|
||||||
|
/// </summary>
|
||||||
|
Mute,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Inserts a new scene marker.
|
||||||
|
/// </summary>
|
||||||
|
SceneMarker,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Automatically skip the intro once during playback.
|
||||||
|
/// </summary>
|
||||||
|
CommercialBreak,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Show a skip button.
|
||||||
|
/// </summary>
|
||||||
|
Intro,
|
||||||
|
}
|
@ -68,4 +68,22 @@ public class Intro
|
|||||||
/// Gets or sets the recommended time to hide the skip intro prompt.
|
/// Gets or sets the recommended time to hide the skip intro prompt.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double HideSkipPromptAt { get; set; }
|
public double HideSkipPromptAt { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Convert this Intro object to a Kodi compatible EDL entry.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="action">User specified configuration EDL action.</param>
|
||||||
|
/// <returns>String.</returns>
|
||||||
|
public string ToEdl(EdlAction action)
|
||||||
|
{
|
||||||
|
if (action == EdlAction.None)
|
||||||
|
{
|
||||||
|
throw new ArgumentException("Cannot serialize an EdlAction of None");
|
||||||
|
}
|
||||||
|
|
||||||
|
var start = Math.Round(IntroStart, 2);
|
||||||
|
var end = Math.Round(IntroEnd, 2);
|
||||||
|
|
||||||
|
return string.Format(System.Globalization.CultureInfo.InvariantCulture, "{0} {1} {2}", start, end, (int)action);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
100
ConfusedPolarBear.Plugin.IntroSkipper/EdlManager.cs
Normal file
100
ConfusedPolarBear.Plugin.IntroSkipper/EdlManager.cs
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.IO;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Update EDL files associated with a list of episodes.
|
||||||
|
/// </summary>
|
||||||
|
public static class EdlManager
|
||||||
|
{
|
||||||
|
private static ILogger? _logger;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initialize EDLManager with a logger.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="logger">ILogger.</param>
|
||||||
|
public static void Initialize(ILogger logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Logs the configuration that will be used during EDL file creation.
|
||||||
|
/// </summary>
|
||||||
|
public static void LogConfiguration()
|
||||||
|
{
|
||||||
|
if (_logger is null)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Logger must not be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
var config = Plugin.Instance!.Configuration;
|
||||||
|
|
||||||
|
if (config.EdlAction == EdlAction.None)
|
||||||
|
{
|
||||||
|
_logger.LogDebug("EDL action: None - taking no further action");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("EDL action: {Action}", config.EdlAction);
|
||||||
|
_logger.LogDebug("Regenerate EDL files: {Regenerate}", config.RegenerateEdlFiles);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// If the EDL action is set to a value other than None, update EDL files for the provided episodes.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="episodes">Episodes to update EDL files for.</param>
|
||||||
|
public static void UpdateEDLFiles(ReadOnlyCollection<QueuedEpisode> episodes)
|
||||||
|
{
|
||||||
|
var regenerate = Plugin.Instance!.Configuration.RegenerateEdlFiles;
|
||||||
|
var action = Plugin.Instance!.Configuration.EdlAction;
|
||||||
|
if (action == EdlAction.None)
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("EDL action is set to none, not updating EDL files");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger?.LogDebug("Updating EDL files with action {Action}", action);
|
||||||
|
|
||||||
|
foreach (var episode in episodes)
|
||||||
|
{
|
||||||
|
var id = episode.EpisodeId;
|
||||||
|
|
||||||
|
if (!Plugin.Instance!.Intros.TryGetValue(id, out var intro))
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("Episode {Id} did not have an introduction, skipping", id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (!intro.Valid)
|
||||||
|
{
|
||||||
|
_logger?.LogDebug("Episode {Id} did not have a valid introduction, skipping", id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
var edlPath = GetEdlPath(Plugin.Instance!.GetItemPath(id));
|
||||||
|
|
||||||
|
_logger?.LogTrace("Episode {Id} has EDL path {Path}", id, edlPath);
|
||||||
|
|
||||||
|
if (!regenerate && File.Exists(edlPath))
|
||||||
|
{
|
||||||
|
_logger?.LogTrace("Refusing to overwrite existing EDL file {Path}", edlPath);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
File.WriteAllText(edlPath, intro.ToEdl(action));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Given the path to an episode, return the path to the associated EDL file.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="mediaPath">Full path to episode.</param>
|
||||||
|
/// <returns>Full path to EDL file.</returns>
|
||||||
|
public static string GetEdlPath(string mediaPath)
|
||||||
|
{
|
||||||
|
return Path.ChangeExtension(mediaPath, "edl");
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using ConfusedPolarBear.Plugin.IntroSkipper.Configuration;
|
|||||||
using MediaBrowser.Common.Configuration;
|
using MediaBrowser.Common.Configuration;
|
||||||
using MediaBrowser.Common.Plugins;
|
using MediaBrowser.Common.Plugins;
|
||||||
using MediaBrowser.Controller.Configuration;
|
using MediaBrowser.Controller.Configuration;
|
||||||
|
using MediaBrowser.Controller.Library;
|
||||||
using MediaBrowser.Model.Plugins;
|
using MediaBrowser.Model.Plugins;
|
||||||
using MediaBrowser.Model.Serialization;
|
using MediaBrowser.Model.Serialization;
|
||||||
|
|
||||||
@ -17,6 +18,7 @@ namespace ConfusedPolarBear.Plugin.IntroSkipper;
|
|||||||
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
||||||
{
|
{
|
||||||
private IXmlSerializer _xmlSerializer;
|
private IXmlSerializer _xmlSerializer;
|
||||||
|
private ILibraryManager _libraryManager;
|
||||||
private string _introPath;
|
private string _introPath;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -25,13 +27,16 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
/// <param name="applicationPaths">Instance of the <see cref="IApplicationPaths"/> interface.</param>
|
||||||
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
/// <param name="xmlSerializer">Instance of the <see cref="IXmlSerializer"/> interface.</param>
|
||||||
/// <param name="serverConfiguration">Server configuration manager.</param>
|
/// <param name="serverConfiguration">Server configuration manager.</param>
|
||||||
|
/// <param name="libraryManager">Library manager.</param>
|
||||||
public Plugin(
|
public Plugin(
|
||||||
IApplicationPaths applicationPaths,
|
IApplicationPaths applicationPaths,
|
||||||
IXmlSerializer xmlSerializer,
|
IXmlSerializer xmlSerializer,
|
||||||
IServerConfigurationManager serverConfiguration)
|
IServerConfigurationManager serverConfiguration,
|
||||||
|
ILibraryManager libraryManager)
|
||||||
: base(applicationPaths, xmlSerializer)
|
: base(applicationPaths, xmlSerializer)
|
||||||
{
|
{
|
||||||
_xmlSerializer = xmlSerializer;
|
_xmlSerializer = xmlSerializer;
|
||||||
|
_libraryManager = libraryManager;
|
||||||
|
|
||||||
// Create the base & cache directories (if needed).
|
// Create the base & cache directories (if needed).
|
||||||
FingerprintCachePath = Path.Join(applicationPaths.PluginConfigurationsPath, "intros", "cache");
|
FingerprintCachePath = Path.Join(applicationPaths.PluginConfigurationsPath, "intros", "cache");
|
||||||
@ -134,6 +139,16 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the full path for an item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">Item id.</param>
|
||||||
|
/// <returns>Full path to item.</returns>
|
||||||
|
internal string GetItemPath(Guid id)
|
||||||
|
{
|
||||||
|
return _libraryManager.GetItemById(id).Path;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public IEnumerable<PluginPageInfo> GetPages()
|
public IEnumerable<PluginPageInfo> GetPages()
|
||||||
{
|
{
|
||||||
|
@ -93,6 +93,8 @@ public class FingerprinterTask : IScheduledTask
|
|||||||
_queueLogger = loggerFactory.CreateLogger<QueueManager>();
|
_queueLogger = loggerFactory.CreateLogger<QueueManager>();
|
||||||
|
|
||||||
_fingerprintCache = new Dictionary<Guid, ReadOnlyCollection<uint>>();
|
_fingerprintCache = new Dictionary<Guid, ReadOnlyCollection<uint>>();
|
||||||
|
|
||||||
|
EdlManager.Initialize(_logger);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -140,6 +142,9 @@ public class FingerprinterTask : IScheduledTask
|
|||||||
"No episodes to analyze. If you are limiting the list of libraries to analyze, check that all library names have been spelled correctly.");
|
"No episodes to analyze. If you are limiting the list of libraries to analyze, check that all library names have been spelled correctly.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Log EDL settings
|
||||||
|
EdlManager.LogConfiguration();
|
||||||
|
|
||||||
// Include the previously processed episodes in the percentage reported to the UI.
|
// Include the previously processed episodes in the percentage reported to the UI.
|
||||||
var totalProcessed = CountProcessedEpisodes();
|
var totalProcessed = CountProcessedEpisodes();
|
||||||
var options = new ParallelOptions()
|
var options = new ParallelOptions()
|
||||||
@ -153,10 +158,12 @@ public class FingerprinterTask : IScheduledTask
|
|||||||
|
|
||||||
minimumIntroDuration = Plugin.Instance!.Configuration.MinimumIntroDuration;
|
minimumIntroDuration = Plugin.Instance!.Configuration.MinimumIntroDuration;
|
||||||
|
|
||||||
|
// 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 workerStart = DateTime.Now;
|
var workerStart = DateTime.Now;
|
||||||
var first = season.Value[0];
|
var first = season.Value[0];
|
||||||
|
var writeEdl = false;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -164,6 +171,7 @@ public class FingerprinterTask : IScheduledTask
|
|||||||
// (instead of just using the number of episodes in the current season).
|
// (instead of just using the number of episodes in the current season).
|
||||||
var analyzed = AnalyzeSeason(season, cancellationToken);
|
var analyzed = AnalyzeSeason(season, cancellationToken);
|
||||||
Interlocked.Add(ref totalProcessed, analyzed);
|
Interlocked.Add(ref totalProcessed, analyzed);
|
||||||
|
writeEdl = analyzed > 0 || Plugin.Instance!.Configuration.RegenerateEdlFiles;
|
||||||
}
|
}
|
||||||
catch (FingerprintException ex)
|
catch (FingerprintException ex)
|
||||||
{
|
{
|
||||||
@ -191,15 +199,29 @@ public class FingerprinterTask : IScheduledTask
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (writeEdl && Plugin.Instance!.Configuration.EdlAction != EdlAction.None)
|
||||||
|
{
|
||||||
|
EdlManager.UpdateEDLFiles(season.Value.AsReadOnly());
|
||||||
|
}
|
||||||
|
|
||||||
progress.Report((totalProcessed * 100) / Plugin.Instance!.TotalQueued);
|
progress.Report((totalProcessed * 100) / Plugin.Instance!.TotalQueued);
|
||||||
|
|
||||||
analysisStatistics.TotalCPUTime.AddDuration(workerStart);
|
analysisStatistics.TotalCPUTime.AddDuration(workerStart);
|
||||||
Plugin.Instance!.AnalysisStatistics = analysisStatistics;
|
Plugin.Instance!.AnalysisStatistics = analysisStatistics;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update analysis statistics
|
||||||
analysisStatistics.TotalTaskTime.AddDuration(taskStart);
|
analysisStatistics.TotalTaskTime.AddDuration(taskStart);
|
||||||
Plugin.Instance!.AnalysisStatistics = analysisStatistics;
|
Plugin.Instance!.AnalysisStatistics = analysisStatistics;
|
||||||
|
|
||||||
|
// Turn the regenerate EDL flag off after the scan completes.
|
||||||
|
if (Plugin.Instance!.Configuration.RegenerateEdlFiles)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("Turning EDL file regeneration flag off");
|
||||||
|
Plugin.Instance!.Configuration.RegenerateEdlFiles = false;
|
||||||
|
Plugin.Instance!.SaveConfiguration();
|
||||||
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
16
docs/edl.md
Normal file
16
docs/edl.md
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# EDL support
|
||||||
|
|
||||||
|
The timestamps of discovered introductions can be written to [EDL](https://kodi.wiki/view/Edit_decision_list) files alongside your media files. EDL files are saved when:
|
||||||
|
* Scanning an episode for the first time, or
|
||||||
|
* If requested with the regenerate checkbox
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Jellyfin must have read/write access to your TV show libraries in order to make use of this feature.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
To have the plugin create EDL files:
|
||||||
|
1. Change the EDL action from the default of None to any of the other supported EDL actions
|
||||||
|
2. Check the "Regenerate EDL files during next analysis" checkbox
|
||||||
|
1. If this option is not selected, only seasons with a newly analyzed episode will have EDL files created.
|
Loading…
x
Reference in New Issue
Block a user