✨ Image analyzing and transcoding
This commit is contained in:
parent
4512f47299
commit
b9fc1907fc
@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
|
||||||
|
<PackageReference Include="Blurhash.ImageSharp" Version="4.0.0" />
|
||||||
<PackageReference Include="Casbin.NET" Version="2.12.0" />
|
<PackageReference Include="Casbin.NET" Version="2.12.0" />
|
||||||
<PackageReference Include="Casbin.NET.Adapter.EFCore" Version="2.5.0" />
|
<PackageReference Include="Casbin.NET.Adapter.EFCore" Version="2.5.0" />
|
||||||
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
<PackageReference Include="EFCore.NamingConventions" Version="9.0.0" />
|
||||||
@ -31,6 +32,9 @@
|
|||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="9.0.4" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.Design" Version="1.1.0" />
|
||||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL.NodaTime" Version="9.0.4" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp.Drawing" Version="2.1.5" />
|
||||||
|
<PackageReference Include="SixLabors.ImageSharp.Web" Version="3.1.4" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="8.1.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="8.1.0" />
|
||||||
<PackageReference Include="tusdotnet" Version="2.8.1" />
|
<PackageReference Include="tusdotnet" Version="2.8.1" />
|
||||||
|
@ -1,22 +1,59 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using FFMpegCore;
|
using FFMpegCore;
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using Blurhash.ImageSharp;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Minio;
|
using Minio;
|
||||||
using Minio.DataModel.Args;
|
using Minio.DataModel.Args;
|
||||||
using Minio.DataModel.Tags;
|
|
||||||
using NodaTime;
|
using NodaTime;
|
||||||
|
using SixLabors.ImageSharp.PixelFormats;
|
||||||
|
using SixLabors.ImageSharp.Metadata.Profiles.Exif;
|
||||||
|
using ExifTag = SixLabors.ImageSharp.Metadata.Profiles.Exif.ExifTag;
|
||||||
|
|
||||||
namespace DysonNetwork.Sphere.Storage;
|
namespace DysonNetwork.Sphere.Storage;
|
||||||
|
|
||||||
public class FileService(AppDatabase db, IConfiguration configuration)
|
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,
|
Account.Account account,
|
||||||
string fileId,
|
string fileId,
|
||||||
Stream stream,
|
Stream stream,
|
||||||
string fileName,
|
string fileName,
|
||||||
string? contentType
|
string? contentType,
|
||||||
|
string? filePath = null
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
var fileSize = stream.Length;
|
var fileSize = stream.Length;
|
||||||
@ -24,7 +61,7 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
|||||||
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
|
contentType ??= !fileName.Contains('.') ? "application/octet-stream" : MimeTypes.GetMimeType(fileName);
|
||||||
|
|
||||||
var existingFile = await db.Files.Where(f => f.Hash == hash).FirstOrDefaultAsync();
|
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
|
var file = new CloudFile
|
||||||
{
|
{
|
||||||
@ -38,6 +75,53 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
|||||||
|
|
||||||
switch (contentType.Split('/')[0])
|
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 "video":
|
||||||
case "audio":
|
case "audio":
|
||||||
var mediaInfo = await FFProbe.AnalyseAsync(stream);
|
var mediaInfo = await FFProbe.AnalyseAsync(stream);
|
||||||
@ -56,7 +140,7 @@ public class FileService(AppDatabase db, IConfiguration configuration)
|
|||||||
|
|
||||||
db.Files.Add(file);
|
db.Files.Add(file);
|
||||||
await db.SaveChangesAsync();
|
await db.SaveChangesAsync();
|
||||||
return file;
|
return (file, stream);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static async Task<string> HashFileAsync(Stream stream, int chunkSize = 1024 * 1024, long? fileSize = null)
|
private static async Task<string> HashFileAsync(Stream stream, int chunkSize = 1024 * 1024, long? fileSize = null)
|
||||||
|
@ -11,8 +11,11 @@
|
|||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4a28847852ee9ba45fd3107526c0a749a733bd4f4ebf33aa3c9a59737a3f758_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEntityFrameworkServiceCollectionExtensions_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FSourcesCache_003F4a28847852ee9ba45fd3107526c0a749a733bd4f4ebf33aa3c9a59737a3f758_003FEntityFrameworkServiceCollectionExtensions_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F832399abc13b45b6bdbabfa022e4a28487e00_003F7f_003F7aece4dd_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEnumerable_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F832399abc13b45b6bdbabfa022e4a28487e00_003F7f_003F7aece4dd_003FEnumerable_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AEvents_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003F20_003F86914b63_003FEvents_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003Fd7_003F0472c800_003FExifTag_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AExifTag_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fef3339e864a448e2b1ec6fa7bbf4c6661fee00_003F5c_003F8ed75f18_003FExifTag_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AFileResult_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F0b5acdd962e549369896cece0026e556214600_003F8c_003F9f6e3f4f_003FFileResult_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AForwardedHeaders_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fcfe5737f9bb84738979cbfedd11822a8ea00_003F50_003F9a335f87_003FForwardedHeaders_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AImageFile_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa932cb9090ed48088111ae919dcdd9021ba00_003F71_003F0a804432_003FImageFile_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AIntentType_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fbf_003Ffcb84131_003FIntentType_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AITusStore_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F8bb08a178b5b43c5bac20a5a54159a5b2a800_003Fb1_003F7e861de5_003FITusStore_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMediaAnalysis_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fd7_003F5c138865_003FMediaAnalysis_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AMediaAnalysis_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002E_002E_003FLibrary_003FApplication_0020Support_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Ffef366b36a224d469ff150d30f9a866d23c00_003Fd7_003F5c138865_003FMediaAnalysis_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user