Wednesday, September 20, 2023

Sitecore Content Hub - User Group Display Component!


Hello there, fellow foodies of the coding world! Welcome to our delightful restaurant of mistaken orders, where our primary dish today is the spicy, flavorful, and ever so complex: "Sitecore Content Hub User Group Display Component!" Don your aprons and chef hats, and let's dive deep into the recipe of this code dish. ๐Ÿณ

๐Ÿ› The Idea:

Before we stir the pot, let's discuss the idea. The code dish we're preparing serves to display the user groups that a user belongs to, right on their profile image. Imagine a pizza, and each slice is a user group. As a user, you can see which slices (or groups) you're a part of, just by looking at the pizza (your profile image). Savory, right?

User Profile Usergroups








๐Ÿœ Ingredients:

  • jQuery: The aromatic herb we'll be using to traverse and manipulate our HTML.
  • MutationObserver: The secret sauce ensuring that our ingredients (DOM elements) are ready before we start our culinary magic.
  • User Groups: The meaty chunks that we'll be refining to suit our taste.

๐Ÿณ Cooking Instructions:

Preparing the Meat (User Groups):

First, we filter out the basic flavors. We want our dish to stand out, so we're removing groups containing ".Base" or ".Role".

But wait! If our list contains the spicy "M.Consumer.US" flavor, we further refine by removing the "DML.Consumer.Base" group. It's all about balance!

Marinating the Meat:

To add some zest, we tweak the naming of our user groups. We remove any unwanted prefixes like "m" or "M." and replace any dot (.) with a space, making it more palatable.

Refining the Dish:

We don't want any overpowering flavors. Thus, we remove groups like "Everyone" or "TermsAndConditions".

To ensure uniqueness, we don't want duplicate ingredients. We get the unique groups and further refine them by removing words like "Base" or "Role".

Serving the Dish:

Now, we place our refined and unique user groups on the plate (or rather, into the HTML). Each group is garnished with a fancy icon and made clickable, though it doesn't lead anywhere right now (maybe to more recipes in the future? ๐Ÿ˜‰).

Ensuring Perfect Temperature:

We wait for the right moment (using our secret sauce, the MutationObserver) to ensure our profile settings link is ready.

Once ready, we serve our dish hot! When you hover over the profile settings, our delicious user groups are displayed, and they hide when you move away.

๐Ÿฅ˜ Pro Tips from the Chef:

The kitchen can be unpredictable. Thus, we wrap parts of our code in try-catch to handle any unforeseen spills or burns (errors).

The waitForElm function is our oven timer. It ensures we don't start garnishing until our main dish (DOM element) is fully baked and ready.

๐Ÿฐ Dessert:

After savoring this flavorful dish, remember: coding, like cooking, is an art. Sometimes it’s the mistaken orders that bring out the most delightful flavors. And while our restaurant might serve orders with a twist, it's always a gastronomic journey to remember!

The Code (code tab):

$(document).ready(function () {
var optionsUserGroups = options.userGroups.filter(x => !(x.includes(".Base") || x.includes(".Role")));

if (optionsUserGroups.includes("M.Consumer.US")) {
optionsUserGroups = jQuery.grep(optionsUserGroups, function (value) {
return value !== "M.Consumer.Base";
});
}

try {
var userGroups = optionsUserGroups.map(function (value) {
var newValue = value.replace(/m|M\./g, "");
return newValue ? newValue.replace(".", " ") : value;
});
} catch (err) {
console.log(`There was an error in Getting usergroups for user profile. ${err}`);
}

try {
if (userGroups.length > 0) {
var updatedUserGroupsArray = userGroups.filter(function (value) {
return !/Everyone|TermsAndConditions/.test(value);
});
}
} catch (err) {
console.log(`There was an error in Getting usergroups for user profile. ${err}`);
}

try {
if (updatedUserGroupsArray.length > 0) {
var uniqueUserGroups = [...new Set(updatedUserGroupsArray)];
var finalUserGroups = uniqueUserGroups.map(function (value) {
var newValue = value.replace(/Base|Role/g, "");
return newValue ? newValue.replace(".", " ") : value;
});
$.each(finalUserGroups, function (index, value) {
$(`<a class="dropdown-item" href="#"><li class="m-icon m-icon-people"></li>${value}</a>"`).insertAfter(".dropdown-divider");
});
}
} catch (err) {
console.log(`There was an error in Getting usergroups for user profile. ${err}`);
}

var selector = 'a[title*="Profile and settings"]';
waitForElm(selector).then((elem) => {
$(selector).click(function() {
$("#profile-groups-menu").hide();
$("#profile-groups-menu").addClass("hide");
});

$(selector).hover(function () {
$("#profile-groups-menu").show();
$("#profile-groups-menu").removeClass("hide");
}, function () {
$("#profile-groups-menu").hide();
$("#profile-groups-menu").addClass("hide");
});
});
});

