diff --git a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj index 2b01994..d824a99 100644 --- a/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj +++ b/DysonNetwork.Sphere/DysonNetwork.Sphere.csproj @@ -10,6 +10,7 @@ + @@ -31,6 +32,9 @@ + + + diff --git a/DysonNetwork.Sphere/Storage/FileService.cs b/DysonNetwork.Sphere/Storage/FileService.cs index f801432..0653496 100644 --- a/DysonNetwork.Sphere/Storage/FileService.cs +++ b/DysonNetwork.Sphere/Storage/FileService.cs @@ -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 AnalyzeFileAsync( + private static readonly List 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(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 exif = []; + + if (exifProfile is not null) + { + exif = exifProfile.Values + .Where(v => !BlacklistExifTags.Contains((ExifTag)v.Tag)) + .ToList(); + + 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 + { + ["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 HashFileAsync(Stream stream, int chunkSize = 1024 * 1024, long? fileSize = null) diff --git a/DysonNetwork.sln.DotSettings.user b/DysonNetwork.sln.DotSettings.user index 90eb646..4093039 100644 --- a/DysonNetwork.sln.DotSettings.user +++ b/DysonNetwork.sln.DotSettings.user @@ -11,8 +11,11 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded + ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded