Fix bug where timestamps were reset to zero.

This commit is contained in:
rlauu 2024-11-06 10:30:00 +01:00
parent 327fe99de0
commit f68fd9d06a
10 changed files with 63 additions and 63 deletions

View File

@ -36,20 +36,21 @@ namespace IntroSkipper.Analyzers
/// <param name="originalIntros">Original introductions.</param>
/// <param name="mode">Analysis mode.</param>
/// <returns>Modified Intro Timestamps.</returns>
public Dictionary<Guid, Segment> AdjustIntroTimes(
public IReadOnlyList<Segment> AdjustIntroTimes(
IReadOnlyList<QueuedEpisode> episodes,
IReadOnlyDictionary<Guid, Segment> originalIntros,
IReadOnlyList<Segment> originalIntros,
AnalysisMode mode)
{
return episodes
.Where(episode => originalIntros.TryGetValue(episode.EpisodeId, out var _))
.ToDictionary(
episode => episode.EpisodeId,
episode => AdjustIntroForEpisode(episode, originalIntros[episode.EpisodeId], mode));
return originalIntros.Select(i => AdjustIntroForEpisode(episodes.FirstOrDefault(e => originalIntros.Any(i => i.EpisodeId == e.EpisodeId)), i, mode)).ToList();
}
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);
var adjustedIntro = new Segment(originalIntro);

View File

@ -37,9 +37,7 @@ public class BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) : IMediaFile
throw new NotImplementedException("mode must equal Credits");
}
var creditTimes = new Dictionary<Guid, Segment>();
var episodeAnalysisQueue = new List<QueuedEpisode>(analysisQueue);
var creditTimes = new List<Segment>();
bool isFirstEpisode = true;
@ -47,7 +45,7 @@ public class BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) : IMediaFile
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)
{
@ -116,16 +114,16 @@ public class BlackFrameAnalyzer(ILogger<BlackFrameAnalyzer> logger) : IMediaFile
searchStart = episode.Duration - credit.Start + (0.5 * searchDistance);
creditTimes.Add(episode.EpisodeId, credit);
creditTimes.Add(credit);
episode.SetAnalyzed(mode, true);
}
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>

View File

@ -24,7 +24,7 @@ namespace IntroSkipper.Analyzers;
/// <param name="logger">Logger.</param>
public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyzer
{
private ILogger<ChapterAnalyzer> _logger = logger;
private readonly ILogger<ChapterAnalyzer> _logger = logger;
/// <inheritdoc />
public async Task<IReadOnlyList<QueuedEpisode>> AnalyzeMediaFiles(
@ -32,10 +32,7 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
AnalysisMode mode,
CancellationToken cancellationToken)
{
var skippableRanges = new Dictionary<Guid, Segment>();
// Episode analysis queue.
var episodeAnalysisQueue = new List<QueuedEpisode>(analysisQueue);
var skippableRanges = new List<Segment>();
var expression = mode == AnalysisMode.Introduction ?
Plugin.Instance!.Configuration.ChapterAnalyzerIntroductionPattern :
@ -46,7 +43,7 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
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)
{
@ -64,13 +61,13 @@ public class ChapterAnalyzer(ILogger<ChapterAnalyzer> logger) : IMediaFileAnalyz
continue;
}
skippableRanges.Add(episode.EpisodeId, skipRange);
skippableRanges.Add(skipRange);
episode.SetAnalyzed(mode, true);
}
await Plugin.Instance.UpdateTimestamps(skippableRanges, mode).ConfigureAwait(false);
return episodeAnalysisQueue;
return analysisQueue;
}
/// <summary>

View File

@ -208,9 +208,9 @@ public class ChromaprintAnalyzer : IMediaFileAnalyzer
// Adjust all introduction times.
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;
}

View File

@ -75,14 +75,14 @@ public class SkipIntroController(MediaSegmentUpdateManager mediaSegmentUpdateMan
if (timestamps?.Introduction.End > 0.0)
{
var tr = 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);
var seg = new Segment(id, new TimeRange(timestamps.Introduction.Start, timestamps.Introduction.End));
await Plugin.Instance!.UpdateTimestamps([seg], AnalysisMode.Introduction).ConfigureAwait(false);
}
if (timestamps?.Credits.End > 0.0)
{
var tr = 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);
var seg = new Segment(id, new TimeRange(timestamps.Credits.Start, timestamps.Credits.End));
await Plugin.Instance!.UpdateTimestamps([seg], AnalysisMode.Credits).ConfigureAwait(false);
}
if (Plugin.Instance.Configuration.UpdateMediaSegments)

