Redo clips api
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
using System.Text.RegularExpressions;
|
||||
using FFMpegCore;
|
||||
using NostrStreamer.Database;
|
||||
|
||||
namespace NostrStreamer.Services.Clips;
|
||||
|
||||
@ -7,28 +7,56 @@ public class ClipGenerator
|
||||
{
|
||||
private readonly ILogger<ClipGenerator> _logger;
|
||||
private readonly Config _config;
|
||||
private readonly HttpClient _client;
|
||||
|
||||
public ClipGenerator(ILogger<ClipGenerator> logger, Config config)
|
||||
public ClipGenerator(ILogger<ClipGenerator> logger, Config config, HttpClient client)
|
||||
{
|
||||
_logger = logger;
|
||||
_config = config;
|
||||
_client = client;
|
||||
}
|
||||
|
||||
public async Task<string> GenerateClip(UserStream stream)
|
||||
public async Task<string> CreateClipFromSegments(List<ClipSegment> segments, float start, float length)
|
||||
{
|
||||
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)); })
|
||||
.FromConcatInput(segments.Select(a => a.GetPath()),
|
||||
inOpt => { inOpt.Seek(TimeSpan.FromSeconds(start)); })
|
||||
.OutputToFile(path, true, o => { o.WithDuration(TimeSpan.FromSeconds(length)); })
|
||||
.CancellableThrough(new CancellationTokenSource(TimeSpan.FromSeconds(60)).Token);
|
||||
|
||||
|
||||
_logger.LogInformation("Running command {cmd}", cmd.Arguments);
|
||||
await cmd.ProcessAsynchronously();
|
||||
return path;
|
||||
}
|
||||
|
||||
public async Task<List<ClipSegment>> GetClipSegments(Guid id)
|
||||
{
|
||||
var ret = new List<ClipSegment>();
|
||||
var playlist = new Uri(_config.DataHost, $"stream/{id}.m3u8");
|
||||
|
||||
var rsp = await _client.GetStreamAsync(playlist);
|
||||
using var sr = new StreamReader(rsp);
|
||||
while (await sr.ReadLineAsync() is { } line)
|
||||
{
|
||||
if (line.StartsWith("#EXTINF"))
|
||||
{
|
||||
var trackPath = await sr.ReadLineAsync();
|
||||
var seg = Regex.Match(trackPath!, @"-(\d+)\.ts");
|
||||
var idx = int.Parse(seg.Groups[1].Value);
|
||||
var clipSeg = new ClipSegment(id, idx);
|
||||
var outPath = clipSeg.GetPath();
|
||||
if (!File.Exists(outPath))
|
||||
{
|
||||
var segStream = await _client.GetStreamAsync(new Uri(_config.DataHost, trackPath));
|
||||
await using var fsOut = new FileStream(outPath, FileMode.Create, FileAccess.ReadWrite);
|
||||
await segStream.CopyToAsync(fsOut);
|
||||
}
|
||||
|
||||
ret.Add(clipSeg);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,17 @@ namespace NostrStreamer.Services.Clips;
|
||||
|
||||
public interface IClipService
|
||||
{
|
||||
Task<ClipResult?> CreateClip(Guid streamId, string takenBy);
|
||||
Task<List<ClipSegment>?> PrepareClip(Guid streamId);
|
||||
|
||||
Task<ClipResult?> MakeClip(string takenBy, List<ClipSegment> segments, float start, float length);
|
||||
}
|
||||
|
||||
public record ClipResult(Uri Url);
|
||||
|
||||
public record ClipSegment(Guid Id, int Index)
|
||||
{
|
||||
public string GetPath()
|
||||
{
|
||||
return Path.Join(Path.GetTempPath(), Id.ToString(), $"{Index}.ts");
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ public class S3ClipService : IClipService
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public async Task<ClipResult?> CreateClip(Guid streamId, string takenBy)
|
||||
public async Task<List<ClipSegment>?> PrepareClip(Guid streamId)
|
||||
{
|
||||
var stream = await _context.Streams
|
||||
.Include(a => a.User)
|
||||
@ -32,12 +32,19 @@ public class S3ClipService : IClipService
|
||||
return default;
|
||||
}
|
||||
|
||||
var tmpClip = await _generator.GenerateClip(stream);
|
||||
return await _generator.GetClipSegments(streamId);
|
||||
}
|
||||
|
||||
public async Task<ClipResult?> MakeClip(string takenBy, List<ClipSegment> segments, float start, float length)
|
||||
{
|
||||
if (segments.Count == 0) return default;
|
||||
|
||||
var streamId = segments.First().Id;
|
||||
var clip = await _generator.CreateClipFromSegments(segments, start, length);
|
||||
var clipId = Guid.NewGuid();
|
||||
var s3Path = $"{stream.Id}/clips/{clipId}.mp4";
|
||||
var s3Path = $"{streamId}/clips/{clipId}.mp4";
|
||||
|
||||
await using var fs = new FileStream(tmpClip, FileMode.Open, FileAccess.Read);
|
||||
await using var fs = new FileStream(clip, FileMode.Open, FileAccess.Read);
|
||||
await _client.PutObjectAsync(new()
|
||||
{
|
||||
BucketName = _config.S3Store.BucketName,
|
||||
@ -67,7 +74,7 @@ public class S3ClipService : IClipService
|
||||
var clipObj = new UserStreamClip()
|
||||
{
|
||||
Id = clipId,
|
||||
UserStreamId = stream.Id,
|
||||
UserStreamId = streamId,
|
||||
TakenByPubkey = takenBy,
|
||||
Url = ub.Uri.ToString()
|
||||
};
|
||||
|
Reference in New Issue
Block a user