Convert binary data to base64 does not work with btoa unescape - javascript

I am new to React and want to display an image downloaded as binary data. I download the image data from api call to adobe lightroom api. The api call works since the image is displayed in Postman without problems. I can also save the image data to a jpeg-file and it is displayed ok.
In React I want to do <img src={`data:image/jpeg;base64,${theImage}`} /> and for that to work I need to convert the binary data to a base64 encoded string. When i convert the downloaded jpeg using cat image.jpeg|base64 > base64.txt the resulting string works in my React app.
But when I try var theImage = btoa(binarydata) in React I get Unhandled Rejection (InvalidCharacterError): Failed to execute 'btoa' on 'Window': The string to be encoded contains characters outside of the Latin1 range.
After searching the issue I try use var theImage = btoa(unescape(encodeURIComponent( binarydata ))) and similar proposed solution but resulting strings from those does not turn out to be a valid base64 encodings of the jpeg as it seem (I try the result from the conversions in online base64->image services and no image is shown). I have also tried other proposed solution such as base64-js and js-base64 libraries and non of those create a valid base64 valid image that can be shown in my React code.
How do you convert jpeg binary data to valid Base64 image encoding when btoa throws latin1 exception?

You've said you're using axios.get to get the image from the server. What you'll presumably get back will be a Buffer or ArrayBuffer or Blob, etc., but it depends on what you do with the response you get from axios.
I don't use axios (never felt the need to), but you can readily get a data URI for binary data from the server via fetch:
// 1.
const response = await fetch("/path/to/resource");
// 2.
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
// 3.
const buffer = await response.arrayBuffer();
// 4.
const byteArray = new Uint8Array(buffer);
// 5.
const charArray = Array.from(byteArray, byte => String.fromCharCode(byte));
// 6.
const binaryString = charArray.join("");
// 7.
const theImage = btoa(binaryString);
Or more concisely:
const response = await fetch("/path/to/resource");
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
const buffer = await response.arrayBuffer();
const binaryString = Array.from(new Uint8Array(buffer), byte => String.fromCharCode(byte)).join("");
const theImage = btoa(binaryString);
Here's how that works:
We request the image data.
We check that the request worked (fetch only rejects its promise on network errors, not HTTP errors; those are reported via the status and ok props.
We read the body of the response into an ArrayBuffer; the buffer will have the binary image data.
We want to convert that buffer data into a binary string. To do that, we need to access the bytes individually, so we create a Uint8Array (using that buffer) to access them.
To convert that byte array into a binary string, we need to convert each byte into its equivalent character, and join those together into a string. Let's do that by using Array.from and in its mapping function (called for each byte), we'll use String.fromCharCode to convert the byte to a character. (It's not really much of a conversion. The byte 25 [for instance] becomes the character with character code 25 in the string.)
Now we create the binary string by joining the characters in that array together into one string.
Finally, we convert that string to Base64.
Looking at the docs, it looks like axios lets you provide the option responseType: "arraybuffer" to get an array buffer. If I'm reading right, you could use axios like this:
const response = await axios.get("/path/to/resource", {responseType: "arraybuffer"});
const binaryString = Array.from(new Uint8Array(response.body), v => String.fromCharCode(v)).join("");
const theImage = btoa(binaryString);

Fetch your image as a Blob and generate a blob:// URI from it.
data:// URLs are completely inefficient and require far more memory space than blob:// URLs. The data:// URL takes 34% more space than the actual data it represents and it must be stored in the DOM + decoded as binary again to be read by the image decoder. The blob:// URI on the other hand is just a pointer to the binary data in memory.
blob:// URLs are not perfect, but until browsers implement srcDoc correctly, it's still the best we have.
So if as per the comments you are using axios in a browser, you can do
const blob = await axios.get("/path/to/resource", {responseType: "blob"});
const theImage = URL.createObjectURL(blob);
And if you want to use the fetch API
const response = await fetch("/path/to/resource");
if (!response.ok) {
throw new Error("HTTP error " + response.status);
}
const blob = await response.blob();
const theImage = URL.createObjectURL(blob);
Ps:
If you don't have any particular reason to fetch this image through AJAX (e.g credentials or special POST params), then pass directly the URL of the resource as the src of your image:
<img src="/path/to/resource" />

Related

How do I store and fetch bytea data through hasura?

I've got a blob of audio data confirmed to play in the browser but fails to play after storing, retrieving, and conversion of the same data. I've tried a few methods without success, each time returning the error:
Uncaught (in promise) DOMException: Failed to load because no supported source was found
Hasura notes that bytea data must be passed in as a String, so I tried a couple things.
Converting the blob into base64 stores fine but the retrieval and playing of the data doesn't work. I've tried doing conversions within the browser to base64 and then back into blob. I think it's just the data doesn't store properly as bytea if I convert it to base64 first:
// Storing bytea data as base64 string
const arrayBuffer = await blob.arrayBuffer();
const byteArray = new Uint8Array(arrayBuffer);
const charArray = Array.from(byteArray, (x: number) => String.fromCharCode(x));
const encodedString = window.btoa(charArray.join(''));
hasuraRequest....
`
mutation SaveAudioBlob ($input: String) {
insert_testerooey_one(
object: {
blubberz: $input
}
) {
id
blubberz
}
}
`,
{ input: encodedString }
);
// Decoding bytea data
const decodedString = window.atob(encodedString);
const decodedByteArray = new Uint8Array(decodedString.length).map((_, i) =>
decodedString.charCodeAt(i)
);
const decodedBlob = new Blob([decodedByteArray.buffer], { type: 'audio/mpeg' });
const audio4 = new Audio();
audio4.src = URL.createObjectURL(decodedBlob);
audio4.play();
Then I came across a Github issue (https://github.com/hasura/graphql-engine/issues/3336) suggesting the use of a computed field to convert the bytea data to base64, so I tried using that instead of my decoding attempt, only to be met with the same error:
CREATE OR REPLACE FUNCTION public.content_base64(mm testerooey)
RETURNS text
LANGUAGE sql
STABLE
AS $function$
SELECT encode(mm.blobberz, 'base64')
$function$
It seemed like a base64 string was not the way to store bytea data, so I tried converting the data to a hex string prior to storing. It stores ok, I think, but upon retrieval the data doesn't play, and I think it's a similar problem as storing as base64:
// Encoding to hex string
const arrayBuffer = await blob.arrayBuffer();
const byteArray = new Uint8Array(arrayBuffer);
const hexString = Array.from(byteArray, (byte) =>
byte.toString(16).padStart(2, '0')
).join('');
But using the decoded data didn't work again, regardless of whether I tried the computed field method or my own conversion methods. So, am I just not converting it right? Is my line of thinking incorrect? Or what is it I'm doing wrong?
I've got it working if I just convert to base64 and store as a text field but I'd prefer to store as bytea because it takes up less space. I think something's wrong with how the data is either stored, retrieved, or converted, but I don't know how to do it. I know the blob itself is fine because when generated I can play audio with it, it only bugs out after fetching and attempted conversion its stored value. Any ideas?
Also, I'd really like to not store the file in another service like s3, even if drastically simpler.

How to send multiple images from FastAPI backend to JavaScript frontend using StreamingResponse?

I have a FastAPI endpoint /image/ocr that accepts, processes and returns multiple images as a StreamingResponse:
async def streamer(images):
for image in images:
# Bytes
image_bytes = await image.read()
# PIL PNG
image_data = Image.open(BytesIO(image_bytes))
# Process
# image_processed = ...
# Convert from PIL PNG to JPEG Bytes
image_ocr = BytesIO()
image_ocr.save(image_processed, format='JPEG')
yield image_ocr.getvalue()
#router.post('/ocr', summary='Process images')
async def ocr(images: List[UploadFile]):
return StreamingResponse(
streamer(images),
status_code=status.HTTP_200_OK,
media_type='image/jpeg'
)
In my React application I send multiple images to the /image/ocr endpoint using Axios configured to arraybuffer, and convert the returned images to Base64 using btoa, String.fromCharCode and Unit8Array:
const imageSubmit = async data => {
const formData = new FormData()
// A `useRef` that stores the uploaded images
data?.current?.files?.successful.map(image =>
formData.append('images', image.data)
)
try {
const response = await request.post(
'/image/ocr',
formData,
{responseType: 'arraybuffer'}
)
const base64String = btoa(
String.fromCharCode(
...new Uint8Array(response.data)
)
)
const contentType = response.headers['content-type']
const fullBase64String = `data:${contentType};base64,${base64String}`
return fullBase64String
} catch (error) {
// Error log
}
}
The problem is that I'm sending multiple images, for example, this image and this image, that are being accepted, processed and returned by the endpoint /image/ocr, but when I convert the response.data using the method in the last code snippet I only get one Base64 image.
I took a look at the raw response to see if I could iterate over the ArrayBuffer that I get through response.data:
Array Buffer object
But it didn't work with a for(const image of response.data) {}. I saw that ArrayBuffer has a slice method, but I'm not sure if it means that both images are in the ArrayBuffer and I need to slice and then convert, which is strange since at least one image is being converted to Base64 as of now, or convert it to another type since I saw that it can be done in some different ways here, here and here.
If the slice option is the right one I'm not sure how to select the start and end since the values in the ArrayBuffer are just random numbers and symbols.
Any idea on how to get both images in Base64 on my React application?
After hours of research I gave up on Axios since the documentation says that it "makes XMLHttpRequests from the browser", meaning that it doesn't support stream.
Reading more about the Streams API and watching a Google's series on Youtube, I started using Fetch API, as it supports streaming for both writing (16:26) and reading (6:24).
By replacing the try block in my question with this:
const response = await fetch('http://localhost:4000/image/ocr', {
method: 'POST',
headers: {
Authorization: ''
},
body: formData
})
const reader = response.body.getReader()
while (true) {
const { value, done } = await reader.read()
// Base64
const base64 = `data:${response.headers['content-type']};base64,${btoa(String.fromCharCode(...new Uint8Array(value)))}`
console.log(base64)
if (done) break
}
I'm able to use the asynchronous generator along with the StreamingResponse and get multiple images in my React application.
Its worth to dive deeper in the stream API since it can also work as a Websocket without having to write a specific back-end that deals with sockets

Is my function to convert image URLs to base64 returning corrupt data?

I'm using this script to convert image URLs to base64, but it's generating some funky strings that appear corrupt and I don't know why.
Regardless of if they are corrupt, Airtable is rejecting some of data strings that this function is generating and it's not easy to debug their error which is error: "INVALID_VALUE_FOR_COLUMN" message: "Field \"LogoBase64\" cannot accept the provided value"
The only thing Airtable should care about is if it's a primitive string or not.
Example incomeing URL: https://dl.airtable.com/.attachmentThumbnails/afffa0bf1eed11920c56956b71ee5195/5b979e22 but I don't think that's the problem. It just fails on certain images, when updating the db record, but some of base64 data returning from the function looks off to my uneducated eye.
I would love help strengthening this function to avoid generating corrupt data and in general, better handle thousands of images that might be JPG, PNG, HEIF, BMP and just skip incompatible formats.
async getBase64FromUrl(url) {
const data = await fetch(url);
const blob = await data.blob();
return new Promise((resolve) => {
const reader = new FileReader();
reader.readAsDataURL(blob);
reader.onloadend = () => {
const base64data = reader.result;
resolve(base64data);
};
});
},
Example base64 output from one record below the function that seems corrupt to me, but maybe the endless AAAAAAAAAA's are whitespace?
data:image/jpeg;base64,/9j/4AAQSkZJRgABAQEAYABgAAD/4RDuRXhpZgAATU0AKgAAAAgABAE7AAIAAAAMAAAISodpAAQAAAABAAAIVpydAAEAAAAYAAAQzuocAAcAAAgMAAAAPgAAAAAc6gAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAENvbm9yIE1vcmFuAAAFkAMAAgAAABQAABCkkAQAAgAAABQAABC4kpEAAgAAAAM1NQAAkpIAAgAAAAM1NQAA6hwABwAACAwAAAiYAAAAABzqAAAACAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAMjAxOTowNDozMCAxNzoyOTo1OAAyMDE5OjA0OjMwIDE3OjI5OjU4AAAAQwBvAG4AbwByACAATQBvAHIAYQBuAAAA/+ELHmh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSfvu78nIGlkPSdXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQnPz4NCjx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iPjxyZGY6UkRGIHhtbG5zOnJkZj0iaHR0cDovL3d3dy53My5vcmcvMTk5OS8wMi8yMi1yZGYtc3ludGF4LW5zIyI+PHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9InV1aWQ6ZmFmNWJkZDUtYmEzZC0xMWRhLWFkMzEtZDMzZDc1MTgyZjFiIiB4bWxuczpkYz0iaHR0cDovL3B1cmwub3JnL2RjL2VsZW1lbnRzLzEuMS8iLz48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyI+PHhtcDpDcmVhdGVEYXRlPjIwMTktMDQtMzBUMTc6Mjk6NTguNTUzPC94bXA6Q3JlYXRlRGF0ZT48L3JkZjpEZXNjcmlwdGlvbj48cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0idXVpZDpmYWY1YmRkNS1iYTNkLTExZGEtYWQzMS1kMzNkNzUxODJmMWIiIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyI+PGRjOmNyZWF0b3I+PHJkZjpTZXEgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj48cmRmOmxpPkNvbm9yIE1vcmFuPC9yZGY6bGk+PC9yZGY6U2VxPg0KCQkJPC9kYzpjcmVhdG9yPjwvcmRmOkRlc2NyaXB0aW9uPjwvcmRmOlJERj48L3g6eG1wbWV0YT4NCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgPD94cGFja2V0IGVuZD0n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```

Sending image file via HTTP post prepends unwanted characters to the file

I'm trying to send a simple image file to a lambda function. Once it gets to the function I need to turn it into a buffer and then manipulate it. Right now when data is received there are a bunch of characters prepended to the data:
"body": "--X-INSOMNIA-BOUNDARY\r\nContent-Disposition: form-data; name=\"image\"; filename=\"americanflag.png\"\r\nContent-Type: image/png\r\n\r\n�PNG\r\n\n\rIHDR0�\b�;BIDATx��]u|����{g��.H\b^h�F)PJ�������Www﫻P���\"E��$!nk3���3�l���{��=�L�����=��=�|)����ٿ)��\"�$��q�����\r���'s��4����֦M��\"C�y��*U�YbUEc����|�ƼJ���#�=�/ �6���OD�p�����[�Q�D��\b�<hheB��&2���}�F�*�1M�u������BR�%\b�1RD�Q�������Q��}��R )%ĉ�Idv�݌髝�S��_W�Z�xSaZ��p�5k�{�|�\\�?
I have no idea how to handle that. My plan has just been to create a buffer as you normally would in Node:Buffer.from(data, 'utf8'). But it's throwing an error
Things I've tried:
I've been testing the function with Insomniac and Postman, both with the same result.
I've gone with both a multipart/form and binary file for the body
of the request.
I've tried multiple image files.
I've set the header of content-type to image/png and other file
types.
I've removed the headers.
I know that I could upload the files to S3 and that would be much easier but it negates the point of what I'm writing. I don't want to store the images I just want to manipulate them and then discard them.
This is what the response looks like when I send it back to myself.
Edit: The full code is uploaded. Again, I'm not sending via node at this very moment. It's simply through Postman/Insomniac. If the answer is simply "write your own encoder" then please put that as an answer.
Because you did not upload full code so based on my best prediction I post an answer here. There are probably any of the solutions may help to you.
Encoding Base64 Strings:
'use strict';
let data = '`stackoverflow.com`';
let buff = new Buffer(data);
let base64data = buff.toString('base64');
console.log('"' + data + '" converted to Base64 is "' + base64data + '"');
Decoding Base64 Strings:
'use strict';
let data = 'YHN0YWNrb3ZlcmZsb3cuY29tYA==';
let buff = new Buffer(data, 'base64');
let text = buff.toString('ascii');
console.log('"' + data + '" converted from Base64 to ASCII is "' + text + '"');
Encoding binary data to base64 string:
'use strict';
const fs = require('fs');
let buff = fs.readFileSync('image-log.png');
let base64data = buff.toString('base64');
console.log('Image converted to base 64 is:\n\n' + base64data);
Decoding Base64 Strings to Binary Data:
'use strict';
const fs = require('fs');
let data = 'encoded binary string';
let buff = new Buffer(data, 'base64');
fs.writeFileSync('image-log.png', buff);
console.log('Base64 image data converted to file: image-log.png');
Base64 encoding is the way to converting binary data into plain ASCII text. It is a very useful format for communicating between one or more systems that cannot easily handle binary data, like images in HTML markup or web requests.
In Node.js the Buffer object can be used to encode and decode Base64 strings to and from many other formats, allowing you to easily convert data back and forth as needed.

Download and encode image in base64 using JavaScript

lets say i have a URL given. I would like to:
1) download it and convert to base64
2) upload it to some key/value storage (as text)
3) download it from key/value storage (with text/plain mimetype), reencode it from base64, display it.
Best Regards
If someone is still searching for downloading images and encoding them in base64 string, I recently find this kind of outdated method but really reliable. The advantage is that it's pure Javascript so there is no need to install any external library. I previously tried using fetch and axios but for some reason the encoded string was not in a correct format.
NB: If you are encoding this image to send it to an API, some of them require to delete the leading data type including the , at the start of the encoded string.
function toDataURL (url, callback) {
const xhRequest = new XMLHttpRequest()
xhRequest.onload = function () {
const reader = new FileReader()
reader.onloadend = function () {
callback(reader.result)
}
reader.readAsDataURL(xhRequest.response)
}
xhRequest.open('GET', url)
xhRequest.responseType = 'blob'
xhRequest.send()
}
const URL = "https://upload.wikimedia.org/wikipedia/commons/f/f7/Stack_Overflow_logo.png"
const logCallback = (base64image) => {
// Base64 encoded string with leading data type like
// data:image/png;base64,RAW_ENCODED_DATA______
console.log(base64image)
}
toDataURL(URL, logCallback)

Categories