Skip to content

Commit

Permalink
Merge pull request #2 from skylab-tech/version-2.1
Browse files Browse the repository at this point in the history
Version 2.1
  • Loading branch information
kev-le authored Feb 21, 2024
2 parents ace0565 + 1b6eb0f commit 7d77b7e
Show file tree
Hide file tree
Showing 5 changed files with 96 additions and 34 deletions.
19 changes: 12 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@ For all examples, assume:
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 };
var apiClient = new StudioClient(Environment.GetEnvironmentVariable("SKYLAB_API_TOKEN"), studioOptions);
```

```dotnet
Expand Down Expand Up @@ -138,12 +143,6 @@ api.UpdateProfile(profileId, new { name = $"Test Profile", enable_crop = false,

For all payload options, consult the [API documentation](https://studio-docs.skylabtech.ai/#tag/profile/operation/updateProfileById).

#### List all photos

```dotnet
api.ListPhotos();
```

#### Get photo

```dotnet
Expand Down Expand Up @@ -173,7 +172,13 @@ This function handles downloading the output photos to a specified directory.
```dotnet
JArray photosList = completedJob.photos;
api.DownloadAllPhotos(photosList, completedJob.profile, "/output/folder/");
DownloadAllPhotosResult downloadResults = await apiClient.DownloadAllPhotos(photosList, completedJob.profile, "/output/folder/");
Console.WriteLine($"Success photos: [{string.Join(", ", downloadResults.SuccessPhotos)}]");
Console.WriteLine($"Erorred photos: [{string.Join(", ", downloadResults.ErroredPhotos)}]");
Output:
Success photos: [1.jpg, 2.jpg, 3.jpg]
Erorred photos: [4.jpg]
```

OR
Expand Down
13 changes: 9 additions & 4 deletions StudioClient.Example/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ class Program
{
static async Task Main(string[] args)
{
var apiClient = new StudioClient(Environment.GetEnvironmentVariable("SKYLAB_API_TOKEN"));
var studioOptions = new StudioOptions { MaxConcurrentDownloads = 5 };
var apiClient = new StudioClient(Environment.GetEnvironmentVariable("SKYLAB_API_TOKEN"), studioOptions);

try
{
Expand All @@ -29,17 +30,21 @@ static async Task Main(string[] args)
// QUEUE JOB
dynamic queuedJob = await apiClient.QueueJob(job.id.Value, new { callback_url = "YOUR_CALLBACK_ENDPOINT" });

// FETCH COMPLETED JOB (wait until job status is completed)
// ...
// !(wait until job status is completed by waiting for callback or by polling)!
// FETCH COMPLETED JOB
dynamic completedJob = await apiClient.GetJob(queuedJob.id.Value);

// DOWNLOAD COMPLETED JOB PHOTOS
JArray photosList = completedJob.photos;
await apiClient.DownloadAllPhotos(photosList, completedJob.profile, "/output/folder/");
DownloadAllPhotosResult downloadResults = await apiClient.DownloadAllPhotos(photosList, completedJob.profile, "/output/folder/");
Console.WriteLine($"Success photos: [{string.Join(", ", downloadResults.SuccessPhotos)}]");
Console.WriteLine($"Erorred photos: [{string.Join(", ", downloadResults.ErroredPhotos)}]");

}
catch (Exception ex)
{
Console.WriteLine($"An error occurred: {ex.Message}");
Console.Error.WriteLine($"An error occurred: {ex.Message}");
}
}
}
Expand Down
84 changes: 63 additions & 21 deletions StudioClient/DownloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,18 @@

namespace SkylabStudio
{
public class DownloadAllPhotosResult
{
public List<string> SuccessPhotos { get; set; }
public List<string> ErroredPhotos { get; set; }

// Default constructor (parameterless)
public DownloadAllPhotosResult()
{
SuccessPhotos = new List<string>();
ErroredPhotos = new List<string>();
}
}
public partial class StudioClient
{
private async Task<List<Image>?> DownloadBgImages(dynamic profile)
Expand All @@ -21,11 +33,14 @@ public partial class StudioClient
}

return tempBgs;

}

private static async Task<byte[]?> DownloadImageAsync(string imageUrl)
{
if (!imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) {
throw new Exception($"Invalid retouchedUrl: \"{imageUrl}\" - Please ensure the job is complete");
}

try
{
using (HttpClient httpClient = new HttpClient())
Expand All @@ -38,7 +53,7 @@ public partial class StudioClient
}
catch (HttpRequestException ex)
{
Console.WriteLine($"Error downloading image: {ex.Message}");
Console.Error.WriteLine($"Error downloading image: {ex.Message}");
return null;
}
}
Expand Down Expand Up @@ -70,13 +85,16 @@ private async Task<bool> DownloadReplacedBackgroundImage(string fileName, Image
}
catch (Exception ex)
{
Console.WriteLine($"Error downloading background image: {ex.Message}");
Console.Error.WriteLine($"Error downloading background image: {ex.Message}");
return false;
}
}

public async Task<bool> DownloadAllPhotos(JArray photosList, dynamic profile, string outputPath)
public async Task<DownloadAllPhotosResult> DownloadAllPhotos(JArray photosList, dynamic profile, string outputPath)
{
List<string> successPhotos = new List<string>();
List<string> erroredPhotos = new List<string>();

try {
profile = await GetProfile(profile.id.Value);
List<Image> bgs = await DownloadBgImages(profile);
Expand All @@ -87,36 +105,56 @@ public async Task<bool> DownloadAllPhotos(JArray photosList, dynamic profile, st

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

// Wait for all download tasks to complete
await Task.WhenAll(downloadTasks);
IEnumerable<Tuple<string, bool>> results = await Task.WhenAll(downloadTasks);

return true;
foreach (var result in results)
{
if (result.Item2) {
successPhotos.Add(result.Item1);
} else {
erroredPhotos.Add(result.Item1);
}
}

DownloadAllPhotosResult downloadResults = new DownloadAllPhotosResult {
SuccessPhotos = successPhotos,
ErroredPhotos = erroredPhotos
};

return downloadResults;
} catch (Exception _e) {
Console.WriteLine(_e);
return false;
Console.Error.WriteLine(_e);

DownloadAllPhotosResult downloadResults = new DownloadAllPhotosResult {
SuccessPhotos = successPhotos,
ErroredPhotos = erroredPhotos
};

return downloadResults;
}
}
public async Task<bool> DownloadPhoto(long photoId, string outputPath, dynamic? profile = null, dynamic? options = null, SemaphoreSlim? semaphore = null)
public async Task<Tuple<string,bool>> DownloadPhoto(long photoId, string outputPath, dynamic? profile = null, dynamic? options = null, SemaphoreSlim? semaphore = null)
{
try {
if (semaphore != null) await semaphore.WaitAsync(); // Wait until a slot is available
dynamic photo = await GetPhoto(photoId);
long profileId = photo.job.profileId;

dynamic photo = await GetPhoto(photoId);
long profileId = photo.job.profileId;
string fileName = photo.name.Value;

string fileName = photo.name.Value;
try {
if (semaphore != null) await semaphore.WaitAsync(); // Wait until a slot is available

if (profile == null) {
profile = await GetProfile(profileId);
}
bool isExtract = Convert.ToBoolean(profile.enableExtract.Value);
bool replaceBackground = Convert.ToBoolean(profile.enableExtract.Value);
bool replaceBackground = Convert.ToBoolean(profile.replaceBackground.Value);
bool isDualFileOutput = Convert.ToBoolean(profile.dualFileOutput.Value);
bool enableStripPngMetadata = Convert.ToBoolean(profile.enableStripPngMetadata.Value);
List<Image>? bgs = options?.bgs;
Expand All @@ -139,18 +177,22 @@ public async Task<bool> DownloadPhoto(long photoId, string outputPath, dynamic?
}

// Regular Extract output
if (!isDualFileOutput) image.WriteToFile(Path.Combine(outputPath, pngFileName));
if (!isDualFileOutput && !replaceBackground) 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;
return new (fileName, true);
} catch (Exception _e)
{
Console.WriteLine($"Failed to download photo id: {photoId}");
Console.WriteLine(_e);
return false;
Console.Error.WriteLine($"Failed to download photo id: {photoId}");
Console.Error.WriteLine(_e);

return new (fileName, false);
} finally
{
if (semaphore != null) semaphore.Release(); // Release the semaphore
}
}
}
Expand Down
5 changes: 5 additions & 0 deletions StudioClient/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public async Task<dynamic> CancelJob(long jobId)
return await Request($"jobs/{jobId}/cancel", HttpMethod.Post);
}

public async Task<dynamic> DeleteJob(long jobId)
{
return await Request($"jobs/{jobId}", HttpMethod.Delete);
}

public async Task<dynamic> JobsInFront(long jobId)
{
return await Request($"jobs/{jobId}/jobs_in_front", HttpMethod.Get);
Expand Down
9 changes: 7 additions & 2 deletions StudioClient/StudioClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,26 @@


namespace SkylabStudio {
public class StudioOptions
{
public int? MaxConcurrentDownloads { get; set; }
}

public partial class StudioClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly int _maxConcurrentDownloads = 5;

public StudioClient(string? apiKey = null, dynamic? options = null)
public StudioClient(string? apiKey = null, StudioOptions? 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;
_maxConcurrentDownloads = options?.MaxConcurrentDownloads ?? 5;
}

private async Task<dynamic> Request(string endpoint, HttpMethod httpMethod, object? payload = null)
Expand Down

0 comments on commit 7d77b7e

Please sign in to comment.