Javascript Binary String to MP3 - javascript

I'm working on a project in browser that receives a multipart response from a server.
The first part of the response is a bit of JSON metadata
The second part of the response is a binary MP3 file.
I need a way to extract that MP3 file from the multipart response and play it in an HTML 5 audio element.
Has anyone encountered this or something similar?

I ended up solving my problem. I'm providing the issue and my solution to anyone needing to do this in the future.
Background info:
I'm using AngularJS when I make this ajax request, but the idea is the same for both that, jquery ajax, and regular xhr.
Code:
//creating a form object and assigning everything
//to it is so that XHR can automatically
//generate proper multipart formatting
var form = new FormData();
var data = {};
data['messageHeader'] = {};
var jsonData = JSON.stringify(data);
var jsonBlob = new Blob([jsonData],{type: "application/json"});
//assign json metadata blob and audio blob to the form
form.append("request", jsonData);
form.append("audio",response); //Response is the audio blob
//make the post request
//Notes:
//content-type set to undefined so angular can auto assign type
//transformRequest: angular.identity allows for angular to create multipart
//response: arraybuffer so untouched binary data can be received
$http({method:"POST",
url: endpoint + path,
headers: {
'Authorization': 'Bearer ' + $cookies.get('token'),
'Content-Type': undefined
},
transformRequest: angular.identity,
data: form,
responseType: "arraybuffer"
})
.success(function(data){
//data: ArrayBuffer of multipart response
//toss ArrayBuffer into Uint8Array
//lets you iterate over the bytes
var audioArray = new Uint8Array(data);
//toss a UTF-8 version of the response into
//a variable. Used to extract metadata
var holder = "";
for (var i = 0; i < audioArray.length; i++){
holder += String.fromCharCode(audioArray[i]);
}
//get the boundary from the string. Eg contents of first line
var boundary = holder.substr(0, holder.indexOf("\n"));
//break response into array at each boundary string
var temp = holder.split(boundary);
var parts = [];
//loop through array to remove empty parts
for (var i = 0; i < temp.length; i++){
if (temp[i] != ""){
parts.push(temp[i]);
}
}
//PARSE FIRST PART
//get index of first squiggly, indicator of start of JSON
var jsonStart = parts[0].indexOf('{');
//string to JSON on { index to end of part substring
var JSONResponse = JSON.parse(parts[0].substring(jsonStart));
//PARSE SECOND PART
var audioStart = holder.indexOf('mpeg') + 8;
//get an ArrayBuffer from UInt8Buffer from the audio
//start point to the end of the array
var audio = audioArray.buffer.slice(audioStart);
//hand off audio to AudioContext for automatic decoding
audio_context.decodeAudioData(audio, function(buffer) {
var audioBuffer = buffer;
//create a sound source
var source = audio_context.createBufferSource();
//attach audioBuffer to sound source
source.buffer = audioBuffer;
//wire source to speakers
source.connect(audio_context.destination);
//on audio completion, re-enable mic button
source.onended = function() {
console.log("ended");
$scope.$apply(function(){
$scope.playing = false;
});
}
//start playing audio
source.start(0);
}, function (){
//callback for when there is an error
console.log("error decoding audio");
});
})
Overview:
You need to accept the response as pure binary data (ArrayBuffer). Most libraries will give it to you as a string, which is cool for normal requests but bad for binary data.
You then step through the data to find the multipart boundaries.
You then split at the boundaries.
Get the index of the boundary you know is binary data
and then retrieve the original binary from the ArrayBuffer.
In my case I send that binary into the speakers, however if its an image you can build a blob, get a url from FileReader and then set that as a source of an image.

Related

How can I POST canvas data in javascript?

