Skip to content

Commit

Permalink
Add V2 features. Multi photo download, download extract, download ext…
Browse files Browse the repository at this point in the history
…ract with replace bg, dual fileoutput
  • Loading branch information
kev-le committed Jan 24, 2024
1 parent f37e140 commit 40c4541
Show file tree
Hide file tree
Showing 4 changed files with 169 additions and 3 deletions.
158 changes: 158 additions & 0 deletions StudioClient/DownloadHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using Newtonsoft.Json.Linq;
using NetVips;


namespace SkylabStudio
{
public partial class StudioClient
{
private async Task<List<Image>?> DownloadBgImages(dynamic profile)
{
var httpClient = new HttpClient();
SemaphoreSlim semaphore = new SemaphoreSlim(_maxConcurrentDownloads);

List<Image> tempBgs = new List<Image>();
List<JToken> bgPhotos = ((JArray) profile!.photos).Where(photo => photo["jobId"] != null).ToList();

foreach (dynamic bg in bgPhotos)
{
byte[] bgBuffer = await DownloadImageAsync(bg.originalUrl.Value, semaphore);
Image bgImage = Image.NewFromBuffer(bgBuffer);
tempBgs.Add(bgImage);
}

return tempBgs;

}

private static async Task<byte[]?> DownloadImageAsync(string imageUrl, SemaphoreSlim? semaphore = null)
{
try
{
if (semaphore != null) await semaphore.WaitAsync(); // Wait until a slot is available

using (HttpClient httpClient = new HttpClient())
{
// Download the image into a byte array
byte[] imageBuffer = await httpClient.GetByteArrayAsync(imageUrl);

return imageBuffer;
}
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error downloading image: {ex.Message}");
return null;
}
}

private async Task<bool> DownloadReplacedBackgroundImage(string fileName, Image inputImage, string outputPath, dynamic? profile = null, List<Image>? bgs = null)
{
try
{
string outputFileType = profile?.outputFileType?.Value ?? "png";

if (bgs == null && profile?.photos?.Count > 0) {
bgs = await DownloadBgImages(profile);
}

Image alphaChannel = inputImage.ExtractBand(3);
Image rgbChannel = inputImage.ExtractBand(0, 3);
Image rgbCutout = rgbChannel.Bandjoin(alphaChannel);

if (bgs != null && bgs.Count > 0){
for (int i = 0; i < bgs.Count; i++) {
string newFileName = i == 0 ? $"{Path.GetFileNameWithoutExtension(fileName)}.{outputFileType}": $"{Path.GetFileNameWithoutExtension(fileName)} ({i+1}).{outputFileType}";
Image resizedBgImage = bgs[i].ThumbnailImage(inputImage.Width, inputImage.Height, crop: Enums.Interesting.Centre);
Image resultImage = resizedBgImage.Composite2(rgbCutout, Enums.BlendMode.Over);
resultImage.WriteToFile(Path.Combine(outputPath, newFileName));
}
}

return true;
}
catch (Exception ex)
{
Console.WriteLine($"Error downloading background image: {ex.Message}");
return false;
}
}

public async Task<bool> DownloadAllPhotos(JArray photosList, dynamic profile, string outputPath)
{
try {
profile = await GetProfile(profile.id.Value);
List<Image> bgs = await DownloadBgImages(profile);

var httpClient = new HttpClient();

List<string> photoIds = photosList.Select(photo => photo?["id"]?.ToString() ?? "").ToList() ?? new List<string>();

// Use a semaphore to control access to the download operation
var semaphore = new SemaphoreSlim(_maxConcurrentDownloads);
List<Task> downloadTasks = new List<Task>();
foreach (string photoId in photoIds)
{
downloadTasks.Add(DownloadPhoto(long.Parse(photoId), outputPath, profile));
}

// Wait for all download tasks to complete
await Task.WhenAll(downloadTasks);

return true;
} catch (Exception _e) {
Console.WriteLine(_e);
return false;
}
}
public async Task<bool> DownloadPhoto(long photoId, string outputPath, dynamic? profile = null, dynamic? options = null)
{
try {
dynamic photo = await GetPhoto(photoId);
long profileId = photo.job.profileId;

string fileName = photo.name.Value;

if (profile == null) {
profile = await GetProfile(profileId);
}
bool isExtract = Convert.ToBoolean(profile.enableExtract.Value);
bool replaceBackground = Convert.ToBoolean(profile.enableExtract.Value);
bool isDualFileOutput = Convert.ToBoolean(profile.dualFileOutput.Value);
bool enableStripPngMetadata = Convert.ToBoolean(profile.enableStripPngMetadata.Value);
List<Image>? bgs = options?.bgs;

// Load output image
byte[] imageBuffer = await DownloadImageAsync(photo.retouchedUrl.Value);
Image image = Image.NewFromBuffer(imageBuffer);

if (isExtract) { // Output extract image
string pngFileName = $"{Path.GetFileNameWithoutExtension(fileName)}.png";

// Dual File Output will provide an image in the format specified in the outputFileType field
// and an extracted image as a PNG.
if (isDualFileOutput) {
image.WriteToFile(Path.Combine(outputPath, pngFileName));
}

if (replaceBackground) {
await DownloadReplacedBackgroundImage(fileName, image, outputPath, profile, bgs);
}

// Regular Extract output
if (!isDualFileOutput) image.WriteToFile(Path.Combine(outputPath, pngFileName));
} else { // Non-extracted regular image output
image.WriteToFile(Path.Combine(outputPath, fileName));
}

Console.WriteLine($"Successfully downloaded: {fileName}");
return true;
} catch (Exception _e)
{
Console.WriteLine($"Failed to download photo id: {photoId}");
Console.WriteLine(_e);
return false;
}
}
}
}
5 changes: 5 additions & 0 deletions StudioClient/Profile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,10 @@ public async Task<dynamic> UpdateProfile(long profileId, object payload)
{
return await Request($"profiles/{profileId}", HttpMethod.Put, payload);
}

