file gets crashed after resize in front end - javascript

For SEO optimizaion I'm attemting to low off the size of the files that the user attempts to send (I know I could have some size limitation or something not doing so because of the UX). and I'm doing it in the front-end cause I want to use pre-signed URL method (AWS S3)
process(event: any, imageInputElement: any, maxWidth: number): any {
return new Promise<any>((resolve, reject) => {
try {
const file = event.target.files[0]
console.log('🚀 ~ file: index.vue ~ line 143 ~ process ~ file', file)
const fileSize = file.size
if (fileSize < 100000) return
if (!file) return
const reader = new FileReader()
reader.readAsDataURL(file)
reader.onload = function (event: any) {
const src = event.target.result
const canvas = document.createElement('canvas') as any
const imgElement = document.createElement('img') as any
imgElement.src = src
imageInputElement.src = event.target?.result
console.log(maxWidth)
imageInputElement.onload = function (e: any) {
const scaleSize = maxWidth / e.target.width
canvas.width = maxWidth
canvas.height = e.target.height * scaleSize
const ctx = canvas.getContext('2d')
ctx.drawImage(e.target, 0, 0, canvas.width, canvas.height)
const compressPer = (data: number) => {
const result = 10000000 / data
if (Math.trunc(result) >= 100) {
return 100
} else if (Math.trunc(result) < 1) {
return 1
} else {
return Math.trunc(result)
}
}
const srcEncoded = ctx.canvas.toDataURL(
e.target,
'image/jpeg',
compressPer(fileSize)
)
const result = new File([srcEncoded], `${file.name}`, {
type: 'image/jpeg',
})
console.log(
'🚀 ~ file: index.vue ~ line 186 ~ process ~ result',
result
)
resolve(result)
}
}
} catch (error: any) {
reject(error)
}
})
},
This function gets called every time the user changes a file input.
event: is the default change event that includes the file itself.
imageInputElement: is the element that I want to render the new file in it. and maxWidth is the width that I pass to the function to specify the max width
The actual problem: the file will become visible in the browser and gets uploaded to the s3 bucket but the file is crashed when I want to download it again.

instead of
const file = event.target.files[0]
I should have used
var blobBin = atob(srcEncoded.split(',')[1]);
var array = [];
for(let i = 0; i < blobBin.length; i++) {
array.push(blobBin.charCodeAt(i));
}
const result:any = new Blob([new Uint8Array(array)], {type: 'image/png'});
got my answer from here

Related

With javascript, how do I wait for all .toBlob functions to complete before proceeding?

