Files
MilkyShots/Lactose/Jobs/FfmpegRunner.cs
T
REDCODE f940d5603a feat: convert GIFs to mp4 via ffmpeg with first-frame thumbnail
Add ConvertAnimatedJob (EJobType.AnimatedConversion) targeting image/gif
assets: extracts a static WebP first-frame thumbnail and converts the full
animation to mp4/H.264, leaving originals untouched. New ConvertedPath/
ConvertedFormat/FfmpegPath settings, PathUtils extension parameter,
FfmpegRunner helper, and Dockerfile ffmpeg install. Thumbnail/preview
jobs now skip GIFs (handled by the conversion job) and IntegrityCheck
validates converted outputs. Failed conversions are marked broken.

Refs #181
2026-08-18 11:13:48 +02:00

54 lines
2.2 KiB
C#

using System.Diagnostics;
using Microsoft.Extensions.Logging;
namespace Lactose.Jobs;
/// <summary>
/// Runs ffmpeg/ffprobe invocations for media conversion jobs.
/// </summary>
internal static class FfmpegRunner {
/// <summary>
/// Executes ffmpeg with the given arguments, capturing output for logging.
/// </summary>
/// <param name="ffmpegPath">Path to the ffmpeg executable.</param>
/// <param name="arguments">Command-line arguments to pass to ffmpeg.</param>
/// <param name="logger">Logger for ffmpeg output.</param>
/// <param name="token">Cancellation token; the process is killed if cancelled.</param>
/// <exception cref="InvalidOperationException">Thrown when ffmpeg exits with a non-zero exit code.</exception>
internal static async Task RunAsync(string ffmpegPath, string arguments, ILogger logger, CancellationToken token) {
var startInfo = new ProcessStartInfo {
FileName = ffmpegPath,
Arguments = arguments,
RedirectStandardOutput = true,
RedirectStandardError = true,
UseShellExecute = false,
CreateNoWindow = true
};
using var process = new Process { StartInfo = startInfo };
process.Start();
var stdoutTask = process.StandardOutput.ReadToEndAsync();
var stderrTask = process.StandardError.ReadToEndAsync();
try {
await process.WaitForExitAsync(token);
} catch (OperationCanceledException) {
try { process.Kill(entireProcessTree: true); } catch { /* already exited */ }
throw;
}
var stdout = await stdoutTask;
var stderr = await stderrTask;
if (!string.IsNullOrEmpty(stdout))
logger.LogTrace("ffmpeg stdout: {Output}", stdout);
if (!string.IsNullOrEmpty(stderr))
logger.LogTrace("ffmpeg stderr: {Output}", stderr);
if (process.ExitCode != 0) {
logger.LogError("ffmpeg exited with code {Code}. Stderr: {Error}", process.ExitCode, stderr);
throw new InvalidOperationException($"ffmpeg exited with code {process.ExitCode}.");
}
}
}