From e6daa12abd7b1263b9df3bf9340f099952c20b38 Mon Sep 17 00:00:00 2001 From: Jason Dove <1695733+jasongdove@users.noreply.github.com> Date: Tue, 3 Feb 2026 08:12:37 -0600 Subject: [PATCH] refactor parsing ffmpeg progress/speed --- .../StartTroubleshootingPlaybackHandler.cs | 27 +++++-------------- ErsatzTV.Core/FFmpeg/FFmpegProgressParser.cs | 20 ++++++++++++++ ErsatzTV.FFmpeg/GlobalOption/NoStatsOption.cs | 2 +- .../GlobalOption/ProgressOption.cs | 6 +++++ .../Pipeline/PipelineBuilderBase.cs | 1 + 5 files changed, 34 insertions(+), 22 deletions(-) create mode 100644 ErsatzTV.Core/FFmpeg/FFmpegProgressParser.cs create mode 100644 ErsatzTV.FFmpeg/GlobalOption/ProgressOption.cs diff --git a/ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs b/ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs index b4724e8da..6a5529023 100644 --- a/ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs +++ b/ErsatzTV.Application/Troubleshooting/Commands/StartTroubleshootingPlaybackHandler.cs @@ -2,10 +2,10 @@ using System.IO.Pipelines; using System.Runtime.InteropServices; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.RegularExpressions; using CliWrap; using ErsatzTV.Core; using ErsatzTV.Core.Domain; +using ErsatzTV.Core.FFmpeg; using ErsatzTV.Core.Interfaces.Locking; using ErsatzTV.Core.Interfaces.Streaming; using ErsatzTV.Core.Interfaces.Troubleshooting; @@ -17,7 +17,7 @@ using Serilog.Events; namespace ErsatzTV.Application.Troubleshooting; -public partial class StartTroubleshootingPlaybackHandler( +public class StartTroubleshootingPlaybackHandler( ITroubleshootingNotifier notifier, IMediator mediator, IEntityLocker entityLocker, @@ -138,9 +138,12 @@ public partial class StartTroubleshootingPlaybackHandler( linkedCts.Token); } + var progressParser = new FFmpegProgressParser(); + CommandResult commandResult = await processWithPipe .WithWorkingDirectory(FileSystemLayout.TranscodeTroubleshootingFolder) .WithStandardErrorPipe(PipeTarget.Null) + .WithStandardOutputPipe(PipeTarget.ToDelegate(progressParser.ParseLine)) .WithValidation(CommandResultValidation.None) .ExecuteAsync(linkedCts.Token); @@ -160,26 +163,11 @@ public partial class StartTroubleshootingPlaybackHandler( // do nothing } - Option maybeSpeed = Option.None; - Option maybeFile = Directory.GetFiles(FileSystemLayout.TranscodeTroubleshootingFolder, "ffmpeg*.log").HeadOrNone(); - foreach (string file in maybeFile) - { - await foreach (string line in File.ReadLinesAsync(file, linkedCts.Token)) - { - Match match = FFmpegSpeed().Match(line); - if (match.Success && double.TryParse(match.Groups[1].Value, out double speed)) - { - maybeSpeed = speed; - break; - } - } - } - await mediator.Publish( new PlaybackTroubleshootingCompletedNotification( commandResult.ExitCode, Option.None, - maybeSpeed), + progressParser.Speed), linkedCts.Token); if (commandResult.ExitCode != 0) @@ -210,7 +198,4 @@ public partial class StartTroubleshootingPlaybackHandler( loggingLevelSwitches.StreamingLevelSwitch.MinimumLevel = currentStreamingLevel; } } - - [GeneratedRegex(@"speed=\s*([\d\.]+)x", RegexOptions.IgnoreCase)] - private static partial Regex FFmpegSpeed(); } diff --git a/ErsatzTV.Core/FFmpeg/FFmpegProgressParser.cs b/ErsatzTV.Core/FFmpeg/FFmpegProgressParser.cs new file mode 100644 index 000000000..7b331e4f5 --- /dev/null +++ b/ErsatzTV.Core/FFmpeg/FFmpegProgressParser.cs @@ -0,0 +1,20 @@ +using System.Text.RegularExpressions; + +namespace ErsatzTV.Core.FFmpeg; + +public partial class FFmpegProgressParser +{ + public Option Speed { get; private set; } = Option.None; + + public void ParseLine(string line) + { + Match match = FFmpegSpeed().Match(line); + if (match.Success && double.TryParse(match.Groups[1].Value, out double speed)) + { + Speed = speed; + } + } + + [GeneratedRegex(@"speed=\s*([\d\.]+)x", RegexOptions.IgnoreCase)] + private static partial Regex FFmpegSpeed(); +} diff --git a/ErsatzTV.FFmpeg/GlobalOption/NoStatsOption.cs b/ErsatzTV.FFmpeg/GlobalOption/NoStatsOption.cs index b1aaf6ed2..2a96cea7b 100644 --- a/ErsatzTV.FFmpeg/GlobalOption/NoStatsOption.cs +++ b/ErsatzTV.FFmpeg/GlobalOption/NoStatsOption.cs @@ -2,5 +2,5 @@ public class NoStatsOption : GlobalOption { - public override string[] GlobalOptions => new[] { "-nostats" }; + public override string[] GlobalOptions => ["-nostats"]; } diff --git a/ErsatzTV.FFmpeg/GlobalOption/ProgressOption.cs b/ErsatzTV.FFmpeg/GlobalOption/ProgressOption.cs new file mode 100644 index 000000000..20796068a --- /dev/null +++ b/ErsatzTV.FFmpeg/GlobalOption/ProgressOption.cs @@ -0,0 +1,6 @@ +namespace ErsatzTV.FFmpeg.GlobalOption; + +public class ProgressOption : GlobalOption +{ + public override string[] GlobalOptions => ["-progress", "-"]; +} diff --git a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs index 7672023df..30779e663 100644 --- a/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs +++ b/ErsatzTV.FFmpeg/Pipeline/PipelineBuilderBase.cs @@ -211,6 +211,7 @@ public abstract class PipelineBuilderBase : IPipelineBuilder new NoStandardInputOption(), new HideBannerOption(), new NoStatsOption(), + new ProgressOption(), new LoglevelErrorOption(), new StandardFormatFlags(), new NoDemuxDecodeDelayOutputOption(),