Polish AmiReel UI and improve render workflow

This commit is contained in:
2026-08-11 13:55:44 +02:00
parent ad205bb534
commit d62ae0b9fb
38 changed files with 2711 additions and 183 deletions
+7 -5
View File
@@ -92,17 +92,18 @@ public sealed partial class ProcessRunner
private static bool IsNoise(ProcessOutput output)
{
if (output.Frame is not null || output.Time is not null)
return true;
string line = output.Line.TrimStart();
if (string.IsNullOrWhiteSpace(line))
return true;
if (line.StartsWith("frame=", StringComparison.OrdinalIgnoreCase))
return true;
if (output.Frame is not null || output.Time is not null)
return true;
if (line.StartsWith("ERROR:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("WARNING:", StringComparison.OrdinalIgnoreCase)
|| line.Contains("selected.", StringComparison.OrdinalIgnoreCase)
|| line.Contains("Falling back to FFmpeg from PATH", StringComparison.OrdinalIgnoreCase)
|| line.Contains("skipped", StringComparison.OrdinalIgnoreCase)
|| line.Contains("could not", StringComparison.OrdinalIgnoreCase)
|| line.Contains("failed", StringComparison.OrdinalIgnoreCase)
@@ -128,6 +129,7 @@ public sealed partial class ProcessRunner
|| line.StartsWith("Side data:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("encoder :", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("title :", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("CPB properties:", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Press [q] to stop", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("[", StringComparison.OrdinalIgnoreCase)
|| line.StartsWith("Duration:", StringComparison.OrdinalIgnoreCase)
+10 -11
View File
@@ -48,7 +48,7 @@ public sealed class RenderPipeline
"-r", settings.FramesPerSecond.ToString(CultureInfo.InvariantCulture)];
joinArgs.AddRange(EncoderArguments(encoder));
joinArgs.AddRange(["-c:a", "aac", "-b:a", "384k", "-ar", "48000", main]);
await RunStageAsync(tools.Ffmpeg, joinArgs, log, progress, "Joining recordings", 2, 48, joinDuration, token);
await RunStageAsync(tools.Ffmpeg, joinArgs, log, progress, "Joining recordings", 2, 48, joinDuration, settings.FramesPerSecond, token);
double originalDuration = await probe.ProbeDurationAsync(tools, main, token);
double trimmedDuration = originalDuration - settings.TrimStart;
@@ -104,7 +104,7 @@ public sealed class RenderPipeline
"-map", "[v]", "-map", "[a]", "-r", settings.FramesPerSecond.ToString()]);
finalArgs.AddRange(EncoderArguments(encoder));
finalArgs.AddRange(["-c:a", "aac", "-b:a", "384k", "-ar", "48000", "-movflags", "+faststart", final]);
await RunStageAsync(tools.Ffmpeg, finalArgs, log, progress, "Final render", 60, 99, finalDuration, token);
await RunStageAsync(tools.Ffmpeg, finalArgs, log, progress, "Final render", 60, 99, finalDuration, settings.FramesPerSecond, token);
progress.Report(new(100, "Complete", final));
}
finally
@@ -120,7 +120,7 @@ public sealed class RenderPipeline
try
{
await _runner.RunAsync(tools.Ffmpeg,
["-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "color=c=black:s=128x128:r=1",
["-hide_banner", "-loglevel", "error", "-f", "lavfi", "-i", "color=c=black:s=640x360:r=1",
"-frames:v", "1", "-an", "-c:v", "h264_nvenc", "-preset", "p6", "-f", "null", "-"],
null, null, token);
nvenc = true;
@@ -129,10 +129,9 @@ public sealed class RenderPipeline
{
nvenc = false;
}
if (nvenc) { log("NVIDIA NVENC selected."); return "nvenc"; }
if (nvenc) return "nvenc";
if (requested == EncoderMode.NvidiaNvenc)
throw new InvalidOperationException("NVIDIA NVENC was requested but could not initialize.");
log("NVENC unavailable; CPU libx264 selected.");
log("WARNING: NVIDIA NVENC was requested but could not initialize. Falling back to CPU libx264.");
return "x264";
}
@@ -175,7 +174,6 @@ public sealed class RenderPipeline
"AmiReel found FFprobe on PATH, but it could not be started.",
token);
log($"Configured or embedded FFmpeg tools are unusable. Falling back to FFmpeg from PATH: '{systemTools.Ffmpeg}' and '{systemTools.Ffprobe}'.");
return systemTools;
}
}
@@ -235,7 +233,7 @@ public sealed class RenderPipeline
}
private async Task RunStageAsync(string executable, IEnumerable<string> args, Action<string> log,
IProgress<RenderProgress> progress, string stageName, double from, double to, double? duration, CancellationToken token)
IProgress<RenderProgress> progress, string stageName, double from, double to, double? duration, int framesPerSecond, CancellationToken token)
{
await _runner.RunAsync(executable, args, log, null, token, outputHandler: output =>
{
@@ -243,22 +241,23 @@ public sealed class RenderPipeline
return;
double percent = from + Math.Min(1, time.TotalSeconds / duration.Value) * (to - from);
string message = BuildProgressMessage(time, duration.Value, output.FramesPerSecond, output.Speed, output.Frame);
string message = BuildProgressMessage(time, duration.Value, output.FramesPerSecond, output.Speed, output.Frame, framesPerSecond);
progress.Report(new(percent, stageName, message));
});
}
private static string BuildProgressMessage(TimeSpan current, double durationSeconds, double? fps, double? speed, int? frame)
private static string BuildProgressMessage(TimeSpan current, double durationSeconds, double? fps, double? speed, int? frame, int framesPerSecond)
{
TimeSpan total = TimeSpan.FromSeconds(durationSeconds);
List<string> parts = [$"{current:hh\\:mm\\:ss} / {total:hh\\:mm\\:ss}"];
int totalFrames = Math.Max(1, (int)Math.Ceiling(durationSeconds * framesPerSecond));
if (fps is > 0)
parts.Add($"{fps:0.#} fps");
if (speed is > 0)
parts.Add($"{speed:0.##}x");
if (frame is > 0)
parts.Add($"frame {frame.Value:N0}");
parts.Add($"frame {frame.Value:N0} / {totalFrames:N0}");
return string.Join(" · ", parts);
}
+3
View File
@@ -19,6 +19,9 @@ public static class ToolExtractor
return new ToolPaths(ffmpegPath, ffprobePath);
}
if (TryResolveFromSystemPath(out ToolPaths? systemTools) && systemTools is not null)
return systemTools;
return await ExtractAsync(cancellationToken);
}