I built a simple project using javascript and FastAPI. I would like to POST canvas image data in javascript to the server(FastAPI).
But, I get the 422 Unprocessable Entity error
# FastAPI
#app.post("/post")
async def upload_files(input_data: UploadFile = File(...)):
return JSONResponse(status_code = 200, content={'result' : 'success'})
// javascript
let canvas = document.getElementById("canvas");
let ctx = canvas.getContext("2d");
imageData = ctx.getImageData(0, 0, 112, 112);
fetch("http://{IP}/post", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
input_data: imageData,
}),
})
.then((response) => response.json())
here is my code. I am new one in javascript. so I don't know how can post data to server.
How can I do? Thanks in advance.
MatsLindh's way is correct but you can try upload image by formdata so you haven't convert to base64 then convert back to image
const dataURItoBlob = function(dataURI : string) {
// convert base64/URLEncoded data component to raw binary data held in a string
var byteString : string;
if (dataURI.split(',')[0].indexOf('base64') >= 0)
byteString = atob(dataURI.split(',')[1]);
else
byteString = unescape(dataURI.split(',')[1]);
// separate out the mime component
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0];
// write the bytes of the string to a typed array
var ia = new Uint8Array(byteString.length);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
return new Blob([ia], {type:mimeString});
}
const url = canvas.toDataURL()
const file = dataURItoBlob(url)
//add file to formdata
const form = new FormData()
form.append('file', item.file)
//post this form in body
UploadFile expects a file being posted to the endpoint in the multipart/form-data format - i.e. as a regular POST with a bit of added metadata.
Since you're retrieving the image data and submitting it as JSON body, that won't match the expected format. You also want to use canvas.toDataURL() to convert the content to base64 before trying to submit it through your API to avoid any UTF-8 issues with JSON.
const pngData = canvas.toDataURL();
// then in your request:
body: JSON.stringify({
input_data: pngData,
}),
To handle this request change your view function signature to receive an input_data parameter (and use a better endpoint name than post):
#app.post("/images")
async def upload_files(input_data: str = Body(...)):
# check the expected format (content-type;<base64 content>)
# .. and decode it with base64.b64decode()
return {'content': input_data}

JavaScript Blob to FormData with SAPUI5 mobile app