function waitForElm(selector) {
return new Promise(resolve => {
if (document.querySelector(selector)) {
return resolve(document.querySelector(selector));
}
const observer = new MutationObserver(mutations => {
if (document.querySelector(selector)) {
resolve(document.querySelector(selector));
observer.disconnect();
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
});
}
<!-- / TEMPLATE / -->
<style>
body,
html {
/* overflow-y: hidden; */
}

#profile-groups-menu {
position: fixed;
min-height: 200px;
min-width: 238px;
max-height: 800px;
width: 238px;
border-left: 1px solid transparent;
border-right: 1px solid transparent;
border-bottom: 1px solid #f9f9f9;
top: 5em;
right: 1em;
z-index: 10000 !important;
}

.dropdown-menu {
content: "";
}

.hide {
display: none;
}
</style>
<div id="profile-groups-menu" class="hide dropdown-menu dropdown-menu-right">
<a class="dropdown-item" href="#">
<h3><i class="m-icon m-icon-user-circle"></i>Group memberships</h3></a>
<div class="dropdown-divider"></div>
</div>

Sunday, September 17, 2023

Diving Deep into the Video Caption Editor's Integration with Azure Functions

Introduction

In our previous post, we introduced the Video Caption Editor for the Sitecore Content Hub and shed light on its importance in terms of efficiency and cost-saving. Today, we'll journey deeper into the technical aspects of this groundbreaking tool, especially its integration with Azure Functions.

Video Processing and Properties

Before sending video data to the Content Hub, it's crucial to obtain specific properties of the video that can aid in its correct representation and playback. This involves understanding the compression formats, such as the Apple ProRes 422 HQ.

Converting HTML to Images

One of the fascinating features of the Video Caption Editor is its capability to transform HTML content into images. This is particularly useful for generating overlays from HTML-based video captions. Leveraging powerful libraries such as SkiaSharp and HtmlAgilityPack, this feature provides high-fidelity conversions.

using HtmlAgilityPack;
using SkiaSharp;
using System.Collections.Generic;
using Topten.RichTextKit;

namespace Sitecore.CH.Implementation.AzFunctions.Model.Video
{
public class VideoHtmlToImage
{
public byte[] ConvertHtmlToImage(string html, int originalTextBoxWidth, int originalTextBoxHeight, int targetTextBoxWidth, int targetTextBoxHeight, SKColor backgroundColor, SKColor textColor, int videoWidth)
{
// Calculate aspect ratios
float originalAspectRatio = (float)originalTextBoxWidth / originalTextBoxHeight;
float targetAspectRatio = (float)targetTextBoxWidth / targetTextBoxHeight;

// Calculate scaled dimensions based on the aspect ratio
int scaledWidth, scaledHeight;
if (originalAspectRatio > targetAspectRatio)
{
scaledWidth = targetTextBoxWidth;
scaledHeight = (int)(targetTextBoxWidth / originalAspectRatio);
}
else
{
scaledWidth = (int)(targetTextBoxHeight * originalAspectRatio);
scaledHeight = targetTextBoxHeight;
}

// Create a new image surface
using (SKBitmap bitmap = new SKBitmap(targetTextBoxWidth, targetTextBoxHeight))
using (SKCanvas canvas = new SKCanvas(bitmap))
{
canvas.Clear(backgroundColor);
// Convert the HTML to a RichString
var richString = HtmlToRichString(html, textColor, videoWidth);
richString.MaxWidth = scaledWidth;
richString.MaxHeight = scaledHeight;
richString.Paint(canvas, new SKPoint(15, 20));

// Encode the bitmap as PNG
using (SKData data = SKImage.FromBitmap(bitmap). Encode(SKEncodedImageFormat.Png, 100))
{
return data.ToArray();
}
}
}

Dictionary<string, string> fontMap = new Dictionary<string, string>
{
{ "ql-font-timesnewroman", "Times New Roman" },
{ "ql-font-arial", "Arial" },
{ "ql-font-verdana", "Verdana" },
{ "ql-font-couriernew", "Courier New" },
{ "ql-font-georgia", "Georgia" },
{ "ql-font-comicsansms", "Comic Sans MS" },
{ "ql-font-consolas", "Consolas" },
{ "ql-font-impact", "Impact" },
{ "ql-font-lucidaconsole", "Lucida Console" },
{ "ql-font-lucidasansunicode", "Lucida Sans Unicode" },
{ "ql-font-microsoftsansserif", "Microsoft Sans Serif" },
{ "ql-font-palatinolinotype", "Palatino Linotype" },
{ "ql-font-segoe-ui", "Segoe UI" },
{ "ql-font-tahoma", "Tahoma" },
{ "ql-font-trebuchetms", "Trebuchet MS" },
{ "ql-font-symbol", "Symbol" },
{ "ql-font-webdings", "Webdings" },
{ "ql-font-wingdings", "Wingdings" }
};

// Additional methods and functionalities...
}
}

Asset Management in Content Hub

The core functionality revolves around managing video assets within the Content Hub. This encompasses creating, updating, and fetching assets. The Sitecore Content Hub SDK is at the heart of these operations, ensuring seamless integration.

Creating Asset Files

When introducing new video assets to the Content Hub, we utilize Azure Blob Storage for efficient storage and retrieval.

public async Task<(long ResponseId, long FileSize)> UploadVideoToContentHub(string videoBlobUrl, string videoExtension)
{
long responseId = 0;
long fileSize = 0;

try
{
_logger.LogInformation("Starting the video upload process...");
var uri = new Uri(videoBlobUrl, UriKind.Absolute);
var mClient = _mClientFactory.Client;
var cancellationTokenSource = new CancellationTokenSource();

using (var memoryStream = await MemoryStreamHelper. GetMemoryStreamFromUrlAsync(uri, cancellationTokenSource.Token))
{
string shortUniqueId = Guid.NewGuid().ToString().Substring(0, 7);
var contentType = VideoHelper.GetVideoContentType(videoExtension);
var name = $"video_caption_editor_{shortUniqueId}.{videoExtension}";
var uploadSource = new StreamUploadSource(memoryStream, contentType, name);
fileSize = memoryStream.Length;
var request = new UploadRequest(uploadSource, "AssetUploadConfiguration", "NewAsset")
{
ActionParameters = new Dictionary<string, object>
{
{ "FileSize", fileSize },
}
};

// Initiate upload and wait for its completion.
var response = await mClient.Uploads.UploadAsync(request, cancellationTokenSource.Token).ConfigureAwait(false);
responseId = (long)await mClient.LinkHelper.IdFromEntityAsync(response.Headers.Location).ConfigureAwait(false);
}
_logger.LogInformation("Video upload process completed successfully.");
return (responseId, fileSize);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
_logger.LogError(ex, "Authentication failed. Check your credentials or authorization token.");
throw new AuthenticationException("Authentication failed. Check your c redentials or authorization token.");
}
catch (Exception ex)
{
_logger.LogError(ex, $"File upload failed: {ex.Message}");
throw new VideoUploadException($"File upload failed: {ex.Message}");
}
}
}
}


