✨ Image analyzing and transcoding
This commit is contained in:
@ -1,22 +1,59 @@
|
||||
using System.Globalization;
|
||||
using FFMpegCore;
|
||||
using System.Security.Cryptography;
|
||||
using Blurhash.ImageSharp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Minio;
|
||||
using Minio.DataModel.Args;
|
||||
using Minio.DataModel.Tags;
|
||||
using NodaTime;
|
||||
using SixLabors.ImageSharp.PixelFormats;
|
||||
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
||||
using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag;
|
||||
|
||||
namespace DysonNetwork.Sphere.Storage;
|
||||
|
||||
public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
{
|
||||
public async Task<CloudFile> AnalyzeFileAsync(
|
||||
private static readonly List<ExifTag> BlacklistExifTags =
|
||||
[
|
||||
ExifTag.GPSLatitudeRef,
|
||||
ExifTag.GPSLatitude,
|
||||
ExifTag.GPSLongitudeRef,
|
||||
ExifTag.GPSLongitude,
|
||||
ExifTag.GPSAltitudeRef,
|
||||
ExifTag.GPSAltitude,
|
||||
ExifTag.GPSSatellites,
|
||||
ExifTag.GPSStatus,
|
||||
ExifTag.GPSMeasureMode,
|
||||
ExifTag.GPSDOP,
|
||||
ExifTag.GPSSpeedRef,
|
||||
ExifTag.GPSSpeed,
|
||||
ExifTag.GPSTrackRef,
|
||||
ExifTag.GPSTrack,
|
||||
ExifTag.GPSImgDirectionRef,
|
||||
ExifTag.GPSImgDirection,
|
||||
ExifTag.GPSMapDatum,
|
||||
ExifTag.GPSDestLatitudeRef,
|
||||
ExifTag.GPSDestLatitude,
|
||||
ExifTag.GPSDestLongitudeRef,
|
||||
ExifTag.GPSDestLongitude,
|
||||
ExifTag.GPSDestBearingRef,
|
||||
ExifTag.GPSDestBearing,
|
||||
ExifTag.GPSDestDistanceRef,
|
||||
ExifTag.GPSDestDistance,
|
||||
ExifTag.GPSProcessingMethod,
|
||||
ExifTag.GPSAreaInformation,
|
||||
ExifTag.GPSDateStamp,
|
||||
ExifTag.GPSDifferential
|
||||
];
|
||||
|
||||
public async Task<(CloudFile, Stream)> AnalyzeFileAsync(
|
||||
Account.Account account,
|
||||
string fileId,
|
||||
Stream stream,
|
||||
string fileName,
|
||||
string? contentType
|
||||
string? contentType,
|
||||
string? filePath = null
|
||||
)
|
||||
{
|
||||
var fileSize = stream.Length;
|
||||
@ -24,7 +61,7 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
|
||||
|
||||
var existingFile = await db.Files.Where(f => f.Hash == hash).FirstOrDefaultAsync();
|
||||
if (existingFile is not null) return existingFile;
|
||||
if (existingFile is not null) return (existingFile, stream);
|
||||
|
||||
var file = new CloudFile
|
||||
{
|
||||
@ -38,6 +75,53 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
|
||||
switch (contentType.Split('/')[0])
|
||||
{
|
||||
case "image":
|
||||
stream.Position = 0;
|
||||
using (var imageSharp = await Image.LoadAsync<Rgba32>(stream))
|
||||
{
|
||||
var width = imageSharp.Width;
|
||||
var height = imageSharp.Height;
|
||||
var blurhash = Blurhasher.Encode(imageSharp, 3, 3);
|
||||
var format = imageSharp.Metadata.DecodedImageFormat?.Name ?? "unknown";
|
||||
|
||||
var exifProfile = imageSharp.Metadata.ExifProfile;
|
||||
ushort orientation = 1;
|
||||
List<IExifValue> exif = [];
|
||||
|
||||
if (exifProfile is not null)
|
||||
{
|
||||
exif = exifProfile.Values
|
||||
.Where(v => !BlacklistExifTags.Contains((ExifTag)v.Tag))
|
||||
.ToList<IExifValue>();
|
||||
|
||||
if (exifProfile.Values.FirstOrDefault(e => e.Tag == ExifTag.Orientation)
|
||||
?.GetValue() is ushort o)
|
||||
orientation = o;
|
||||
}
|
||||
|
||||
if (orientation is 6 or 8)
|
||||
(width, height) = (height, width);
|
||||
|
||||
var aspectRatio = height != 0 ? (double)width / height : 0;
|
||||
|
||||
file.FileMeta = new Dictionary<string, object>
|
||||
{
|
||||
["blur"] = blurhash,
|
||||
["format"] = format,
|
||||
["width"] = width,
|
||||
["height"] = height,
|
||||
["orientation"] = orientation,
|
||||
["ratio"] = aspectRatio,
|
||||
["exif"] = exif
|
||||
};
|
||||
|
||||
var newStream = new MemoryStream();
|
||||
await imageSharp.SaveAsWebpAsync(newStream);
|
||||
file.MimeType = "image/webp";
|
||||
stream = newStream;
|
||||
}
|
||||
|
||||
break;
|
||||
case "video":
|
||||
case "audio":
|
||||
var mediaInfo = await FFProbe.AnalyseAsync(stream);
|
||||
@ -56,7 +140,7 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
||||
|
||||
db.Files.Add(file);
|
||||
await db.SaveChangesAsync();
|
||||
return file;
|
||||
return (file, stream);
|
||||
}
|
||||
|
||||
private static async Task<string> HashFileAsync(Stream stream, int chunkSize = 1024 * 1024, long? fileSize = null)
|
||||
|
Reference in New Issue
Block a user