I know there are several threads about this topic, but I was not able to identify the problem in my case.
I have an application, where I upload an image to an endpoint-URL and after processing I'll receive a response. Works fine so far. The file is contained within a formdata object when using FileUploader-Control from SAPUI5.
When switching from file upload to "taking a picture with smartphone-camera", I dont have a file, I have an base64 dataurl (XString) image object.
var oImage = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABQAA…8ryQAbwUjsV5VUaAX/y+YSPJii2Z9GAAAAABJRU5ErkJggg=="} // some lines are missing > 1 million lines
I thought converting it to blob and appending it to FormData might be the solution, but it does not work at all.
var blob = this.toBlob(oImage)
console.log("Blob", blob); // --> Blob(857809) {size: 857809, type: "image/png"} size: 857809 type: "image/png" __proto__: Blob
var formData = new window.FormData();
formData.append("files", blob, "test.png");
console.log("FormData", formData); // seems empty --> FormData {}__proto__: FormData
Functions (works fine from my perspective)
toBlob: function dataURItoBlob(dataURI) {
var byteString = atob(dataURI.split(',')[1]);
var mimeString = dataURI.split(',')[0].split(':')[1].split(';')[0]
var ab = new ArrayBuffer(byteString.length);
var ia = new Uint8Array(ab);
for (var i = 0; i < byteString.length; i++) {
ia[i] = byteString.charCodeAt(i);
}
var bb = new Blob([ab], {
"type": mimeString
});
return bb;
},
This is my problem, FormData is empty and my POST-request throws an undefined error (Loading of data failed: TypeError: Cannot read property 'status' of undefined at constructor.eval (...m/resources/sap/ui/core/library-preload.js?eval:2183:566))
//Create JSON Model with URL
var oModel = new sap.ui.model.json.JSONModel();
var sHeaders = {
"content-type": "multipart/form-data; boundary=---011000010111000001101001",
"APIKey": "<<myKey>>"
};
var oData = {
formData
};
oModel.loadData("/my-destination/service", oData, true, "POST", null, false, sHeaders);
oModel.attachRequestCompleted(function (oEvent) {
var oData = oEvent.getSource().oData;
console.log("Final Response XHR: ", oData);
});
Thanks for any hint
The upload collection is a complex standard control that can be used for attachment management. On desktop it opens a file dialog, on mobile it opens the ios or android photo options, which means picking a photo from the camera roll, or taking a new photo.
Fairly basic example, including the upload URL's and other handlers you'll need. More options are available, adjust to suit your needs. In your XML:
<UploadCollection
uploadUrl="{path:'Key',formatter:'.headerUrl'}/Attachments"
items="{Attachments}"
change="onAttachUploadChange"
fileDeleted="onAttachDelete"
uploadEnabled="true"
uploadComplete="onAttachUploadComplete">
<UploadCollectionItem
documentId="{DocID}"
contributor="{CreatedBy}"
fileName="{ComponentName}"
fileSize="{path:'ComponentSize',formatter:'.formatter.parseFloat'}"
mimeType="{MIMEType}"
thumbnailUrl="{parts:[{path:'MIMEType'},{path:'DocID'}],formatter:'.thumbnailURL'}"
uploadedDate="{path:'CreatedAt', formatter:'.formatter.Date'}" url="{path:'DocID',formatter:'.attachmentURL'}" visibleEdit="false"
visibleDelete="true" />
</UploadCollection>
Here's the handlers. Especially the onAttachUploadChange is important. I should mention there's no explicit post. If the uploadUrl is set correctly a post is triggered anyway.
onAttachUploadChange: function(oEvent) {
var csrf = this.getModel().getSecurityToken();
var oUploader = oEvent.getSource();
var fileName = oEvent.getParameter('files')[0].name;
oUploader.removeAllHeaderParameters();
oUploader.insertHeaderParameter(new UploadCollectionParameter({
name: 'x-csrf-token',
value: csrf
}));
oUploader.insertHeaderParameter(new UploadCollectionParameter({
name: 'Slug',
value: fileName
}));
},
onAttachDelete: function(oEvent) {
var id = oEvent.getParameter('documentId');
var oModel = this.getModel();
//set busy indicator maybe?
oModel.remove(`/Attachments('${encodeURIComponent(id)}')`, {
success: (odata, response) => {
//successful removal
//oModel.refresh();
},
error: err => console.log(err)
});
},
onAttachUploadComplete: function(oEvent) {
var mParams = oEvent.getParameter('mParameters');
//handle errors an success in here. Check `mParams`.
}
as for the formatters to determine URLs, that depends on your setup. In the case below, the stream is set up on the current binding contect, in which case this is one way to do it. You'll need the whole uri so including the /sap/opu/... etc bits.
headerUrl: function() {
return this.getModel().sServiceUrl + this.getView().getBindingContext().getPath()
},
URL for attachments is similar, but generally points to an entity of the attachment service itself.
attachmentURL: function(docid) {
return this.getModel().sServiceUrl + "/Attachments('" + docid + "')/$value";
},
You could fancy it up to check if it's an image, in which case you could include the mime type to show a thumbnail.
There might be better ways of doing this, but I've found this fairly flexible...

How can I send the POST request to the other server binding file into formdata

I have a pdf file which is generated into my local server with my server side code. I want to send a request to the another server requesting POST. The post method take parameter as FormData where formdata types
one is string and another is file type.
content-type
form-data
Body
 PDF file (file type)
string value
 
Is it possible to make the POST request without browsing the file location?
Doing some R&D I have overcome this problem with following some steps, as there is no way to get the file object from the physical location automatically in client side (basically in js) except browsing for security reason.
In my local server I have created a REST service. which response base64 string of the desired file.
Than I call the REST api from my javaScript and as a response I receive the base64 string. And than I convert it into bytes array and than Blob object and than File object.
base64 string==>bytes array==>Blob object==>File object
var base64 = this.getpdfFromLocal() //get the base64 string
var byteArray= this.base64ToByte(base64 );
var file = this.getFileFromByteArray(byteArray);
//return the byte array form the base64 string
MyApi.prototype.base64ToByte= function(base64) {
var binaryString = window.atob(base64);
var binaryLen = binaryString.length;
var bytes = new Uint8Array(binaryLen);
for (var i = 0; i < binaryLen; i++) {
var ascii = binaryString.charCodeAt(i);
bytes[i] = ascii;
}
return bytes;
};
MyApi.prototype.getFileFromByteArray=function(byteArray) {
var blob = new Blob([byteArray]);
var file = new File([blob], "resource.pdf");
return file;
};
Lastly I make from data using file object and send request the another server REST web services.
var formdata = new FormData();
formdata.append("some_value", "Some String");
formdata.append("file", file);
var url = "http://yoururl.com";
var result =$.ajax({
url : url ,
type : 'POST',
data : formdata,
contentType : false,
cache : false,
processData : false,
scriptCharset : 'utf-8',
async : false
}).responseText;

