📄
src/Domain/ClipGenerator.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
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, ClipDescriber clipDescriber ) { 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"); var description = await clipDescriber.DescribeClip(media, start, duration, cancellationToken); await clipExtractor.ExtractClip(media, start, duration, clipPath, cancellationToken); var clip = new Clip(clipId, media.Id, clipPath, start, duration, utcNow, description); 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>(); services.AddClipDescriber(); 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; } } }