Clips
This commit is contained in:
34
NostrStreamer/Services/Clips/ClipGenerator.cs
Normal file
34
NostrStreamer/Services/Clips/ClipGenerator.cs
Normal file
@ -0,0 +1,34 @@
|
||||
using FFMpegCore;
|
||||
using NostrStreamer.Database;
|
||||
|
||||
namespace NostrStreamer.Services.Clips;
|
||||
|
||||
public class ClipGenerator
|
||||
{
|
||||
private readonly ILogger<ClipGenerator> _logger;
|
||||
private readonly Config _config;
|
||||
|
||||
public ClipGenerator(ILogger<ClipGenerator> logger, Config config)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
}
|
||||
|
||||
public async Task<string> GenerateClip(UserStream stream)
|
||||
{
|
||||
const int clipLength = 20;
|
||||
var path = Path.ChangeExtension(Path.GetTempFileName(), ".mp4");
|
||||
var cmd = FFMpegArguments
|
||||
.FromUrlInput(new Uri(_config.DataHost, $"stream/{stream.Id}.m3u8"),
|
||||
inOpt =>
|
||||
{
|
||||
inOpt.WithCustomArgument($"-ss -{clipLength}");
|
||||
})
|
||||
.OutputToFile(path, true, o => { o.WithDuration(TimeSpan.FromSeconds(clipLength)); })
|
||||
.CancellableThrough(new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token);
|
||||
|
||||
_logger.LogInformation("Running command {cmd}", cmd.Arguments);
|
||||
await cmd.ProcessAsynchronously();
|
||||
return path;
|
||||
}
|
||||
}
|
8
NostrStreamer/Services/Clips/IClipService.cs
Normal file
8
NostrStreamer/Services/Clips/IClipService.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace NostrStreamer.Services.Clips;
|
||||
|
||||
public interface IClipService
|
||||
{
|
||||
Task<ClipResult?> CreateClip(Guid streamId, string takenBy);
|
||||
}
|
||||
|
||||
public record ClipResult(Uri Url);
|
80
NostrStreamer/Services/Clips/S3ClipService.cs
Normal file
80
NostrStreamer/Services/Clips/S3ClipService.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using Amazon.S3;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NostrStreamer.Database;
|
||||
|
||||
namespace NostrStreamer.Services.Clips;
|
||||
|
||||
public class S3ClipService : IClipService
|
||||
{
|
||||
private readonly ClipGenerator _generator;
|
||||
private readonly AmazonS3Client _client;
|
||||
private readonly Config _config;
|
||||
private readonly StreamerContext _context;
|
||||
|
||||
public S3ClipService(ClipGenerator generator, Config config, StreamerContext context)
|
||||
{
|
||||
_generator = generator;
|
||||
_client = config.S3Store.CreateClient();
|
||||
;
|
||||
_config = config;
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ClipResult?> CreateClip(Guid streamId, string takenBy)
|
||||
{
|
||||
var stream = await _context.Streams
|
||||
.Include(a => a.User)
|
||||
.Include(a => a.Endpoint)
|
||||
.FirstOrDefaultAsync(a => a.Id == streamId);
|
||||
|
||||
if (stream == default)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var tmpClip = await _generator.GenerateClip(stream);
|
||||
|
||||
var clipId = Guid.NewGuid();
|
||||
var s3Path = $"{stream.Id}/clips/{clipId}.mp4";
|
||||
|
||||
await using var fs = new FileStream(tmpClip, FileMode.Open, FileAccess.Read);
|
||||
await _client.PutObjectAsync(new()
|
||||
{
|
||||
BucketName = _config.S3Store.BucketName,
|
||||
Key = s3Path,
|
||||
InputStream = fs,
|
||||
AutoCloseStream = false,
|
||||
AutoResetStreamPosition = false,
|
||||
ContentType = "video/mp4",
|
||||
DisablePayloadSigning = _config.S3Store.DisablePayloadSigning
|
||||
});
|
||||
|
||||
var uri = _client.GetPreSignedURL(new()
|
||||
{
|
||||
BucketName = _config.S3Store.BucketName,
|
||||
Key = s3Path,
|
||||
Expires = DateTime.UtcNow.AddYears(1000)
|
||||
});
|
||||
|
||||
var ub = new UriBuilder(uri)
|
||||
{
|
||||
Scheme = _config.S3Store.PublicHost.Scheme,
|
||||
Host = _config.S3Store.PublicHost.Host,
|
||||
Port = _config.S3Store.PublicHost.Port
|
||||
};
|
||||
|
||||
|
||||
var clipObj = new UserStreamClip()
|
||||
{
|
||||
Id = clipId,
|
||||
UserStreamId = stream.Id,
|
||||
TakenByPubkey = takenBy,
|
||||
Url = ub.Uri.ToString()
|
||||
};
|
||||
|
||||
_context.Clips.Add(clipObj);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
return new(ub.Uri);
|
||||
}
|
||||
}
|
@ -41,7 +41,8 @@ public class StreamEventBuilder
|
||||
new("status", status),
|
||||
new("p", user.PubKey, "", "host"),
|
||||
new("relays", _config.Relays),
|
||||
new("starts", new DateTimeOffset(stream.Starts).ToUnixTimeSeconds().ToString())
|
||||
new("starts", new DateTimeOffset(stream.Starts).ToUnixTimeSeconds().ToString()),
|
||||
new("service", new Uri(_config.ApiHost, "/api/nostr").ToString())
|
||||
};
|
||||
|
||||
if (status == "live")
|
||||
|
Reference in New Issue
Block a user