// Additional methods and functionalities...

Updating Asset Files

The Video Caption Editor allows for updating existing assets. This is vital for making real-time edits to video captions and ensuring the Content Hub contains the most recent version.


public async Task<(long ResponseId, long FileSize)> UploadVideoToContentHub(IEntity asset, string fileName, string videoBlobUrl, string videoExtension)
{
long responseId = 0;
long fileSize = 0;
long assetId = asset.Id.Value;

try
{
_logger.LogInformation("Starting the video upload process...");
var uri = new Uri(videoBlobUrl, UriKind.Absolute);
var mClient = _mClientFactory.Client;
var cancellationTokenSource = new CancellationTokenSource();

using (var memoryStream = await MemoryStreamHelper.GetMemoryStreamFromUrlAsync(uri, cancellationTokenSource.Token))
{
var contentType = VideoHelper.GetVideoContentType(videoExtension);
var uploadSource = new StreamUploadSource(memoryStream, contentType, fileName);
fileSize = memoryStream.Length;
var request = new UploadRequest(uploadSource, "AssetUploadConfiguration", "NewMainFile")
{
ActionParameters = new Dictionary<string, object>
{
{ "AssetId", assetId},
{ "FileName", $"{assetId}_{fileName}" },
{ "FileSize", fileSize },
}
};

var response = await mClient.Uploads.UploadAsync(request, cancellationTokenSource.Token).ConfigureAwait(false);
responseId = (long)await mClient.LinkHelper.IdFromEntityAsync(response.Headers.Location).ConfigureAwait(false);
}
_logger.LogInformation("Video upload process completed successfully.");
return (responseId, fileSize);
}
catch (HttpRequestException ex) when (ex.StatusCode == HttpStatusCode.Forbidden)
{
_logger.LogError(ex, "Authentication failed. Check your credentials or authorization token.");
throw new AuthenticationException("Authentication failed. Check your credentials or authorization token.");
}
catch (Exception ex)
{
_logger.LogError(ex, $"File upload failed: {ex.Message}");
throw new VideoUploadException($"File upload failed: {ex.Message}");
}
}


