Skip to content

Commit

Permalink
Merge branch 'main' into version-0.0.9
Browse files Browse the repository at this point in the history
  • Loading branch information
kev-le authored Mar 2, 2024
2 parents c830149 + 5626de4 commit 5129213
Show file tree
Hide file tree
Showing 5 changed files with 207 additions and 10 deletions.
59 changes: 53 additions & 6 deletions StudioClient/DownloadHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ namespace SkylabStudio
public class PhotoOptions
{
public List<Image>? Bgs { get; set; }
public bool? ReturnOnError { get; set; }

// Default constructor (parameterless)
public PhotoOptions()
{
Bgs = new List<Image>();
ReturnOnError = false;
}
}

Expand All @@ -30,6 +31,11 @@ public DownloadAllPhotosResult()
}
public partial class StudioClient
{
/// <summary>
/// Downloads background images based on the provided profile.
/// </summary>
/// <param name="profile">The profile associated to the job.</param>
/// <returns>List of downloaded background images.</returns>
private async Task<List<Image>?> DownloadBgImages(dynamic profile)
{
List<Image> tempBgs = new List<Image>();
Expand All @@ -45,6 +51,11 @@ public partial class StudioClient
return tempBgs;
}

/// <summary>
/// Downloads an image asynchronously from the specified URL.
/// </summary>
/// <param name="imageUrl">The URL of the image to download.</param>
/// <returns>The downloaded image as a byte array.</returns>
private static async Task<byte[]?> DownloadImageAsync(string imageUrl)
{
if (!imageUrl.StartsWith("http", StringComparison.OrdinalIgnoreCase)) {
Expand All @@ -71,6 +82,16 @@ public partial class StudioClient
}
}

/// <summary>
/// Downloads and replaces the background image in the input image.
/// Required: Profile should have a background uploaded and replace background toggled on
/// </summary>
/// <param name="fileName">The name of the file being processed.</param>
/// <param name="inputImage">The input image to process.</param>
/// <param name="outputPath">The path where the processed images will be saved.</param>
/// <param name="profile">The profile associated to the job.</param>
/// <param name="bgs">List of background images.</param>
/// <returns>True if the operation is successful; otherwise, false.</returns>
private async Task<bool> DownloadReplacedBackgroundImage(string fileName, Image inputImage, string outputPath, dynamic? profile = null, List<Image>? bgs = null)
{
try
Expand Down Expand Up @@ -98,11 +119,20 @@ private async Task<bool> DownloadReplacedBackgroundImage(string fileName, Image
}
catch (Exception ex)
{
Console.Error.WriteLine($"Error downloading background image: {ex.Message}");
return false;
string errorMsg = $"Error downloading background image: {ex.Message}";
Console.Error.WriteLine(errorMsg);

throw new Exception(errorMsg);
}
}

/// <summary>
/// Downloads all photos based on a list of photo IDs.
/// </summary>
/// <param name="photosList">List of photo objects with IDs.</param>
/// <param name="profile">The profile associated to the job.</param>
/// <param name="outputPath">The path where photos will be downloaded.</param>
/// <returns>Download result containing lists of success and errored photo names.</returns>
public async Task<DownloadAllPhotosResult> DownloadAllPhotos(JArray photosList, dynamic profile, string outputPath)
{
if (!Directory.Exists(outputPath))
Expand All @@ -127,6 +157,7 @@ public async Task<DownloadAllPhotosResult> DownloadAllPhotos(JArray photosList,
List<Task<Tuple<string, bool>>> downloadTasks = new List<Task<Tuple<string, bool>>>();
PhotoOptions photoOptions = new PhotoOptions
{
ReturnOnError = true,
Bgs = bgs
};
foreach (string photoId in photoIds)
Expand Down Expand Up @@ -163,6 +194,20 @@ public async Task<DownloadAllPhotosResult> DownloadAllPhotos(JArray photosList,
return downloadResults;
}
}

/// <summary>
/// Downloads a photo based on the specified photo ID.
/// </summary>
/// <param name="photoId">The ID of the photo to download.</param>
/// <param name="outputPath">The path where the downloaded photo will be saved. Could either be </param>
/// <param name="profile">Optional: The profile containing photo processing options.</param>
/// <param name="options">Optional: Additional options for photo processing.</param>
/// <param name="semaphore">Optional - *Used Interally with DownloadAllPhotos* : SemaphoreSlim for controlling concurrent photo downloads.</param>
/// <returns>
/// A tuple containing the downloaded photo's filename and a boolean indicating
/// whether the download was successful.
/// </returns>
/// <exception cref="Exception">Thrown on any download error when DownloadPhoto is called without ReturnOnError option.</exception>
public async Task<Tuple<string,bool>> DownloadPhoto(long photoId, string outputPath, dynamic? profile = null, PhotoOptions? options = null, SemaphoreSlim? semaphore = null)
{
string fileName = "";
Expand Down Expand Up @@ -194,7 +239,7 @@ public async Task<Tuple<string,bool>> DownloadPhoto(long photoId, string output
// 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";

Expand All @@ -218,8 +263,10 @@ public async Task<Tuple<string,bool>> DownloadPhoto(long photoId, string output
return new (fileName, true);
} catch (Exception _e)
{
Console.Error.WriteLine($"Failed to download photo id: {photoId}");
Console.Error.WriteLine(_e);
string errorMsg = $"Failed to download photo id: {photoId} - ${_e}";
if (options?.ReturnOnError == null) {
throw new Exception(errorMsg);
}

return new (fileName, false);
} finally
Expand Down
46 changes: 46 additions & 0 deletions StudioClient/Job.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,92 @@ namespace SkylabStudio
{
public partial class StudioClient
{
/// <summary>
/// Retrieves a list of all jobs.
/// </summary>
/// <returns>A dynamic object representing the list of jobs.</returns>
public async Task<dynamic> ListJobs()
{
return await Request("jobs", RestSharp.Method.Get);
}

/// <summary>
/// Creates a new job with the specified payload.
/// </summary>
/// <param name="payload">The job payload to be sent in the request.</param>
/// <returns>A dynamic object representing the created job.</returns>
public async Task<dynamic> CreateJob(object payload)
{
return await Request("jobs", RestSharp.Method.Post, payload);
}

/// <summary>
/// Retrieves information about a specific job based on its ID.
/// </summary>
/// <param name="jobId">The ID of the job to retrieve.</param>
/// <returns>A dynamic object representing the requested job.</returns>
public async Task<dynamic> GetJob(long jobId)
{
return await Request($"jobs/{jobId}", RestSharp.Method.Get);
}

/// <summary>
/// Retrieves information about a job based on its name.
/// </summary>
/// <param name="jobName">The name of the job to retrieve.</param>
/// <returns>A dynamic object representing the requested job.</returns>
public async Task<dynamic> GetJobByName(string jobName)
{
return await Request($"jobs/find_by_name/?name={jobName}", RestSharp.Method.Get);
}

/// <summary>
/// Updates a specific job with the provided payload.
/// </summary>
/// <param name="jobId">The ID of the job to update.</param>
/// <param name="payload">The job payload to be sent in the request.</param>
/// <returns>A dynamic object representing the updated job.</returns>
public async Task<dynamic> UpdateJob(long jobId, object payload)
{
return await Request($"jobs/{jobId}", RestSharp.Method.Put, payload);
}

/// <summary>
/// Queues a specific job with the provided payload.
/// </summary>
/// <param name="jobId">The ID of the job to queue.</param>
/// <param name="payload">The job payload to be sent in the request.</param>
/// <returns>A dynamic object representing the queued job.</returns>
public async Task<dynamic> QueueJob(long jobId, object payload)
{
return await Request($"jobs/{jobId}/queue", RestSharp.Method.Post, payload);
}

/// <summary>
/// Cancels a specific job based on its ID.
/// </summary>
/// <param name="jobId">The ID of the job to cancel.</param>
/// <returns>A dynamic object representing the result of the cancellation.</returns>
public async Task<dynamic> CancelJob(long jobId)
{
return await Request($"jobs/{jobId}/cancel", RestSharp.Method.Post);
}

/// <summary>
/// Deletes a specific job based on its ID.
/// </summary>
/// <param name="jobId">The ID of the job to delete.</param>
/// <returns>A dynamic object representing the result of the deletion.</returns>
public async Task<dynamic> DeleteJob(long jobId)
{
return await Request($"jobs/{jobId}", RestSharp.Method.Delete);
}

/// <summary>
/// Retrieves information about jobs that are in front of the specified job.
/// </summary>
/// <param name="jobId">The ID of the reference job.</param>
/// <returns>A dynamic object representing jobs that are in front of the reference job.</returns>
public async Task<dynamic> JobsInFront(long jobId)
{
return await Request($"jobs/{jobId}/jobs_in_front", RestSharp.Method.Get);
Expand Down
53 changes: 51 additions & 2 deletions StudioClient/Photo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,41 +7,89 @@ namespace SkylabStudio
{
public partial class StudioClient
{
public static readonly string[] VALID_EXTENSIONS = { ".png", ".jpg", ".jpeg", "webp" };
/// <summary>
/// Array of valid file extensions for photos.
/// </summary>
public static readonly string[] VALID_EXTENSIONS = { ".png", ".jpg", ".jpeg", ".webp" };

/// <summary>
/// Maximum allowed size for a photo in bytes.
/// </summary>
public const int MAX_PHOTO_SIZE = 27 * 1024 * 1024;

/// <summary>
/// Creates a new photo with the specified payload.
/// </summary>
/// <param name="payload">The photo payload to be sent in the request.</param>
/// <returns>A dynamic object representing the created photo.</returns>
public async Task<dynamic> CreatePhoto(object payload)
{
return await Request("photos", Method.Post, payload);
}

/// <summary>
/// Retrieves information about a specific photo based on its ID.
/// </summary>
/// <param name="photoId">The ID of the photo to retrieve.</param>
/// <returns>A dynamic object representing the requested photo.</returns>
public async Task<dynamic> GetPhoto(long photoId)
{
return await Request($"photos/{photoId}", Method.Get);
}

/// <summary>
/// Deletes a specific photo based on its ID.
/// </summary>
/// <param name="photoId">The ID of the photo to delete.</param>
/// <returns>A dynamic object representing the result of the deletion.</returns>
public async Task<dynamic> DeletePhoto(long photoId)
{
return await Request($"photos/{photoId}", Method.Delete);
}

/// <summary>
/// Retrieves a presigned URL for uploading a photo.
/// </summary>
/// <param name="photoId">The ID of the photo for which to get the upload URL.</param>
/// <param name="md5">The MD5 hash of the photo data.</param>
/// <param name="useCacheUpload">Flag indicating whether to use cache upload.</param>
/// <returns>A dynamic object representing the presigned upload URL.</returns>
public async Task<dynamic> GetUploadUrl(long photoId, string md5 = "", bool useCacheUpload = false)
{
string queryParams = $"use_cache_upload={useCacheUpload.ToString().ToLower()}&photo_id={photoId}&content_md5={md5}";

return await Request($"photos/upload_url?{queryParams}", Method.Get);
}

/// <summary>
/// Uploads a photo associated with a job.
/// </summary>
/// <param name="photoPath">The path to the photo file.</param>
/// <param name="jobId">The ID of the job associated with the photo.</param>
/// <returns>A dynamic object representing the uploaded photo.</returns>
public async Task<dynamic> UploadJobPhoto(string photoPath, long jobId)
{
return await UploadPhoto(photoPath, "job", jobId);
}

/// <summary>
/// Uploads a photo associated with a profile.
/// </summary>
/// <param name="photoPath">The path to the photo file.</param>
/// <param name="profileId">The ID of the profile associated with the photo.</param>
/// <returns>A dynamic object representing the uploaded photo.</returns>
public async Task<dynamic> UploadProfilePhoto(string photoPath, long profileId)
{
return await UploadPhoto(photoPath, "profile", profileId);
}

/// <summary>
/// Uploads a photo associated with a job or profile.
/// </summary>
/// <param name="photoPath">The path to the photo file.</param>
/// <param name="modelName">The name of the model (job or profile).</param>
/// <param name="modelId">The ID of the model associated with the photo.</param>
/// <returns>A dynamic object representing the uploaded photo.</returns>
private async Task<dynamic> UploadPhoto(string photoPath, string modelName, long modelId)
{
string[] availableModels = { "job", "profile" };
Expand All @@ -51,7 +99,7 @@ private async Task<dynamic> UploadPhoto(string photoPath, string modelName, long
string fileExtension = Path.GetExtension(photoBasename).ToLower();
if (!VALID_EXTENSIONS.Contains(fileExtension))
{
throw new Exception("Photo has invalid extension. Supported extensions (.jpg, .jpeg, .png, .webp)");
throw new Exception("Photo has an invalid extension. Supported extensions (.jpg, .jpeg, .png, .webp)");
}

var photoObject = new JObject
Expand Down Expand Up @@ -82,6 +130,7 @@ private async Task<dynamic> UploadPhoto(string photoPath, string modelName, long

request.AddParameter("application/octet-stream", fileBytes, ParameterType.RequestBody);
request.AddHeader("Content-MD5", Convert.ToBase64String(md5.ComputeHash(fileBytes)));

if (modelName == "job") request.AddHeader("X-Amz-Tagging", "job=photo&api=true");

// Upload image via PUT request to presigned url
Expand Down
25 changes: 25 additions & 0 deletions StudioClient/Profile.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,51 @@ namespace SkylabStudio
{
public partial class StudioClient
{
/// <summary>
/// Creates a new profile with the specified payload.
/// </summary>
/// <param name="payload">The payload containing profile information.</param>
/// <returns>A dynamic object representing the created profile.</returns>
public async Task<dynamic> CreateProfile(object payload)
{
return await Request("profiles", RestSharp.Method.Post, payload);
}

/// <summary>
/// Retrieves a list of profiles.
/// </summary>
/// <returns>A dynamic object representing a list of profiles.</returns>
public async Task<dynamic> ListProfiles()
{
return await Request("profiles", RestSharp.Method.Get);
}

/// <summary>
/// Retrieves the profile with the specified ID.
/// </summary>
/// <param name="profileId">The ID of the profile to retrieve.</param>
/// <returns>A dynamic object representing the retrieved profile.</returns>
public async Task<dynamic> GetProfile(long profileId)
{
return await Request($"profiles/{profileId}", RestSharp.Method.Get);
}

/// <summary>
/// Updates the profile with the specified ID using the provided payload.
/// </summary>
/// <param name="profileId">The ID of the profile to update.</param>
/// <param name="payload">The payload containing updated profile information.</param>
/// <returns>A dynamic object representing the updated profile.</returns>
public async Task<dynamic> UpdateProfile(long profileId, object payload)
{
return await Request($"profiles/{profileId}", RestSharp.Method.Put, payload);
}

/// <summary>
/// Retrieves background photos associated with the profile identified by the given profile ID.
/// </summary>
/// <param name="profileId">The ID of the profile to retrieve background photos for.</param>
/// <returns>A dynamic object representing background photos of the profile.</returns>
public async Task<dynamic> GetProfileBgs(long profileId)
{
return await Request($"profiles/{profileId}/bg_photos", RestSharp.Method.Get);
Expand Down
Loading

0 comments on commit 5129213

Please sign in to comment.