using ExifLibrary;
using FFMpegCore;
namespace VoidCat.Services.Files;
///
/// Service which utilizes ffmpeg to strip metadata from media
/// and compress media to reduce storage costs
///
public class CompressContent
{
private readonly ILogger _logger;
public CompressContent(ILogger logger)
{
_logger = logger;
}
public async Task TryCompressMedia(string input, string output, CancellationToken cts)
{
try
{
string? outMime = null;
var inExt = Path.GetExtension(input).ToLower();
var isImage = false;
switch (inExt)
{
case ".jpg":
case ".jpeg":
case ".gif":
case ".png":
case ".bmp":
case ".tiff":
case ".heic":
{
output = Path.ChangeExtension(output, ".webp");
outMime = "image/webp";
isImage = true;
break;
}
}
var ffProbe = await TryProbe(input, cts);
var probe = isImage ? await ImageFile.FromFileAsync(input) : default;
var ffmpeg = FFMpegArguments
.FromFileInput(input)
.OutputToFile(output, true, o =>
{
o.WithoutMetadata();
if (inExt == ".gif")
{
o.Loop(0);
}
if (probe != default)
{
var orientation = probe.Properties.Get>(ExifTag.Orientation);
if (orientation != default && orientation.Value != Orientation.Normal)
{
if (orientation.Value == Orientation.RotatedRight)
{
o.WithCustomArgument("-metadata:s:v rotate=\"90\"");
}
else if (orientation.Value == Orientation.RotatedLeft)
{
o.WithCustomArgument("-metadata:s:v rotate=\"-90\"");
}
}
}
})
.CancellableThrough(cts);
_logger.LogInformation("Running: {command}", ffmpeg.Arguments);
var result = await ffmpeg.ProcessAsynchronously();
return new(result, output)
{
MimeType = outMime,
Width = ffProbe?.PrimaryVideoStream?.Width,
Height = ffProbe?.PrimaryVideoStream?.Height,
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Could not strip metadata");
}
return new(false, output);
}
private async Task TryProbe(string path, CancellationToken cts)
{
try
{
return await FFProbe.AnalyseAsync(path, cancellationToken: cts);
}
catch
{
// ignored
}
return default;
}
public record CompressResult(bool Success, string OutPath)
{
public string? MimeType { get; init; }
public int? Width { get; init; }
public int? Height { get; init; }
}
}