// Additional methods and functionalities...

Azure Batch Video Processing

For efficient video processing, especially when dealing with bulk operations, the Video Caption Editor integrates with Azure Batch. This provides scalability and ensures videos are processed in a timely manner without overloading resources.

BatchSharedKeyCredentials credentials = new BatchSharedKeyCredentials(batchAccountUrl, batchAccountName, batchAccountKey);
using (BatchClient batchClient = BatchClient.Open(credentials))
{
// Additional methods and functionalities...
            }

Conclusion

The Video Caption Editor, with its integration into the Sitecore Content Hub and Azure Functions, represents a significant advancement in DAM systems. Whether you're converting HTML captions to images, managing assets, or processing videos in batches, this tool ensures a streamlined and efficient workflow.

Stay tuned as we continue to explore more features and dive deeper into the world of DAM through the lens of our Video Caption Editor for Sitecore Content Hub.

Monday, September 11, 2023

Introducing the Video Caption Editor for Sitecore Content Hub - A Game-Changer for DAM


The "Why" Behind the Video Caption Editor

In the fast-paced world of Digital Asset Management (DAM), time is money. And when you're dealing with video content, this couldn't be truer. That's where our Video Caption Editor comes into play—a specialized tool built for Sitecore Content Hub, tailored to make your life easier, your workflow smoother, and your costs lower.

The Need for a Video Caption Editor in DAM

Videos are a powerful medium but managing them effectively within a DAM system like Sitecore Content Hub can be challenging. Editing captions, ensuring compliance, and enhancing accessibility are tasks that can consume a significant amount of time and resources. Our Video Caption Editor aims to simplify these tasks, reducing both time and financial expenditure.

Here are some of the benefits of using a Video Caption Editor in DAM:

  • Increased efficiency: The Video Caption Editor can help you edit captions more quickly and easily, freeing up your time for other tasks.
  • Improved compliance: The Video Caption Editor can help you ensure that your captions are compliant with accessibility standards, such as WCAG 2.1.
  • Added flexibility: The Video Caption Editor can be used to add additional text to videos, such as titles, copyrights, and other annotations.
  • Automated workflow: The Video Caption Editor is integrated with Azure Functions to automate the captioning process, freeing up your team to focus on other tasks.