I'm processing images using javascript. For each image, I'm creating 4 files using .toBlob. The problem lies in the fact that .toBlob is asynchronous such that one .toBlob process can be running while the others have completed. await async does not seem to work. A counter for the number of images processed does not seem to work because the last image can be processed while a previous image has not finished. The last image increments the counter to the number of images and triggers a save.
I can do a setTimeout on the last image but that's just guessing at the max time.
Here's the code:
let kpData;
let repositoryData;
let collection;
let skuString = `,`;
let numImages;
let numImagesProcessed;
$(document).on(`click`, `.saveSkuImages`, function() {
numImagesProcessed = 0;
kpData = new FormData();
repositoryData = new FormData();
skuString = `,`;
const skuContainer = $(this).closest(`.skuContainer`);
const sku = skuContainer.attr(`sku`);
numImages = skuContainer.find(`.skuImgContainer`).length;
let i = 0;
skuContainer.find(`.skuImgContainer`).each(function() {
i++;
sic = $(this);
const skuImg = sic.find(`.skuImg`);
const imgContainer = skuImg.find(`.imgContainer`);
const img = imgContainer.find(`img:first`);
cvs = $(`#${img.attr(`forcanvas`)}`);
imgNum = parseInt(skuImg.attr(`imgnum`));
const filename = `${sku}${imgNum > 1 ? `-alt${imgNum}` : ``}.jpg`;
img.attr(`filename`, filename);
if (cvs.length) {
createImages(cvs[0], imgNum, filename,i);
} else { //if an image already exists, we didn't create a canvas for it and we don't need to recreate it.
numImagesProcessed++;
}
if (sic.find(`.useForSeasonal`).is(`:checked`)) {
kpData.append(`parentImage`, filename);
}
});
});
sgiArr = [`L`, `LI`, `I`, `K`, `Y`];
function createImages(loadedData,imgNum,filename,i) {
const mime_type = `image/jpeg`;
var cvs = document.createElement(`canvas`);
//Create the detail version of the image
cvs.width = 800;
cvs.height = 800;
ctx = cvs.getContext(`2d`);
ctx.drawImage(loadedData, 0, 0, 800, 800);
if (imgNum === 1 && sgiArr.indexOf(filename.split(`-`)[2]) >= 0) {
// attach the size watermark to our primary image if it's a kid's product
let watermark = document.getElementById(`sgi${filename.split(`-`)[2]}`);
ctx.drawImage(watermark, 10, 720);
}
const newImageData = cvs.toDataURL(mime_type);
const result_image_obj = new Image();
result_image_obj.src = newImageData;
if (imgNum === 1 && sgiArr.indexOf(filename.split(`-`)[2]) >= 0) {
// display the image if we've attached the size watermark to it
$(`img[filename="${filename}"]`).attr(`src`, result_image_obj.src);
}
cvs.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
kpData.append(`detail`, file, filename);
}, `image/jpeg`,0.96);
//create the general image
cvs.width = 370;
cvs.height = 370;
cvs.getContext(`2d`).drawImage(loadedData, 0, 0, 370, 370);
cvs.toDataURL(mime_type);
cvs.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
kpData.append(`general`, file, filename);
}, `image/jpeg`,0.96);
//create the thumbnail
cvs.width = 240;
cvs.height = 240;
cvs.getContext(`2d`).drawImage(loadedData, 0, 0, 240, 240);
cvs.toDataURL(mime_type);
cvs.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
kpData.append(`thumb`, file, filename);
}, `image/jpeg`,0.96);
//create the repository image for Amazon, Zulilly, Zappos and our wholesale customers. Zullily has the greatest minimum requirements so we'll use those for everyone
loadedData.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
repositoryData.append(`imgfiles`, file, filename);
numImagesProcessed++;
console.log(`repository data created: `, numImagesProcessed, imgNum);
if (numImagesProcessed === numImages) { // the process can get to here yet not all the previous .toBlob statments from previous images may have completed.
console.log(`finished. Trigger save`);
saveit();
}
}, `image/jpeg`, 0.92);
}
I've tried using an array of promises:
skuContainer.find(`.skuImgContainer`).each(function() {
promises.push(processSku($(this),sku));
});
$.when.apply($, promises).done(function() {
console.log(`promises are done:`,numFiles, numFilesCreated);
saveit();
});
That made it even worse. The done function kicked off before the second iteration has even completed.
I tried incrementing a counter in each of the .toBlob functions but the counter didn't increment in time for a "if(counter===numFilesToCreate) saveIt();"
I'm at a loss.
Thanks
UPDATE Here is the code I'm using for trying a Promise array:
$(document).on(`click`, `.saveSkuImages`, function() {
numImagesProcessed = 0;
numFilesCreated = 0;
kpData = new FormData();
repositoryData = new FormData();
skuString = `,`;
const skuContainer = $(this).closest(`.skuContainer`);
const sku = skuContainer.attr(`sku`);
numImages = skuContainer.find(`.skuImgContainer`).length;
numFiles = numImages * 4;
let i = 0;
def = [];
def = [];
skuContainer.find(`.skuImgContainer`).each(function() {
let skuFunc = new Promise((resolve, reject) => {
processSku($(this), sku);
resolve(``);
});
def.push(skuFunc);
});
ResolveAll();
});
async function ResolveAll() {
await Promise.all(def);
console.log(`promises are done:`, numFiles, numFilesCreated);
saveit();
}
function processSku(sic, sku) {
console.log(sku);
const skuImg = sic.find(`.skuImg`);
const imgContainer = skuImg.find(`.imgContainer`);
const img = imgContainer.find(`img:first`);
cvs = $(`#${img.attr(`forcanvas`)}`);
imgNum = parseInt(skuImg.attr(`imgnum`));
const filename = `${sku}${imgNum > 1 ? `-alt${imgNum}` : ``}.jpg`;
img.attr(`filename`, filename);
if (sic.find(`.useForSeasonal`).is(`:checked`)) {
kpData.append(`parentImage`, filename);
}
if (cvs.length) {
console.log(`creating images`);
createImages(cvs[0], imgNum, filename, i);
} else { //if an image already exists, we didn't create a canvas for it and we don't need to recreate it.
numImagesProcessed++;
numFilesCreated += 4;
}
UPDATE 2
The only reliable way to make this work is to check the fileCreatedCount against a fileExpected count -- in this case numFilesCreated vs numFiles -- in the .toBlob function:
function createImages(loadedData,imgNum,filename,i) {
const mime_type = `image/jpeg`;
var cvs = document.createElement(`canvas`);
//Create the detail version of the image
cvs.width = 800;
cvs.height = 800;
ctx = cvs.getContext(`2d`);
ctx.drawImage(loadedData, 0, 0, 800, 800);
if (imgNum === 1 && sgiArr.indexOf(filename.split(`-`)[2]) >= 0) {
// attach the size watermark to our primary image if it's a kid's product
let watermark = document.getElementById(`sgi${filename.split(`-`)[2]}`);
ctx.drawImage(watermark, 10, 720);
}
const newImageData = cvs.toDataURL(mime_type);
const result_image_obj = new Image();
result_image_obj.src = newImageData;
if (imgNum === 1 && sgiArr.indexOf(filename.split(`-`)[2]) >= 0) {
// display the image if we've attached the size watermark to it
$(`img[filename="${filename}"]`).attr(`src`, result_image_obj.src);
}
cvs.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
kpData.append(`detail`, file, filename);
numFilesCreated++;
console.log(numFilesCreated);
if (numFilesCreated === numFiles) {
console.log(`got the right number of files`);
saveit();
}
}, `image/jpeg`, 0.96);
//create the general image
cvs.width = 370;
cvs.height = 370;
cvs.getContext(`2d`).drawImage(loadedData, 0, 0, 370, 370);
cvs.toDataURL(mime_type);
cvs.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
kpData.append(`general`, file, filename);
numFilesCreated++;
console.log(numFilesCreated);
if (numFilesCreated === numFiles) {
console.log(`got the right number of files`);
saveit();
}
}, `image/jpeg`, 0.96);
//create the thumbnail
cvs.width = 240;
cvs.height = 240;
cvs.getContext(`2d`).drawImage(loadedData, 0, 0, 240, 240);
cvs.toDataURL(mime_type);
cvs.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
kpData.append(`thumb`, file, filename);
numFilesCreated++;
console.log(numFilesCreated);
if (numFilesCreated === numFiles) {
console.log(`got the right number of files`);
saveit();
}
}, `image/jpeg`, 0.96);
//create the repository image for Amazon, Zulilly, Zappos and our wholesale customers. Zullily has the greatest minimum requirements so we'll use those for everyone
loadedData.toBlob((blob) => {
let file = new File([blob], filename, { type: `image/jpeg` });
repositoryData.append(`imgfiles`, file, filename);
numImagesProcessed++;
numFilesCreated++;
console.log(numFilesCreated);
console.log(`repository data created: `, numImagesProcessed, imgNum);
if (numFilesCreated === numFiles) {
console.log(`got the right number of files`);
saveit();
}
}, `image/jpeg`, 0.92);
}
So, each of the four .toBlob functions in each iteration of createImages checks to see if it is the last file that needs to be created and if it is, it kicks off the save.
The problem was the createImages was reporting as having finished before all the files were created which then meant that creating a promise from that was meaningless. If .toBlob had a defer method or some way to .toBlob().then(), it would work.

