📄 src/Infrastructure/Ffmpeg/FrameExtractor.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using FFMpegCore;
using FFMpegCore.Pipes;
using Microsoft.Extensions.Logging;
using Slopper.Domain;

namespace Slopper.Infrastructure.Ffmpeg;

internal sealed class FrameExtractor(ILogger<FrameExtractor> logger) : IFrameExtractor
{
    public async Task<IReadOnlyList<byte[]>> ExtractFrames(
        MediaItem media,
        IReadOnlyList<TimeSpan> timestamps,
        CancellationToken cancellationToken
    )
    {
        var frames = new List<byte[]>(timestamps.Count);
        foreach (var timestamp in timestamps)
        {
            using var stream = new MemoryStream();
            var args = FFMpegArguments
                .FromFileInput(media.Path, addArguments: options => options.Seek(timestamp))
                .OutputToPipe(
                    new StreamPipeSink(stream),
                    addArguments: options =>
                        options
                            .WithArgument("-frames:v 1")
                            .WithVideoFilters(filter => filter.Scale(-1, 720))
                            .ForceFormat("image2pipe")
                            .WithArgument("-c:v png")
                )
                .CancellableThrough(cancellationToken);
            using var activity = Tracing.StartExtractFrame(media.Path, timestamp);
            logger.LogInformation("Running ffmpeg {FfmpegArguments}", args.Arguments);
            await args.ProcessAsynchronously();
            frames.Add(stream.ToArray());
        }
        return frames;
    }
}