Demo: See It to Believe It

Words can only say so much, and that's why we believe a demo is worth a thousand words.

From the intuitive UI to real-time caption editing, the demo offers a glimpse into how the Video Caption Editor can revolutionize your DAM operations.

Why We Built This

The motivation behind developing this Video Caption Editor was simple: Efficiency and Cost-Savings. We identified a gap in the market for a tool that could make the process of editing video captions within a DAM system more streamlined and less resource-intensive. And so, the Video Caption Editor was born.

Setting the Stage

This is just the tip of the iceberg. In our upcoming posts, we will delve into the technical aspects of this groundbreaking tool, discuss its integration with Azure Functions, and evaluate its pros and cons. So stay tuned for an enlightening journey into the world of DAM, all through the lens of our Video Caption Editor for Sitecore Content Hub.

Tuesday, August 15, 2023


Azure Blob Storage is an essential component for many businesses, offering scalable and secure storage for documents, images, and other types of data. But providing secure access to these blobs can be challenging. That's where Shared Access Signatures (SAS) come into play.

What is a Shared Access Signature (SAS)?

A Shared Access Signature (SAS) is a URI that grants restricted access rights to Azure Storage resources. With SAS, you can provide clients with access to data without sharing your account keys.

Breaking Down the SAS Token Generator:

1. Setting Up:

The function starts by parsing the storage account connection string to get a reference to the storage account. With this, it sets up the blob client and gets a reference to the blob container.

CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageAccountConnectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);

2. Defining the SAS Token's Time Window and Permissions:

The function then defines a time window for the SAS token's validity. By default, it's set to be valid for 4 hours from the current time. It also specifies the permissions for the SAS token, which include both read and write access.

DateTime startTime = DateTime.UtcNow.AddMinutes(-5);
DateTime expiryTime = startTime.AddHours(1);
SharedAccessBlobPolicy sasPolicy = new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read,
SharedAccessStartTime = startTime,
SharedAccessExpiryTime = expiryTime
};

3. Generating the SAS Token and URL:

Finally, the function generates the SAS token for the specified blob and constructs the SAS URL.

CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
string sasToken = blob.GetSharedAccessSignature(sasPolicy);
string sasUrl = blob.Uri + sasToken;

Why Use SAS Tokens?

Fine-grained Control: You can define what operations (read, write, delete) a user can perform on the blob.

Time-bound Access: The access you grant using SAS is for a limited duration, ensuring that even if someone gets the SAS token, they can't misuse it indefinitely.

Security: No need to share your Azure storage account keys.

Conclusion:

Managing access to your Azure Blob Storage doesn't have to be daunting. With the power of SAS tokens and the right functions in place, you can ensure security and ease of access for your users. Whether you're a seasoned Azure developer or just starting, understanding and utilizing SAS is a game-changer. Dive in and make the most of Azure Blob Storage!

Complete code:

private static string GetSasUrl(string storageAccountConnectionString, string containerName, string blobName)
{
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageAccountConnectionString);
CloudBlobClient blobClient = storageAccount.CreateCloudBlobClient();
CloudBlobContainer container = blobClient.GetContainerReference(containerName);

// Set the SAS token time window
DateTime startTime = DateTime.UtcNow.AddMinutes(-5);
DateTime expiryTime = startTime.AddHours(4);

// Set the permissions for the SAS token
SharedAccessBlobPolicy sasPolicy = new SharedAccessBlobPolicy()
{
Permissions = SharedAccessBlobPermissions.Write | SharedAccessBlobPermissions.Read,
SharedAccessStartTime = startTime,
SharedAccessExpiryTime = expiryTime
};

// Generate the SAS token for the blob
CloudBlockBlob blob = container.GetBlockBlobReference(blobName);
string sasToken = blob.GetSharedAccessSignature(sasPolicy);

// Construct the SAS URL for the blob
string sasUrl = blob.Uri + sasToken;

return sasUrl;
}


Friday, August 4, 2023

