first commit
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
using Fragalytics.Web.Infrastructure;
|
||||
using Fragalytics.Web.Models;
|
||||
using Microsoft.Extensions.Options;
|
||||
|
||||
namespace Fragalytics.Web.Services;
|
||||
|
||||
public sealed class ClipMediaService
|
||||
{
|
||||
private static readonly HashSet<string> AllowedExtensions = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
".mp4",
|
||||
".webm"
|
||||
};
|
||||
|
||||
private readonly JsonAppStore _store;
|
||||
private readonly FragalyticsOptions _options;
|
||||
|
||||
public ClipMediaService(JsonAppStore store, IOptions<FragalyticsOptions> options)
|
||||
{
|
||||
_store = store;
|
||||
_options = options.Value;
|
||||
}
|
||||
|
||||
public async Task<int> AttachClipAssetAsync(
|
||||
int clipJobId,
|
||||
string fileName,
|
||||
string? contentType,
|
||||
Stream input,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var extension = Path.GetExtension(fileName);
|
||||
if (!AllowedExtensions.Contains(extension))
|
||||
{
|
||||
throw new InvalidOperationException("Only .mp4 and .webm clip uploads are supported.");
|
||||
}
|
||||
|
||||
var clipsDirectory = Path.Combine(Path.GetFullPath(_options.DataDirectory), "clips");
|
||||
Directory.CreateDirectory(clipsDirectory);
|
||||
|
||||
var safeFileName = Path.GetFileName(fileName);
|
||||
var storedName = $"{DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()}_{clipJobId}{extension}";
|
||||
var storedPath = Path.Combine(clipsDirectory, storedName);
|
||||
|
||||
long fileSize;
|
||||
await using (var output = File.Create(storedPath))
|
||||
{
|
||||
await input.CopyToAsync(output, cancellationToken);
|
||||
fileSize = output.Length;
|
||||
}
|
||||
|
||||
return await _store.UpdateAsync(state =>
|
||||
{
|
||||
var clipJob = state.ClipJobs.FirstOrDefault(item => item.Id == clipJobId)
|
||||
?? throw new InvalidOperationException("Clip job not found.");
|
||||
|
||||
if (clipJob.ClipAssetId is int existingAssetId)
|
||||
{
|
||||
var existingAsset = state.ClipAssets.FirstOrDefault(item => item.Id == existingAssetId);
|
||||
if (existingAsset is not null && File.Exists(existingAsset.StoredPath))
|
||||
{
|
||||
File.Delete(existingAsset.StoredPath);
|
||||
}
|
||||
state.ClipAssets.RemoveAll(item => item.Id == existingAssetId);
|
||||
}
|
||||
|
||||
var asset = new ClipAssetRecord
|
||||
{
|
||||
Id = state.NextClipAssetId++,
|
||||
ClipJobId = clipJobId,
|
||||
FileName = safeFileName,
|
||||
StoredPath = storedPath,
|
||||
ContentType = string.IsNullOrWhiteSpace(contentType) ? InferContentType(extension) : contentType,
|
||||
FileSizeBytes = fileSize
|
||||
};
|
||||
|
||||
state.ClipAssets.Add(asset);
|
||||
clipJob.ClipAssetId = asset.Id;
|
||||
clipJob.Status = "rendered";
|
||||
clipJob.ErrorMessage = null;
|
||||
clipJob.CompletedAtUtc = DateTime.UtcNow;
|
||||
clipJob.Payload["playback"] = "available";
|
||||
return asset.Id;
|
||||
}, cancellationToken);
|
||||
}
|
||||
|
||||
private static string InferContentType(string extension) => extension.ToLowerInvariant() switch
|
||||
{
|
||||
".webm" => "video/webm",
|
||||
_ => "video/mp4"
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user