I have an upload button on my page which user can click to upload a video. If video is large, Javascript's File Reader can't handle it. So I've implemented a function that slices uploaded video into chunks with 'data:application/octet-stream;base64,' label at the beginning. Meanwhile the function sends each chunk to the NodeJS backend, where I use some 'fs' approaches to merge the video file back and save it as an mp4 file on my disc. The problem is that the file is 'empty'. It's recognized by my PC as an mp4 file, there is no error of wrong file format. But the video has no content and it's 0 seconds long. How can I merge chunks properly to make the mp4 file work?
Frontend:
let reader;
let file;
const slice_size = 1024;
const start_chunks_upload = (e) => {
event.preventDefault();
reader = new FileReader();
file = e.target.files[0];
upload_file_chunks(0);
}
const upload_file_chunks = (start) => {
const next_slice = start + slice_size + 1;
const blob = file.slice(start, next_slice);
reader.onloadend = (e) => {
if (e.target.readyState !== FileReader.DONE) {
return;
}
$.ajax({
type: 'POST',
url: 'http://localhost:5000/video',
data: {
video: e.target.result.replace('data:application/octet-stream;base64,', ''),
is_done: next_slice < file.size ? null : true,
},
success: function(data) {
const size_done = start + slice_size;
const percent_done = Math.floor((size_done / file.size) * 100);
console.log(percent_done + '%');
if (next_slice < file.size) {
upload_file_chunks(next_slice);
} else {
console.log('done');
}
}
});
};
reader.readAsDataURL(blob);
}
video_upload_button.addEventListener('change', (e) => start_chunks_upload(e));
Backend:
let string = '';
app.post('/video', (req, res) => {
string += req.body.video;
if (req.body.is_done) {
let buff = new Buffer.from(string, 'base64');
fs.writeFileSync('/one.mp4', buff);
fs.writeFile("/two.mp4", string, 'base64', function(err) {
console.log(err);
});
fs.appendFile('/log.txt', string, function (err) {
if (err) {
console.log(err);
}
})
} else {
return res.json({ok: 1,});
}
})
Related
I'm using loodash cloneDeep to upload files, however I need files to not be duplicated and only be able to upload a file once. How can I do this using cloneDeep?
I don't know how to do it, I googled, but the solution was only for jquery
const [files, setFiles] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
If relying on the filenames is enough, you can try to store them to check if it has been uploaded already :
const [files, setFiles] = useState([]);
//state to store uploaded file's name
const [fileNames, setFileNames] = useState([]);
const onSelectFile = (e) => {
try {
let fileArr = cloneDeep(files);
let promises = [];
for (let file of e.target.files) {
promises.push(
new Promise((resolve, reject) => {
const fileName = file.name
//if the file has not been already uploaded
if (!fileNames.includes(fileName)) {
//add the current fileName in state
setFileNames([fileName, ...fileNames]);
const type = file.type;
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function (evt) {
const fileData = evt.target.result;
fileArr.push({
name: fileName,
type: type,
data: fileData,
comment: "",
id: `${new Date().getTime()}_${fileName}`,
canDelete: true
});
if (typeof props.onFileSelected == "function")
props.onFileSelected(fileArr);
resolve(true);
}
reader.onerror = function (evt) {
console.log("error reading file");
reject(false);
}
} else {
alert("File has already been uploaded");
reject(false);
}
})
);
}
Promise.all(promises).then(r => {
setFiles(fileArr);
})
}
catch(e) {
console.log(e);
}
}
Note: this will not prevent the case when the user upload a file, then refresh the website and upload the same file again
If you want to prevent that you have to ask your backend if the file has already been upload or not.
I was trying to create an API endpoint for rotating images uploaded from client side. I'm sending images as base64 type, converted from blob (from simple <input tag), as follows:
const addImageBase64 = async (fileData) => {
const file = fileData;
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (event) => {
resolve(event.target.result);
};
reader.onerror = (err) => {
reject(err);
};
reader.readAsDataURL(file);
});
};
Then, on the server side, that's how the endpoint looks like:
app.post("/api/rotate-image", async (req, res) => {
try {
let buffer = Buffer.from(req.body.imageData, "base64"); //not working
let array = new Uint8Array(buffer); //not working
const image = await sharp(buffer)
.rotate(180)
.png({ quality: 100 })
.toBuffer();
console.log("success");
res.status(200).send({
success: true,
result: image,
});
} catch (e) {
console.warn(e);
}
});
And here, every my attempt is ending up with '[Error: Input buffer contains unsupported image format]' - either for Buffer or Uint8Array. Can anyone help me with this issue? What is the right input type for Sharp that acctually works?
Edit:
Error with logged buffer obj:
Hello I have already read and attempted the thread: Saving desktopCapturer to video file in Electron
This is what I have so far:
const { desktopCapturer } = require('electron')
var fs = require('fs');
function startRecording(){
desktopCapturer.getSources({ types: ['window', 'screen'] }).then(async sources => {
for (const source of sources) {
if (source.name === 'Entire Screen') {
try {
const stream = await navigator.mediaDevices.getUserMedia({
audio: false,
video: {
mandatory: {
chromeMediaSource: 'desktop',
chromeMediaSourceId: source.id,
minWidth: 1280,
maxWidth: 1280,
minHeight: 720,
maxHeight: 720
}
}
})
handleStream(stream)
} catch (e) {
handleError(e)
}
return
}
}})
}
function handleStream(stream) {
recorder = new MediaRecorder(stream);
blobs = [];
recorder.ondataavailable = function(event) {
blobs.push(event.data);
};
recorder.start();
}
function stopRecording() {
recorder.stop();
console.log(blobs);
toArrayBuffer(new Blob(blobs, {type: 'video/webm'}), function(ab) {
var buffer = toBuffer(ab);
var file = `./videos/example.webm`;
fs.writeFile(file, buffer, function(err) {
if (err) {
console.error('Failed to save video ' + err);
} else {
console.log('Saved video: ' + file);
}
});
});
}
function handleUserMediaError(e) {
console.error('handleUserMediaError', e);
}
function toArrayBuffer(blob, cb) {
let fileReader = new FileReader();
fileReader.onload = function() {
let arrayBuffer = this.result;
cb(arrayBuffer);
};
fileReader.readAsArrayBuffer(blob);
}
function toBuffer(ab) {
let buffer = Buffer.alloc(ab.byteLength);
let arr = new Uint8Array(ab);
for (let i = 0; i < arr.byteLength; i++) {
buffer[i] = arr[i];
}
return buffer;
}
// Record for 3.5 seconds and save to disk
startRecording();
setTimeout(function() { stopRecording() }, 3500)
I just want to save the recorded video to a file.
The file ends up being empty when it saves. I have been stuck at this awhile and would appreciate any advice. Thank you
Welp I stumbled upon this thread: Saving desktopCapturer to video file from Electron app
and changed my stopRecording() function to :
function stopRecording() {
const save = () =>{
toArrayBuffer(new Blob(blobs, {type: 'video/webm'}), function(ab) {
var buffer = toBuffer(ab);
var file = `./videos/example.webm`;
fs.writeFile(file, buffer, function(err) {
if (err) {
console.error('Failed to save video ' + err);
} else {
console.log('Saved video: ' + file);
}
});
});
}
recorder.onstop = save;
recorder.stop();
}
and it seems to be working. Cool!
I am trying to convert an image file to blob but get the error below:
{"code":2, "message":"SECURITY_ERR"}
Any idea why this might be happening? I tried a lot of online resources but really could not find out what is going on.
I have the following code:
choose(){
this.filechooser.open().then((uri)=>{
this.file.resolveLocalFilesystemUrl(uri).then((newurl)=>{
let dirPath = newurl.nativeURL;
alert(dirPath);
let dirPathsegments = dirPath.split('/')
dirPathsegments.pop();
dirPath = dirPathsegments.join('/');
this.file.readAsArrayBuffer(dirPath, newurl.name).then((buffer)=>{
let blob = new Blob([buffer], {type: "image/jpeg"});
alert('blob creation success');
}, Error=>{
alert('Blob Error '+ JSON.stringify(Error));
});
});
},Error=>{
alert('Error Choosing File ' + Error);
});
}
Is there any other way to convert the file into a blob?
The current APIs that I am using only accepts Binary or Multipart attachments
It works. Tested on ionic 2 & 3
uploadFile() {
this.fileChooser.open().then((url) => {
(<any>window).FilePath.resolveNativePath(url, (nativeFilepath) => {
this.readFileFromStorage(nativeFilepath);
}
)
})
}
readFileFromStorage(nativeFilepath) {
let fileName = this.getfilename(nativeFilepath);
let fileExt = fileName.substring(fileName.lastIndexOf('.') + 1);
let blogType = { type: 'image/'+fileExt };
(<any>window).resolveLocalFileSystemURL(nativeFilepath, (res) => {
res.file((resFile) => {
var reader = new FileReader();
reader.readAsArrayBuffer(resFile);
reader.onloadend = (evt: any) => {
var imgBlob = new Blob([evt.target.result], blogType);
//Upload blob to firebase
this.uploadToFirebase(imgBlob,fileName);
}
})
})
}
getfilename(filePath){
let fileName : string;
fileName = filePath.replace(/^.*[\\\/]/, '')
return fileName;
}
uploadToFirebase(fileBlob, name) {
let storage = firebase.storage();
storage.ref('images/' + name).put(fileBlob).then((d) => {
alert("Done");
}).catch((error) => {
alert("Error: " + JSON.stringify(error));
});
}
Problem
I am trying to create an app with react native and firebase. One of the features I would like for this app is the ability to upload images. I am having some trouble uploading the images to firebase storage though. I am using expo's image picker to find the path of the image that the user wants to upload, but once I have the path I don't know how to convert that to something I can upload to firebase.
Can somebody help me convert the path of an image to something I can upload to firebase storage with react native?
What I've tried
I tried using:
_pickImage = async () => {
let result = await ImagePicker.launchImageLibraryAsync({
MediaTypeOptions: 'Images',
quality: 0.4,
_uploadAsByteArray = async (pickerResultAsByteArray, progressCallback) => {
try {
var metadata = {
contentType: 'image/jpeg',
};
var storageRef = firebase.storage().ref();
var ref = storageRef.child('images/'+expoID+'/'+this.state.time)
let uploadTask = ref.put(pickerResultAsByteArray, metadata)
uploadTask.on('state_changed', function (snapshot) {
progressCallback && progressCallback(snapshot.bytesTransferred / snapshot.totalBytes)
var progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
console.log('Upload is ' + progress + '% done');
}, function (error) {
console.log("in _uploadAsByteArray ", error)
}, function () {
var downloadURL = uploadTask.snapshot.downloadURL;
console.log("_uploadAsByteArray ", uploadTask.snapshot.downloadURL)
this.setState({imageUploaded:true})
});
} catch (ee) {
console.log("when trying to load _uploadAsByteArray ", ee)
}
}
convertToByteArray = (input) => {
var binary_string = this.atob(input);
var len = binary_string.length;
var bytes = new Uint8Array(len);
for (var i = 0; i < len; i++) {
bytes[i] = binary_string.charCodeAt(i);
}
return bytes
}
atob = (input) => {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
let str = input.replace(/=+$/, '');
let output = '';
if (str.length % 4 == 1) {
throw new Error("'atob' failed: The string to be decoded is not correctly encoded.");
}
for (let bc = 0, bs = 0, buffer, i = 0;
buffer = str.charAt(i++);
~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer,
bc++ % 4) ? output += String.fromCharCode(255 & bs >> (-2 * bc & 6)) : 0
) {
buffer = chars.indexOf(buffer);
}
return output;
}
uploadImage(bsfdata){
this.setState({imageUploaded:false})
this._uploadAsByteArray(this.convertToByteArray(bsfdata), (progress) => {
this.setState({ progress:progress })
})
}
base64:true,
});
/* if (!result.cancelled) {
this.setState({ image: result.uri });
let formData = new FormData();
formData.append('photo', {
uri,
name: `photo.${fileType}`,
type: `image/${fileType}`,
});}*/
this.uploadImage(result.base64);
};
}
I've tried it with the commented code added, which doesn't upload anything, and I've tried it with how the code is now, which gives me the error Can currently only create a Blob from other Blobs, and the uploading progress never gets above 0%.
If you are using expo (>=26), then you can do it easily with the following lines of code.
uploadImage = async(imageUri) => {
const response = await fetch(imageUri);
const blob = await response.blob();
var ref = firebase.storage().ref().child("image.jpg");
return ref.put(blob);
}
Reference: https://youtu.be/KkZckepfm2Q
Refer this link - https://github.com/dailydrip/react-native-firebase-storage/blob/master/src/App.js#L43-L69
Following block of code is working fine.
uploadImage(uri, mime = 'application/octet-stream') {
return new Promise((resolve, reject) => {
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri
let uploadBlob = null
const imageRef = FirebaseClient.storage().ref('images').child('image_001')
fs.readFile(uploadUri, 'base64')
.then((data) => {
return Blob.build(data, { type: `${mime};BASE64` })
})
.then((blob) => {
uploadBlob = blob
return imageRef.put(blob, { contentType: mime })
})
.then(() => {
uploadBlob.close()
return imageRef.getDownloadURL()
})
.then((url) => {
resolve(url)
})
.catch((error) => {
reject(error)
})
})
}
You need to install rn-fetch-blob module:
npm install --save rn-fetch-blob
Then, do the following:
import RNFetchBlob from 'rn-fetch-blob';
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
function uploadImage(path) {
const imageFile = RNFetchBlob.wrap(path);
// 'path/to/image' is where you wish to put your image in
// the database, if you would like to put it in the folder
// 'subfolder' inside 'mainFolder' and name it 'myImage', just
// replace it with 'mainFolder/subfolder/myImage'
const ref = firebase.storage().ref('path/to/image');
var uploadBlob = null;
Blob.build(imageFile, { type: 'image/jpg;' })
.then((imageBlob) => {
uploadBlob = imageBlob;
return ref.put(imageBlob, { contentType: 'image/jpg' });
})
.then(() => {
uploadBlob.close();
return ref.getDownloadURL();
})
.((url) => {
// do something with the url if you wish to
})
.catch(() => {
dispatch({
type: UPDATE_PROFILE_INFO_FAIL,
payload: 'Unable to upload profile picture, please try again'
});
});
}
Please do ask if there's any part of the code that you don't understand. To upload multiple images, simply wrap this code with a for loop. Or if you want to make sure that every image is uploaded without any error, use Promise
Not sure whom this might help, but if you're using MediaLibrary to load images from the gallery, then the uri comes in the format of uri = file:///storage/emulated/0/DCIM/Camera/filename.jpg
In this case, using fetch(uri) didn't help me get the blob.
But if you use fetch(uri.replace("file:///","file:/")) and then follow #sriteja Sugoor's answer, you'll be able to upload the file blob.
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
let uploadBlob;
await fs
.readFile(params?.file.path, 'base64')
.then((data) => {
return Blob.build(data, {type: `BASE64`});
})
.then((blob) => {
uploadBlob = blob;
console.log(uploadBlob, 'uploadBlob');
});