js can I read a portion of a file with FileReader? [duplicate]

I have long file I need to parse. Because it's very long I need to do it chunk by chunk. I tried this:
function parseFile(file){
var chunkSize = 2000;
var fileSize = (file.size - 1);
var foo = function(e){
console.log(e.target.result);
};
for(var i =0; i < fileSize; i += chunkSize)
{
(function( fil, start ) {
var reader = new FileReader();
var blob = fil.slice(start, chunkSize + 1);
reader.onload = foo;
reader.readAsText(blob);
})( file, i );
}
}
After running it I see only the first chunk in the console. If I change 'console.log' to jquery append to some div I see only first chunk in that div. What about other chunks? How to make it work?
FileReader API is asynchronous so you should handle it with block calls. A for loop wouldn't do the trick since it wouldn't wait for each read to complete before reading the next chunk.
Here's a working approach.
function parseFile(file, callback) {
var fileSize = file.size;
var chunkSize = 64 * 1024; // bytes
var offset = 0;
var self = this; // we need a reference to the current object
var chunkReaderBlock = null;
var readEventHandler = function(evt) {
if (evt.target.error == null) {
offset += evt.target.result.length;
callback(evt.target.result); // callback for handling read chunk
} else {
console.log("Read error: " + evt.target.error);
return;
}
if (offset >= fileSize) {
console.log("Done reading file");
return;
}
// of to the next chunk
chunkReaderBlock(offset, chunkSize, file);
}
chunkReaderBlock = function(_offset, length, _file) {
var r = new FileReader();
var blob = _file.slice(_offset, length + _offset);
r.onload = readEventHandler;
r.readAsText(blob);
}
// now let's start the read with the first block
chunkReaderBlock(offset, chunkSize, file);
}
You can take advantage of Response (part of fetch) to convert most things to anything else blob, text, json and also get a ReadableStream that can help you read the blob in chunks đź‘Ť
var dest = new WritableStream({
write (str) {
console.log(str)
}
})
var blob = new Blob(['bloby']);
(blob.stream ? blob.stream() : new Response(blob).body)
// Decode the binary-encoded response to string
.pipeThrough(new TextDecoderStream())
.pipeTo(dest)
.then(() => {
console.log('done')
})
Old answer (WritableStreams pipeTo and pipeThrough was not implemented before)
I came up with a interesting idéa that is probably very fast since it will convert the blob to a ReadableByteStreamReader probably much easier too since you don't need to handle stuff like chunk size and offset and then doing it all recursive in a loop
function streamBlob(blob) {
const reader = new Response(blob).body.getReader()
const pump = reader => reader.read()
.then(({ value, done }) => {
if (done) return
// uint8array chunk (use TextDecoder to read as text)
console.log(value)
return pump(reader)
})
return pump(reader)
}
streamBlob(new Blob(['bloby'])).then(() => {
console.log('done')
})
The second argument of slice is actually the end byte. Your code should look something like:
function parseFile(file){
var chunkSize = 2000;
var fileSize = (file.size - 1);
var foo = function(e){
console.log(e.target.result);
};
for(var i =0; i < fileSize; i += chunkSize) {
(function( fil, start ) {
var reader = new FileReader();
var blob = fil.slice(start, chunkSize + start);
reader.onload = foo;
reader.readAsText(blob);
})(file, i);
}
}
Or you can use this BlobReader for easier interface:
BlobReader(blob)
.readText(function (text) {
console.log('The text in the blob is', text);
});
More information:
README.md
Docs
Revamped #alediaferia answer in a class (typescript version here) and returning the result in a promise. Brave coders would even have wrapped it into an async iterator…
class FileStreamer {
constructor(file) {
this.file = file;
this.offset = 0;
this.defaultChunkSize = 64 * 1024; // bytes
this.rewind();
}
rewind() {
this.offset = 0;
}
isEndOfFile() {
return this.offset >= this.getFileSize();
}
readBlockAsText(length = this.defaultChunkSize) {
const fileReader = new FileReader();
const blob = this.file.slice(this.offset, this.offset + length);
return new Promise((resolve, reject) => {
fileReader.onloadend = (event) => {
const target = (event.target);
if (target.error == null) {
const result = target.result;
this.offset += result.length;
this.testEndOfFile();
resolve(result);
}
else {
reject(target.error);
}
};
fileReader.readAsText(blob);
});
}
testEndOfFile() {
if (this.isEndOfFile()) {
console.log('Done reading file');
}
}
getFileSize() {
return this.file.size;
}
}
Example printing a whole file in the console (within an async context)
const fileStreamer = new FileStreamer(aFile);
while (!fileStreamer.isEndOfFile()) {
const data = await fileStreamer.readBlockAsText();
console.log(data);
}
Parsing the large file into small chunk by using the simple method:
//Parse large file in to small chunks
var parseFile = function (file) {
var chunkSize = 1024 * 1024 * 16; //16MB Chunk size
var fileSize = file.size;
var currentChunk = 1;
var totalChunks = Math.ceil((fileSize/chunkSize), chunkSize);
while (currentChunk <= totalChunks) {
var offset = (currentChunk-1) * chunkSize;
var currentFilePart = file.slice(offset, (offset+chunkSize));
console.log('Current chunk number is ', currentChunk);
console.log('Current chunk data', currentFilePart);
currentChunk++;
}
};

