// mode : "shots", "scenes", "purge", "load" // [HttpPost] public async Task Importviscenes(string mode, string videoId, string viAcctID, string viSubKey, string viLocation, string translationLang) { if (mode == "load") { return; } Functions.SendProgress("Cleaning...", 0, 1); // Pass a list of blob URIs in ViewBag CloudStorageAccount account = CloudStorageAccount.Parse(ConfigurationManager.AppSettings["StorageConnectionString"]); CloudBlobClient client = account.CreateCloudBlobClient(); CloudBlobContainer containerVideoId = client.GetContainerReference(videoId); // Let's purge the data // let's purge all existing blobs List <IListBlobItem> blobsList = new List <IListBlobItem>(); try { blobsList = containerVideoId.ListBlobs().ToList(); List <Task> myTasks = new List <Task>(); foreach (IListBlobItem item in containerVideoId.ListBlobs(useFlatBlobListing: true)) { var blob = item as CloudBlockBlob; if (blob != null) { myTasks.Add(blob.DeleteAsync()); } } await Task.WhenAll(myTasks.ToArray()).ConfigureAwait(false); } catch (Exception exc) { ViewBag.ErrorMsg = exc.ToString(); Functions.SendProgress("Error in ListBlobs:" + exc.Message, 0, 1); } // user wants only purge if (mode == "purge") { return; } Functions.SendProgress("Initialization...", 0, 1); await containerVideoId.CreateIfNotExistsAsync(BlobContainerPublicAccessType.Container, null, null).ConfigureAwait(false); // Translator credentials string translatorSubscriptionKey = ConfigurationManager.AppSettings["TranslatorSubscriptionKey"]; string translatorEndpoint = ConfigurationManager.AppSettings["TranslatorEndpoint"]; // Computer vision init ComputerVisionClient vision = new ComputerVisionClient( new ApiKeyServiceClientCredentials(ConfigurationManager.AppSettings["VisionSubscriptionKey"]), new System.Net.Http.DelegatingHandler[] { } ); vision.Endpoint = ConfigurationManager.AppSettings["VisionEndpoint"]; VisualFeatureTypes[] features = new VisualFeatureTypes[] { VisualFeatureTypes.Description }; // test code get all thumbnails string jsonData = ""; VideoIndexer myVI = new VideoIndexer(viAcctID, viLocation, viSubKey); try { //videoToken = await myVI.GetVideoAccessTokenAsync(videoId).ConfigureAwait(false); jsonData = await myVI.GetInsightsAsync(videoId).ConfigureAwait(false); } catch (Exception exc) { ViewBag.ErrorMsg = exc.ToString(); Functions.SendProgress("Error in GetInsightsAsync:" + exc.Message, 0, 1); return; } // SAVING INSIGHTS AS A BLOB CloudBlockBlob insightsBlob = containerVideoId.GetBlockBlobReference(dirInsights + "/" + fileInsights); JObject jObj = JObject.Parse(jsonData); await insightsBlob.UploadTextAsync(jObj.ToString(Formatting.Indented)).ConfigureAwait(false); List <BlobInfo> blobs = new List <BlobInfo>(); Dictionary <TimeSpan, string> shotsTimingAndThumbnailsId = new Dictionary <TimeSpan, string>(); Dictionary <TimeSpan, string> scenesTimingAndThumbnailId = new Dictionary <TimeSpan, string>(); dynamic viInsights = JsonConvert.DeserializeObject <dynamic>(jsonData); var video = viInsights.videos[0]; var shots = video.insights.shots; var scenes = video.insights.scenes; // list of shots foreach (var shot in shots) { foreach (var keyFrame in shot.keyFrames) { foreach (var instance in keyFrame.instances) { string thumbnailId = (string)instance.thumbnailId; string thumbnailStartTime = (string)instance.adjustedStart; shotsTimingAndThumbnailsId.Add(TimeSpan.Parse(thumbnailStartTime, CultureInfo.InvariantCulture), thumbnailId); } } } var listTimings = shotsTimingAndThumbnailsId.Select(d => d.Key).ToList().OrderBy(d => d); //list of scenes (a scene contains several shots, but in the JSON, thumbnails are not defined in scenes) if (scenes != null) // sometimes, there is no scene ! { foreach (var scene in scenes) { TimeSpan start = TimeSpan.Parse((string)scene.instances[0].adjustedStart, CultureInfo.InvariantCulture); var closestTime = listTimings.OrderBy(t => Math.Abs((t - start).Ticks)) .First(); scenesTimingAndThumbnailId.Add(closestTime, shotsTimingAndThumbnailsId[closestTime]); } } // it's the list of thumbnails we want to process (scenes or all shots) Dictionary <TimeSpan, string> thumbnailsToProcessTimeAndId = new Dictionary <TimeSpan, string>(); if (mode == "scenes") // scenes only { if (scenes == null) // no scenes, let's quit { Functions.SendProgress($"No scenes !", 10, 10); return; } thumbnailsToProcessTimeAndId = scenesTimingAndThumbnailId; } else // all shots { thumbnailsToProcessTimeAndId = shotsTimingAndThumbnailsId;; } int index = 0; foreach (var thumbnailEntry in thumbnailsToProcessTimeAndId) { Functions.SendProgress($"Processing {thumbnailsToProcessTimeAndId.Count} thumbnails...", index, thumbnailsToProcessTimeAndId.Count); index++; //if (index == 100) break; string thumbnailId = thumbnailEntry.Value; var thumbnailStartTime = thumbnailEntry.Key; // Get the video thumbnail data and upload to photos folder var thumbnailHighResStream = await myVI.GetVideoThumbnailAsync(videoId, thumbnailId).ConfigureAwait(false); CloudBlockBlob thumbnailHighResBlob = containerVideoId.GetBlockBlobReference(dirHighRes + "/" + thumbnailId + ".jpg"); await thumbnailHighResBlob.UploadFromStreamAsync(thumbnailHighResStream).ConfigureAwait(false); // let's create the low res version using (var thumbnailLowResStream = new MemoryStream()) { thumbnailHighResStream.Seek(0L, SeekOrigin.Begin); var settings = new ResizeSettings { MaxWidth = 192 }; ImageBuilder.Current.Build(thumbnailHighResStream, thumbnailLowResStream, settings); thumbnailLowResStream.Seek(0L, SeekOrigin.Begin); CloudBlockBlob thumbnailLowRes = containerVideoId.GetBlockBlobReference(dirLowRes + "/" + thumbnailId + ".jpg"); await thumbnailLowRes.UploadFromStreamAsync(thumbnailLowResStream).ConfigureAwait(false); } // Submit the image to Azure's Computer Vision API var result = await vision.AnalyzeImageAsync(thumbnailHighResBlob.Uri.ToString(), features).ConfigureAwait(false); // cleaning metadata on blobs thumbnailHighResBlob.Metadata.Clear(); thumbnailHighResBlob.Metadata.Add("Index", index.ToString(CultureInfo.InvariantCulture)); // Record the image description and tags in blob metadata if (result.Description.Captions.Count > 0) { thumbnailHighResBlob.Metadata.Add("Description", result.Description.Captions[0].Text); thumbnailHighResBlob.Metadata.Add("Confidence", (result.Description.Captions[0].Confidence * 100).ToString("F1", CultureInfo.InvariantCulture)); if (!string.IsNullOrEmpty(translationLang)) { try { string descriptionTranslated = await Translator.TranslateTextRequest(translatorSubscriptionKey, translatorEndpoint, result.Description.Captions[0].Text, translationLang).ConfigureAwait(false); thumbnailHighResBlob.Metadata.Add("DescriptionTranslated", Convert.ToBase64String(Encoding.UTF8.GetBytes(descriptionTranslated))); } catch (Exception exc) { ViewBag.ErrorMsg = exc.ToString(); Functions.SendProgress("Error in TranslateTextRequest:" + exc.Message, 0, 1); } } //var guidThumbnail = Path.GetFileNameWithoutExtension(thumbnailHighResBlob.Name).Substring(18); } thumbnailHighResBlob.Metadata.Add("AdjustedStart", thumbnailStartTime.ToString()); for (int i = 0; i < result.Description.Tags.Count; i++) { string key = String.Format(CultureInfo.InvariantCulture, "Tag{0}", i); thumbnailHighResBlob.Metadata.Add(key, result.Description.Tags[i]); } await thumbnailHighResBlob.SetMetadataAsync().ConfigureAwait(false); } // 100% Functions.SendProgress($"Processing {thumbnailsToProcessTimeAndId.Count} thumbnails...", 10, 10); //return RedirectToAction("Index"); }
/// <summary> /// Retrieve all thumbnails from a video in VI and upload them to blob storage. /// Currently cycles through the thumbnails for every instance, in every keyframe, in every shot. /// </summary> /// <param name="videoid">The VI ID of the video to be parsed for thumbnails.</param> /// <returns>Awaitable task.</returns> private static async Task SaveThumbnailsFromVideoShots(string videoid) { string msg; try { // Tracking the start time for performance analytics DateTime startTimestamp = DateTime.Now; msg = $"[{startTimestamp.ToShortTimeString()}] Retrieving Video Indexer insights for video id: {videoid}... "; Console.Write(msg); int thumbcount = 0; // Retrieve all the insights from a given video to parse them for thumbnails var insights = await client.GetInsightsAsync(videoid).ConfigureAwait(false); if (insights != null && insights.Videos.Count > 0) { Console.WriteLine("Completed!"); msg = $"[Saving all shot thumbnails for video id: {videoid}. Please wait..."; Console.WriteLine(msg); // Initialization for saving thumbnails to a local file folder string foldername = null; if (config["savefolder"]?.Length > 0) { // Create the selected director if it doesn't exist, the video ID is used as a subfolder foldername = config["savefolder"].TrimEnd(new char[] { '/', '\\' }); DirectoryInfo folder = Directory.CreateDirectory(Path.Combine(foldername, videoid)); foldername = folder.FullName; } // Initialization for blob storage upload // The container name is provided as a command line parameter BlobContainerClient containerClient = null; if (config["savecontainer"]?.Length > 0) { if (blobAccount.Length > 0 && blobConnectionString.Length > 0) { // Create a BlobServiceClient object which will be used to create a container client BlobServiceClient blobServiceClient = new BlobServiceClient(blobConnectionString); if (blobServiceClient != null) { // Create the container and return a container client object. The video ID is used to aggregate thumbnails by video. // TODO: Do we need to insure uniqueness of the container name to be created/used in blob storage? containerClient = blobServiceClient.GetBlobContainerClient($"{config["savecontainer"]}-{videoid}".ToLowerInvariant()); await containerClient.CreateIfNotExistsAsync().ConfigureAwait(false); if ((await(containerClient?.ExistsAsync()).ConfigureAwait(false)) == true) { // Prepare metadata to augment the blob container var blobMetadata = new Dictionary <string, string>() { { "videoid", videoid }, { "videoname", insights.Name }, }; await containerClient.SetMetadataAsync(blobMetadata).ConfigureAwait(false); } } } } // Cycle through each video in the playlist returned by VI foreach (Video video in insights.Videos) { // Cycle through each shot indexed by VI Console.WriteLine($"No of shots in video: {insights.Videos[0].Insights.Shots.Count}"); foreach (Shot shot in video.Insights.Shots) { // Cycle through each keyframe in every shot foreach (Keyframe keyframe in shot.KeyFrames) { // Cycle through each thumbnail instance in a keyframe foreach (Instance instance in keyframe.Instances) { // Not all instances are guaranteed to have a thumbnail if (instance.ThumbnailId.Length > 0) { // Retrieve the thumbnail for that video using (MemoryStream thumbnail = await client.GetThumbnailAsync(videoid, instance.ThumbnailId).ConfigureAwait(false) as MemoryStream) { if (thumbnail != null && thumbnail.Length > 0) { using (var image = new Bitmap(thumbnail)) { string thumbfile = $"{videoid}_{shot.Id}_{keyframe.Id}_{thumbcount}.jpg"; // Saving the thumbnail to a folder if (foldername?.Length > 0) { Console.WriteLine($"Saving thumbnail image file: {thumbfile}"); image.Save(Path.Combine(foldername, thumbfile)); } // Saving the thumbnail to Azure blob storage if (config["savecontainer"]?.Length > 0) { if (containerClient != null) { // Get a reference to a blob BlobClient blobClient = containerClient.GetBlobClient(thumbfile); Console.WriteLine($"Saving thumbnail image to blob storage: {thumbfile}"); // Open the file and upload its data thumbnail.Position = 0; await blobClient.UploadAsync(thumbnail, true).ConfigureAwait(false); if ((await(blobClient?.ExistsAsync()).ConfigureAwait(false)) == true) { // Prepare metadata to augment the blob var blobMetadata = new Dictionary <string, string>() { { "videoid", videoid }, { "videoname", insights.Name }, { "shotid", shot.Id.ToString(CultureInfo.InvariantCulture) }, { "keyframeid", keyframe.Id.ToString(CultureInfo.InvariantCulture) }, { "starttime", instance.Start }, }; await blobClient.SetMetadataAsync(blobMetadata).ConfigureAwait(false); } } } } } thumbnail.Close(); } } thumbcount++; } } } } } // Reporting elapsed time for performance analytics DateTime endTimestamp = DateTime.Now; msg = $"[Elapsed time: {endTimestamp - startTimestamp:c}] Operation completed for video {videoid}. Total of {thumbcount} thumbnails extracted."; Console.WriteLine(msg); } // Exception likely raised by Video Indexer API calls. catch (WebException exc) { msg = $"Video Indexer API error retrieving indexed insights for video {videoid} in account {accountId}:" + Environment.NewLine + exc.Message; } // Exception likely raised by calls to Azure Blob storage. catch (Azure.RequestFailedException exc) { msg = $"Azure Blob Storage error while saving thumbnails for video {videoid} in account {accountId}:" + Environment.NewLine + exc.Message; } // Final fallback to report generic exceptions not captured above. // TODO: Add more specific exceptions to be captured above based on scenarios that crop up during testing. catch (Exception exc) { msg = $"General error retrieving indexed insights for video {videoid} in account {accountId}:" + Environment.NewLine + exc.Message; Console.WriteLine(msg); } }