From f963a151872aa83eb87ef647abe261bb6f34276d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 24 Sep 2024 12:06:59 -0700 Subject: [PATCH 1/8] Add auto resizing if over dimension limit --- .../StudioClient.Example.csproj | 4 +- StudioClient/Photo.cs | 144 +++++++++++++++++- 2 files changed, 146 insertions(+), 2 deletions(-) diff --git a/StudioClient.Example/StudioClient.Example.csproj b/StudioClient.Example/StudioClient.Example.csproj index 4c4a7bd..a377786 100644 --- a/StudioClient.Example/StudioClient.Example.csproj +++ b/StudioClient.Example/StudioClient.Example.csproj @@ -6,9 +6,11 @@ Exe - net7.0 + net48 enable enable + latest + AnyCPU;x64 diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index 28fab1f..06fd5b1 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -1,4 +1,6 @@ +using System.Drawing.Imaging; using System.Security.Cryptography; +using NetVips; using Newtonsoft.Json.Linq; using RestSharp; @@ -12,11 +14,40 @@ public partial class StudioClient /// public static readonly string[] VALID_EXTENSIONS = { ".png", ".jpg", ".jpeg", ".webp" }; + /// + /// Maximum allowed pixels for a photo. + /// + public const int MAX_PHOTO_PIXELS = 27_000_000; + /// /// Maximum allowed size for a photo in bytes. /// public const int MAX_PHOTO_SIZE = 27 * 1024 * 1024; + /// + /// Maximum allowed pixel length for height/width. + /// + public const int MAX_DIMENSION = 6400; + + public class Dimensions + { + public int Width { get; set; } + public int Height { get; set; } + } + + public class PhotoMetadata + { + public ImageFormat? Format { get; set; } + public int? Width { get; set; } + public int? Height { get; set; } + public int? Orientation { get; set; } + + public long? Bytes { get; set; } + + + public PhotoMetadata() { } + } + /// /// Creates a new photo with the specified payload. /// @@ -104,7 +135,7 @@ private async Task UploadPhoto(string photoPath, string modelName, long var photoObject = new JObject { - { "name", $"{Guid.NewGuid()}{fileExtension}" }, + { "name", photoBasename }, { "path", photoPath } }; if (modelName == "job") photoObject["job_id"] = modelId; else photoObject["profile_id"] = modelId; @@ -117,6 +148,12 @@ private async Task UploadPhoto(string photoPath, string modelName, long throw new Exception($"{photoPath} exceeds 27MB"); } + PhotoMetadata photoMetadata = GetImageMetadata(photoData); + Image image = Image.NewFromBuffer(photoData); + + Image modifiedImage = ResizeImage(image, photoMetadata); + + var md5 = MD5.Create(); byte[] md5Hash = md5.ComputeHash(photoData); string md5Base64 = Convert.ToBase64String(md5Hash); @@ -148,5 +185,110 @@ private async Task UploadPhoto(string photoPath, string modelName, long throw new Exception("An error has occurred uploading the photo."); } + + private PhotoMetadata GetImageMetadata(byte[] imageData) + { + PhotoMetadata photoMetadata = new PhotoMetadata(); + + // Load the image from the byte array + using (MemoryStream ms = new MemoryStream(imageData)) + { + photoMetadata.Bytes = ms.Length; + using (System.Drawing.Image image = System.Drawing.Image.FromStream(ms)) + { + photoMetadata.Width = image.Width; photoMetadata.Height = image.Height; + + photoMetadata.Orientation = GetImageOrientation(image); + + // Example: Save the image to a different file + ImageFormat format = image.RawFormat; + photoMetadata.Format = format; + + Console.WriteLine(format); + } + } + + return photoMetadata; + } + + private int GetImageOrientation(System.Drawing.Image image) + { + // The EXIF orientation tag number is 0x0112 (274 in decimal) + const int orientationId = 0x0112; + + // Check if the image has property items (EXIF data) + if (image.PropertyIdList.Contains(orientationId)) + { + // Get the orientation property + PropertyItem propItem = image.GetPropertyItem(orientationId); + + // The orientation value is stored as a short (16-bit integer) + return BitConverter.ToUInt16(propItem.Value, 0); + } + else + { + // If no orientation property exists, assume "normal" orientation (1) + return 1; + } + } + + private bool IsSizeInvalid(long? sizeInBytes, int? width, int? height) + { + return ( + sizeInBytes > 27 * 1024 * 1024 || // MB + width > 6400 || + height > 6400 || + width * height > 27000000 // >27MP + ); + } + + private Dimensions GetNormalSize(int width, int height, int? orientation) + { + return (orientation ?? 0) >= 5 + ? new Dimensions { Width = height, Height = width } + : new Dimensions { Width = width, Height = height }; + } + + private Dimensions CalculateFinalSize(int? width, int? height, int? orientation) + { + if (width == null && height == null) + { + throw new ArgumentNullException(nameof(width)); + } + + var normalSize = GetNormalSize(width ?? 0, height ?? 0, orientation); + + double ratio = (double)normalSize.Width / normalSize.Height; + int pixels = normalSize.Width * normalSize.Height; + double scale = Math.Sqrt((double)pixels / MAX_PHOTO_PIXELS); + + int finalHeight = (int)Math.Floor(normalSize.Height / scale); + int finalWidth = (int)Math.Floor((ratio * normalSize.Height) / scale); + + return new Dimensions { Width = finalWidth, Height = finalHeight }; + } + + + private Image ResizeImage(Image image, PhotoMetadata metadata) + { + if (metadata == null || !metadata.Bytes.HasValue || !metadata.Width.HasValue || !metadata.Height.HasValue) + { + return image; + } + + if (IsSizeInvalid(metadata.Bytes, metadata.Width, metadata.Height)) + { + // resize image to calculated final size + Dimensions finalDimensions = CalculateFinalSize(metadata.Width, metadata.Height, metadata.Orientation); + + Console.WriteLine($" {finalDimensions.Width} x {finalDimensions.Height} "); + + Image finalImage = image.ThumbnailImage(finalDimensions.Width, finalDimensions.Height, noRotate: false, crop: Enums.Interesting.Centre, size: Enums.Size.Both); + + return finalImage; + } + + return image; + } } } \ No newline at end of file From 283129f467e80e252e8cf929e8d969fcc44cb171 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 24 Sep 2024 12:53:25 -0700 Subject: [PATCH 2/8] Add option for resizing image to below skylab limit --- README.md | 8 +++++--- StudioClient.sln | 29 +++++++++++++++++++++++------ StudioClient/Photo.cs | 2 +- StudioClient/StudioClient.cs | 3 +++ StudioClient/StudioClient.csproj | 1 + 5 files changed, 33 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index baa0258..afd7f3e 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,11 @@ using SkylabStudio; var apiClient = new StudioClient("YOUR_SKYLAB_API_TOKEN"); -// option to configure max concurrent downloads (for when using DownloadAllPhotos method) -// defaults to 5 concurrent downloads at a time -var studioOptions = new StudioOptions { MaxConcurrentDownloads = 5 }; +// optional: to configure max concurrent downloads (for when using DownloadAllPhotos method) +// - defaults to 5 concurrent downloads at a time +// optional: to resize oversized images to be below Skylab dimension limit +// - defaults to false +var studioOptions = new StudioOptions { MaxConcurrentDownloads = 5, ResizeImageIfOversized = true }; var apiClient = new StudioClient(Environment.GetEnvironmentVariable("SKYLAB_API_TOKEN"), studioOptions); ``` diff --git a/StudioClient.sln b/StudioClient.sln index 290b88b..e6ffc49 100644 --- a/StudioClient.sln +++ b/StudioClient.sln @@ -3,32 +3,49 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudioClient", "StudioClient\StudioClient.csproj", "{0F41D41D-9264-4E44-8313-4F959BC82C31}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StudioClient", "StudioClient\StudioClient.csproj", "{0F41D41D-9264-4E44-8313-4F959BC82C31}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudioClient.Example", "StudioClient.Example\StudioClient.Example.csproj", "{1D0ACA01-210A-42F3-BC79-B772BF55D9C0}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StudioClient.Example", "StudioClient.Example\StudioClient.Example.csproj", "{1D0ACA01-210A-42F3-BC79-B772BF55D9C0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "StudioClient.Tests", "StudioClient.Tests\StudioClient.Tests.csproj", "{5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "StudioClient.Tests", "StudioClient.Tests\StudioClient.Tests.csproj", "{5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {0F41D41D-9264-4E44-8313-4F959BC82C31}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {0F41D41D-9264-4E44-8313-4F959BC82C31}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0F41D41D-9264-4E44-8313-4F959BC82C31}.Debug|x64.ActiveCfg = Debug|Any CPU + {0F41D41D-9264-4E44-8313-4F959BC82C31}.Debug|x64.Build.0 = Debug|Any CPU {0F41D41D-9264-4E44-8313-4F959BC82C31}.Release|Any CPU.ActiveCfg = Release|Any CPU {0F41D41D-9264-4E44-8313-4F959BC82C31}.Release|Any CPU.Build.0 = Release|Any CPU + {0F41D41D-9264-4E44-8313-4F959BC82C31}.Release|x64.ActiveCfg = Release|Any CPU + {0F41D41D-9264-4E44-8313-4F959BC82C31}.Release|x64.Build.0 = Release|Any CPU {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Debug|x64.ActiveCfg = Debug|x64 + {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Debug|x64.Build.0 = Debug|x64 {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Release|Any CPU.ActiveCfg = Release|Any CPU {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Release|Any CPU.Build.0 = Release|Any CPU + {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Release|x64.ActiveCfg = Release|x64 + {1D0ACA01-210A-42F3-BC79-B772BF55D9C0}.Release|x64.Build.0 = Release|x64 {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Debug|x64.ActiveCfg = Debug|Any CPU + {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Debug|x64.Build.0 = Debug|Any CPU {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Release|Any CPU.ActiveCfg = Release|Any CPU {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Release|Any CPU.Build.0 = Release|Any CPU + {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Release|x64.ActiveCfg = Release|Any CPU + {5AB68DEF-291E-4A80-9B64-69DAFC3EA10B}.Release|x64.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {CE6262B9-9C94-4708-9C5F-2044818CEED3} EndGlobalSection EndGlobal diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index 06fd5b1..92718c4 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -271,7 +271,7 @@ private Dimensions CalculateFinalSize(int? width, int? height, int? orientation) private Image ResizeImage(Image image, PhotoMetadata metadata) { - if (metadata == null || !metadata.Bytes.HasValue || !metadata.Width.HasValue || !metadata.Height.HasValue) + if (_resizeImageIfOversized != true || metadata == null || !metadata.Bytes.HasValue || !metadata.Width.HasValue || !metadata.Height.HasValue) { return image; } diff --git a/StudioClient/StudioClient.cs b/StudioClient/StudioClient.cs index 93c9f5e..c42c4ab 100644 --- a/StudioClient/StudioClient.cs +++ b/StudioClient/StudioClient.cs @@ -8,6 +8,7 @@ namespace SkylabStudio { public class StudioOptions { public int? MaxConcurrentDownloads { get; set; } + public bool? ResizeImageIfOversized { get; set; } } public partial class StudioClient @@ -15,6 +16,7 @@ public partial class StudioClient private readonly RestClient _httpClient; private readonly string _apiKey; private readonly int _maxConcurrentDownloads = 5; + private readonly bool _resizeImageIfOversized = false; /// /// Initializes a new instance of the class with the specified API key and options. @@ -29,6 +31,7 @@ public StudioClient(string? apiKey = null, StudioOptions? options = null) _httpClient = new RestClient(baseUrl); _apiKey = apiKey; _maxConcurrentDownloads = options?.MaxConcurrentDownloads ?? 5; + _resizeImageIfOversized = options?.ResizeImageIfOversized ?? false; } /// diff --git a/StudioClient/StudioClient.csproj b/StudioClient/StudioClient.csproj index f76e5e0..84a5632 100644 --- a/StudioClient/StudioClient.csproj +++ b/StudioClient/StudioClient.csproj @@ -25,6 +25,7 @@ + From e71b1ce25a8932b2968a2c088dcb0e91694357ce Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 24 Sep 2024 13:19:46 -0700 Subject: [PATCH 3/8] Revert photo object name to uuid when uploading --- StudioClient/Photo.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index 92718c4..19d3176 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -135,7 +135,7 @@ private async Task UploadPhoto(string photoPath, string modelName, long var photoObject = new JObject { - { "name", photoBasename }, + { "name", $"{Guid.NewGuid()}{fileExtension}" }, { "path", photoPath } }; if (modelName == "job") photoObject["job_id"] = modelId; else photoObject["profile_id"] = modelId; From f1007bc064c30678a775c540905c4af32ca8f06d Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 24 Sep 2024 14:36:09 -0700 Subject: [PATCH 4/8] Add upload retries --- StudioClient/Photo.cs | 61 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index 19d3176..e1acd0c 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -1,3 +1,4 @@ +using System; using System.Drawing.Imaging; using System.Security.Cryptography; using NetVips; @@ -98,9 +99,35 @@ public async Task GetUploadUrl(long photoId, string md5 = "", bool useC /// The path to the photo file. /// The ID of the job associated with the photo. /// A dynamic object representing the uploaded photo. - public async Task UploadJobPhoto(string photoPath, long jobId) + public async Task UploadJobPhoto(string photoPath, long jobId) { - return await UploadPhoto(photoPath, "job", jobId); + const int maxRetries = 3; + int attempt = 0; + + while (attempt < maxRetries) + { + try + { + return await UploadPhoto(photoPath, "job", jobId); + } + catch (Exception ex) + { + attempt++; + + // If we've reached the max number of retries, rethrow the exception + if (attempt == maxRetries) + { + throw new Exception($"Failed to upload photo after {maxRetries} attempts", ex); + } + + // Wait for 2 seconds before retrying + await Task.Delay(2000); + } + } + + // This will never be reached due to the return in the loop, + // but we include it to satisfy the method's return type. + return null; } /// @@ -109,9 +136,35 @@ public async Task UploadJobPhoto(string photoPath, long jobId) /// The path to the photo file. /// The ID of the profile associated with the photo. /// A dynamic object representing the uploaded photo. - public async Task UploadProfilePhoto(string photoPath, long profileId) + public async Task UploadProfilePhoto(string photoPath, long profileId) { - return await UploadPhoto(photoPath, "profile", profileId); + const int maxRetries = 3; + int attempt = 0; + + while (attempt < maxRetries) + { + try + { + return await UploadPhoto(photoPath, "profile", profileId); + } + catch (Exception ex) + { + attempt++; + + // If we've reached the max number of retries, rethrow the exception + if (attempt == maxRetries) + { + throw new Exception($"Failed to upload photo after {maxRetries} attempts", ex); + } + + // Wait for 2 seconds before retrying + await Task.Delay(2000); + } + } + + // This will never be reached due to the return in the loop, + // but we include it to satisfy the method's return type. + return null; } /// From 4ccb78cc26f2bbdcebfc747bc1193ed91e5c97b9 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Tue, 24 Sep 2024 14:37:37 -0700 Subject: [PATCH 5/8] Remove logs --- StudioClient/Photo.cs | 6 ------ 1 file changed, 6 deletions(-) diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index e1acd0c..66ba330 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -1,4 +1,3 @@ -using System; using System.Drawing.Imaging; using System.Security.Cryptography; using NetVips; @@ -253,11 +252,8 @@ private PhotoMetadata GetImageMetadata(byte[] imageData) photoMetadata.Orientation = GetImageOrientation(image); - // Example: Save the image to a different file ImageFormat format = image.RawFormat; photoMetadata.Format = format; - - Console.WriteLine(format); } } @@ -334,8 +330,6 @@ private Image ResizeImage(Image image, PhotoMetadata metadata) // resize image to calculated final size Dimensions finalDimensions = CalculateFinalSize(metadata.Width, metadata.Height, metadata.Orientation); - Console.WriteLine($" {finalDimensions.Width} x {finalDimensions.Height} "); - Image finalImage = image.ThumbnailImage(finalDimensions.Width, finalDimensions.Height, noRotate: false, crop: Enums.Interesting.Centre, size: Enums.Size.Both); return finalImage; From aae88c642d24eaa852270d5f316a096c79290204 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 26 Sep 2024 14:46:53 -0700 Subject: [PATCH 6/8] Fix resized image not uploading --- StudioClient/Photo.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index 66ba330..510d926 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -202,12 +202,15 @@ private async Task UploadPhoto(string photoPath, string modelName, long PhotoMetadata photoMetadata = GetImageMetadata(photoData); Image image = Image.NewFromBuffer(photoData); - Image modifiedImage = ResizeImage(image, photoMetadata); - + string format = ".jpg"; + if (photoMetadata.Format != null && photoMetadata.Format.Equals(ImageFormat.Png)) { + format = ".png"; + } + byte[] fileBytes = modifiedImage.WriteToBuffer($".{format}[Q=98]"); var md5 = MD5.Create(); - byte[] md5Hash = md5.ComputeHash(photoData); + byte[] md5Hash = md5.ComputeHash(fileBytes); string md5Base64 = Convert.ToBase64String(md5Hash); dynamic uploadObj = await GetUploadUrl(photo.id.Value, System.Net.WebUtility.UrlEncode(md5Base64)); @@ -215,7 +218,6 @@ private async Task UploadPhoto(string photoPath, string modelName, long using (RestClient httpClient = new RestClient()) { - byte[] fileBytes = File.ReadAllBytes(photoPath); RestRequest request = new RestRequest(presignedUrl, Method.Put); request.AddParameter("application/octet-stream", fileBytes, ParameterType.RequestBody); From 7755339fbb7c0b06f36022ef7c74a2ace665acca Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 2 Oct 2024 16:34:36 -0700 Subject: [PATCH 7/8] add internal resized param, add photo buffer option to read image via buffer instead of path --- StudioClient/Photo.cs | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/StudioClient/Photo.cs b/StudioClient/Photo.cs index 510d926..bb430e2 100644 --- a/StudioClient/Photo.cs +++ b/StudioClient/Photo.cs @@ -97,8 +97,9 @@ public async Task GetUploadUrl(long photoId, string md5 = "", bool useC /// /// The path to the photo file. /// The ID of the job associated with the photo. + /// The photo buffer byte array (reads photo from byte array instead of photo path) /// A dynamic object representing the uploaded photo. - public async Task UploadJobPhoto(string photoPath, long jobId) + public async Task UploadJobPhoto(string photoPath, long jobId, byte[]? photoBuffer = null) { const int maxRetries = 3; int attempt = 0; @@ -107,7 +108,7 @@ public async Task GetUploadUrl(long photoId, string md5 = "", bool useC { try { - return await UploadPhoto(photoPath, "job", jobId); + return await UploadPhoto(photoPath, "job", jobId, photoBuffer); } catch (Exception ex) { @@ -134,8 +135,9 @@ public async Task GetUploadUrl(long photoId, string md5 = "", bool useC /// /// The path to the photo file. /// The ID of the profile associated with the photo. + /// The photo buffer byte array (reads photo from byte array instead of photo path) /// A dynamic object representing the uploaded photo. - public async Task UploadProfilePhoto(string photoPath, long profileId) + public async Task UploadProfilePhoto(string photoPath, long profileId, byte[] photoBuffer = null) { const int maxRetries = 3; int attempt = 0; @@ -144,7 +146,7 @@ public async Task GetUploadUrl(long photoId, string md5 = "", bool useC { try { - return await UploadPhoto(photoPath, "profile", profileId); + return await UploadPhoto(photoPath, "profile", profileId, photoBuffer); } catch (Exception ex) { @@ -172,8 +174,9 @@ public async Task GetUploadUrl(long photoId, string md5 = "", bool useC /// The path to the photo file. /// The name of the model (job or profile). /// The ID of the model associated with the photo. + /// The photo buffer byte array (reads photo from byte array instead of photo path) /// A dynamic object representing the uploaded photo. - private async Task UploadPhoto(string photoPath, string modelName, long modelId) + private async Task UploadPhoto(string photoPath, string modelName, long modelId, byte[]? photoBuffer) { string[] availableModels = { "job", "profile" }; if (!availableModels.Contains(modelName)) throw new Exception("Invalid model name. Must be 'job' or 'profile'"); @@ -185,16 +188,7 @@ private async Task UploadPhoto(string photoPath, string modelName, long throw new Exception("Photo has an invalid extension. Supported extensions (.jpg, .jpeg, .png, .webp)"); } - var photoObject = new JObject - { - { "name", $"{Guid.NewGuid()}{fileExtension}" }, - { "path", photoPath } - }; - if (modelName == "job") photoObject["job_id"] = modelId; else photoObject["profile_id"] = modelId; - - dynamic photo = await CreatePhoto(photoObject); - - byte[] photoData = File.ReadAllBytes(photoPath); + byte[] photoData = photoBuffer != null ? photoBuffer : File.ReadAllBytes(photoPath); if (photoData.Length > 0 && ((photoData.Length / 1024 / 1024) > MAX_PHOTO_SIZE)) { throw new Exception($"{photoPath} exceeds 27MB"); @@ -204,6 +198,16 @@ private async Task UploadPhoto(string photoPath, string modelName, long Image image = Image.NewFromBuffer(photoData); Image modifiedImage = ResizeImage(image, photoMetadata); + var photoObject = new JObject + { + { "name", $"{Guid.NewGuid()}{fileExtension}" }, + { "path", photoPath }, + { "resized", image.Height != modifiedImage.Height } + }; + if (modelName == "job") photoObject["job_id"] = modelId; else photoObject["profile_id"] = modelId; + + dynamic photo = await CreatePhoto(photoObject); + string format = ".jpg"; if (photoMetadata.Format != null && photoMetadata.Format.Equals(ImageFormat.Png)) { format = ".png"; From 5b5ed341093debf45165c12bf7ebd8782244b682 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Wed, 2 Oct 2024 16:39:30 -0700 Subject: [PATCH 8/8] Fix indent csproj --- StudioClient/StudioClient.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/StudioClient/StudioClient.csproj b/StudioClient/StudioClient.csproj index 84a5632..4d81b59 100644 --- a/StudioClient/StudioClient.csproj +++ b/StudioClient/StudioClient.csproj @@ -25,7 +25,7 @@ - +