tf.browser.fromPixels returns all zeros from img element

I am using tensorflowjs to do some front-end image classification. I am trying to use tf.browser.fromPixels to convert an img element to a tensor. However, I am getting all zeros of shape [160, 160, 3]. I am using the FileReader api to read an image from the file system via the <input type="file"> element. Here's some of the code:
function getFiles(event) {
const files = event.target.files;
let tempStore = [];
for (let i = 0; i < files.length; ++i) {
tempStore.push(files[i]);
}
return tempStore;
}
const imageElement = document.getElementById("upload");
imageElement.addEventListener("change", event => {
const files = getFiles(event);
Promise.all(files.map(loadImg)).then(d => {
console.log("All done !!!", d);
});
});
const loadImg = imgFile => {
return new Promise((resolve, reject) => {
let reader = new FileReader();
let imgEl = document.createElement("img");
reader.onload = async e => {
imgEl.src = e.target.result;
imgEl.setAttribute("width", 160);
imgEl.setAttribute("height", 160);
document.body.append(imgEl);
const fromPixels = tf.browser.fromPixels(imgEl);
resolve(fromPixels);
};
reader.onerror = reject;
reader.readAsDataURL(imgFile);
});
};
The image gets appended to document body properly.
The imageElement is of the form:
<img src="data:image/jpeg;base64,....." width=160 height=160>
You are creating the tensor from the image when the img tag has not yet been loaded. Here is the way to go
imgEl.src = e.target.result;
imgEl.setAttribute("width", 160);
imgEl.setAttribute("height", 160);
document.body.append(imgEl);
im.onload = () => {
// create the tensor after the image has loaded
const fromPixels = tf.browser.fromPixels(imgEl);
resolve(fromPixels);
}