public async Task<dynamic> GetProfileBgs(long profileId)
{
return await Request($"profiles/{profileId}/bg_photos", HttpMethod.Get);
}
}
}
4 changes: 3 additions & 1 deletion StudioClient/StudioClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,17 @@ public partial class StudioClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly int _maxConcurrentDownloads = 5;

public StudioClient(string? apiKey = null)
public StudioClient(string? apiKey = null, dynamic? options = null)
{
if (apiKey == null) throw new Exception("No API key provided");

string baseUrl = Environment.GetEnvironmentVariable("SKYLAB_API_URL") ?? "https://studio.skylabtech.ai";
_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(baseUrl);
_apiKey = apiKey;
_maxConcurrentDownloads = options?.maxConcurrentDownloads ?? 5;
}

private async Task<dynamic> Request(string endpoint, HttpMethod httpMethod, object? payload = null)
Expand Down
5 changes: 3 additions & 2 deletions StudioClient/StudioClient.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@
<PackageProjectUrl>https://github.com/skylab-tech/studio_client_dotnet</PackageProjectUrl>
<PackageIconUrl>https://avatars.githubusercontent.com/u/45469060</PackageIconUrl>
<PackageTags>studio skylab skylabtech api library .net nuget</PackageTags>
<TargetFrameworks>net47;net472;net48;net6.0;net7.0;netstandard2.0</TargetFrameworks>
<TargetFrameworks>net5.0;net6.0;net7.0;netstandard2.1;netcoreapp3.0;netcoreapp3.1</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageReadmeFile>README.md</PackageReadmeFile>
<Copyright>Copyright (c) Skylab Technologies Inc. 2023</Copyright>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNet.WebApi.SelfHost" Version="5.3.0" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="NetVips" Version="2.4.0" />
<PackageReference Include="NetVips.Native.osx-arm64" Version="8.15.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<None Include="..\README.md" Pack="true" PackagePath="\" />
</ItemGroup>
Expand Down

0 comments on commit 40c4541

Please sign in to comment.