View File

@ -11,7 +11,7 @@ namespace IntroSkipper.Data;
/// </summary>
public class QueuedEpisode
{
private readonly Dictionary<AnalysisMode, bool> _isAnalyzed = [];
private readonly bool[] _isAnalyzed = new bool[2];
/// <summary>
/// Gets or sets the series name.
@ -36,7 +36,7 @@ public class QueuedEpisode
/// <summary>
/// Gets a value indicating whether this media has been already analyzed.
/// </summary>
public IReadOnlyDictionary<AnalysisMode, bool> IsAnalyzed => _isAnalyzed;
public IReadOnlyList<bool> IsAnalyzed => _isAnalyzed;
/// <summary>
/// Gets or sets the full path to episode.
@ -80,7 +80,7 @@ public class QueuedEpisode
/// <param name="value">Value to set.</param>
public void SetAnalyzed(AnalysisMode mode, bool value)
{
_isAnalyzed[mode] = value;
_isAnalyzed[(int)mode] = value;
}
/// <summary>
@ -90,6 +90,6 @@ public class QueuedEpisode
/// <returns>Value of the analyzed mode.</returns>
public bool GetAnalyzed(AnalysisMode mode)
{
return _isAnalyzed.TryGetValue(mode, out var value) && value;
return _isAnalyzed[(int)mode];
}
}

View File

@ -33,8 +33,8 @@ public class Segment
public Segment(Guid episode)
{
EpisodeId = episode;
Start = 0;
End = 0;
Start = 0.0;
End = 0.0;
}
/// <summary>
@ -88,7 +88,7 @@ public class Segment
/// Gets a value indicating whether this introduction is valid or not.
/// Invalid results must not be returned through the API.
/// </summary>
public bool Valid => End > 0;
public bool Valid => End > 0.0;
/// <summary>
/// Gets the duration of this intro.

View File

@ -292,7 +292,7 @@ namespace IntroSkipper.Manager
VerifyQueue(IReadOnlyList<QueuedEpisode> candidates, IReadOnlyCollection<AnalysisMode> modes)
{
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)
{
@ -315,8 +315,10 @@ namespace IntroSkipper.Manager
{
candidate.SetAnalyzed(mode, true);
}
reqModes.Remove(mode);
}
else
{
reqModes.Add(mode);
}
}
}

View File

@ -301,36 +301,37 @@ public class Plugin : BasePlugin<PluginConfiguration>, IHasWebPages
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);
// Get all existing segments in a single query
var existingSegments = db.DbSegment
.Where(s => newTimestamps.Keys.Contains(s.ItemId) && s.Type == mode)
.ToDictionary(s => s.ItemId);
var segments = newTimestamps.Select(s => new DbSegment(s, mode)).ToList();
// Batch updates and inserts
var segmentsToAdd = new List<DbSegment>();
var newItemIds = segments.Select(s => s.ItemId).ToHashSet();
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 (existingSegments.TryGetValue(itemId, out var existing))
if (existingIds.Contains(segment.ItemId))
{
db.Entry(existing).CurrentValues.SetValues(dbSegment);
db.DbSegment.Update(segment);
}
else
{
segmentsToAdd.Add(dbSegment);
db.DbSegment.Add(segment);
}
}
if (segmentsToAdd.Count > 0)
{
await db.DbSegment.AddRangeAsync(segmentsToAdd).ConfigureAwait(false);
}
await db.SaveChangesAsync().ConfigureAwait(false);
}

View File

@ -205,22 +205,22 @@ public class BaseItemAnalyzerTask
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>()));
}
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>()));
}
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>()));
}
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>()));
}
@ -234,8 +234,9 @@ public class BaseItemAnalyzerTask
}
// Add items without intros/credits to blacklist.
var blacklisted = items.Where(e => !e.GetAnalyzed(mode)).ToList();
await Plugin.Instance!.UpdateTimestamps(blacklisted.ToDictionary(e => e.EpisodeId, e => new Segment(e.EpisodeId)), mode).ConfigureAwait(false);
var blacklisted = new List<Segment>(items.Where(e => !e.GetAnalyzed(mode)).Select(e => new Segment(e.EpisodeId)));
_logger.LogDebug("Blacklisting {Count} items for mode {Mode}", blacklisted.Count, mode);
await Plugin.Instance!.UpdateTimestamps(blacklisted, mode).ConfigureAwait(false);
totalItems -= blacklisted.Count;
return totalItems;