public async Task <ActionResult> Claim(string id) { var userIdKey = KeyUtils.IntToKey(0); var longTimeKey = id + this.GetExtId(); var blobName = ExerciseUtils.FormatBlobName(userIdKey, longTimeKey, "metadata", "json"); var blob = AzureStorageUtils.GetBlob(AzureStorageUtils.ContainerNames.Artifacts, blobName); var metadataJson = await blob.DownloadTextAsync(); var metadata = JObject.Parse(metadataJson); var serviceType = (string)metadata["serviceType"]; var cardId = (Guid?)metadata["cardId"]; var title = (string)metadata["title"]; var comment = (string)metadata["comment"]; var details = metadata["recordingDetails"]; var recordingDetails = details.ToObject <RecordingDetails>(); var exerciseId = await ExerciseUtils.CreateExercise(recordingDetails.BlobName, this.GetUserId(), serviceType, ArtifactType.Mp3, recordingDetails.TotalDuration, title, cardId, comment, details.ToString(Formatting.None)); //~~ Redirect to the View exercise page. return(RedirectToAction("View", new { Id = exerciseId })); }
public async Task <IHttpActionResult> PostTracks(string metadataName) { //~~ Validate. if (!Request.Content.IsMimeMultipartContent()) { return(BadRequest()); } // Parse the request body parts. var streamProvider = await Request.Content.ReadAsMultipartAsync(); // Check mediaType of the files var mediaTypes = streamProvider.Contents .Select(i => i.Headers) .Where(i => i.ContentDisposition.Name.Trim('"') != metadataName) .Select(i => i.ContentType) //.Where(i => i != null) // ContentType is null in the 'metadata' part. .Select(i => i.MediaType) .Distinct() ; var mediaType = mediaTypes.FirstOrDefault(); var acceptedMediaTypes = new[] { MediaType.Mpeg, MediaType.Mp3, MediaType.Amr, MediaType.Gpp, MediaType.QuickTime }; if ((mediaTypes.Count() != 1) || !acceptedMediaTypes.Contains(mediaType)) { return(BadRequest(String.Join(", ", mediaTypes))); } //~~ Save the original media files to blobs. var userId = this.GetUserId(); // May be 0 if the user is unauthenticated var userIdKey = KeyUtils.IntToKey(userId); var timeKey = KeyUtils.GetTimeAsBase32(); // The length of extId is 12 chars. var extId = (userId != 0) ? null : this.GetExtId(); var longTimeKey = timeKey + extId; // If an operand of string concatenation is null, an empty string is substituted. var extension = MediaType.GetExtension(mediaType); var tracks = new List <KeyValuePair <string, MemoryStream> >(); var fileNames = new List <string>(); foreach (var content in streamProvider.Contents) { var name = content.Headers.ContentDisposition.Name.Trim('"'); var contentType = content.Headers.ContentType; // We have checked above, if ContentType has a value, it has the right MediaType. if ((name != metadataName) && (contentType != null)) { fileNames.Add(name); var stream = await content.ReadAsStreamAsync(); using (stream) { var memStream = new MemoryStream(); await stream.CopyToAsync(memStream); var pair = new KeyValuePair <string, MemoryStream>(name, memStream); tracks.Add(pair); } } } // Upload all blobs in parallel. var tasks = tracks.Select(i => { var blobName = ExerciseUtils.FormatBlobName(userIdKey, longTimeKey, i.Key, extension); return(AzureStorageUtils.UploadBlobAsync(i.Value, AzureStorageUtils.ContainerNames.Artifacts, blobName, mediaType)); }); await Task.WhenAll(tasks); //~~ Call the transcoding service. ///* We send the file names as a comma separated list. There is also a binding in the Web API like this: //public IHttpActionResult GetFoo([FromUri] int[] ids); Call: /Foo?ids=1&ids=2&ids=3 or /Foo?ids[0]=1&ids[1]=2&ids[2]=3 //public IHttpActionResult GetFoo([FromUri] List<string> ids); Call: /Foo?ids[]="a"&ids[]="b"&ids[]="c" //*/ //var host = ConfigurationManager.AppSettings["RecorderHost"]; //var urlFormat = "http://{0}/api/recordings/transcoded/?userIdKey={1}&timeKey={2}&extension={3}&fileNames={4}"; //var url = String.Format(urlFormat, host, userIdKey, longTimeKey, extension, String.Join(",", fileNames)); //HttpClient client = new HttpClient(); //HttpResponseMessage response = await client.GetAsync(url); //if (!response.IsSuccessStatusCode) //{ // // return RedirectToAction(referrerAction, new { error = "transcoding_error" }); // return InternalServerError(new Exception("Transcoding error. " + response.StatusCode.ToString())); //} //// Error is returned as HTML. Then we get error here: No MediaTypeFormatter is available to read an object of type 'JObject' from content with media type 'text/html'. //var recordingDetails = await response.Content.ReadAsAsync<RecordingDetails>(); //// Make sure the duration is known. If the transcoder has failed to parse the ffmpeg logs, it returns DurationMsec = 0. //if (recordingDetails.TotalDuration == 0) //{ // // Read the blob and try to determine the duration directly. // recordingDetails.TotalDuration = // await RecordingUtils.GetMp3Duration(AzureStorageUtils.ContainerNames.Artifacts, recordingDetails.BlobName); //} var transcoder = new RecordingTranscoder(); var recordingDetails = await transcoder.Transcode(tracks, userIdKey, longTimeKey, extension); // Release the memory streams. tracks.ForEach(i => { i.Value.Dispose(); }); //~~ Read the metadata. // Chrome wraps the part name in double-quotes. var metadataContent = streamProvider.Contents .Single(i => i.Headers.ContentDisposition.Name.Trim('"') == metadataName) ; var metadataJson = await metadataContent.ReadAsStringAsync(); var metadata = JObject.Parse(metadataJson); var serviceType = (string)metadata["serviceType"]; var cardId = (Guid?)metadata["cardId"]; var title = (string)metadata["title"]; var comment = (string)metadata["comment"]; var serializer = new JsonSerializer() { ContractResolver = new CamelCasePropertyNamesContractResolver() }; var details = JObject.FromObject(recordingDetails, serializer); if (userId != 0) { //~~ Create a database record. var exerciseId = await ExerciseUtils.CreateExercise(recordingDetails.BlobName, userId, serviceType, ArtifactType.Mp3, recordingDetails.TotalDuration, title, cardId, comment, details.ToString(Formatting.None)); //~~ The client will redirect to the View exercise page. return(Ok(new { ExerciseId = exerciseId })); } else { //~~ Save the details for future use. metadata.Add(new JProperty("recordingDetails", details)); var blobName = ExerciseUtils.FormatBlobName(userIdKey, longTimeKey, "metadata", "json"); await AzureStorageUtils.UploadTextAsync(metadata.ToString(), AzureStorageUtils.ContainerNames.Artifacts, blobName, MediaType.Json); //~~ The client will redirect to the Signup page. longTimeKey will be built from timeKey on the Claim page. return(Ok(new { Key = timeKey })); } }