3 Commits

Author SHA1 Message Date
8b5803c085 Make media seekable
Some checks failed
Build and Push Image / build-and-push (push) Has been cancelled
2025-07-09 20:48:55 -04:00
5e2a7b53c9 Added additional media types
Some checks failed
Build and Push Image / build-and-push (push) Has been cancelled
2025-07-09 20:30:45 -04:00
d88cfa2cd3 Fixes to video thumb generation
Some checks failed
Build and Push Image / build-and-push (push) Has been cancelled
2025-07-09 20:06:59 -04:00
4 changed files with 35 additions and 19 deletions

View File

@@ -50,6 +50,10 @@ public class Media
{ ".xml", MediaType.Code }, { ".xml", MediaType.Code },
{ ".json", MediaType.Code }, { ".json", MediaType.Code },
{ ".py", MediaType.Code }, { ".py", MediaType.Code },
{ ".rs", MediaType.Code },
{ ".zed", MediaType.Code },
{ ".ts", MediaType.Code },
{ ".astro", MediaType.Code },
}; };
[BsonConstructor] [BsonConstructor]

View File

@@ -2,13 +2,15 @@ using AobaCore.Models;
using MaybeError.Errors; using MaybeError.Errors;
using Microsoft.Extensions.Logging;
using MongoDB.Bson; using MongoDB.Bson;
using MongoDB.Driver; using MongoDB.Driver;
using MongoDB.Driver.GridFS; using MongoDB.Driver.GridFS;
namespace AobaCore.Services; namespace AobaCore.Services;
public class AobaService(IMongoDatabase db) public class AobaService(IMongoDatabase db, ThumbnailService thumbnailService, ILogger<AobaService> logger)
{ {
private readonly IMongoCollection<Media> _media = db.GetCollection<Media>("media"); private readonly IMongoCollection<Media> _media = db.GetCollection<Media>("media");
private readonly GridFSBucket _gridFs = new(db); private readonly GridFSBucket _gridFs = new(db);
@@ -69,6 +71,7 @@ public class AobaService(IMongoDatabase db)
{ {
var fileId = await _gridFs.UploadFromStreamAsync(filename, data, cancellationToken: cancellationToken); var fileId = await _gridFs.UploadFromStreamAsync(filename, data, cancellationToken: cancellationToken);
var media = new Media(fileId, filename, owner); var media = new Media(fileId, filename, owner);
await AddMediaAsync(media, cancellationToken); await AddMediaAsync(media, cancellationToken);
return media; return media;
} }

View File

@@ -24,7 +24,6 @@ namespace AobaCore.Services;
public class ThumbnailService(IMongoDatabase db, AobaService aobaService) public class ThumbnailService(IMongoDatabase db, AobaService aobaService)
{ {
private readonly GridFSBucket _gridfs = new GridFSBucket(db); private readonly GridFSBucket _gridfs = new GridFSBucket(db);
private Lock _lock = new();
/// <summary> /// <summary>
/// ///
@@ -121,26 +120,36 @@ public class ThumbnailService(IMongoDatabase db, AobaService aobaService)
public Maybe<Stream> GenerateVideoThumbnail(Stream data, ThumbnailSize size, CancellationToken cancellationToken = default) public Maybe<Stream> GenerateVideoThumbnail(Stream data, ThumbnailSize size, CancellationToken cancellationToken = default)
{ {
var w = (int)size; var w = (int)size;
var source = new MemoryStream(); var fn = ObjectId.GenerateNewId().ToString();
var filePath = $"/tmp/{fn}.in";
using var source = new FileStream(filePath, FileMode.CreateNew);
data.CopyTo(source); data.CopyTo(source);
source.Position = 0; source.Flush();
source.Dispose();
data.Dispose();
try
{
var output = new MemoryStream(); var output = new MemoryStream();
FFMpegArguments.FromPipeInput(new StreamPipeSource(source), opt => FFMpegArguments.FromFileInput(filePath, false, opt =>
{ {
opt.WithCustomArgument("-t 5"); opt.WithCustomArgument("-t 5");
}).OutputToPipe(new StreamPipeSink(output), opt => }).OutputToPipe(new StreamPipeSink(output), opt =>
{ {
opt.WithCustomArgument($"-vf \"crop='min(in_w,in_h)':'min(in_w,in_h)',scale={w}:{w}\" -loop 0 -r 15") opt.WithCustomArgument($"-vf \"crop='min(in_w,in_h)':'min(in_w,in_h)',scale={w}:{w}\" -loop 0 -r 15")
.ForceFormat("webp"); .ForceFormat("webp");
}).Configure(cfg =>
{
#if !DEBUG
cfg.BinaryFolder = "/usr/bin";
#endif
}).ProcessSynchronously(); }).ProcessSynchronously();
output.Position = 0; output.Position = 0;
return output; return output;
} }
catch(Exception ex)
{
return ex;
}
finally
{
File.Delete(filePath);
}
}
public async Task<Maybe<Stream>> GenerateDocumentThumbnailAsync(Stream data, ThumbnailSize size, CancellationToken cancellationToken = default) public async Task<Maybe<Stream>> GenerateDocumentThumbnailAsync(Stream data, ThumbnailSize size, CancellationToken cancellationToken = default)
{ {

View File

@@ -17,7 +17,7 @@ public class MediaController(AobaService aobaService, ILogger<MediaController> l
[ResponseCache(Duration = int.MaxValue)] [ResponseCache(Duration = int.MaxValue)]
public async Task<IActionResult> MediaAsync(ObjectId id, [FromServices] MongoClient client, CancellationToken cancellationToken) public async Task<IActionResult> MediaAsync(ObjectId id, [FromServices] MongoClient client, CancellationToken cancellationToken)
{ {
var file = await aobaService.GetFileStreamAsync(id, cancellationToken: cancellationToken); var file = await aobaService.GetFileStreamAsync(id, seekable: true, cancellationToken: cancellationToken);
if (file.HasError) if (file.HasError)
{ {
logger.LogError(file.Error.Exception, "Failed to load media stream"); logger.LogError(file.Error.Exception, "Failed to load media stream");