How to get image width and height in a web-worker?

Here's my code:
// process-image.js (web-worker)
self.addEventListener('message', ev => {
const {id,file} = ev.data;
const reader = new FileReader();
reader.onload = ev => {
const imageFile = new Image(); // <-- error is here
imageFile.src = ev.target.result;
imageFile.onload = () => {
const fit = makeFit(imageFile);
self.postMessage({
id,
file: {
src: ev.target.result,
width: fit.width,
height: fit.height,
}
})
}
};
reader.readAsDataURL(file);
});
This was working fine in the main UI thread, but apparently I don't have access to Image inside of a web-worker. The specific error is:
Uncaught ReferenceError: Image is not defined
at FileReader.reader.onload (process-image.js:12)
Is there another way get the width and height of an image?
I'd like to support as many file types as possible, but just JPG is good enough for now if there's some sort of library that can do this and will run in a web-worker.
Here's the relevant bit from the UI thread:
// some-react-component.js
componentDidMount() {
this.worker = new ImageWorker();
this.worker.addEventListener('message', ev => {
const {id, file} = ev.data;
this.setState(({files}) => {
files = files.update(id, img => ({
...img,
...file,
}))
const withWidths = files.filter(f => f.width);
const avgImgWidth = withWidths.reduce((acc, img) => acc + img.width, 0) / withWidths.size;
return {files, avgImgWidth};
});
});
}
onDrop = ev => {
ev.preventDefault();
Array.prototype.forEach.call(ev.dataTransfer.files, file => {
const id = shortid();
this.worker.postMessage({id, file});
const img = {
id,
width: null,
height: null,
src: null,
name: file.name,
}
this.setState(({files}) => ({files: files.set(id, img)}));
});
}
Only thing worth noting here is that id is just a random UUID, and file is a File. I'm passing the whole thing to the web-worker so that I can do all the processing there.
I think there might be a simpler solution:
https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/createImageBitmap
I managed to get the size without using FileReader at all:
http://jsfiddle.net/hL1Ljkmv/2/
var response = `self.addEventListener('message', async ev => {
const {id,file} = ev.data;
console.log('received data');
let image = await self.createImageBitmap(file);
console.log(image);
});`;
const blob = new Blob([response], {type: 'application/javascript'});
const worker = new Worker(URL.createObjectURL(blob));
const input = document.querySelector('#file-input');
input.addEventListener('change', (e) => {
worker.postMessage({file: input.files[0], id: 1})
});
I managed to get the width and height of most JPEGs by following this spec.
self.addEventListener('message', ev => {
const {id, file} = ev.data;
const reader = new FileReader();
reader.onload = ev => {
const view = new DataView(reader.result);
let offset = 0;
let header = view.getUint16(offset, false);
if(header !== 0xFFD8) {
throw new Error(`Not a JPEG`);
}
offset += 2;
for(; ;) {
header = view.getUint16(offset, false);
offset += 2;
let length = view.getUint16(offset, false);
if(header === 0xFFC0) break;
offset += length;
}
const height = view.getUint16(offset + 3);
const width = view.getUint16(offset + 5);
const fit = makeFit({width, height});
self.postMessage({
id,
file: {
src: URL.createObjectURL(file),
width: fit.width,
height: fit.height,
}
})
};
reader.readAsArrayBuffer(file);
});
This code is flakey as heck, but surprisingly it works. I'm still looking for a more robust solution.