Managing Assets in the Restaurant of Mistaken Orders: Creating or Updating Main Files from External URLs

In a software kitchen, like the Restaurant of Mistaken Orders, where unexpected dishes (files) are often served, managing the menu (assets) can be a challenging task. In this article, we'll learn how to create or update an asset's main file from an external URL, using a recipe that even the most distracted waiter can follow.

Ingredients

  • Sitecore Content Hub SDK: A rich library to manage your digital assets.
  • C#: Our programming language to cook the code.
  • HttpClient: To fetch the external URL.
  • MemoryStream: To hold the content of the file.
  • A Mistaken Order (External URL): The URL pointing to the new main file.

The Recipe

Step 1: Preparing the Mistaken Order

First, we need a method to fetch the content from the mistaken order (external URL). Think of it as a waiter running to grab a dish from a neighboring restaurant.

public class MemoryStreamHelper
{
public static async Task<MemoryStream> GetMemoryStreamFromUrlAsync(Uri uri, CancellationToken cancellationToken)
{
using (var httpClient = new HttpClient())
using (var response = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead, cancellationToken))
{
response.EnsureSuccessStatusCode();
var memoryStream = new MemoryStream();
await response.Content.CopyToAsync(memoryStream);
memoryStream.Position = 0;
return memoryStream;
}
}
}

Step 2: Cooking the New MainFile (Asset Creation)

If the order is new and the asset doesn't exist yet, we'll create a new dish.

public async Task<long> CreateNewAsset(string videoBlobUrl)
{
var uri = new Uri(videoBlobUrl, UriKind.Absolute);
var memoryStream = await MemoryStreamHelper.GetMemoryStreamFromUrlAsync(uri, CancellationToken.None);
var request = new UploadRequest(new StreamUploadSource(memoryStream, "video/mp4", "video.mp4"), "AssetUploadConfiguration", "NewAsset")
{
// Add any additional parameters as needed
};

var response = await mClient.Uploads.UploadAsync(request, CancellationToken.None);
return (long)await mClient.LinkHelper.IdFromEntityAsync(response.Headers.Location);
}

Step 3: Updating the MainFile (Asset Update)

If the asset already exists, and we want to replace the main file, we'll need to follow a different recipe.

public async Task UpdateAssetMainFile(long assetId, string videoBlobUrl)
{
var uri = new Uri(videoBlobUrl, UriKind.Absolute);
var memoryStream = await MemoryStreamHelper.GetMemoryStreamFromUrlAsync(uri, CancellationToken.None);

var request = new UploadRequest(new StreamUploadSource(memoryStream, "video/mp4", "video.mp4"), "AssetUploadConfiguration", "NewMainFile")
{
ActionParameters = new Dictionary<string, object>
{
{ "AssetId", assetId }, // Specify the asset ID to update
}
};

await mClient.Uploads.UploadAsync(request, CancellationToken.None);
}

Conclusion

In the Restaurant of Mistaken Orders, managing assets and main files can be as delightful as tasting a new dish. Whether it's creating a new asset or updating an existing one, the ingredients and steps outlined in this article provide a nourishing recipe for success.

So the next time a customer (user) comes in with a mistaken order (external URL), you'll know exactly how to cook up the perfect solution. Bon appรฉtit! ๐Ÿฝ️

Tuesday, July 4, 2023

Azure to the Rescue: How The Restaurant of Mistaken Orders Served Up Flawless Video Content


Picture this: In a bustling corner of Tokyo, there is a unique eatery known as The Restaurant of Mistaken Orders. What sets this establishment apart is its endearing waitstaff, who all have dementia. It's a place where you never know what you’ll get, but whatever arrives at your table is served with love and joy. The owners wanted to share this special experience with the world by creating videos that showcase the warmth, understanding, and acceptance that defines their restaurant. There was just one catch: they needed a way to process and upload these videos efficiently and on a budget. The Azure cloud provided just the solution!

Cooking Up A Solution with Azure Batch

