Fix bug where timestamps were reset to zero.
This commit is contained in:
parent
327fe99de0
commit
f68fd9d06a
@ -36,20 +36,21 @@ namespace IntroSkipper.Analyzers
|
|||||||
/// <param name="originalIntros">Original introductions.</param>
|
/// <param name="originalIntros">Original introductions.</param>
|
||||||
/// <param name="mode">Analysis mode.</param>
|
/// <param name="mode">Analysis mode.</param>
|
||||||
/// <returns>Modified Intro Timestamps.</returns>
|
/// <returns>Modified Intro Timestamps.</returns>
|
||||||
public Dictionary<Guid, Segment> AdjustIntroTimes(
|
public IReadOnlyList<Segment> AdjustIntroTimes(
|
||||||
IReadOnlyList<QueuedEpisode> episodes,
|
IReadOnlyList<QueuedEpisode> episodes,
|
||||||
IReadOnlyDictionary<Guid, Segment> originalIntros,
|
IReadOnlyList<Segment> originalIntros,
|
||||||
AnalysisMode mode)
|
AnalysisMode mode)
|
||||||
{
|
{
|
||||||
return episodes
|
return originalIntros.Select(i => AdjustIntroForEpisode(episodes.FirstOrDefault(e => originalIntros.Any(i => i.EpisodeId == e.EpisodeId)), i, mode)).ToList();
|
||||||
.Where(episode => originalIntros.TryGetValue(episode.EpisodeId, out var _))
|
|
||||||
.ToDictionary(
|
|
||||||
episode => episode.EpisodeId,
|
|
||||||
episode => AdjustIntroForEpisode(episode, originalIntros[episode.EpisodeId], mode));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Segment AdjustIntroForEpisode(QueuedEpisode episode, Segment originalIntro, AnalysisMode mode)
|
private Segment AdjustIntroForEpisode(QueuedEpisode? episode, Segment originalIntro, AnalysisMode mode)
|
||||||
{
|
{
|
||||||
|
if (episode is null)
|
||||||
|
{
|
||||||
|
return new Segment(originalIntro.EpisodeId);
|
||||||
|
}
|
||||||
|
|
||||||
_logger.LogTrace("{Name} original intro: {Start} - {End}", episode.Name, originalIntro.Start, originalIntro.End);
|
_logger.LogTrace("{Name} original intro: {Start} - {End}", episode.Name, originalIntro.Start, originalIntro.End);
|
||||||
|
|
||||||
var adjustedIntro = new Segment(originalIntro);
|
var adjustedIntro = new Segment(originalIntro);
|
||||||
|
@ -37,9 +37,7 @@ public class BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) : IMediaFile
|
|||||||
throw new NotImplementedException("mode must equal Credits");
|
throw new NotImplementedException("mode must equal Credits");
|
||||||
}
|
}
|
||||||
|
|
||||||
var creditTimes = new Dictionary<Guid, Segment>();
|
var creditTimes = new List<Segment>();
|
||||||
|
|
||||||
var episodeAnalysisQueue = new List<QueuedEpisode>(analysisQueue);
|
|
||||||
|
|
||||||
bool isFirstEpisode = true;
|
bool isFirstEpisode = true;
|
||||||
|
|
||||||
@ -47,7 +45,7 @@ public class BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) : IMediaFile
|
|||||||
|
|
||||||
var searchDistance = 2 * _minimumCreditsDuration;
|
var searchDistance = 2 * _minimumCreditsDuration;
|
||||||
|
|
||||||
foreach (var episode in episodeAnalysisQueue.Where(e => !e.GetAnalyzed(mode)))
|
foreach (var episode in analysisQueue.Where(e => !e.GetAnalyzed(mode)))
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@ -116,16 +114,16 @@ public class BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) : IMediaFile
|
|||||||
|
|
||||||
searchStart = episode.Duration - credit.Start + (0.5 * searchDistance);
|
searchStart = episode.Duration - credit.Start + (0.5 * searchDistance);
|
||||||
|
|
||||||
creditTimes.Add(episode.EpisodeId, credit);
|
creditTimes.Add(credit);
|
||||||
episode.SetAnalyzed(mode, true);
|
episode.SetAnalyzed(mode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
var analyzerHelper = new AnalyzerHelper(_logger);
|
var analyzerHelper = new AnalyzerHelper(_logger);
|
||||||
creditTimes = analyzerHelper.AdjustIntroTimes(analysisQueue, creditTimes, mode);
|
var adjustedCreditTimes = analyzerHelper.AdjustIntroTimes(analysisQueue, creditTimes, mode);
|
||||||
|
|
||||||
await Plugin.Instance!.UpdateTimestamps(creditTimes, mode).ConfigureAwait(false);
|
await Plugin.Instance!.UpdateTimestamps(adjustedCreditTimes, mode).ConfigureAwait(false);
|
||||||
|
|
||||||
return episodeAnalysisQueue;
|
return analysisQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -24,7 +24,7 @@ namespace IntroSkipper.Analyzers;
|
|||||||
/// <param name="logger">Logger.</param>
|
/// <param name="logger">Logger.</param>
|
||||||
public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyzer
|
public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyzer
|
||||||
{
|
{
|
||||||
private ILogger<ChapterAnalyzer> _logger = logger;
|
private readonly ILogger<ChapterAnalyzer> _logger = logger;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<IReadOnlyList<QueuedEpisode>> AnalyzeMediaFiles(
|
public async Task<IReadOnlyList<QueuedEpisode>> AnalyzeMediaFiles(
|
||||||
@ -32,10 +32,7 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
|
|||||||
AnalysisMode mode,
|
AnalysisMode mode,
|
||||||
CancellationToken cancellationToken)
|
CancellationToken cancellationToken)
|
||||||
{
|
{
|
||||||
var skippableRanges = new Dictionary<Guid, Segment>();
|
var skippableRanges = new List<Segment>();
|
||||||
|
|
||||||
// Episode analysis queue.
|
|
||||||
var episodeAnalysisQueue = new List<QueuedEpisode>(analysisQueue);
|
|
||||||
|
|
||||||
var expression = mode == AnalysisMode.Introduction ?
|
var expression = mode == AnalysisMode.Introduction ?
|
||||||
Plugin.Instance!.Configuration.ChapterAnalyzerIntroductionPattern :
|
Plugin.Instance!.Configuration.ChapterAnalyzerIntroductionPattern :
|
||||||
@ -46,7 +43,7 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
|
|||||||
return analysisQueue;
|
return analysisQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var episode in episodeAnalysisQueue.Where(e => !e.GetAnalyzed(mode)))
|
foreach (var episode in analysisQueue.Where(e => !e.GetAnalyzed(mode)))
|
||||||
{
|
{
|
||||||
if (cancellationToken.IsCancellationRequested)
|
if (cancellationToken.IsCancellationRequested)
|
||||||
{
|
{
|
||||||
@ -64,13 +61,13 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
skippableRanges.Add(episode.EpisodeId, skipRange);
|
skippableRanges.Add(skipRange);
|
||||||
episode.SetAnalyzed(mode, true);
|
episode.SetAnalyzed(mode, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Plugin.Instance.UpdateTimestamps(skippableRanges, mode).ConfigureAwait(false);
|
await Plugin.Instance.UpdateTimestamps(skippableRanges, mode).ConfigureAwait(false);
|
||||||
|
|
||||||
return episodeAnalysisQueue;
|
return analysisQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -208,9 +208,9 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
|
|||||||
|
|
||||||
// Adjust all introduction times.
|
// Adjust all introduction times.
|
||||||
var analyzerHelper = new AnalyzerHelper(_logger);
|
var analyzerHelper = new AnalyzerHelper(_logger);
|
||||||
seasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, seasonIntros, _analysisMode);
|
var adjustedSeasonIntros = analyzerHelper.AdjustIntroTimes(analysisQueue, [.. seasonIntros.Values], _analysisMode);
|
||||||
|
|
||||||
await Plugin.Instance!.UpdateTimestamps(seasonIntros, _analysisMode).ConfigureAwait(false);
|
await Plugin.Instance!.UpdateTimestamps(adjustedSeasonIntros, _analysisMode).ConfigureAwait(false);
|
||||||
|
|
||||||
return episodeAnalysisQueue;
|
return episodeAnalysisQueue;
|
||||||
}
|
}
|
||||||
|
@ -75,14 +75,14 @@ public class SkipIntroController(MediaSegmentUpdateManager mediaSegmentUpdateMan
|
|||||||
|
|
||||||
if (timestamps?.Introduction.End > 0.0)
|
if (timestamps?.Introduction.End > 0.0)
|
||||||
{
|
{
|
||||||
var tr = new TimeRange(timestamps.Introduction.Start, timestamps.Introduction.End);
|
var seg = new Segment(id, new TimeRange(timestamps.Introduction.Start, timestamps.Introduction.End));
|
||||||
await Plugin.Instance!.UpdateTimestamps(new Dictionary<Guid, Segment> { [id] = new Segment(id, tr) }, AnalysisMode.Introduction).ConfigureAwait(false);
|
await Plugin.Instance!.UpdateTimestamps([seg], AnalysisMode.Introduction).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timestamps?.Credits.End > 0.0)
|
if (timestamps?.Credits.End > 0.0)
|
||||||
{
|
{
|
||||||
var tr = new TimeRange(timestamps.Credits.Start, timestamps.Credits.End);
|
var seg = new Segment(id, new TimeRange(timestamps.Credits.Start, timestamps.Credits.End));
|
||||||
await Plugin.Instance!.UpdateTimestamps(new Dictionary<Guid, Segment> { [id] = new Segment(id, tr) }, AnalysisMode.Credits).ConfigureAwait(false);
|
await Plugin.Instance!.UpdateTimestamps([seg], AnalysisMode.Credits).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Plugin.Instance.Configuration.UpdateMediaSegments)
|
if (Plugin.Instance.Configuration.UpdateMediaSegments)
|
||||||
|
@ -11,7 +11,7 @@ namespace IntroSkipper.Data;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class QueuedEpisode
|
public class QueuedEpisode
|
||||||
{
|
{
|
||||||
private readonly Dictionary<AnalysisMode, bool> _isAnalyzed = [];
|
private readonly bool[] _isAnalyzed = new bool[2];
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the series name.
|
/// Gets or sets the series name.
|
||||||
@ -36,7 +36,7 @@ public class QueuedEpisode
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets a value indicating whether this media has been already analyzed.
|
/// Gets a value indicating whether this media has been already analyzed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IReadOnlyDictionary<AnalysisMode, bool> IsAnalyzed => _isAnalyzed;
|
public IReadOnlyList<bool> IsAnalyzed => _isAnalyzed;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets or sets the full path to episode.
|
/// Gets or sets the full path to episode.
|
||||||
@ -80,7 +80,7 @@ public class QueuedEpisode
|
|||||||
/// <param name="value">Value to set.</param>
|
/// <param name="value">Value to set.</param>
|
||||||
public void SetAnalyzed(AnalysisMode mode, bool value)
|
public void SetAnalyzed(AnalysisMode mode, bool value)
|
||||||
{
|
{
|
||||||
_isAnalyzed[mode] = value;
|
_isAnalyzed[(int)mode] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -90,6 +90,6 @@ public class QueuedEpisode
|
|||||||
/// <returns>Value of the analyzed mode.</returns>
|
/// <returns>Value of the analyzed mode.</returns>
|
||||||
public bool GetAnalyzed(AnalysisMode mode)
|
public bool GetAnalyzed(AnalysisMode mode)
|
||||||
{
|
{
|
||||||
return _isAnalyzed.TryGetValue(mode, out var value) && value;
|
return _isAnalyzed[(int)mode];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,8 +33,8 @@ public class Segment
|
|||||||
public Segment(Guid episode)
|
public Segment(Guid episode)
|
||||||
{
|
{
|
||||||
EpisodeId = episode;
|
EpisodeId = episode;
|
||||||
Start = 0;
|
Start = 0.0;
|
||||||
End = 0;
|
End = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -88,7 +88,7 @@ public class Segment
|
|||||||
/// Gets a value indicating whether this introduction is valid or not.
|
/// Gets a value indicating whether this introduction is valid or not.
|
||||||
/// Invalid results must not be returned through the API.
|
/// Invalid results must not be returned through the API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Valid => End > 0;
|
public bool Valid => End > 0.0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the duration of this intro.
|
/// Gets the duration of this intro.
|
||||||
|
@ -292,7 +292,7 @@ namespace IntroSkipper.Manager
|
|||||||
VerifyQueue(IReadOnlyList<QueuedEpisode> candidates, IReadOnlyCollection<AnalysisMode> modes)
|
VerifyQueue(IReadOnlyList<QueuedEpisode> candidates, IReadOnlyCollection<AnalysisMode> modes)
|
||||||
{
|
{
|
||||||
var verified = new List<QueuedEpisode>();
|
var verified = new List<QueuedEpisode>();
|
||||||
var reqModes = new HashSet<AnalysisMode>(modes); // Start with all modes and remove completed ones
|
var reqModes = new HashSet<AnalysisMode>();
|
||||||
|
|
||||||
foreach (var candidate in candidates)
|
foreach (var candidate in candidates)
|
||||||
{
|
{
|
||||||
@ -315,8 +315,10 @@ namespace IntroSkipper.Manager
|
|||||||
{
|
{
|
||||||
candidate.SetAnalyzed(mode, true);
|
candidate.SetAnalyzed(mode, true);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
reqModes.Remove(mode);
|
else
|
||||||
|
{
|
||||||
|
reqModes.Add(mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -301,36 +301,37 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
|
|||||||
return _itemRepository.GetChapters(item);
|
return _itemRepository.GetChapters(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task UpdateTimestamps(IReadOnlyDictionary<Guid, Segment> newTimestamps, AnalysisMode mode)
|
internal async Task UpdateTimestamps(IReadOnlyList<Segment> newTimestamps, AnalysisMode mode)
|
||||||
{
|
{
|
||||||
|
if (newTimestamps.Count == 0)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_logger.LogDebug("Starting UpdateTimestamps with {Count} segments for mode {Mode}", newTimestamps.Count, mode);
|
||||||
|
|
||||||
using var db = new IntroSkipperDbContext(_dbPath);
|
using var db = new IntroSkipperDbContext(_dbPath);
|
||||||
|
|
||||||
// Get all existing segments in a single query
|
var segments = newTimestamps.Select(s => new DbSegment(s, mode)).ToList();
|
||||||
var existingSegments = db.DbSegment
|
|
||||||
.Where(s => newTimestamps.Keys.Contains(s.ItemId) && s.Type == mode)
|
|
||||||
.ToDictionary(s => s.ItemId);
|
|
||||||
|
|
||||||
// Batch updates and inserts
|
var newItemIds = segments.Select(s => s.ItemId).ToHashSet();
|
||||||
var segmentsToAdd = new List<DbSegment>();
|
var existingIds = db.DbSegment
|
||||||
|
.Where(s => s.Type == mode && newItemIds.Contains(s.ItemId))
|
||||||
|
.Select(s => s.ItemId)
|
||||||
|
.ToHashSet();
|
||||||
|
|
||||||
foreach (var (itemId, segment) in newTimestamps)
|
foreach (var segment in segments)
|
||||||
{
|
{
|
||||||
var dbSegment = new DbSegment(segment, mode);
|
if (existingIds.Contains(segment.ItemId))
|
||||||
if (existingSegments.TryGetValue(itemId, out var existing))
|
|
||||||
{
|
{
|
||||||
db.Entry(existing).CurrentValues.SetValues(dbSegment);
|
db.DbSegment.Update(segment);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
segmentsToAdd.Add(dbSegment);
|
db.DbSegment.Add(segment);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (segmentsToAdd.Count > 0)
|
|
||||||
{
|
|
||||||
await db.DbSegment.AddRangeAsync(segmentsToAdd).ConfigureAwait(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
await db.SaveChangesAsync().ConfigureAwait(false);
|
await db.SaveChangesAsync().ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -205,22 +205,22 @@ public class BaseItemAnalyzerTask
|
|||||||
|
|
||||||
var analyzers = new Collection<IMediaFileAnalyzer>();
|
var analyzers = new Collection<IMediaFileAnalyzer>();
|
||||||
|
|
||||||
if (action == AnalyzerAction.Chapter || action == AnalyzerAction.Default)
|
if (action is AnalyzerAction.Chapter or AnalyzerAction.Default)
|
||||||
{
|
{
|
||||||
analyzers.Add(new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>()));
|
analyzers.Add(new ChapterAnalyzer(_loggerFactory.CreateLogger<ChapterAnalyzer>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (first.IsAnime && !first.IsMovie && (action == AnalyzerAction.Chromaprint || action == AnalyzerAction.Default))
|
if (first.IsAnime && !first.IsMovie && action is AnalyzerAction.Chromaprint or AnalyzerAction.Default)
|
||||||
{
|
{
|
||||||
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
|
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode == AnalysisMode.Credits && (action == AnalyzerAction.BlackFrame || action == AnalyzerAction.Default))
|
if (mode is AnalysisMode.Credits && action is AnalyzerAction.BlackFrame or AnalyzerAction.Default)
|
||||||
{
|
{
|
||||||
analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger<BlackFrameAnalyzer>()));
|
analyzers.Add(new BlackFrameAnalyzer(_loggerFactory.CreateLogger<BlackFrameAnalyzer>()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!first.IsAnime && !first.IsMovie && (action == AnalyzerAction.Chromaprint || action == AnalyzerAction.Default))
|
if (!first.IsAnime && !first.IsMovie && action is AnalyzerAction.Chromaprint or AnalyzerAction.Default)
|
||||||
{
|
{
|
||||||
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
|
analyzers.Add(new ChromaprintAnalyzer(_loggerFactory.CreateLogger<ChromaprintAnalyzer>()));
|
||||||
}
|
}
|
||||||
@ -234,8 +234,9 @@ public class BaseItemAnalyzerTask
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add items without intros/credits to blacklist.
|
// Add items without intros/credits to blacklist.
|
||||||
var blacklisted = items.Where(e => !e.GetAnalyzed(mode)).ToList();
|
var blacklisted = new List<Segment>(items.Where(e => !e.GetAnalyzed(mode)).Select(e => new Segment(e.EpisodeId)));
|
||||||
await Plugin.Instance!.UpdateTimestamps(blacklisted.ToDictionary(e => e.EpisodeId, e => new Segment(e.EpisodeId)), mode).ConfigureAwait(false);
|
_logger.LogDebug("Blacklisting {Count} items for mode {Mode}", blacklisted.Count, mode);
|
||||||
|
await Plugin.Instance!.UpdateTimestamps(blacklisted, mode).ConfigureAwait(false);
|
||||||
totalItems -= blacklisted.Count;
|
totalItems -= blacklisted.Count;
|
||||||
|
|
||||||
return totalItems;
|
return totalItems;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user