MemoryStream to HttpResponseMessage

I have generated a Pdf on the server, and need to return it as a response to my web client, so that I get a 'Save As' dialog.
The pdf is generated, and saved to a Memory stream... which is then returned to my method which will return the HttpResponseMessage.
The is the method:
[Route("GeneratePdf"), HttpPost]
public HttpResponseMessage GeneratePdf(PlateTemplateExtendedDto data)
{
var doc = GeneratePdf(DataForThePdf);
//using (var file = File.OpenWrite("c:\\temp\\test.pdf"))
// doc.CopyTo(file); // no need for manual stream copy and buffers
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
byte[] buffer = new byte[0];
//get buffer
buffer = doc.GetBuffer();
//content length for use in header
var contentLength = buffer.Length;
response.Headers.AcceptRanges.Add("bytes");
response.StatusCode = HttpStatusCode.OK;
response.Content = new StreamContent(doc);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("render");
response.Content.Headers.ContentDisposition.FileName = "yes.pdf";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = doc.Length;
return response;
}
However, the document renders as a blank file, and although it has a file size, and properties of the document I created (pdf information if File Properties is all right, as well as page width and height), the document displays as blank.
If I un-comment the code that is commented out, to save locally, the file is perfect. File size is 228,889 bytes. However, when I let it go to my web page and save it, it's 405,153 bytes and the filename is 'undefined'.
If I breakpoint, I see these results:
On the front end script, I handle the downloaded object like this:
$.post("/api/PlateTemplate/GeneratePdf", data).done(function (data, status, headers) {
// headers = headers();
var filename = headers['x-filename'];
var contentType = headers['content-type'];
//Create a url to the blob
var blob = new Blob([data], { type: contentType });
var url = window.URL.createObjectURL(blob);
var linkElement = document.createElement('a');
linkElement.setAttribute('href', url);
linkElement.setAttribute("download", filename);
//Force a download
var clickEvent = new MouseEvent("click", {
"view": window,
"bubbles": true,
"cancelable": false
});
linkElement.dispatchEvent(clickEvent);
});
I'm unsure where the file is being corrupted. What am I doing wrong?
Edit: Using the following code as suggested:
$.post("/api/PlateTemplate/GeneratePdf", data).done(function (data, status, headers) {
alert(data.length);
var xhr = new XMLHttpRequest();
$("#pdfviewer").attr("src", URL.createObjectURL(new Blob([data], {
type: "application/pdf"
})))
.Net code:
var doc = GeneratePdf(pdfParams);
HttpResponseMessage response = Request.CreateResponse(HttpStatusCode.OK);
byte[] buffer = new byte[0];
//get buffer
buffer = doc.ToArray();
//content length for use in header
var contentLength = buffer.Length;
response.Headers.AcceptRanges.Add("bytes");
response.StatusCode = HttpStatusCode.OK;
response.Content = new StreamContent(doc);
response.Content.Headers.ContentDisposition = new ContentDispositionHeaderValue("render");
response.Content.Headers.ContentDisposition.FileName = "yes.pdf";
response.Content.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
response.Content.Headers.ContentLength = doc.Length;
return response;
It seems I am losing data.
The alert is the length of the 'data.length' in my javascript, after I get data back from the call.
The file properties is the original pdf file info.
File sends from api, size is 227,564, which matches the byte size on disk if I save it. So it SEEMS the sending is OK. But on the javascript size, when I read in the file, it's 424946, when I do: var file = new Blob([data], { type: 'application/pdf' }); (Where data is the response from the server).
The ContentLength setting looks somewhat suspicious (not consequent):
//content length for use in header
var contentLength = buffer.Length;
response.Content.Headers.ContentLength = doc.Length;
I 'fixed' this by using base64 encoded string from the .Net controller to the javascript web api call result, and then allowed the browser to convert it into binary by specifying the type ('application/pdf').

XMLHttpRequest: Multipart/Related POST with XML and image as payload

I'm trying to POST an image (with Metadata) to Picasa Webalbums from within a Chrome-Extension. Note that a regular post with Content-Type image/xyz works, as I described here. However, I wish to include a description/keywords and the protocol specification describes a multipart/related format with a XML and data part.
I'm getting the Data through HTML5 FileReader and user file input. I retrieve a binary
String using
FileReader.readAsBinaryString(file);
Assume this is my callback code once the FileReader has loaded the string:
function upload_to_album(binaryString, filetype, albumid) {
var method = 'POST';
var url = 'http://picasaweb.google.com/data/feed/api/user/default/albumid/' + albumid;
var request = gen_multipart('Title', 'Description', binaryString, filetype);
var xhr = new XMLHttpRequest();
xhr.open(method, url, true);
xhr.setRequestHeader("GData-Version", '3.0');
xhr.setRequestHeader("Content-Type", 'multipart/related; boundary="END_OF_PART"');
xhr.setRequestHeader("MIME-version", "1.0");
// Add OAuth Token
xhr.setRequestHeader("Authorization", oauth.getAuthorizationHeader(url, method, ''));
xhr.onreadystatechange = function(data) {
if (xhr.readyState == 4) {
// .. handle response
}
};
xhr.send(request);
}
The gen_multipart function just generates the multipart from the input values and the XML template and produces the exact same output as the specification (apart from ..binary image data..), but for sake of completeness, here it is:
function gen_multipart(title, description, image, mimetype) {
var multipart = ['Media multipart posting', " \n", '--END_OF_PART', "\n",
'Content-Type: application/atom+xml',"\n","\n",
"<entry xmlns='http://www.w3.org/2005/Atom'>", '<title>', title, '</title>',
'<summary>', description, '</summary>',
'<category scheme="http://schemas.google.com/g/2005#kind" term="http://schemas.google.com/photos/2007#photo" />',
'</entry>', "\n", '--END_OF_PART', "\n",
'Content-Type:', mimetype, "\n\n",
image, "\n", '--END_OF_PART--'];
return multipart.join("");
}
The problem is, that the POST payload differs from the raw image data, and thus leads to a Bad Request (Picasa won't accept the image), although it worked fine when using
xhr.send(file) // With content-type set to file.type
My question is, how do I get the real binary image to include it in the multipart? I assume it is mangled by just appending it to the xml string, but I can't seem to get it fixed.
Note that due to an old bug in Picasa, base64 is not the solution.
The XMLHttpRequest specification states that the data send using the .send() method is converted to unicode, and encoded as UTF-8.
The recommended way to upload binary data is through FormData API. However, since you're not just uploading a file, but wrapping the binary data within XML, this option is not useful.
The solution can be found in the source code of the FormData for Web Workers Polyfill, which I've written when I encountered a similar problem. To prevent the Unicode-conversion, all data is added to an array, and finally transmitted as an ArrayBuffer. The byte sequences are not touched on transmission, per specification.
The code below is a specific derivative, based on the FormData for Web Workers Polyfill:
function gen_multipart(title, description, image, mimetype) {
var multipart = [ "..." ].join(''); // See question for the source
var uint8array = new Uint8Array(multipart.length);
for (var i=0; i<multipart.length; i++) {
uint8array[i] = multipart.charCodeAt(i) & 0xff;
}
return uint8array.buffer; // <-- This is an ArrayBuffer object!
}
The script becomes more efficient when you use .readAsArrayBuffer instead of .readAsBinaryString:
function gen_multipart(title, description, image, mimetype) {
image = new Uint8Array(image); // Wrap in view to get data
var before = ['Media ... ', 'Content-Type:', mimetype, "\n\n"].join('');
var after = '\n--END_OF_PART--';
var size = before.length + image.byteLength + after.length;
var uint8array = new Uint8Array(size);
var i = 0;
// Append the string.
for (; i<before.length; i++) {
uint8array[i] = before.charCodeAt(i) & 0xff;
}
// Append the binary data.
for (var j=0; j<image.byteLength; i++, j++) {
uint8array[i] = image[j];
}
// Append the remaining string
for (var j=0; j<after.length; i++, j++) {
uint8array[i] = after.charCodeAt(j) & 0xff;
}
return uint8array.buffer; // <-- This is an ArrayBuffer object!
}

Categories

Resources