javascript FileReader - parsing long file in chunks

I have long file I need to parse. Because it's very long I need to do it chunk by chunk. I tried this:
function parseFile(file){
var chunkSize = 2000;
var fileSize = (file.size - 1);
var foo = function(e){
console.log(e.target.result);
};
for(var i =0; i < fileSize; i += chunkSize)
{
(function( fil, start ) {
var reader = new FileReader();
var blob = fil.slice(start, chunkSize + 1);
reader.onload = foo;
reader.readAsText(blob);
})( file, i );
}
}
After running it I see only the first chunk in the console. If I change 'console.log' to jquery append to some div I see only first chunk in that div. What about other chunks? How to make it work?
FileReader API is asynchronous so you should handle it with block calls. A for loop wouldn't do the trick since it wouldn't wait for each read to complete before reading the next chunk.
Here's a working approach.
function parseFile(file, callback) {
var fileSize = file.size;
var chunkSize = 64 * 1024; // bytes
var offset = 0;
var self = this; // we need a reference to the current object
var chunkReaderBlock = null;
var readEventHandler = function(evt) {
if (evt.target.error == null) {
offset += evt.target.result.length;
callback(evt.target.result); // callback for handling read chunk
} else {
console.log("Read error: " + evt.target.error);
return;
}
if (offset >= fileSize) {
console.log("Done reading file");
return;
}
// of to the next chunk
chunkReaderBlock(offset, chunkSize, file);
}
chunkReaderBlock = function(_offset, length, _file) {
var r = new FileReader();
var blob = _file.slice(_offset, length + _offset);
r.onload = readEventHandler;
r.readAsText(blob);
}
// now let's start the read with the first block
chunkReaderBlock(offset, chunkSize, file);
}
You can take advantage of Response (part of fetch) to convert most things to anything else blob, text, json and also get a ReadableStream that can help you read the blob in chunks đź‘Ť
var dest = new WritableStream({
write (str) {
console.log(str)
}
})
var blob = new Blob(['bloby']);
(blob.stream ? blob.stream() : new Response(blob).body)
// Decode the binary-encoded response to string
.pipeThrough(new TextDecoderStream())
.pipeTo(dest)
.then(() => {
console.log('done')
})
Old answer (WritableStreams pipeTo and pipeThrough was not implemented before)
I came up with a interesting idéa that is probably very fast since it will convert the blob to a ReadableByteStreamReader probably much easier too since you don't need to handle stuff like chunk size and offset and then doing it all recursive in a loop
function streamBlob(blob) {
const reader = new Response(blob).body.getReader()
const pump = reader => reader.read()
.then(({ value, done }) => {
if (done) return
// uint8array chunk (use TextDecoder to read as text)
console.log(value)
return pump(reader)
})
return pump(reader)
}
streamBlob(new Blob(['bloby'])).then(() => {
console.log('done')
})
The second argument of slice is actually the end byte. Your code should look something like:
function parseFile(file){
var chunkSize = 2000;
var fileSize = (file.size - 1);
var foo = function(e){
console.log(e.target.result);
};
for(var i =0; i < fileSize; i += chunkSize) {
(function( fil, start ) {
var reader = new FileReader();
var blob = fil.slice(start, chunkSize + start);
reader.onload = foo;
reader.readAsText(blob);
})(file, i);
}
}
Or you can use this BlobReader for easier interface:
BlobReader(blob)
.readText(function (text) {
console.log('The text in the blob is', text);
});
More information:
README.md
Docs
Revamped #alediaferia answer in a class (typescript version here) and returning the result in a promise. Brave coders would even have wrapped it into an async iterator…
class FileStreamer {
constructor(file) {
this.file = file;
this.offset = 0;
this.defaultChunkSize = 64 * 1024; // bytes
this.rewind();
}
rewind() {
this.offset = 0;
}
isEndOfFile() {
return this.offset >= this.getFileSize();
}
readBlockAsText(length = this.defaultChunkSize) {
const fileReader = new FileReader();
const blob = this.file.slice(this.offset, this.offset + length);
return new Promise((resolve, reject) => {
fileReader.onloadend = (event) => {
const target = (event.target);
if (target.error == null) {
const result = target.result;
this.offset += result.length;
this.testEndOfFile();
resolve(result);
}
else {
reject(target.error);
}
};
fileReader.readAsText(blob);
});
}
testEndOfFile() {
if (this.isEndOfFile()) {
console.log('Done reading file');
}
}
getFileSize() {
return this.file.size;
}
}
Example printing a whole file in the console (within an async context)
const fileStreamer = new FileStreamer(aFile);
while (!fileStreamer.isEndOfFile()) {
const data = await fileStreamer.readBlockAsText();
console.log(data);
}
Parsing the large file into small chunk by using the simple method:
//Parse large file in to small chunks
var parseFile = function (file) {
var chunkSize = 1024 * 1024 * 16; //16MB Chunk size
var fileSize = file.size;
var currentChunk = 1;
var totalChunks = Math.ceil((fileSize/chunkSize), chunkSize);
while (currentChunk <= totalChunks) {
var offset = (currentChunk-1) * chunkSize;
var currentFilePart = file.slice(offset, (offset+chunkSize));
console.log('Current chunk number is ', currentChunk);
console.log('Current chunk data', currentFilePart);
currentChunk++;
}
};

Categories