Azure Batch is like the kitchen of a restaurant. In the kitchen, there are multiple chefs (nodes) working together to prepare different dishes (tasks) that make up the complete meal (job). The Restaurant of Mistaken Orders decided to use Azure Batch to process their video content, much like how their chefs work together to whip up delightful dishes.

In the provided C# code, an Azure Function called H_AzureBatchVideoProcessing is defined. Azure Functions are serverless, meaning they don’t require you to manage any infrastructure. This is like having an automatic dishwasher that takes care of the dishes while the chefs focus on cooking.

The Ingredients

To start, the Restaurant of Mistaken Orders needs to gather all the necessary ingredients (configurations and credentials) for Azure Batch and Blob Storage.

string batchAccountUrl = _videoBatchConfig.BatchAccountUrl;
string batchAccountName = _videoBatchConfig.BatchAccountName;
string batchAccountKey = _videoBatchConfig.BatchAccountKey;
string storageAccountConnectionString = _blobStorageConfig.ConnectionString;

Much like preparing the perfect broth, setting up the credentials is an essential first step.

Preparing The Kitchen

Just like how the kitchen must be ready before cooking can start, Azure Batch requires a pool of compute nodes. In the code, the CreatePool method sets up a pool in the Batch account.

string poolId = "ffmpeg-pool";
CreatePool(batchClient, poolId, log);

Think of the poolId as the name of the kitchen. The code ensures there’s a kitchen ready with all the tools required, like knives and cutting boards (compute nodes).

Chefs, Ready Your Stations!

Now that the kitchen is ready, it’s time to define the job. A job in Azure Batch is like a complete meal consisting of several dishes.

string jobId = CreateJob(batchClient, poolId, log);

This sets up a job that will use the kitchen (pool) we previously set up. It’s like telling the chefs to start prepping!

The Main Course - Processing The Videos

The AddTask method adds a task to the job, which is like assigning a chef to prepare a dish. The Restaurant of Mistaken Orders is preparing a video, so this is the main course!

AddTask(batchClient, jobId, inputVideoUrl, inputVideoName, outputVideoName, curlBlobSasUrl, ffmpegBlobSasUrl, azcopyBlobSasUrl, storageAccountConnectionString, "video-storage", log);

In this line, a task is added to the job to download a video, process it, and upload the output to Azure Blob Storage.

Patiently Waiting For The Dish To Cook

Just as you have to wait for a dish to cook properly, you have to wait for the video processing task to complete.

log.LogInformation("Task added to job. Waiting for completion...");

var monitor = batchClient.Utilities.CreateTaskStateMonitor();
var cloudTask = await batchClient.JobOperations.GetTaskAsync(jobId, "ffmpegTask");

await monitor.WhenAll(
new List<CloudTask> { cloudTask },
TaskState.Completed,
TimeSpan.FromMinutes(30)
);

This part of the code monitors the task until it’s completed, like watching the oven to make sure the dish doesn’t burn!

Serving The Dish

Once the video is processed, the function cleans up the task and returns a success message, akin to a chef serving the dish to an eagerly awaiting customer.

log.LogInformation("Task Completed");
await batchClient.JobOperations.DeleteTaskAsync(jobId, "ffmpegTask");
return new OkObjectResult("Video processed successfully.");

Voilร ! A Delicious Video is Served

The Restaurant of Mistaken Orders now has a powerful Azure solution to process and share their heartwarming videos with the world. Through Azure Batch and Blob Storage, they can focus on what they do best - serving love and acceptance, one mistaken order at a time.

Exciting News! This post is part of a series on integrating Sitecore Content Hub with Azure for video editing! Sitecore Content Hub is an integrated content management solution that enables organizations to manage and deliver content efficiently across various channels. By leveraging Azure’s powerful processing capabilities, this series will take you through how you can edit and optimize videos directly within Sitecore Content Hub. Stay tuned for more mouth-watering tips and tricks in upcoming posts! ๐ŸŒŸ

Whether you are a restaurant owner, a marketer, or a developer, the combination of Azure and Sitecore Content Hub opens up endless possibilities for content creation and delivery. Grab your chef's hat and join us on this culinary tech adventure! ๐Ÿฝ️