📄 src/Domain/ClipGenerator.cs
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;

namespace Slopper.Domain;

public sealed class ClipGenerator(
    IOptions<ClipGeneratorOptions> options,
    TimeProvider timeProvider,
    IMediaRepository mediaRepository,
    ClipSelector clipSelector,
    IClipExtractor clipExtractor,
    IClipRepository clipRepository
)
{
    private readonly string clipDirectory = options.Value.ClipDirectory;

    public async Task<Clip> Generate(CancellationToken cancellationToken)
    {
        var media = await mediaRepository.GetRandomMediaItem(cancellationToken);
        var (start, duration) = await clipSelector.PickClip(media, cancellationToken);

        var utcNow = timeProvider.GetUtcNow();
        var clipId = Guid.CreateVersion7();
        var clipPath = Path.Join(clipDirectory, $"{clipId}.mp4");

        await clipExtractor.ExtractClip(media, start, duration, clipPath, cancellationToken);

        var clip = new Clip(clipId, media.Id, clipPath, start, duration, utcNow);
        await clipRepository.Save(clip, cancellationToken);

        return clip;
    }
}

public static class ClipGeneratorServiceCollectionExtensions
{
    extension(IServiceCollection services)
    {
        public IServiceCollection AddClipGenerator()
        {
            services.AddOptions<ClipGeneratorOptions>().BindConfiguration("").ValidateOnStart();
            services.AddTransient<IValidateOptions<ClipGeneratorOptions>, ClipGeneratorOptionsValidator>();

            services.TryAddSingleton(TimeProvider.System);
            services.AddTransient<ClipGenerator>();

            return services;
        }
    }
}

public sealed class ClipGeneratorOptions
{
    public required string ClipDirectory { get; set; }
}

public sealed class ClipGeneratorOptionsValidator : IValidateOptions<ClipGeneratorOptions>
{
    public ValidateOptionsResult Validate(string? name, ClipGeneratorOptions options)
    {
        var builder = new ValidateOptionsResultBuilder();

        ValidateClipDirectory(name, options.ClipDirectory, builder);

        return builder.Build();
    }

    private static void ValidateClipDirectory(string? name, string clipDirectory, ValidateOptionsResultBuilder builder)
    {
        string propertyName = string.IsNullOrEmpty(name)
            ? nameof(ClipGeneratorOptions.ClipDirectory)
            : $"{name}.{nameof(ClipGeneratorOptions.ClipDirectory)}";

        if (string.IsNullOrWhiteSpace(clipDirectory))
        {
            builder.AddError("Cannot be null or empty.", propertyName);
            return;
        }

        if (!Directory.Exists(clipDirectory))
        {
            builder.AddError("Directory must exist.", propertyName);
            return;
        }
    }
}