From 6603500132f36684893e64c02cf2cf9c657d8d2f Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Wed, 12 Nov 2025 20:07:54 -0600 Subject: [PATCH] fix content_total_duration in graphics engine (#2643) --- CHANGELOG.md | 4 +++- ...layoutItemProcessByChannelNumberHandler.cs | 2 ++ .../PrepareTroubleshootingPlaybackHandler.cs | 1 + .../FFmpeg/FFmpegLibraryProcessService.cs | 4 +++- .../FFmpeg/IFFmpegProcessService.cs | 1 + .../Streaming/GraphicsEngineContext.cs | 3 ++- .../BlockPlayoutFillerBuilder.cs | 20 +++++++++---------- .../Streaming/Graphics/GraphicsEngine.cs | 2 +- .../Core/FFmpeg/TranscodingTests.cs | 2 ++ 9 files changed, 24 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 43b08ee25..80476e3fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -50,7 +50,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - All `etv:` nodes will be stripped from the XMLTV data when requested by a client - Add channel troubleshooting button to channels list - This will open the playback troubleshooting tool in "channel" mode - - This mode requires entering a date and time, and will play up to 30 seconds of *one item from that channel's playout* + - This mode requires entering a date and time, and will play up to 30 seconds of *one item from that channel's playout* starting at the entered date and time ### Fixed - Fix HLS Direct playback with Jellyfin 10.11 @@ -75,6 +75,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Fix (3 year old) bug removing tags from local libraries when they are removed from NFO files (all content types) - New scans will properly remove old tags; NFO files may need to be touched to force updating during a scan - Fix bug where looping motion graphics wouldn't be displayed when seeking into second half of content +- Fix `content_total_duration` value in graphics engine opacity expressions + - This bug caused some graphics elements to display too early after first joining a channel ### Changed - Use smaller batch size for search index updates (100, down from 1000) diff --git a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs index 274a08c56..834ebbdef 100644 --- a/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs +++ b/ErsatzTV.Application/Streaming/Queries/GetPlayoutItemProcessByChannelNumberHandler.cs @@ -280,6 +280,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< TimeSpan outPoint = playoutItemWithPath.PlayoutItem.OutPoint; DateTimeOffset effectiveNow = request.StartAtZero ? start : now; TimeSpan duration = finish - effectiveNow; + TimeSpan originalDuration = duration; bool isComplete = true; @@ -434,6 +435,7 @@ public class GetPlayoutItemProcessByChannelNumberHandler : FFmpegProcessHandler< start, finish, effectiveNow, + originalDuration, watermarks, graphicsElements, channel.FFmpegProfile.VaapiDisplay, diff --git a/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs b/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs index 55bb09cfb..0f5afb374 100644 --- a/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs +++ b/ErsatzTV.Application/Troubleshooting/Commands/PrepareTroubleshootingPlaybackHandler.cs @@ -303,6 +303,7 @@ public class PrepareTroubleshootingPlaybackHandler( now, now + duration, now, + duration, watermarks, graphicsElements.Map(ge => new PlayoutItemGraphicsElement { GraphicsElement = ge }).ToList(), ffmpegProfile.VaapiDisplay, diff --git a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs index cdd5e309e..53ebee4f1 100644 --- a/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs +++ b/ErsatzTV.Core/FFmpeg/FFmpegLibraryProcessService.cs @@ -85,6 +85,7 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService DateTimeOffset start, DateTimeOffset finish, DateTimeOffset now, + TimeSpan originalContentDuration, List watermarks, List graphicsElements, string vaapiDisplay, @@ -530,7 +531,8 @@ public class FFmpegLibraryProcessService : IFFmpegProcessService channelStartTime, start, await playbackSettings.StreamSeek.IfNoneAsync(TimeSpan.Zero), - finish - now); + finish - now, + originalContentDuration); context = await _graphicsElementLoader.LoadAll(context, graphicsElements, cancellationToken); diff --git a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs index 499431436..b9a8273ca 100644 --- a/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs +++ b/ErsatzTV.Core/Interfaces/FFmpeg/IFFmpegProcessService.cs @@ -26,6 +26,7 @@ public interface IFFmpegProcessService DateTimeOffset start, DateTimeOffset finish, DateTimeOffset now, + TimeSpan originalContentDuration, List watermarks, List graphicsElements, string vaapiDisplay, diff --git a/ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs b/ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs index 65c609c18..708389fad 100644 --- a/ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs +++ b/ErsatzTV.Core/Interfaces/Streaming/GraphicsEngineContext.cs @@ -15,7 +15,8 @@ public record GraphicsEngineContext( DateTimeOffset ChannelStartTime, DateTimeOffset ContentStartTime, TimeSpan Seek, - TimeSpan Duration); + TimeSpan Duration, + TimeSpan ContentTotalDuration); public abstract record GraphicsElementContext; diff --git a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs index 1f1773f38..d5132c4e5 100644 --- a/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs +++ b/ErsatzTV.Core/Scheduling/BlockScheduling/BlockPlayoutFillerBuilder.cs @@ -65,18 +65,15 @@ public class BlockPlayoutFillerBuilder( var collectionEnumerators = new Dictionary(); - // history doesn't have an equivalent to "remove before" - // so for break content, let's remove all corresponding items that should be removed - var itemsForBreakContent = allItems.Where(x => x.FinishOffset >= removeBefore).ToList(); - var breakContentResult = await AddBreakContent( playout, referenceData, mode, collectionEnumerators, - itemsForBreakContent, + allItems, filteredExistingHistory, result.AddedHistory, + result.RemoveBefore, cancellationToken); // merge break content result @@ -96,17 +93,14 @@ public class BlockPlayoutFillerBuilder( .ToList(); allItems.AddRange(result.AddedItems); - // history doesn't have an equivalent to "remove before" - // so for break content, let's remove all corresponding items that should be removed - itemsForBreakContent = allItems.Where(x => x.FinishOffset >= removeBefore).ToList(); - result = await AddDefaultFiller( playout, referenceData, result, collectionEnumerators, - itemsForBreakContent, + allItems, filteredExistingHistory, + result.RemoveBefore, cancellationToken); return result; @@ -120,6 +114,7 @@ public class BlockPlayoutFillerBuilder( IReadOnlyCollection allItems, IReadOnlyCollection filteredExistingHistory, IReadOnlyCollection addedHistory, + Option removeBefore, CancellationToken cancellationToken) { var result = PlayoutBuildResult.Empty; @@ -134,7 +129,9 @@ public class BlockPlayoutFillerBuilder( // guide group is template item id // they are reused over multiple days, so we only want to group consecutive items - IEnumerable> consecutiveBlocks = allItems.GroupConsecutiveBy(item => item.GuideGroup); + IEnumerable> consecutiveBlocks = allItems + .Where(i => i.FinishOffset > result.RemoveBefore.IfNone(SystemTime.MinValueUtc)) + .GroupConsecutiveBy(item => item.GuideGroup); foreach (IGrouping blockGroup in consecutiveBlocks) { var itemsInBlock = blockGroup.ToList(); @@ -306,6 +303,7 @@ public class BlockPlayoutFillerBuilder( Dictionary collectionEnumerators, List allItems, List filteredExistingHistory, + Option removeBefore, CancellationToken cancellationToken) { // find all unscheduled periods diff --git a/ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsEngine.cs b/ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsEngine.cs index 96b46dc65..82f7f2e2b 100644 --- a/ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsEngine.cs +++ b/ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsEngine.cs @@ -94,7 +94,7 @@ public class GraphicsEngine( try { // `content_total_seconds` - the total number of seconds in the content - TimeSpan contentTotalTime = context.Seek + context.Duration; + TimeSpan contentTotalTime = context.Seek + context.ContentTotalDuration; while (!cancellationToken.IsCancellationRequested && frameCount < totalFrames) { diff --git a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs index b9ae0c590..fb60b21f4 100644 --- a/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs +++ b/ErsatzTV.Scanner.Tests/Core/FFmpeg/TranscodingTests.cs @@ -390,6 +390,7 @@ public class TranscodingTests now, now + TimeSpan.FromSeconds(3), now, + TimeSpan.FromSeconds(3), watermarks, [], "drm", @@ -710,6 +711,7 @@ public class TranscodingTests now, now + TimeSpan.FromSeconds(3), now, + TimeSpan.FromSeconds(3), watermarks, [], "drm",