Browse Source

fix motion element timing (#2430)

pull/2433/head
Jason Dove 4 months ago committed by GitHub
parent
commit
a0e0bb8753
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
  1. 3
      ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsElement.cs
  2. 5
      ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsEngine.cs
  3. 3
      ErsatzTV.Infrastructure/Streaming/Graphics/IGraphicsElement.cs
  4. 3
      ErsatzTV.Infrastructure/Streaming/Graphics/Image/ImageElement.cs
  5. 3
      ErsatzTV.Infrastructure/Streaming/Graphics/Image/WatermarkElement.cs
  6. 48
      ErsatzTV.Infrastructure/Streaming/Graphics/Motion/MotionElement.cs
  7. 3
      ErsatzTV.Infrastructure/Streaming/Graphics/Subtitle/SubtitleElement.cs
  8. 5
      ErsatzTV.Infrastructure/Streaming/Graphics/Text/TextElement.cs

3
ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsElement.cs

@ -8,12 +8,13 @@ public abstract class GraphicsElement : IGraphicsElement @@ -8,12 +8,13 @@ public abstract class GraphicsElement : IGraphicsElement
{
public int ZIndex { get; protected set; }
public bool IsFailed { get; set; }
public bool IsFinished { get; set; }
public abstract Task InitializeAsync(
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken);
public abstract ValueTask<Option<PreparedElementImage>> PrepareImage(

5
ErsatzTV.Infrastructure/Streaming/Graphics/GraphicsEngine.cs

@ -86,6 +86,7 @@ public class GraphicsEngine( @@ -86,6 +86,7 @@ public class GraphicsEngine(
context.SquarePixelFrameSize,
context.FrameSize,
context.FrameRate,
context.Seek,
cancellationToken)));
long frameCount = 0;
@ -122,7 +123,7 @@ public class GraphicsEngine( @@ -122,7 +123,7 @@ public class GraphicsEngine(
// prepare images outside mutate to allow async image generation
var preparedElementImages = new List<PreparedElementImage>();
foreach (IGraphicsElement element in elements.Where(e => !e.IsFailed).OrderBy(e => e.ZIndex))
foreach (IGraphicsElement element in elements.Where(e => !e.IsFinished).OrderBy(e => e.ZIndex))
{
try
{
@ -137,7 +138,7 @@ public class GraphicsEngine( @@ -137,7 +138,7 @@ public class GraphicsEngine(
}
catch (Exception ex)
{
element.IsFailed = true;
element.IsFinished = true;
logger.LogWarning(
ex,
"Failed to draw graphics element of type {Type}; will disable for this content",

3
ErsatzTV.Infrastructure/Streaming/Graphics/IGraphicsElement.cs

@ -6,12 +6,13 @@ public interface IGraphicsElement @@ -6,12 +6,13 @@ public interface IGraphicsElement
{
int ZIndex { get; }
bool IsFailed { get; set; }
bool IsFinished { get; set; }
Task InitializeAsync(
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken);
ValueTask<Option<PreparedElementImage>> PrepareImage(

3
ErsatzTV.Infrastructure/Streaming/Graphics/Image/ImageElement.cs

@ -15,6 +15,7 @@ public class ImageElement(ImageGraphicsElement imageGraphicsElement, ILogger log @@ -15,6 +15,7 @@ public class ImageElement(ImageGraphicsElement imageGraphicsElement, ILogger log
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken)
{
try
@ -51,7 +52,7 @@ public class ImageElement(ImageGraphicsElement imageGraphicsElement, ILogger log @@ -51,7 +52,7 @@ public class ImageElement(ImageGraphicsElement imageGraphicsElement, ILogger log
}
catch (Exception ex)
{
IsFailed = true;
IsFinished = true;
logger.LogWarning(ex, "Failed to initialize image element; will disable for this content");
}
}

3
ErsatzTV.Infrastructure/Streaming/Graphics/Image/WatermarkElement.cs

@ -32,6 +32,7 @@ public class WatermarkElement : ImageElementBase @@ -32,6 +32,7 @@ public class WatermarkElement : ImageElementBase
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken)
{
try
@ -80,7 +81,7 @@ public class WatermarkElement : ImageElementBase @@ -80,7 +81,7 @@ public class WatermarkElement : ImageElementBase
}
catch (Exception ex)
{
IsFailed = true;
IsFinished = true;
_logger.LogWarning(ex, "Failed to initialize watermark element; will disable for this content");
}
}

48
ErsatzTV.Infrastructure/Streaming/Graphics/Motion/MotionElement.cs

@ -1,4 +1,5 @@ @@ -1,4 +1,5 @@
using System.Buffers;
using System.Globalization;
using System.IO.Pipelines;
using CliWrap;
using ErsatzTV.Core;
@ -55,17 +56,32 @@ public class MotionElement( @@ -55,17 +56,32 @@ public class MotionElement(
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken)
{
try
{
_startTime = TimeSpan.FromSeconds(motionElement.StartSeconds ?? 0);
ProbeResult probeResult = await ProbeMotionElement(frameSize);
var overlayDuration = probeResult.Duration;
// already past the time when this is supposed to play; don't do any more work
if (_startTime + overlayDuration < seek)
{
IsFinished = true;
return;
}
var pipe = new Pipe();
_pipeReader = pipe.Reader;
_startTime = TimeSpan.FromSeconds(motionElement.StartSeconds ?? 0);
var overlaySeekTime = TimeSpan.Zero;
if (_startTime < seek)
{
overlaySeekTime = seek - _startTime;
}
SizeAndDecoder sizeAndDecoder = await ProbeMotionElement(frameSize);
Resolution sourceSize = sizeAndDecoder.Size;
Resolution sourceSize = probeResult.Size;
int scaledWidth = sourceSize.Width;
int scaledHeight = sourceSize.Height;
@ -118,23 +134,30 @@ public class MotionElement( @@ -118,23 +134,30 @@ public class MotionElement(
List<string> arguments = ["-nostdin", "-hide_banner", "-nostats", "-loglevel", "error"];
foreach (string decoder in sizeAndDecoder.Decoder)
foreach (string decoder in probeResult.Decoder)
{
arguments.AddRange(["-c:v", decoder]);
}
if (overlaySeekTime > TimeSpan.Zero)
{
arguments.AddRange(["-ss", overlaySeekTime.TotalSeconds.ToString(CultureInfo.InvariantCulture)]);
}
arguments.AddRange(
[
"-i", motionElement.VideoPath,
]);
var videoFilter = $"fps={frameRate}";
if (motionElement.Scale)
{
arguments.AddRange(["-vf", $"scale={targetSize.Width}:{targetSize.Height}"]);
videoFilter += $",scale={targetSize.Width}:{targetSize.Height}";
}
arguments.AddRange(
[
"-vf", videoFilter,
"-f", "image2pipe",
"-pix_fmt", "bgra",
"-vcodec", "rawvideo",
@ -157,7 +180,7 @@ public class MotionElement( @@ -157,7 +180,7 @@ public class MotionElement(
}
catch (Exception ex)
{
IsFailed = true;
IsFinished = true;
logger.LogWarning(ex, "Failed to initialize motion element; will disable for this content");
}
}
@ -169,7 +192,7 @@ public class MotionElement( @@ -169,7 +192,7 @@ public class MotionElement(
TimeSpan channelTime,
CancellationToken cancellationToken)
{
if (contentTime < _startTime || _isFinished)
if (_isFinished || contentTime < _startTime)
{
return Option<PreparedElementImage>.None;
}
@ -225,7 +248,7 @@ public class MotionElement( @@ -225,7 +248,7 @@ public class MotionElement(
}
}
private async Task<SizeAndDecoder> ProbeMotionElement(Resolution frameSize)
private async Task<ProbeResult> ProbeMotionElement(Resolution frameSize)
{
try
{
@ -249,9 +272,10 @@ public class MotionElement( @@ -249,9 +272,10 @@ public class MotionElement(
};
}
return new SizeAndDecoder(
return new ProbeResult(
new Resolution { Width = mediaVersion.Width, Height = mediaVersion.Height },
decoder);
decoder,
mediaVersion.Duration);
}
}
}
@ -260,8 +284,8 @@ public class MotionElement( @@ -260,8 +284,8 @@ public class MotionElement(
// do nothing
}
return new SizeAndDecoder(frameSize, Option<string>.None);
return new ProbeResult(frameSize, Option<string>.None, TimeSpan.Zero);
}
private record SizeAndDecoder(Resolution Size, Option<string> Decoder);
private record ProbeResult(Resolution Size, Option<string> Decoder, TimeSpan Duration);
}

3
ErsatzTV.Infrastructure/Streaming/Graphics/Subtitle/SubtitleElement.cs

@ -56,6 +56,7 @@ public class SubtitleElement( @@ -56,6 +56,7 @@ public class SubtitleElement(
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken)
{
try
@ -112,7 +113,7 @@ public class SubtitleElement( @@ -112,7 +113,7 @@ public class SubtitleElement(
}
catch (Exception ex)
{
IsFailed = true;
IsFinished = true;
logger.LogWarning(ex, "Failed to initialize subtitle element; will disable for this content");
}
}

5
ErsatzTV.Infrastructure/Streaming/Graphics/Text/TextElement.cs

@ -3,8 +3,6 @@ using ErsatzTV.Core.Domain; @@ -3,8 +3,6 @@ using ErsatzTV.Core.Domain;
using ErsatzTV.Core.Graphics;
using Microsoft.Extensions.Logging;
using NCalc;
using Scriban;
using Scriban.Runtime;
using SkiaSharp;
using RichTextKit = Topten.RichTextKit;
@ -35,6 +33,7 @@ public partial class TextElement( @@ -35,6 +33,7 @@ public partial class TextElement(
Resolution squarePixelFrameSize,
Resolution frameSize,
int frameRate,
TimeSpan seek,
CancellationToken cancellationToken)
{
try
@ -92,7 +91,7 @@ public partial class TextElement( @@ -92,7 +91,7 @@ public partial class TextElement(
}
catch (Exception ex)
{
IsFailed = true;
IsFinished = true;
logger.LogWarning(ex, "Failed to initialize text element; will disable for this content");
}

Loading…
Cancel
Save